From e57b5cf519a6f18b5e85d2fe9bdf7c99ebb12018 Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Mon, 20 Jan 2025 01:13:25 +0800 Subject: [PATCH 1/5] Improve basemaps app * detect case when tileset mismatches style version * improve css of Style JSON popout * add way to clear the dropped archive --- app/src/MapView.tsx | 153 ++++++++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 47 deletions(-) diff --git a/app/src/MapView.tsx b/app/src/MapView.tsx index fea945f0..76da3c58 100644 --- a/app/src/MapView.tsx +++ b/app/src/MapView.tsx @@ -52,6 +52,9 @@ function getSourceLayer(l: LayerSpecification): string { return ""; } +const areSetsEqual = (a: Set, b: Set) => + a.size === b.size && [...a].every((value) => b.has(value)); + const featureIdToOsmId = (raw: string | number) => { return Number(BigInt(raw) & ((BigInt(1) << BigInt(44)) - BigInt(1))); }; @@ -181,7 +184,7 @@ function StyleJsonPane(props: { theme: string; lang: string }) { }); return ( -
+
-
@@ -212,13 +218,15 @@ function MapLibreView(props: { }) { let mapContainer: HTMLDivElement | undefined; let mapRef: MaplibreMap | undefined; - let protocolRef: Protocol | undefined; let hiddenRef: HTMLDivElement | undefined; let longPressTimeout: ReturnType; const [error, setError] = createSignal(); const [timelinessInfo, setTimelinessInfo] = createSignal(); + const [protocolRef, setProtocolRef] = createSignal(); const [zoom, setZoom] = createSignal(0); + const [mismatchedTilesetVersion, setMismatchedTilesetVersion] = + createSignal(false); onMount(() => { props.ref?.({ fit }); @@ -236,7 +244,7 @@ function MapLibreView(props: { } const protocol = new Protocol({ metadata: true }); - protocolRef = protocol; + setProtocolRef(protocol); addProtocol("pmtiles", protocol.tile); const map = new MaplibreMap({ @@ -288,9 +296,9 @@ function MapLibreView(props: { map.on("idle", () => { setZoom(map.getZoom()); setError(undefined); - if (protocolRef && props.tiles) { - const p = protocolRef.tiles.get(props.tiles); - p?.getMetadata().then((metadata) => { + memoizedArchive() + ?.getMetadata() + .then((metadata) => { if (metadata) { const m = metadata as { version?: string; @@ -301,7 +309,6 @@ function MapLibreView(props: { ); } }); - } }); const showContextMenu = (e: MapTouchEvent) => { @@ -348,58 +355,90 @@ function MapLibreView(props: { mapRef = map; return () => { - protocolRef = undefined; + setProtocolRef(undefined); removeProtocol("pmtiles"); map.remove(); }; }); - const fit = async () => { - if (protocolRef) { - let archive = props.droppedArchive; - if (!archive && props.tiles) { - archive = new PMTiles(props.tiles); - protocolRef.add(archive); - } - if (archive) { - const header = await archive.getHeader(); - mapRef?.fitBounds( - [ - [header.minLon, header.minLat], - [header.maxLon, header.maxLat], - ], - { animate: false }, - ); + // ensure the dropped archive is first added to the protocol + const memoizedArchive = () => { + const p = protocolRef(); + if (p) { + if (props.droppedArchive) { + p.add(props.droppedArchive); + return props.droppedArchive; + } else if (props.tiles) { + let archive = p.tiles.get(props.tiles); + if (!archive) { + archive = new PMTiles(props.tiles); + p.add(archive); + } + return archive; } } }; - createEffect(() => { - if (mapRef) { - mapRef.showTileBoundaries = props.showBoxes; - mapRef.showCollisionBoxes = props.showBoxes; + const fit = async () => { + const header = await memoizedArchive()?.getHeader(); + if (header) { + mapRef?.fitBounds( + [ + [header.minLon, header.minLat], + [header.maxLon, header.maxLat], + ], + { animate: false }, + ); } + }; + + const memoizedStyle = createMemo(() => { + return getMaplibreStyle( + props.theme, + props.lang, + props.localSprites, + props.tiles, + props.npmLayers, + props.droppedArchive, + ); }); createEffect(() => { - // ensure the dropped archive is first added to the protocol - if (protocolRef && props.droppedArchive) { - protocolRef.add(props.droppedArchive); + const layersInStyle = new Set( + memoizedStyle() + .layers.map((l) => { + if ("source-layer" in l) { + return l["source-layer"]; + } + }) + .filter((v) => v !== undefined), + ); + memoizedArchive() + ?.getMetadata() + .then((m) => { + if ( + m instanceof Object && + "vector_layers" in m && + m.vector_layers instanceof Array + ) { + const layersInTiles = new Set(m.vector_layers.map((v) => v.id)); + if (!areSetsEqual(layersInTiles, layersInStyle)) { + setMismatchedTilesetVersion(true); + } + } + }); + }); + + createEffect(() => { + if (mapRef) { + mapRef.showTileBoundaries = props.showBoxes; + mapRef.showCollisionBoxes = props.showBoxes; } }); createEffect(() => { if (mapRef) { - mapRef.setStyle( - getMaplibreStyle( - props.theme, - props.lang, - props.localSprites, - props.tiles, - props.npmLayers, - props.droppedArchive, - ), - ); + mapRef.setStyle(memoizedStyle()); } }); @@ -408,11 +447,22 @@ function MapLibreView(props: {