Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
dde9be6
refactor!: rewrite with zustand
gadomski Jan 14, 2026
7bd7ef4
feat: collections stash
gadomski Jan 15, 2026
2a82c7d
fix: working back towards filter and search
gadomski Jan 15, 2026
3d76bbd
fix: back to filtering
gadomski Jan 15, 2026
466716c
fix: leftover variable
gadomski Jan 15, 2026
70869a3
tests: add a few
gadomski Jan 15, 2026
b5c941d
fix: clear filter
gadomski Jan 15, 2026
9677fe0
fix: fetching
gadomski Jan 15, 2026
e19dd51
feat: re-add examples
gadomski Jan 15, 2026
ce77a52
feat: skeleton when loading
gadomski Jan 15, 2026
a1c0bba
feat: add lineClamp
gadomski Jan 15, 2026
3941f00
feat: description clamping
gadomski Jan 15, 2026
5888bb4
feat: add value and fill color
gadomski Jan 15, 2026
9499dd6
feat: add icon
gadomski Jan 15, 2026
bb63fde
feat: add root and parent links
gadomski Jan 15, 2026
58ced5f
feat: fix dependenceis?
gadomski Jan 15, 2026
80cc215
refactor: getLinkHref
gadomski Jan 15, 2026
13302f0
fix: lints
gadomski Jan 15, 2026
055173a
chore: whitespace
gadomski Jan 15, 2026
23d853f
feat: add json
gadomski Jan 15, 2026
4efe488
fix: re-add STAC browser
gadomski Jan 15, 2026
d5867a1
fix: make stac browser configurable
gadomski Jan 15, 2026
420eda7
feat: ready for search
gadomski Jan 15, 2026
f025071
feat: add section-header
gadomski Jan 15, 2026
f114d7f
feat: stuff
gadomski Jan 15, 2026
c750083
fix: filling of value
gadomski Jan 15, 2026
e978f43
feat: search action bar
gadomski Jan 15, 2026
4081e1f
fix: clear everything when we set href
gadomski Jan 15, 2026
e9c86b2
feat: add filter viewport
gadomski Jan 15, 2026
a245e9a
feat: hover collections
gadomski Jan 15, 2026
e1eab51
feat: link map back to sidebar
gadomski Jan 15, 2026
b38a52e
fix: fill
gadomski Jan 15, 2026
24dbc42
fix: add skeleton to root load
gadomski Jan 15, 2026
184ae47
feat: refactor panel
gadomski Jan 15, 2026
4086e3f
fix: re-organize map
gadomski Jan 16, 2026
fb24950
feat: hover collections properly
gadomski Jan 16, 2026
4b03fee
fix: split value panel
gadomski Jan 16, 2026
8bca2fe
feat: add children
gadomski Jan 16, 2026
aeae52b
feat: childrenworking like collections
gadomski Jan 16, 2026
9f3b61f
fix: hovering collections when value is loaded
gadomski Jan 16, 2026
71bec49
fix: reset hoveredCollection
gadomski Jan 16, 2026
bb6bd39
feat: set cursor
gadomski Jan 16, 2026
928f185
fix: section
gadomski Jan 16, 2026
4759618
feat: better viz
gadomski Jan 16, 2026
6e36515
fix: format
gadomski Jan 16, 2026
d454cd8
fix: cast value.description
gadomski Jan 16, 2026
b89c271
fix: picked
gadomski Jan 16, 2026
e9111dd
feat: add sting to bottom left
gadomski Jan 17, 2026
32d1eb3
feat: better search items
gadomski Jan 17, 2026
a0706b8
feat: use Section for Assets
gadomski Jan 20, 2026
1566ff8
Merge branch 'main' into rewrite
gadomski Jan 21, 2026
7b4b3fb
refactor: store slices (#252)
sandrahoang686 Jan 21, 2026
e50bd6d
fix: remove some unneded deps
gadomski Jan 21, 2026
828570d
feat: button to fetch collections in panel
gadomski Jan 21, 2026
701f4e3
fix: item cards
gadomski Jan 21, 2026
c8180a4
fix: don't clear search
gadomski Jan 21, 2026
1d287e7
feat: file upload
gadomski Jan 21, 2026
aad1b26
feat: add upload button
gadomski Jan 21, 2026
39e7981
fix: go through href set
gadomski Jan 21, 2026
a288f43
feat: re-adding stac-geoparquet
gadomski Jan 21, 2026
22497a1
feat: re-add geoparquet
gadomski Jan 22, 2026
c37a4fc
feat: add search settings
gadomski Jan 22, 2026
ce6d0b6
feat: search settings
gadomski Jan 22, 2026
5f11adc
fix: item search
gadomski Jan 22, 2026
9a74e98
refactor: search
gadomski Jan 22, 2026
9bcd824
feat: better search progress
gadomski Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env

This file was deleted.

6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# STAC Natural Query API endpoint
VITE_STAC_NATURAL_QUERY_API=https://api.stac-semantic-search.k8s.labs.ds.io

# Base URL for STAC Browser external links
# Default: https://radiantearth.github.io/stac-browser/#/external/
VITE_STAC_BROWSER_URL="https://radiantearth.github.io/stac-browser/#/external/"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ dist-ssr
*.sw?
tests/**/__screenshots__/
codebook.toml
.env
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stac-map",
"description": "A map-first, single-page, statically-hosted STAC search and visualization tool, with stac-geoparquet support",
"description": "A map-first single-page STAC search and visualization tool with stac-geoparquet support",
"author": {
"name": "Pete Gadomski",
"email": "pete.gadomski@gmail.com"
Expand All @@ -9,6 +9,10 @@
{
"name": "Indraneel Purohit",
"email": "indraneelpurohit@gmail.com"
},
{
"name": "Sandra Hoang",
"email": "sandrahoang686@gmail.com"
}
],
"homepage": "https://github.com/developmentseed/stac-map/",
Expand All @@ -20,7 +24,8 @@
"url": "https://github.com/developmentseed/stac-map/issues"
},
"license": "MIT",
"version": "0.0.1",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -35,11 +40,10 @@
"dependencies": {
"@chakra-ui/react": "^3.31.0",
"@deck.gl/core": "^9.2.6",
"@deck.gl/geo-layers": "^9.2.6",
"@deck.gl/layers": "^9.2.6",
"@deck.gl/mapbox": "^9.2.6",
"@developmentseed/deck.gl-geotiff": "^0.1.0",
"@developmentseed/deck.gl-raster": "^0.1.0",
"@devseed-ui/collecticons-chakra": "^4.0.0",
"@duckdb/duckdb-wasm": "^1.32.0",
"@emotion/react": "^11.13.5",
"@geoarrow/deck.gl-layers": "^0.3.0",
Expand All @@ -60,8 +64,10 @@
"react-icons": "^5.5.0",
"react-map-gl": "^8.1.0",
"react-markdown": "^10.1.0",
"shiki": "^3.21.0",
"stac-ts": "^1.0.4",
"stac-wasm": "^0.0.3"
"stac-wasm": "^0.0.3",
"zustand": "^5.0.10"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
Expand Down
200 changes: 76 additions & 124 deletions src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,123 +1,85 @@
import { useEffect, useMemo, useState } from "react";
import { Box, Container, FileUpload, useFileUpload } from "@chakra-ui/react";
import type { StacCollection, StacItem } from "stac-ts";
import { Toaster } from "./components/ui/toaster";
import useHrefParam from "./hooks/href-param";
import useStacChildren from "./hooks/stac-children";
import useStacFilters from "./hooks/stac-filters";
import useStacValue from "./hooks/stac-value";
import Map from "./layers/map";
import Overlay from "./layers/overlay";
import type { BBox2D, Color } from "./types/map";
import type { DatetimeBounds, StacValue } from "./types/stac";
import getDateTimes from "./utils/datetimes";
import { getCogHref } from "./utils/stac";
import getDocumentTitle from "./utils/title";

// TODO make this configurable by the user.
const lineColor: Color = [207, 63, 2, 100];
const fillColor: Color = [207, 63, 2, 50];
import { useEffect } from "react";
import { LuHeart } from "react-icons/lu";
import { MapProvider } from "react-map-gl/maplibre";
import { Box, Container, FileUpload, HStack, Link } from "@chakra-ui/react";
import { CollecticonBrandDevelopmentSeed2 } from "@devseed-ui/collecticons-chakra";
import { useDuckDb } from "duckdb-wasm-kit";
import Map from "./components/map";
import Overlay from "./components/overlay";
import { useStore } from "./store";
import { getCurrentHref } from "./utils/href";
import { uploadFile } from "./utils/upload";

export default function App() {
// User state
const { href, setHref } = useHrefParam();
const fileUpload = useFileUpload({
maxFiles: 1,
onFileChange: (details) => {
if (details.acceptedFiles.length === 1) {
setHref(details.acceptedFiles[0].name);
}
},
});
const [userCollections, setCollections] = useState<StacCollection[]>();
const [userItems, setItems] = useState<StacItem[]>();
const [picked, setPicked] = useState<StacValue>();
const [bbox, setBbox] = useState<BBox2D>();
const [datetimeBounds, setDatetimeBounds] = useState<DatetimeBounds>();
const [filter, setFilter] = useState(true);
const [stacGeoparquetItemId, setStacGeoparquetItemId] = useState<string>();
const [cogHref, setcogHref] = useState<string>();

// Derived state
const {
value,
error,
items: linkedItems,
geoparqetTable,
stacGeoparquetItem,
} = useStacValue({
href,
fileUpload,
datetimeBounds: filter ? datetimeBounds : undefined,
stacGeoparquetItemId,
});
const collectionsLink = value?.links?.find((link) => link.rel === "data");
const { catalogs, collections: linkedCollections } = useStacChildren({
value,
enabled: !!value && !collectionsLink,
});
const collections = collectionsLink ? userCollections : linkedCollections;
const items = userItems || linkedItems;
const { filteredCollections, filteredItems } = useStacFilters({
collections,
items,
filter,
bbox,
datetimeBounds,
});

const datetimes = useMemo(
() => (value ? getDateTimes(value, items, collections) : null),
[value, items, collections]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ 🧹

);
const href = useStore((state) => state.href);
const setHref = useStore((state) => state.setHref);
const setUploadedFile = useStore((state) => state.setUploadedFile);
const { db } = useDuckDb();
const setConnection = useStore((state) => state.setConnection);

// Effects
useEffect(() => {
document.title = getDocumentTitle(value);
}, [value]);
if (href && getCurrentHref() != href) {
history.pushState(null, "", "?href=" + href);
} else if (href === "") {
history.pushState(null, "", location.pathname);
}
}, [href]);

useEffect(() => {
setPicked(undefined);
setItems(undefined);
setDatetimeBounds(undefined);
setcogHref(value && getCogHref(value));
}, [value]);
function handlePopState() {
setHref(getCurrentHref() ?? "");
}
window.addEventListener("popstate", handlePopState);

useEffect(() => {
setcogHref(picked && getCogHref(picked));
}, [picked]);
if (getCurrentHref()) {
try {
new URL(getCurrentHref());
} catch {
history.pushState(null, "", location.pathname);
}
}

return () => {
window.removeEventListener("popstate", handlePopState);
};
}, [setHref]);

useEffect(() => {
setPicked(stacGeoparquetItem);
}, [stacGeoparquetItem]);
if (db) {
(async () => {
const connection = await db.connect();
await connection.query("LOAD spatial;");
await connection.query("LOAD icu;");
setConnection(connection);
})();
}
}, [db, setConnection]);

return (
<>
<MapProvider>
<Box h={"100dvh"}>
<FileUpload.RootProvider value={fileUpload} unstyled={true}>
<FileUpload.Root
unstyled={true}
onFileAccept={(details) => {
uploadFile({
file: details.files[0],
setUploadedFile,
db,
});
}}
disabled={!db}
>
<FileUpload.HiddenInput />
<FileUpload.Dropzone
disableClick={true}
style={{
height: "100dvh",
width: "100dvw",
}}
>
<Map
value={value}
geoparquetTable={geoparqetTable}
collections={collections}
filteredCollections={filteredCollections}
items={filteredItems}
fillColor={fillColor}
lineColor={lineColor}
setBbox={setBbox}
picked={picked}
setPicked={setPicked}
setStacGeoparquetItemId={setStacGeoparquetItemId}
cogHref={cogHref}
></Map>
<Map />
</FileUpload.Dropzone>
</FileUpload.RootProvider>
</FileUpload.Root>
</Box>
<Container
zIndex={1}
Expand All @@ -129,30 +91,20 @@ export default function App() {
left={0}
pt={4}
>
<Overlay
href={href}
setHref={setHref}
fileUpload={fileUpload}
value={value}
error={error}
catalogs={catalogs}
setCollections={setCollections}
collections={filteredCollections}
totalNumOfCollections={collections?.length}
filter={filter}
setFilter={setFilter}
bbox={bbox}
setPicked={setPicked}
picked={picked}
items={filteredItems}
setItems={setItems}
setDatetimeBounds={setDatetimeBounds}
cogHref={cogHref}
setcogHref={setcogHref}
datetimes={datetimes}
></Overlay>
<Overlay />
</Container>
<Toaster></Toaster>
</>
<HStack
position={"absolute"}
bottom={4}
left={8}
fontWeight={"lighter"}
fontSize={"small"}
>
Created with <LuHeart /> by{" "}
<Link href="https://developmentseed.org/">
Development Seed <CollecticonBrandDevelopmentSeed2 />
</Link>
</HStack>
</MapProvider>
);
}
Loading
Loading