From d8851a704155da3fc9073e7e09e1e75ea9ef854e Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Wed, 5 Oct 2022 10:48:00 +0800 Subject: [PATCH] inspector app uses new JS client --- app/src/Inspector.tsx | 34 +++++----- app/src/LeafletMap.tsx | 10 +-- app/src/Loader.tsx | 30 +-------- app/src/MaplibreMap.tsx | 139 +++++++++++++++++++++++++--------------- app/src/Start.tsx | 5 +- 5 files changed, 114 insertions(+), 104 deletions(-) diff --git a/app/src/Inspector.tsx b/app/src/Inspector.tsx index ee51cbf..d5b24c3 100644 --- a/app/src/Inspector.tsx +++ b/app/src/Inspector.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, Dispatch, SetStateAction } from "react"; import { createPortal } from "react-dom"; -import { PMTiles, Entry } from "../../js"; +import { PMTiles, Entry, tileIdToZxy, TileType } from "../../js"; import { styled } from "./stitches.config"; import { decompressSync } from "fflate"; import Protobuf from "pbf"; @@ -8,7 +8,6 @@ import { VectorTile, VectorTileFeature } from "@mapbox/vector-tile"; import { path } from "d3-path"; import { schemeSet3 } from "d3-scale-chromatic"; import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { introspectTileType, TileType } from "./Loader"; const TableContainer = styled("div", { height: "calc(100vh - $4 - $4)", @@ -44,18 +43,20 @@ const TileRow = (props: { entry: Entry; setSelectedEntry: Dispatch>; }) => { + let [z,x,y] = tileIdToZxy(props.entry.tileId); return ( { props.setSelectedEntry(props.entry); }} > - {props.entry.z} - {props.entry.x} - {props.entry.y} + {props.entry.tileId} + {z} + {x} + {y} {props.entry.offset} {props.entry.length} - {props.entry.is_dir} + {props.entry.runLength} ); }; @@ -178,14 +179,12 @@ const VectorPreview = (props: { useEffect(() => { let fn = async (entry: Entry) => { - let view = await props.file.source.getBytes(entry.offset, entry.length); - if (props.tileType == TileType.MVT_GZ) { - view = new DataView(decompressSync(new Uint8Array(view.buffer)).buffer); - } + let [z,x,y] = tileIdToZxy(entry.tileId); + let resp = await props.file.getZxy(z,x,y); let tile = new VectorTile( new Protobuf( - new Uint8Array(view.buffer, view.byteOffset, view.byteLength) + new Uint8Array(resp!.data) ) ); let newLayers = []; @@ -257,8 +256,9 @@ const RasterPreview = (props: { file: PMTiles; entry: Entry }) => { useEffect(() => { let fn = async (entry: Entry) => { // TODO 0,0,0 is broken - let view = await props.file.source.getBytes(entry.offset, entry.length); - let blob = new Blob([view]); + let [z,x,y] = tileIdToZxy(entry.tileId); + let resp = await props.file.getZxy(z,x,y); + let blob = new Blob([resp!.data]); var imageUrl = window.URL.createObjectURL(blob); setImageSrc(imageUrl); }; @@ -274,14 +274,14 @@ const RasterPreview = (props: { file: PMTiles; entry: Entry }) => { function Inspector(props: { file: PMTiles }) { let [entryRows, setEntryRows] = useState([]); let [selectedEntry, setSelectedEntry] = useState(null); - let [tileType, setTileType] = useState(TileType.UNKNOWN); + let [tileType, setTileType] = useState(TileType.Unknown); useEffect(() => { let fn = async () => { + let header = await props.file.getHeader(); let entries = await props.file.root_entries(); - let tileType = await introspectTileType(props.file); setEntryRows(entries); - setTileType(tileType); + setTileType(header.tileType); }; fn(); @@ -293,7 +293,7 @@ function Inspector(props: { file: PMTiles }) { let tilePreview =
; if (selectedEntry && tileType) { - if (tileType === TileType.MVT || tileType === TileType.MVT_GZ) { + if (tileType === TileType.Mvt) { tilePreview = ( { if (map) { - let tileType = await introspectTileType(props.file); - if (tileType === TileType.PNG || tileType == TileType.JPG) { - currentLayer = rasterLeafletLayer(props.file, { + let header = await props.file.getHeader(); + if (header.tileType === TileType.Png || header.tileType == TileType.Jpeg) { + currentLayer = leafletRasterLayer(props.file, { attribution: '© OpenStreetMap contributors', }); diff --git a/app/src/Loader.tsx b/app/src/Loader.tsx index 78b326d..9b46067 100644 --- a/app/src/Loader.tsx +++ b/app/src/Loader.tsx @@ -10,14 +10,6 @@ import { MagnifyingGlassIcon, ImageIcon } from "@radix-ui/react-icons"; import * as ToolbarPrimitive from "@radix-ui/react-toolbar"; import * as DialogPrimitive from "@radix-ui/react-dialog"; -export enum TileType { - UNKNOWN = 1, - PNG, - JPG, - MVT, - MVT_GZ, -} - const StyledToolbar = styled(ToolbarPrimitive.Root, { display: "flex", height: "$4", @@ -113,25 +105,6 @@ const JsonValue = styled(MetadataValue, { overflowX:"scroll" }) - -export const introspectTileType = async (file: PMTiles): Promise => { - let magic = await file.source.getBytes(512000, 4); - let b0 = magic.getUint8(0); - let b1 = magic.getUint8(1); - let b2 = magic.getUint8(2); - let b3 = magic.getUint8(3); - - if (b0 == 0x89 && b1 == 0x50 && b2 == 0x4e && b3 == 0x47) { - return TileType.PNG; - } else if (b0 == 0xff && b1 == 0xd8 && b2 == 0xff && b3 == 0xe0) { - return TileType.JPG; - } else if (b0 == 0x1f && b1 == 0x8b) { - return TileType.MVT_GZ; - } else { - return TileType.MVT; - } -}; - const Toolbar = StyledToolbar; const ToolbarLink = StyledLink; const ToolbarToggleGroup = StyledToggleGroup; @@ -139,7 +112,6 @@ const ToolbarToggleItem = StyledToggleItem; function Loader(props: { file: PMTiles }) { let [tab, setTab] = useState("inspector"); - let [tileType, setTileType] = useState(TileType.UNKNOWN); let [metadata, setMetadata] = useState<[string, string][]>([]); let [modalOpen, setModalOpen] = useState(false); @@ -155,7 +127,7 @@ function Loader(props: { file: PMTiles }) { useEffect(() => { let pmtiles = props.file; const fetchData = async () => { - let m = await pmtiles.metadata(); + let m = await pmtiles.getMetadata(); let tmp: [string, string][] = []; for (var key in m) { tmp.push([key, m[key]]); diff --git a/app/src/MaplibreMap.tsx b/app/src/MaplibreMap.tsx index 137993b..95af9c5 100644 --- a/app/src/MaplibreMap.tsx +++ b/app/src/MaplibreMap.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from "react"; -import { PMTiles, ProtocolCache } from "../../js"; +import { PMTiles, TileType } from "../../js"; +import { Protocol } from "../../js/adapters" import { styled } from "./stitches.config"; -import { introspectTileType, TileType } from "./Loader"; import maplibregl from "maplibre-gl"; import "maplibre-gl/dist/maplibre-gl.css"; @@ -30,47 +30,83 @@ const rasterStyle = (file: PMTiles) => { }; const vectorStyle = async (file: PMTiles): Promise => { - let metadata = await file.metadata(); + let header = await file.getHeader(); + let metadata = await file.getMetadata(); let layers: any[] = []; + + var tilestats:any; if (metadata.json) { - let root = JSON.parse(metadata.json); - if (root.tilestats) { - for (let layer of root.tilestats.layers) { - if (layer.geometry === "Polygon") { - layers.push({ - id: layer.layer + "_fill", - type: "fill", - source: "source", - "source-layer": layer.layer, - paint: { - "fill-color": "white", - }, - }); - } else if (layer.geometry === "LineString") { - layers.push({ - id: layer.layer + "_stroke", - type: "line", - source: "source", - "source-layer": layer.layer, - paint: { - "line-color": "steelblue", - "line-width": 0.5, - }, - }); - } else { - layers.push({ - id: layer.layer + "_point", - type: "circle", - source: "source", - "source-layer": layer.layer, - paint: { - "circle-color": "red", - }, - }); - } + let j = JSON.parse(metadata.json); + tilestats = j.tilestats; + } else { + tilestats = metadata.tilestats; + } + + if (tilestats) { + for (let layer of tilestats.layers) { + if (layer.geometry === "Polygon") { + layers.push({ + id: layer.layer + "_fill", + type: "fill", + source: "source", + "source-layer": layer.layer, + paint: { + "fill-color": "white", + }, + }); + } else if (layer.geometry === "LineString") { + layers.push({ + id: layer.layer + "_stroke", + type: "line", + source: "source", + "source-layer": layer.layer, + paint: { + "line-color": "steelblue", + "line-width": 0.5, + }, + }); + } else { + layers.push({ + id: layer.layer + "_point", + type: "circle", + source: "source", + "source-layer": layer.layer, + paint: { + "circle-color": "red", + }, + }); } } + } else { + layers.push({ + id:"water", + type:"fill", + source:"source", + "source-layer":"water", + paint: { + "fill-color":"blue" + } + }) + layers.push({ + id:"landuse", + type:"fill", + source:"source", + "source-layer":"landuse", + paint: { + "fill-color":"green" + } + }) + layers.push({ + id:"roads", + type:"line", + source:"source", + "source-layer":"roads", + paint: { + "line-color":"white", + "line-width":0.5 + } + }) } return { @@ -79,7 +115,8 @@ const vectorStyle = async (file: PMTiles): Promise => { source: { type: "vector", tiles: ["pmtiles://" + file.source.getKey() + "/{z}/{x}/{y}"], - maxzoom: 10, + minzoom: header.minZoom, + maxzoom: header.maxZoom }, }, layers: layers, @@ -91,14 +128,15 @@ function MaplibreMap(props: { file: PMTiles }) { let map: maplibregl.Map; useEffect(() => { - let cache = new ProtocolCache(); - maplibregl.addProtocol("pmtiles", cache.protocol); - cache.add(props.file); // this is necessary for non-HTTP sources + let protocol = new Protocol(); + maplibregl.addProtocol("pmtiles", protocol.tileFunc); + protocol.add(props.file); // this is necessary for non-HTTP sources map = new maplibregl.Map({ container: mapContainerRef.current!, + hash: true, zoom: 0, - center: [0, 0], + center: [0,0], style: { version: 8, sources: {}, @@ -116,15 +154,14 @@ function MaplibreMap(props: { file: PMTiles }) { useEffect(() => { let initStyle = async () => { if (map) { - let metadata = await props.file.metadata(); - let bounds = metadata.bounds.split(","); - map.fitBounds([ - [+bounds[0], +bounds[1]], - [+bounds[2], +bounds[3]], - ]); - let tileType = await introspectTileType(props.file); + let header = await props.file.getHeader(); + + // map.fitBounds([ + // [header.minLon, header.minLat], + // [header.maxLon, header.maxLat], + // ]); let style: any; // TODO maplibre types (not any) - if (tileType === TileType.PNG || tileType == TileType.JPG) { + if (header.tileType === TileType.Png || header.tileType == TileType.Jpeg) { map.setStyle(rasterStyle(props.file) as any); } else { let style = await vectorStyle(props.file); diff --git a/app/src/Start.tsx b/app/src/Start.tsx index 3b00a04..caf82ea 100644 --- a/app/src/Start.tsx +++ b/app/src/Start.tsx @@ -1,7 +1,7 @@ import { useState, Dispatch, SetStateAction, useCallback } from "react"; import maplibregl from "maplibre-gl"; import L from "leaflet"; -import { PMTiles, FileSource } from "../../js"; +import { PMTiles, FileAPISource } from "../../js"; import { styled } from "./stitches.config"; import { useDropzone } from "react-dropzone"; @@ -107,8 +107,9 @@ const EXAMPLE_FILES = [ function Start(props: { setFile: Dispatch>; }) { + const onDrop = useCallback((acceptedFiles: File[]) => { - props.setFile(new PMTiles(new FileSource(acceptedFiles[0]))); + props.setFile(new PMTiles(new FileAPISource(acceptedFiles[0]))); }, []); const { acceptedFiles, getRootProps, getInputProps } = useDropzone({