Skip to content

Commit 9e05de4

Browse files
committed
feat: this is coming along
1 parent 64f1870 commit 9e05de4

File tree

13 files changed

+4468
-30
lines changed

13 files changed

+4468
-30
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"react": "^19.1.0",
2828
"react-dom": "^19.1.0",
2929
"react-icons": "^5.5.0",
30-
"react-map-gl": "^7.1.7"
30+
"react-map-gl": "^7.1.7",
31+
"stac-ts": "^1.0.4"
3132
},
3233
"devDependencies": {
3334
"@eslint/js": "^9.25.0",
@@ -41,6 +42,8 @@
4142
"typescript": "~5.8.3",
4243
"typescript-eslint": "^8.30.1",
4344
"vite": "^6.3.5",
45+
"vite-plugin-top-level-await": "^1.5.0",
46+
"vite-plugin-wasm": "^3.4.1",
4447
"vite-tsconfig-paths": "^5.1.4"
4548
},
4649
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"

src/components/map.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default function Map() {
4343
pickable: true,
4444
autoHighlight: true,
4545
highlightColor: [252, 192, 38],
46-
onHover: (info) => {
46+
onClick: (info) => {
4747
if (state.table) {
4848
const id = state.table.getChild("id")?.get(info.index);
4949
dispatch({ type: "set-id", id });
@@ -54,7 +54,7 @@ export default function Map() {
5454
} else {
5555
setLayers([]);
5656
}
57-
}, [state.table]);
57+
}, [state.table, dispatch]);
5858

5959
useEffect(() => {
6060
if (state.metadata) {

src/components/sidebar.tsx

Lines changed: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,126 @@
1-
import { DataList, SimpleGrid, Tabs, Text } from "@chakra-ui/react";
2-
import { LuFolder, LuFolderMinus } from "react-icons/lu";
1+
import {
2+
Card,
3+
DataList,
4+
Heading,
5+
IconButton,
6+
Image,
7+
Link,
8+
SimpleGrid,
9+
Stack,
10+
Tabs,
11+
Wrap,
12+
} from "@chakra-ui/react";
13+
import { useEffect, useState } from "react";
14+
import {
15+
LuExternalLink,
16+
LuFolder,
17+
LuFolderMinus,
18+
LuInfo,
19+
} from "react-icons/lu";
20+
import type { StacItem } from "stac-ts";
321
import type { StacGeoparquetMetadata } from "./stac-geoparquet/context";
422
import { useStacGeoparquet } from "./stac-geoparquet/hooks";
523

6-
function Info({
7-
metadata,
8-
id,
9-
}: {
10-
metadata?: StacGeoparquetMetadata;
11-
id?: string;
12-
}) {
13-
if (metadata) {
14-
return (
24+
function Metadata({ metadata }: { metadata: StacGeoparquetMetadata }) {
25+
return (
26+
<DataList.Root orientation={"horizontal"} size={"sm"}>
27+
<DataList.Item>
28+
<DataList.ItemLabel>Count</DataList.ItemLabel>
29+
<DataList.ItemValue>{metadata.count}</DataList.ItemValue>
30+
</DataList.Item>
31+
</DataList.Root>
32+
);
33+
}
34+
35+
function Item({ item }: { item: StacItem }) {
36+
return (
37+
<Stack>
1538
<DataList.Root orientation={"horizontal"} size={"sm"}>
1639
<DataList.Item>
17-
<DataList.ItemLabel>Count</DataList.ItemLabel>
18-
<DataList.ItemValue>{metadata.count}</DataList.ItemValue>
40+
<DataList.ItemLabel>ID</DataList.ItemLabel>
41+
<DataList.ItemValue>{item.id}</DataList.ItemValue>
1942
</DataList.Item>
2043
<DataList.Item>
21-
<DataList.ItemLabel>Active item</DataList.ItemLabel>
22-
<DataList.ItemValue>{id || "none"}</DataList.ItemValue>
44+
<DataList.ItemLabel>STAC version</DataList.ItemLabel>
45+
<DataList.ItemValue>{item.stac_version}</DataList.ItemValue>
2346
</DataList.Item>
2447
</DataList.Root>
25-
);
26-
} else {
27-
return <Text>No file loaded...</Text>;
28-
}
48+
49+
<Heading size={"sm"} mt={4}>
50+
Assets
51+
</Heading>
52+
53+
<Wrap>
54+
{Object.entries(item.assets).map(([key, asset]) => {
55+
// TODO make this configurable
56+
const showImage =
57+
asset.type && ["image/jpeg", "image/png"].includes(asset.type);
58+
return (
59+
<Card.Root key={item.id + key} size={"sm"}>
60+
<Card.Header>{key}</Card.Header>
61+
<Card.Body>
62+
{asset.title && <Card.Title>{asset.title}</Card.Title>}
63+
{asset.description && (
64+
<Card.Description>{asset.description}</Card.Description>
65+
)}
66+
{showImage && <Image src={asset.href} maxH={200} />}
67+
</Card.Body>
68+
<Card.Footer>
69+
<Link href={asset.href}>
70+
<IconButton variant={"plain"} size={"sm"}>
71+
<LuExternalLink></LuExternalLink>
72+
</IconButton>
73+
</Link>
74+
</Card.Footer>
75+
</Card.Root>
76+
);
77+
})}
78+
</Wrap>
79+
</Stack>
80+
);
2981
}
3082

3183
export default function Sidebar() {
32-
const { metadata, id } = useStacGeoparquet();
84+
const { metadata, item } = useStacGeoparquet();
85+
const [value, setValue] = useState("metadata");
86+
87+
useEffect(() => {
88+
if (item) {
89+
setValue("item");
90+
} else {
91+
setValue("metadata");
92+
}
93+
}, [item]);
3394

3495
return (
35-
<SimpleGrid columns={3} my={2} pointerEvents={"auto"}>
96+
<SimpleGrid columns={3} my={2}>
3697
<Tabs.Root
3798
bg={"bg.muted"}
3899
px={4}
39100
pt={2}
40101
pb={4}
41102
fontSize={"sm"}
42103
rounded={"sm"}
43-
defaultValue={"info"}
104+
value={value}
105+
onValueChange={(e) => setValue(e.value)}
106+
pointerEvents={"auto"}
44107
>
45108
<Tabs.List>
46-
<Tabs.Trigger value="info">
109+
<Tabs.Trigger value="metadata">
47110
{(metadata && <LuFolder></LuFolder>) || (
48111
<LuFolderMinus></LuFolderMinus>
49112
)}
50113
</Tabs.Trigger>
114+
<Tabs.Trigger value="item" disabled={item === undefined}>
115+
<LuInfo></LuInfo>
116+
</Tabs.Trigger>
51117
</Tabs.List>
52-
<Tabs.Content value="info">
53-
<Info metadata={metadata} id={id}></Info>
118+
<Tabs.Content value="metadata">
119+
{(metadata && <Metadata metadata={metadata}></Metadata>) ||
120+
"No file loaded..."}
121+
</Tabs.Content>
122+
<Tabs.Content value="item">
123+
{(item && <Item item={item}></Item>) || "No item selected..."}
54124
</Tabs.Content>
55125
</Tabs.Root>
56126
</SimpleGrid>

src/components/stac-geoparquet/context.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import type { Table } from "apache-arrow";
22
import { LngLatBounds } from "maplibre-gl";
33
import { createContext, type Dispatch } from "react";
4+
import type { StacItem } from "stac-ts";
45

56
export type StacGeoparquetState = {
67
path?: string;
78
metadata?: StacGeoparquetMetadata;
89
table?: Table;
910
id?: string;
11+
item?: StacItem;
1012
};
1113

1214
type StacGeoparquetContextType = {
@@ -23,7 +25,8 @@ export type StacGeoparquetAction =
2325
| { type: "set-path"; path: string }
2426
| { type: "set-metadata"; metadata: StacGeoparquetMetadata }
2527
| { type: "set-table"; table: Table }
26-
| { type: "set-id"; id?: string };
28+
| { type: "set-id"; id?: string }
29+
| { type: "set-item"; item?: StacItem };
2730

2831
export const StacGeoparquetContext =
2932
createContext<StacGeoparquetContextType | null>(null);

src/components/stac-geoparquet/provider.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { useDuckDb } from "duckdb-wasm-kit";
1212
import { LngLatBounds } from "maplibre-gl";
1313
import { useEffect, useReducer, useState, type ReactNode } from "react";
14+
import * as stacWasm from "../../stac-wasm";
1415
import { toaster } from "../ui/toaster";
1516
import {
1617
StacGeoparquetContext,
@@ -115,6 +116,26 @@ export default function StacGeoparquetProvider({
115116
}
116117
}, [state.path, connection, dispatch]);
117118

119+
// TODO I don't like this depending on state.path.
120+
useEffect(() => {
121+
if (state.id) {
122+
if (connection) {
123+
(async () => {
124+
// TODO refactor query so we don't repeat logic.
125+
const result = await connection.query(
126+
`SELECT * EXCLUDE geometry FROM read_parquet('${state.path}', union_by_name=true) where id = '${state.id}'`
127+
);
128+
const item = stacWasm.arrowToStacJson(result)[0];
129+
dispatch({ type: "set-item", item });
130+
})();
131+
} else {
132+
// TODO Handle missing connection
133+
}
134+
} else {
135+
dispatch({ type: "set-item" });
136+
}
137+
}, [state.id, state.path, connection, dispatch]);
138+
118139
return (
119140
<StacGeoparquetContext value={{ state, dispatch }}>
120141
{children}
@@ -132,5 +153,7 @@ function reducer(state: StacGeoparquetState, action: StacGeoparquetAction) {
132153
return { ...state, table: action.table };
133154
case "set-id":
134155
return { ...state, id: action.id };
156+
case "set-item":
157+
return { ...state, item: action.item };
135158
}
136159
}

src/stac-wasm/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "stac-wasm",
3+
"collaborators": [
4+
"Pete Gadomski <pete.gadomski@gmail.com>"
5+
],
6+
"description": "WASM STAC functions",
7+
"version": "0.0.2",
8+
"license": "MIT OR Apache-2.0",
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/stac-utils/rustac"
12+
},
13+
"files": [
14+
"stac_wasm_bg.wasm",
15+
"stac_wasm.js",
16+
"stac_wasm_bg.js",
17+
"stac_wasm.d.ts"
18+
],
19+
"main": "stac_wasm.js",
20+
"homepage": "https://stac-utils.github.io/rustac",
21+
"types": "stac_wasm.d.ts",
22+
"sideEffects": [
23+
"./stac_wasm.js",
24+
"./snippets/*"
25+
]
26+
}

0 commit comments

Comments
 (0)