Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
2 changes: 2 additions & 0 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default function App() {
error,
items: linkedItems,
table,
geometryType,
stacGeoparquetItem,
} = useStacValue({
href,
Expand Down Expand Up @@ -105,6 +106,7 @@ export default function App() {
<Map
value={value}
table={table}
geometryType={geometryType}
collections={collections}
filteredCollections={filteredCollections}
items={filteredItems}
Expand Down
7 changes: 5 additions & 2 deletions src/hooks/stac-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ export default function useStacValue({
});
const value = jsonResult.data || stacGeoparquetResult.data || undefined;
const table = enableStacGeoparquet
? stacGeoparquetTableResult.data || undefined
? stacGeoparquetTableResult.data?.table || undefined
: undefined;
const parquetGeometryType = enableStacGeoparquet
? stacGeoparquetTableResult.data?.geometryType || undefined
: undefined;
const error =
jsonResult.error ||
Expand All @@ -102,11 +105,11 @@ export default function useStacValue({
};
},
});

return {
value,
error,
table,
geometryType: parquetGeometryType,
stacGeoparquetItem: stacGeoparquetItem.data,
items: itemsResult.data.length > 0 ? itemsResult.data : undefined,
};
Expand Down
59 changes: 40 additions & 19 deletions src/layers/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { type DeckProps, Layer } from "@deck.gl/core";
import { TileLayer } from "@deck.gl/geo-layers";
import { BitmapLayer, GeoJsonLayer } from "@deck.gl/layers";
import { MapboxOverlay } from "@deck.gl/mapbox";
import { GeoArrowPolygonLayer } from "@geoarrow/deck.gl-layers";
import {
GeoArrowPolygonLayer,
GeoArrowScatterplotLayer,
} from "@geoarrow/deck.gl-layers";
import bbox from "@turf/bbox";
import bboxPolygon from "@turf/bbox-polygon";
import "maplibre-gl/dist/maplibre-gl.css";
Expand All @@ -18,6 +21,7 @@ import type { BBox, Feature, FeatureCollection } from "geojson";
import { useColorModeValue } from "../components/ui/color-mode";
import type { BBox2D, Color } from "../types/map";
import type { StacValue } from "../types/stac";
import { ValidGeometryType } from "../utils/stac-geoparquet";

export default function Map({
value,
Expand All @@ -32,6 +36,7 @@ export default function Map({
table,
setStacGeoparquetItemId,
cogTileHref,
geometryType,
}: {
value: StacValue | undefined;
collections: StacCollection[] | undefined;
Expand All @@ -45,6 +50,7 @@ export default function Map({
table: Table | undefined;
setStacGeoparquetItemId: (id: string | undefined) => void;
cogTileHref: string | undefined;
geometryType: string | undefined;
}) {
const mapRef = useRef<MapRef>(null);
const mapStyle = useColorModeValue(
Expand Down Expand Up @@ -119,7 +125,6 @@ export default function Map({
},
})
);

layers = [
...layers,
new GeoJsonLayer({
Expand Down Expand Up @@ -166,23 +171,39 @@ export default function Map({
},
}),
];

if (table)
layers.push(
new GeoArrowPolygonLayer({
id: "table",
data: table,
filled: true,
getFillColor: fillColor,
getLineColor: lineColor,
getLineWidth: 2,
lineWidthUnits: "pixels",
pickable: true,
onClick: (info) => {
setStacGeoparquetItemId(table.getChild("id")?.get(info.index));
},
})
);
if (table) {
if (geometryType === ValidGeometryType.Polygon) {
layers.push(
new GeoArrowPolygonLayer({
id: "table-polygon",
data: table,
filled: true,
getFillColor: fillColor,
getLineColor: lineColor,
getLineWidth: 2,
lineWidthUnits: "pixels",
pickable: true,
onClick: (info) => {
setStacGeoparquetItemId(table.getChild("id")?.get(info.index));
},
})
);
} else if (geometryType === ValidGeometryType.Point) {
layers.push(
new GeoArrowScatterplotLayer({
id: "table-point",
data: table,
getFillColor: fillColor,
opacity: 0.9,
radiusMinPixels: 0.1,
pickable: true,
onClick: (info) => {
setStacGeoparquetItemId(table.getChild("id")?.get(info.index));
},
})
);
}
}

useEffect(() => {
if (value && mapRef.current) {
Expand Down
84 changes: 69 additions & 15 deletions src/utils/stac-geoparquet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,33 @@ import {
import * as stacWasm from "stac-wasm";
import type { DatetimeBounds, StacItemCollection } from "../types/stac";

// @ts-expect-error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
export enum ValidGeometryType {
Point = "point",
Polygon = "polygon",
}

const isValidGeometryType = (
geometryType: string
): geometryType is ValidGeometryType => {
return Object.values(ValidGeometryType).includes(
geometryType as ValidGeometryType
);
};

export async function getStacGeoparquet(
href: string,
connection: AsyncDuckDBConnection
) {
const { startDatetimeColumnName, endDatetimeColumnName } =
await getStacGeoparquetDatetimeColumns(href, connection);

const summaryResult = await connection.query(
`SELECT COUNT(*) as count, MIN(bbox.xmin) as xmin, MIN(bbox.ymin) as ymin, MAX(bbox.xmax) as xmax, MAX(bbox.ymax) as ymax, MIN(${startDatetimeColumnName}) as start_datetime, MAX(${endDatetimeColumnName}) as end_datetime FROM read_parquet('${href}')`
);
const query =
startDatetimeColumnName && endDatetimeColumnName
? `SELECT COUNT(*) as count, MIN(bbox.xmin) as xmin, MIN(bbox.ymin) as ymin, MAX(bbox.xmax) as xmax, MAX(bbox.ymax) as ymax, MIN(${startDatetimeColumnName}) as start_datetime, MAX(${endDatetimeColumnName}) as end_datetime FROM read_parquet('${href}')`
: `SELECT COUNT(*) as count, MIN(bbox.xmin) as xmin, MIN(bbox.ymin) as ymin, MAX(bbox.xmax) as xmax, MAX(bbox.ymax) as ymax FROM read_parquet('${href}')`;

const summaryResult = await connection.query(query);
const summaryRow = summaryResult.toArray().map((row) => row.toJSON())[0];

const kvMetadataResult = await connection.query(
Expand Down Expand Up @@ -65,12 +82,21 @@ export async function getStacGeoparquetTable(
const { startDatetimeColumnName, endDatetimeColumnName } =
await getStacGeoparquetDatetimeColumns(href, connection);

let query = `SELECT ST_AsWKB(geometry) as geometry, id FROM read_parquet('${href}')`;
let query = `SELECT ST_AsWKB(geometry) AS geometry, ST_GeometryType(geometry) AS geometry_type, id FROM read_parquet('${href}')`;
if (datetimeBounds) {
query += ` WHERE ${startDatetimeColumnName} >= DATETIME '${datetimeBounds.start.toISOString()}' AND ${endDatetimeColumnName} <= DATETIME '${datetimeBounds.end.toISOString()}'`;
}
const result = await connection.query(query);
const geometry: Uint8Array[] = result.getChildAt(0)?.toArray();
const geometryType: string = result
.getChildAt(1)
?.toArray()[0]
?.toLowerCase();
if (!isValidGeometryType(geometryType)) {
throw new Error(
`Invalid geometry type: ${geometryType}. We currently do not support this type.`
);
}
const wkb = new Uint8Array(geometry?.flatMap((array) => [...array]));
const valueOffsets = new Int32Array(geometry.length + 1);
for (let i = 0, len = geometry.length; i < len; i++) {
Expand All @@ -82,17 +108,35 @@ export async function getStacGeoparquetTable(
data: wkb,
valueOffsets,
});
const polygons = io.parseWkb(data, io.WKBType.Polygon, 2);
const table = new Table({
// @ts-expect-error: 2769
geometry: makeVector(polygons),
id: vectorFromArray(result.getChild("id")?.toArray()),
});
table.schema.fields[0].metadata.set(
"ARROW:extension:name",
"geoarrow.polygon"
);
return table;
let table: Table | undefined = undefined;
if (geometryType === ValidGeometryType.Polygon) {
const polygons = io.parseWkb(data, io.WKBType.Polygon, 2);
table = new Table({
// @ts-expect-error: 2769
geometry: makeVector(polygons),
id: vectorFromArray(result.getChild("id")?.toArray()),
});
table.schema.fields[0].metadata.set(
"ARROW:extension:name",
"geoarrow.polygon"
);
} else if (geometryType === ValidGeometryType.Point) {
const points = io.parseWkb(data, io.WKBType.Point, 2);
table = new Table({
// @ts-expect-error: 2769
geometry: makeVector(points),
id: vectorFromArray(result.getChild("id")?.toArray()),
});
table.schema.fields[0].metadata.set(
"ARROW:extension:name",
"geoarrow.point"
);
}

return {
table: table,
geometryType: geometryType,
};
}

export async function getStacGeoparquetItem(
Expand All @@ -117,6 +161,16 @@ async function getStacGeoparquetDatetimeColumns(
);
const describe = describeResult.toArray().map((row) => row.toJSON());
const columnNames = describe.map((row) => row.column_name);
const containsDates: boolean = columnNames.some((columnName: string) => {
return columnName.includes("date");
});

if (!containsDates)
return {
startDatetimeColumnName: null,
endDatetimeColumnName: null,
};

const startDatetimeColumnName = columnNames.includes("start_datetime")
? "start_datetime"
: "datetime";
Expand Down