Skip to content

Commit 0732eaa

Browse files
authored
feat: extra maplibre layers (#421)
To configure: ```javascript <StacMap defaultHref={defaultHref} auth={auth} stacBrowserUrl={stacBrowserUrl} extraLayers={[ { layer: { id: "oam-global-coverage", type: "fill", "source-layer": "globalcoverage", minzoom: 0, maxzoom: 15, filter: ["==", ["geometry-type"], "Polygon"], paint: { "fill-color": "#d43f3f", "fill-opacity": 0.2, "fill-outline-color": "#d43f3f", }, }, source: { type: "vector", url: "pmtiles://https://s3.amazonaws.com/oin-hotosm-temp/global-coverage.pmtiles", }, }, ]} footer={<Footer version={version} changelog={changelog} />} /> ``` Got: <img width="3024" height="1730" alt="Screenshot 2026-04-30 at 13-33-34 stac-map eoAPI-stac" src="https://github.com/user-attachments/assets/34bd4e58-4ed0-4da6-a631-e6a3897e1d20" />
1 parent 54c8b28 commit 0732eaa

6 files changed

Lines changed: 57 additions & 14 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"duckdb-wasm-kit": "^0.1.38",
5656
"geotiff-geokeys-to-proj4": "^2024.4.13",
5757
"next-themes": "^0.4.3",
58+
"pmtiles": "^4.4.1",
5859
"react-error-boundary": "^6.1.1",
5960
"react-icons": "^5.6.0",
6061
"react-markdown": "^10.1.0",

src/app.tsx renamed to src/components/app.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { AbsoluteCenter, Box, Center, FileUpload } from "@chakra-ui/react";
22
import { useDuckDb } from "duckdb-wasm-kit";
33
import { type ReactNode, useEffect } from "react";
44
import { ErrorBoundary } from "react-error-boundary";
5-
import Map from "./components/map";
6-
import Overlay from "./components/overlay";
7-
import { ErrorBoundaryAlert } from "./components/ui/error-alert";
8-
import { useStore } from "./store";
9-
import { uploadFile } from "./utils/upload";
5+
import { useStore } from "../store";
6+
import { uploadFile } from "../utils/upload";
7+
import Map from "./map";
8+
import Overlay from "./overlay";
9+
import type { ExtraLayerProps } from "./stac-map";
10+
import { ErrorBoundaryAlert } from "./ui/error-alert";
1011

1112
function MapFallback({ error }: { error: unknown }) {
1213
return (
@@ -26,7 +27,13 @@ function OverlayFallback({ error }: { error: unknown }) {
2627
);
2728
}
2829

29-
export default function App({ footer }: { footer?: ReactNode }) {
30+
export default function App({
31+
footer,
32+
extraLayers,
33+
}: {
34+
footer?: ReactNode;
35+
extraLayers?: ExtraLayerProps[];
36+
}) {
3037
const setUploadedFile = useStore((state) => state.setUploadedFile);
3138
const setConnection = useStore((state) => state.setConnection);
3239
const { db } = useDuckDb();
@@ -66,7 +73,7 @@ export default function App({ footer }: { footer?: ReactNode }) {
6673
}}
6774
>
6875
<ErrorBoundary FallbackComponent={MapFallback}>
69-
<Map />
76+
<Map extraLayers={extraLayers} />
7077
</ErrorBoundary>
7178
</FileUpload.Dropzone>
7279
</FileUpload.Root>

src/components/map.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,15 @@ import {
2525
} from "react-map-gl/maplibre";
2626
import { useColorModeValue } from "../components/ui/color-mode";
2727
import { useStore } from "../store";
28+
import type { ExtraLayerProps } from "./stac-map";
2829

2930
type Color = [number, number, number, number];
3031

31-
export default function Map() {
32+
export default function Map({
33+
extraLayers,
34+
}: {
35+
extraLayers?: ExtraLayerProps[];
36+
}) {
3237
const mapRef = useRef<MapRef>(null);
3338
const [isLoaded, setIsLoaded] = useState(false);
3439
const mapStyle = useColorModeValue(
@@ -310,6 +315,12 @@ export default function Map() {
310315
layers={layers}
311316
getCursor={(props) => getCursor(mapRef, props)}
312317
></DeckGLOverlay>
318+
{extraLayers &&
319+
extraLayers.map((layer, i) => (
320+
<Source key={"extra-layer-" + i} {...layer.source}>
321+
<MaplibreLayer {...layer.layer} />
322+
</Source>
323+
))}
313324
</MaplibreMap>
314325
);
315326
}

src/components/stac-map.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import {
55
} from "@chakra-ui/react";
66
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
77
import "maplibre-gl/dist/maplibre-gl.css";
8-
import { type ReactNode, useEffect, useMemo } from "react";
9-
import { MapProvider } from "react-map-gl/maplibre";
8+
import { useEffect, useMemo, type ReactNode } from "react";
9+
import {
10+
MapProvider,
11+
type LayerProps,
12+
type SourceProps,
13+
} from "react-map-gl/maplibre";
1014
import { AuthProvider, type AuthProviderProps } from "react-oidc-context";
11-
import App from "../app";
1215
import { AuthEnabledProvider } from "../contexts/auth-enabled";
1316
import { StacBrowserUrlProvider } from "../contexts/stac-browser";
17+
import App from "./app";
1418
import { HrefBootstrap } from "./href-bootstrap";
1519
import { OidcTokenSync } from "./oidc-token-sync";
1620
import { LoginSplash } from "./ui/auth";
@@ -19,6 +23,11 @@ import {
1923
type ColorModeProviderProps,
2024
} from "./ui/color-mode";
2125

26+
export interface ExtraLayerProps {
27+
source: SourceProps;
28+
layer: LayerProps;
29+
}
30+
2231
export interface StacMapProps {
2332
/** Initial STAC URL to load on mount when no `?href=` is present in the URL. */
2433
defaultHref?: string;
@@ -36,6 +45,8 @@ export interface StacMapProps {
3645
stacBrowserUrl?: string;
3746
/** Optional footer rendered below the map (e.g. version + changelog). */
3847
footer?: ReactNode;
48+
/** Source and layer information for extra maplibre layers */
49+
extraLayers?: ExtraLayerProps[];
3950
}
4051

4152
let mountCount = 0;
@@ -54,6 +65,7 @@ export function StacMap({
5465
auth,
5566
stacBrowserUrl,
5667
footer,
68+
extraLayers,
5769
}: StacMapProps) {
5870
useEffect(() => {
5971
mountCount += 1;
@@ -82,7 +94,7 @@ export function StacMap({
8294
<StacBrowserUrlProvider url={stacBrowserUrl}>
8395
<MapProvider>
8496
<HrefBootstrap defaultHref={defaultHref} syncWithUrl={syncWithUrl}>
85-
<App footer={footer} />
97+
<App footer={footer} extraLayers={extraLayers} />
8698
</HrefBootstrap>
8799
</MapProvider>
88100
</StacBrowserUrlProvider>

tests/app.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
22
import { describe, expect, test } from "vitest";
33
import { render } from "vitest-browser-react";
44
import { userEvent } from "vitest/browser";
5-
import App from "../src/app";
5+
import App from "../src/components/app";
66
import { Provider } from "../src/components/ui/provider";
77

88
async function renderApp() {

yarn.lock

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2883,7 +2883,7 @@
28832883
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
28842884
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
28852885

2886-
"@types/react-dom@^19.1.2":
2886+
"@types/react-dom@^19.2.3":
28872887
version "19.2.3"
28882888
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
28892889
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
@@ -5196,6 +5196,11 @@ fflate@0.7.4:
51965196
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
51975197
integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
51985198

5199+
fflate@^0.8.2:
5200+
version "0.8.2"
5201+
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
5202+
integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
5203+
51995204
figures@^2.0.0:
52005205
version "2.0.0"
52015206
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
@@ -7573,6 +7578,13 @@ playwright@^1.59.1:
75737578
optionalDependencies:
75747579
fsevents "2.3.2"
75757580

7581+
pmtiles@^4.4.1:
7582+
version "4.4.1"
7583+
resolved "https://registry.yarnpkg.com/pmtiles/-/pmtiles-4.4.1.tgz#11ecb2a01453ba7c0ce88027e15f768aba18913b"
7584+
integrity sha512-5oTeQc/yX/ft1evbpIlnoCZugQuug/iYIAj/ZTqIqzdGek4uZEho99En890EE6NOSI3JTI3IG8R7r8+SltphxA==
7585+
dependencies:
7586+
fflate "^0.8.2"
7587+
75767588
pngjs@^7.0.0:
75777589
version "7.0.0"
75787590
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26"

0 commit comments

Comments
 (0)