inspector app uses new JS client

This commit is contained in:
Brandon Liu
2022-10-05 10:48:00 +08:00
parent f831dd113a
commit d8851a7041
5 changed files with 114 additions and 104 deletions

View File

@@ -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<SetStateAction<Entry | null>>;
}) => {
let [z,x,y] = tileIdToZxy(props.entry.tileId);
return (
<TableRow
onClick={() => {
props.setSelectedEntry(props.entry);
}}
>
<td>{props.entry.z}</td>
<td>{props.entry.x}</td>
<td>{props.entry.y}</td>
<td>{props.entry.tileId}</td>
<td>{z}</td>
<td>{x}</td>
<td>{y}</td>
<td>{props.entry.offset}</td>
<td>{props.entry.length}</td>
<td>{props.entry.is_dir}</td>
<td>{props.entry.runLength}</td>
</TableRow>
);
};
@@ -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<Entry[]>([]);
let [selectedEntry, setSelectedEntry] = useState<Entry | null>(null);
let [tileType, setTileType] = useState<TileType>(TileType.UNKNOWN);
let [tileType, setTileType] = useState<TileType>(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 = <div></div>;
if (selectedEntry && tileType) {
if (tileType === TileType.MVT || tileType === TileType.MVT_GZ) {
if (tileType === TileType.Mvt) {
tilePreview = (
<VectorPreview
file={props.file}

View File

@@ -1,8 +1,8 @@
import { useState, useEffect } from "react";
import { PMTiles, leafletLayer as rasterLeafletLayer } from "../../js";
import { PMTiles, TileType } from "../../js";
import { leafletRasterLayer } from "../../js/adapters";
import { leafletLayer as vectorLeafletLayer } from "protomaps";
import { styled } from "./stitches.config";
import { introspectTileType, TileType } from "./Loader";
import L from "leaflet";
import "leaflet/dist/leaflet.css";
@@ -25,9 +25,9 @@ function LeafletMap(props: { file: PMTiles }) {
if (currentLayer) currentLayer.remove();
let initStyle = async () => {
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:
'© <a href="https://openstreetmap.org">OpenStreetMap</a> contributors',
});

View File

@@ -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<TileType> => {
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>(TileType.UNKNOWN);
let [metadata, setMetadata] = useState<[string, string][]>([]);
let [modalOpen, setModalOpen] = useState<boolean>(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]]);

View File

@@ -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,13 +30,21 @@ const rasterStyle = (file: PMTiles) => {
};
const vectorStyle = async (file: PMTiles): Promise<any> => {
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) {
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",
@@ -70,7 +78,35 @@ const vectorStyle = async (file: PMTiles): Promise<any> => {
});
}
}
} 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<any> => {
source: {
type: "vector",
tiles: ["pmtiles://" + file.source.getKey() + "/{z}/{x}/{y}"],
maxzoom: 10,
minzoom: header.minZoom,
maxzoom: header.maxZoom
},
},
layers: layers,
@@ -91,12 +128,13 @@ 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],
style: {
@@ -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);

View File

@@ -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<SetStateAction<PMTiles | undefined>>;
}) {
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({