mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-02-04 10:51:07 +00:00
Add biome linter and configuration to /app viewer.
This commit is contained in:
20
app/biome.json
Normal file
20
app/biome.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
},
|
||||
"formatter": {
|
||||
"indentStyle": "space"
|
||||
},
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useNamingConvention": {}
|
||||
},
|
||||
"nursery": {
|
||||
"noUnusedImports": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
app/package-lock.json
generated
156
app/package-lock.json
generated
@@ -28,6 +28,7 @@
|
||||
"react-use": "^17.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.5.3",
|
||||
"@maplibre/maplibre-gl-style-spec": "^19.3.1",
|
||||
"@types/d3-path": "^3.0.0",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
@@ -448,6 +449,161 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.5.3.tgz",
|
||||
"integrity": "sha512-yvZCa/g3akwTaAQ7PCwPWDCkZs3Qa5ONg/fgOUT9e6wAWsPftCjLQFPXBeGxPK30yZSSpgEmRCfpGTmVbUjGgg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"biome": "bin/biome"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "1.5.3",
|
||||
"@biomejs/cli-darwin-x64": "1.5.3",
|
||||
"@biomejs/cli-linux-arm64": "1.5.3",
|
||||
"@biomejs/cli-linux-arm64-musl": "1.5.3",
|
||||
"@biomejs/cli-linux-x64": "1.5.3",
|
||||
"@biomejs/cli-linux-x64-musl": "1.5.3",
|
||||
"@biomejs/cli-win32-arm64": "1.5.3",
|
||||
"@biomejs/cli-win32-x64": "1.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.5.3.tgz",
|
||||
"integrity": "sha512-ImU7mh1HghEDyqNmxEZBoMPr8SxekkZuYcs+gynKlNW+TALQs7swkERiBLkG9NR0K1B3/2uVzlvYowXrmlW8hw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.5.3.tgz",
|
||||
"integrity": "sha512-vCdASqYnlpq/swErH7FD6nrFz0czFtK4k/iLgj0/+VmZVjineFPgevOb+Sr9vz0tk0GfdQO60bSpI74zU8M9Dw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.5.3.tgz",
|
||||
"integrity": "sha512-cupBQv0sNF1OKqBfx7EDWMSsKwRrBUZfjXawT4s6hKV6ALq7p0QzWlxr/sDmbKMLOaLQtw2Qgu/77N9rm+f9Rg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.3.tgz",
|
||||
"integrity": "sha512-DYuMizUYUBYfS0IHGjDrOP1RGipqWfMGEvNEJ398zdtmCKLXaUvTimiox5dvx4X15mBK5M2m8wgWUgOP1giUpQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.5.3.tgz",
|
||||
"integrity": "sha512-YQrSArQvcv4FYsk7Q91Yv4uuu5F8hJyORVcv3zsjCLGkjIjx2RhjYLpTL733SNL7v33GmOlZY0eFR1ko38tuUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.3.tgz",
|
||||
"integrity": "sha512-UUHiAnlDqr2Y/LpvshBFhUYMWkl2/Jn+bi3U6jKuav0qWbbBKU/ByHgR4+NBxpKBYoCtWxhnmatfH1bpPIuZMw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.5.3.tgz",
|
||||
"integrity": "sha512-HxatYH7vf/kX9nrD+pDYuV2GI9GV8EFo6cfKkahAecTuZLPxryHx1WEfJthp5eNsE0+09STGkKIKjirP0ufaZA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.5.3.tgz",
|
||||
"integrity": "sha512-fMvbSouZEASU7mZH8SIJSANDm5OqsjgtVXlbUqxwed6BP7uuHRSs396Aqwh2+VoW8fwTpp6ybIUoC9FrzB0kyA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.*"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin": {
|
||||
"version": "11.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"react-use": "^17.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.5.3",
|
||||
"@maplibre/maplibre-gl-style-spec": "^19.3.1",
|
||||
"@types/d3-path": "^3.0.0",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { styled, globalStyles } from "./stitches.config";
|
||||
import { PMTiles } from "../../js/index";
|
||||
import { GitHubLogoIcon } from "@radix-ui/react-icons";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { GitHubLogoIcon } from "@radix-ui/react-icons";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { PMTiles } from "../../js/index";
|
||||
import { globalStyles, styled } from "./stitches.config";
|
||||
|
||||
import Start from "./Start";
|
||||
import Loader from "./Loader";
|
||||
import Start from "./Start";
|
||||
|
||||
const Header = styled("div", {
|
||||
height: "$4",
|
||||
@@ -57,15 +57,15 @@ const GIT_SHA = (import.meta.env.VITE_GIT_SHA || "").substr(0, 8);
|
||||
function App() {
|
||||
globalStyles();
|
||||
|
||||
let [errorDisplay, setErrorDisplay] = useState<string | undefined>();
|
||||
let [file, setFile] = useState<PMTiles | undefined>();
|
||||
let [mapHashPassed, setMapHashPassed] = useState<boolean>(false);
|
||||
const [errorDisplay, setErrorDisplay] = useState<string | undefined>();
|
||||
const [file, setFile] = useState<PMTiles | undefined>();
|
||||
const [mapHashPassed, setMapHashPassed] = useState<boolean>(false);
|
||||
|
||||
// initial load
|
||||
useEffect(() => {
|
||||
const loadUrl = new URLSearchParams(location.search).get("url");
|
||||
if (loadUrl) {
|
||||
let initialValue = new PMTiles(loadUrl);
|
||||
const initialValue = new PMTiles(loadUrl);
|
||||
setFile(initialValue);
|
||||
}
|
||||
if (location.hash.includes("map")) {
|
||||
@@ -84,7 +84,7 @@ function App() {
|
||||
// maintaining URL state
|
||||
useEffect(() => {
|
||||
const url = new URL(window.location.href);
|
||||
if (file && file.source.getKey().startsWith("http")) {
|
||||
if (file?.source.getKey().startsWith("http")) {
|
||||
url.searchParams.set("url", file.source.getKey());
|
||||
history.pushState(null, "", url.toString());
|
||||
} else {
|
||||
@@ -93,7 +93,7 @@ function App() {
|
||||
}
|
||||
}, [file]);
|
||||
|
||||
let clear = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
const clear = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
event.preventDefault();
|
||||
setFile(undefined);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useState, useEffect, useRef, Dispatch, SetStateAction } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { PMTiles, Entry, tileIdToZxy, TileType, Header } from "../../js/index";
|
||||
import { styled } from "./stitches.config";
|
||||
import Protobuf from "pbf";
|
||||
import { VectorTile, VectorTileFeature } from "@mapbox/vector-tile";
|
||||
import { VectorTile } from "@mapbox/vector-tile";
|
||||
import { path } from "d3-path";
|
||||
import { schemeSet3 } from "d3-scale-chromatic";
|
||||
import { useMeasure } from "react-use";
|
||||
import Protobuf from "pbf";
|
||||
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
|
||||
import { UncontrolledReactSVGPanZoom } from "react-svg-pan-zoom";
|
||||
import { useMeasure } from "react-use";
|
||||
import { Entry, Header, PMTiles, TileType, tileIdToZxy } from "../../js/index";
|
||||
import { styled } from "./stitches.config";
|
||||
|
||||
const TableContainer = styled("div", {
|
||||
height: "calc(100vh - $4 - $4)",
|
||||
@@ -15,7 +14,7 @@ const TableContainer = styled("div", {
|
||||
width: "calc(100%/3)",
|
||||
});
|
||||
|
||||
const SVGContainer = styled("div", {
|
||||
const SvgContainer = styled("div", {
|
||||
width: "100%",
|
||||
height: "calc(100vh - $4 - $4)",
|
||||
});
|
||||
@@ -45,7 +44,7 @@ const TileRow = (props: {
|
||||
entry: Entry;
|
||||
setSelectedEntry: (val: Entry | null) => void;
|
||||
}) => {
|
||||
let [z, x, y] = tileIdToZxy(props.entry.tileId);
|
||||
const [z, x, y] = tileIdToZxy(props.entry.tileId);
|
||||
return (
|
||||
<TableRow
|
||||
onClick={() => {
|
||||
@@ -59,7 +58,7 @@ const TileRow = (props: {
|
||||
<td>{props.entry.offset}</td>
|
||||
<td>{props.entry.length}</td>
|
||||
<td>
|
||||
{props.entry.runLength == 0
|
||||
{props.entry.runLength === 0
|
||||
? "directory"
|
||||
: `tile(${props.entry.runLength})`}
|
||||
</td>
|
||||
@@ -80,7 +79,7 @@ interface Feature {
|
||||
properties: any;
|
||||
}
|
||||
|
||||
let smartCompare = (a: Layer, b: Layer): number => {
|
||||
const smartCompare = (a: Layer, b: Layer): number => {
|
||||
if (a.name === "earth") return -4;
|
||||
if (a.name === "water") return -3;
|
||||
if (a.name === "natural") return -2;
|
||||
@@ -93,7 +92,7 @@ const FeatureSvg = (props: {
|
||||
feature: Feature;
|
||||
setSelectedFeature: Dispatch<SetStateAction<Feature | null>>;
|
||||
}) => {
|
||||
let [highlighted, setHighlighted] = useState(false);
|
||||
const [highlighted, setHighlighted] = useState(false);
|
||||
let fill = "none";
|
||||
let stroke = "";
|
||||
|
||||
@@ -103,15 +102,15 @@ const FeatureSvg = (props: {
|
||||
stroke = highlighted ? "white" : "currentColor";
|
||||
}
|
||||
|
||||
let mouseOver = () => {
|
||||
const mouseOver = () => {
|
||||
setHighlighted(true);
|
||||
};
|
||||
|
||||
let mouseOut = () => {
|
||||
const mouseOut = () => {
|
||||
setHighlighted(false);
|
||||
};
|
||||
|
||||
let mouseDown = () => {
|
||||
const mouseDown = () => {
|
||||
props.setSelectedFeature(props.feature);
|
||||
};
|
||||
|
||||
@@ -125,7 +124,7 @@ const FeatureSvg = (props: {
|
||||
onMouseOver={mouseOver}
|
||||
onMouseOut={mouseOut}
|
||||
onMouseDown={mouseDown}
|
||||
></path>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -134,12 +133,12 @@ const LayerSvg = (props: {
|
||||
color: string;
|
||||
setSelectedFeature: Dispatch<SetStateAction<Feature | null>>;
|
||||
}) => {
|
||||
let elems = props.layer.features.map((f, i) => (
|
||||
const elems = props.layer.features.map((f, i) => (
|
||||
<FeatureSvg
|
||||
key={i}
|
||||
feature={f}
|
||||
setSelectedFeature={props.setSelectedFeature}
|
||||
></FeatureSvg>
|
||||
/>
|
||||
));
|
||||
return <g color={props.color}>{elems}</g>;
|
||||
};
|
||||
@@ -153,8 +152,8 @@ const StyledFeatureProperties = styled("div", {
|
||||
});
|
||||
|
||||
const FeatureProperties = (props: { feature: Feature }) => {
|
||||
let tmp: [string, string][] = [];
|
||||
for (var key in props.feature.properties) {
|
||||
const tmp: [string, string][] = [];
|
||||
for (const key in props.feature.properties) {
|
||||
tmp.push([key, props.feature.properties[key]]);
|
||||
}
|
||||
|
||||
@@ -180,44 +179,44 @@ const VectorPreview = (props: {
|
||||
entry: Entry;
|
||||
tileType: TileType;
|
||||
}) => {
|
||||
let [layers, setLayers] = useState<Layer[]>([]);
|
||||
let [maxExtent, setMaxExtent] = useState<number>(0);
|
||||
let [selectedFeature, setSelectedFeature] = useState<Feature | null>(null);
|
||||
const Viewer = useRef<UncontrolledReactSVGPanZoom>(null);
|
||||
const [layers, setLayers] = useState<Layer[]>([]);
|
||||
const [maxExtent, setMaxExtent] = useState<number>(0);
|
||||
const [selectedFeature, setSelectedFeature] = useState<Feature | null>(null);
|
||||
const viewer = useRef<UncontrolledReactSVGPanZoom>(null);
|
||||
const [ref, { width, height }] = useMeasure<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
Viewer.current!.zoomOnViewerCenter(0.1);
|
||||
viewer.current?.zoomOnViewerCenter(0.1);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let fn = async (entry: Entry) => {
|
||||
let [z, x, y] = tileIdToZxy(entry.tileId);
|
||||
let resp = await props.file.getZxy(z, x, y);
|
||||
const fn = async (entry: Entry) => {
|
||||
const [z, x, y] = tileIdToZxy(entry.tileId);
|
||||
const resp = await props.file.getZxy(z, x, y);
|
||||
|
||||
let tile = new VectorTile(new Protobuf(new Uint8Array(resp!.data)));
|
||||
let newLayers = [];
|
||||
let max_extent = 0;
|
||||
for (let [name, layer] of Object.entries(tile.layers)) {
|
||||
if (layer.extent > max_extent) {
|
||||
max_extent = layer.extent;
|
||||
const tile = new VectorTile(new Protobuf(new Uint8Array(resp!.data)));
|
||||
const newLayers = [];
|
||||
let maxExtent = 0;
|
||||
for (const [name, layer] of Object.entries(tile.layers)) {
|
||||
if (layer.extent > maxExtent) {
|
||||
maxExtent = layer.extent;
|
||||
}
|
||||
let features: Feature[] = [];
|
||||
for (var i = 0; i < layer.length; i++) {
|
||||
let feature = layer.feature(i);
|
||||
let p = path();
|
||||
let geom = feature.loadGeometry();
|
||||
const features: Feature[] = [];
|
||||
for (let i = 0; i < layer.length; i++) {
|
||||
const feature = layer.feature(i);
|
||||
const p = path();
|
||||
const geom = feature.loadGeometry();
|
||||
|
||||
if (feature.type === 1) {
|
||||
for (let ring of geom) {
|
||||
for (let pt of ring) {
|
||||
for (const ring of geom) {
|
||||
for (const pt of ring) {
|
||||
p.arc(pt.x, pt.y, 20, 0, 2 * Math.PI);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let ring of geom) {
|
||||
for (const ring of geom) {
|
||||
p.moveTo(ring[0].x, ring[0].y);
|
||||
for (var j = 1; j < ring.length; j++) {
|
||||
for (let j = 1; j < ring.length; j++) {
|
||||
p.lineTo(ring[j].x, ring[j].y);
|
||||
}
|
||||
if (feature.type === 3) {
|
||||
@@ -235,7 +234,7 @@ const VectorPreview = (props: {
|
||||
}
|
||||
newLayers.push({ features: features, name: name });
|
||||
}
|
||||
setMaxExtent(max_extent);
|
||||
setMaxExtent(maxExtent);
|
||||
newLayers.sort(smartCompare);
|
||||
setLayers(newLayers);
|
||||
};
|
||||
@@ -245,19 +244,19 @@ const VectorPreview = (props: {
|
||||
}
|
||||
}, [props.entry]);
|
||||
|
||||
let elems = layers.map((l, i) => (
|
||||
const elems = layers.map((l, i) => (
|
||||
<LayerSvg
|
||||
key={i}
|
||||
layer={l}
|
||||
color={schemeSet3[i % 12]}
|
||||
setSelectedFeature={setSelectedFeature}
|
||||
></LayerSvg>
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<SVGContainer ref={ref}>
|
||||
<SvgContainer ref={ref}>
|
||||
<UncontrolledReactSVGPanZoom
|
||||
ref={Viewer}
|
||||
ref={viewer}
|
||||
width={width}
|
||||
height={height}
|
||||
detectAutoPan={false}
|
||||
@@ -268,20 +267,20 @@ const VectorPreview = (props: {
|
||||
<svg viewBox="0 0 4096 4096">{elems}</svg>
|
||||
</UncontrolledReactSVGPanZoom>
|
||||
{selectedFeature ? <FeatureProperties feature={selectedFeature} /> : null}
|
||||
</SVGContainer>
|
||||
</SvgContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const RasterPreview = (props: { file: PMTiles; entry: Entry }) => {
|
||||
let [imgSrc, setImageSrc] = useState<string>("");
|
||||
const [imgSrc, setImageSrc] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
let fn = async (entry: Entry) => {
|
||||
const fn = async (entry: Entry) => {
|
||||
// TODO 0,0,0 is broken
|
||||
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);
|
||||
const [z, x, y] = tileIdToZxy(entry.tileId);
|
||||
const resp = await props.file.getZxy(z, x, y);
|
||||
const blob = new Blob([resp!.data]);
|
||||
const imageUrl = window.URL.createObjectURL(blob);
|
||||
setImageSrc(imageUrl);
|
||||
};
|
||||
|
||||
@@ -290,12 +289,12 @@ const RasterPreview = (props: { file: PMTiles; entry: Entry }) => {
|
||||
}
|
||||
}, [props.entry]);
|
||||
|
||||
return <img src={imgSrc}></img>;
|
||||
return <img src={imgSrc} alt="raster tile" />;
|
||||
};
|
||||
|
||||
function getHashString(entry: Entry) {
|
||||
const [z, x, y] = tileIdToZxy(entry.tileId);
|
||||
let hash = `${z}/${x}/${y}`;
|
||||
const hash = `${z}/${x}/${y}`;
|
||||
|
||||
const hashName = "inspector";
|
||||
let found = false;
|
||||
@@ -318,9 +317,9 @@ function getHashString(entry: Entry) {
|
||||
}
|
||||
|
||||
function Inspector(props: { file: PMTiles }) {
|
||||
let [entryRows, setEntryRows] = useState<Entry[]>([]);
|
||||
let [selectedEntry, setSelectedEntryRaw] = useState<Entry | null>(null);
|
||||
let [header, setHeader] = useState<Header | null>(null);
|
||||
const [entryRows, setEntryRows] = useState<Entry[]>([]);
|
||||
const [selectedEntry, setSelectedEntryRaw] = useState<Entry | null>(null);
|
||||
const [header, setHeader] = useState<Header | null>(null);
|
||||
|
||||
function setSelectedEntry(val: Entry | null) {
|
||||
if (val && val.runLength > 0) {
|
||||
@@ -330,13 +329,13 @@ function Inspector(props: { file: PMTiles }) {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let fn = async () => {
|
||||
let header = await props.file.getHeader();
|
||||
const fn = async () => {
|
||||
const header = await props.file.getHeader();
|
||||
setHeader(header);
|
||||
if (header.specVersion < 3) {
|
||||
setEntryRows([]);
|
||||
} else if (selectedEntry !== null && selectedEntry.runLength === 0) {
|
||||
let entries = await props.file.cache.getDirectory(
|
||||
const entries = await props.file.cache.getDirectory(
|
||||
props.file.source,
|
||||
header.leafDirectoryOffset + selectedEntry.offset,
|
||||
selectedEntry.length,
|
||||
@@ -344,7 +343,7 @@ function Inspector(props: { file: PMTiles }) {
|
||||
);
|
||||
setEntryRows(entries);
|
||||
} else if (selectedEntry === null) {
|
||||
let entries = await props.file.cache.getDirectory(
|
||||
const entries = await props.file.cache.getDirectory(
|
||||
props.file.source,
|
||||
header.rootDirectoryOffset,
|
||||
header.rootDirectoryLength,
|
||||
@@ -357,11 +356,11 @@ function Inspector(props: { file: PMTiles }) {
|
||||
fn();
|
||||
}, [props.file, selectedEntry]);
|
||||
|
||||
let rows = entryRows.map((e, i) => (
|
||||
<TileRow key={i} entry={e} setSelectedEntry={setSelectedEntry}></TileRow>
|
||||
const rows = entryRows.map((e, i) => (
|
||||
<TileRow key={i} entry={e} setSelectedEntry={setSelectedEntry} />
|
||||
));
|
||||
|
||||
let tilePreview = <div></div>;
|
||||
let tilePreview = <div />;
|
||||
if (selectedEntry && header?.tileType) {
|
||||
if (selectedEntry.runLength === 0) {
|
||||
// do nothing
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { PMTiles } from "../../js/index";
|
||||
import { styled } from "./stitches.config";
|
||||
|
||||
@@ -6,7 +6,7 @@ import Inspector from "./Inspector";
|
||||
import MaplibreMap from "./MaplibreMap";
|
||||
import Metadata from "./Metadata";
|
||||
|
||||
import { MagnifyingGlassIcon, ImageIcon } from "@radix-ui/react-icons";
|
||||
import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
|
||||
import * as ToolbarPrimitive from "@radix-ui/react-toolbar";
|
||||
|
||||
const StyledToolbar = styled(ToolbarPrimitive.Root, {
|
||||
@@ -28,7 +28,7 @@ const itemStyles = {
|
||||
fontSize: "$2",
|
||||
alignItems: "center",
|
||||
"&:hover": { backgroundColor: "$hover", color: "$white" },
|
||||
"&:focus": { position: "relative", boxShadow: `0 0 0 2px blue` },
|
||||
"&:focus": { position: "relative", boxShadow: "0 0 0 2px blue" },
|
||||
};
|
||||
|
||||
const StyledLink = styled(
|
||||
@@ -71,9 +71,9 @@ const ToolbarToggleGroup = StyledToggleGroup;
|
||||
const ToolbarToggleItem = StyledToggleItem;
|
||||
|
||||
function Loader(props: { file: PMTiles; mapHashPassed: boolean }) {
|
||||
let [tab, setTab] = useState("maplibre");
|
||||
const [tab, setTab] = useState("maplibre");
|
||||
|
||||
let view;
|
||||
let view: any;
|
||||
if (tab === "maplibre") {
|
||||
view = (
|
||||
<MaplibreMap file={props.file} mapHashPassed={props.mapHashPassed} />
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { renderToString } from "react-dom/server";
|
||||
import { PMTiles, TileType } from "../../js/index";
|
||||
import { Protocol } from "../../js/adapters";
|
||||
import { styled } from "./stitches.config";
|
||||
import {
|
||||
LayerSpecification,
|
||||
StyleSpecification,
|
||||
} from "@maplibre/maplibre-gl-style-spec";
|
||||
import { schemeSet3 } from "d3-scale-chromatic";
|
||||
import maplibregl from "maplibre-gl";
|
||||
import { MapGeoJSONFeature } from "maplibre-gl";
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
import { schemeSet3 } from "d3-scale-chromatic";
|
||||
import base_theme from "protomaps-themes-base";
|
||||
import {
|
||||
StyleSpecification,
|
||||
LayerSpecification,
|
||||
} from "@maplibre/maplibre-gl-style-spec";
|
||||
import baseTheme from "protomaps-themes-base";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { renderToString } from "react-dom/server";
|
||||
import { Protocol } from "../../js/adapters";
|
||||
import { PMTiles, TileType } from "../../js/index";
|
||||
import { styled } from "./stitches.config";
|
||||
|
||||
const INITIAL_ZOOM = 0;
|
||||
const INITIAL_LNG = 0;
|
||||
@@ -102,7 +102,7 @@ interface LayerVisibility {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface PMTilesMetadata {
|
||||
interface Metadata {
|
||||
name?: string;
|
||||
type?: string;
|
||||
tilestats?: unknown;
|
||||
@@ -187,12 +187,12 @@ const LayersVisibilityController = (props: {
|
||||
};
|
||||
|
||||
const rasterStyle = async (file: PMTiles): Promise<StyleSpecification> => {
|
||||
let header = await file.getHeader();
|
||||
let metadata = (await file.getMetadata()) as PMTilesMetadata;
|
||||
const header = await file.getHeader();
|
||||
const metadata = (await file.getMetadata()) as Metadata;
|
||||
let layers: LayerSpecification[] = [];
|
||||
|
||||
if (metadata.type !== "baselayer") {
|
||||
layers = base_theme("basemap", "black");
|
||||
layers = baseTheme("basemap", "black");
|
||||
}
|
||||
|
||||
layers.push({
|
||||
@@ -206,7 +206,7 @@ const rasterStyle = async (file: PMTiles): Promise<StyleSpecification> => {
|
||||
sources: {
|
||||
source: {
|
||||
type: "raster",
|
||||
tiles: ["pmtiles://" + file.source.getKey() + "/{z}/{x}/{y}"],
|
||||
tiles: [`pmtiles://${file.source.getKey()}/{z}/{x}/{y}`],
|
||||
minzoom: header.minZoom,
|
||||
maxzoom: header.maxZoom,
|
||||
},
|
||||
@@ -228,25 +228,23 @@ const vectorStyle = async (
|
||||
style: StyleSpecification;
|
||||
layersVisibility: LayerVisibility[];
|
||||
}> => {
|
||||
let header = await file.getHeader();
|
||||
let metadata = (await file.getMetadata()) as PMTilesMetadata;
|
||||
const header = await file.getHeader();
|
||||
const metadata = (await file.getMetadata()) as Metadata;
|
||||
let layers: LayerSpecification[] = [];
|
||||
let baseOpacity = 0.35;
|
||||
|
||||
if (metadata.type !== "baselayer") {
|
||||
layers = base_theme("basemap", "black");
|
||||
layers = baseTheme("basemap", "black");
|
||||
baseOpacity = 0.9;
|
||||
}
|
||||
|
||||
var tilestats: any;
|
||||
var vector_layers: LayerSpecification[];
|
||||
tilestats = metadata.tilestats;
|
||||
vector_layers = metadata.vector_layers;
|
||||
const tilestats = metadata.tilestats;
|
||||
const vectorLayers = metadata.vector_layers;
|
||||
|
||||
if (vector_layers) {
|
||||
for (let [i, layer] of vector_layers.entries()) {
|
||||
if (vectorLayers) {
|
||||
for (const [i, layer] of vectorLayers.entries()) {
|
||||
layers.push({
|
||||
id: layer.id + "_fill",
|
||||
id: `${layer.id}_fill`,
|
||||
type: "fill",
|
||||
source: "source",
|
||||
"source-layer": layer.id,
|
||||
@@ -268,7 +266,7 @@ const vectorStyle = async (
|
||||
filter: ["==", ["geometry-type"], "Polygon"],
|
||||
});
|
||||
layers.push({
|
||||
id: layer.id + "_stroke",
|
||||
id: `${layer.id}_stroke`,
|
||||
type: "line",
|
||||
source: "source",
|
||||
"source-layer": layer.id,
|
||||
@@ -284,7 +282,7 @@ const vectorStyle = async (
|
||||
filter: ["==", ["geometry-type"], "LineString"],
|
||||
});
|
||||
layers.push({
|
||||
id: layer.id + "_point",
|
||||
id: `${layer.id}_point`,
|
||||
type: "circle",
|
||||
source: "source",
|
||||
"source-layer": layer.id,
|
||||
@@ -315,7 +313,7 @@ const vectorStyle = async (
|
||||
sources: {
|
||||
source: {
|
||||
type: "vector",
|
||||
tiles: ["pmtiles://" + file.source.getKey() + "/{z}/{x}/{y}"],
|
||||
tiles: [`pmtiles://${file.source.getKey()}/{z}/{x}/{y}`],
|
||||
minzoom: header.minZoom,
|
||||
maxzoom: header.maxZoom,
|
||||
bounds: bounds,
|
||||
@@ -331,7 +329,7 @@ const vectorStyle = async (
|
||||
glyphs: "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf",
|
||||
layers: layers,
|
||||
},
|
||||
layersVisibility: vector_layers.map((l: LayerSpecification) => ({
|
||||
layersVisibility: vectorLayers.map((l: LayerSpecification) => ({
|
||||
id: l.id,
|
||||
visible: true,
|
||||
})),
|
||||
@@ -339,11 +337,13 @@ const vectorStyle = async (
|
||||
};
|
||||
|
||||
function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
|
||||
let mapContainerRef = useRef<HTMLDivElement>(null);
|
||||
let [hamburgerOpen, setHamburgerOpen] = useState<boolean>(true);
|
||||
let [showAttributes, setShowAttributes] = useState<boolean>(false);
|
||||
let [showTileBoundaries, setShowTileBoundaries] = useState<boolean>(false);
|
||||
let [layersVisibility, setLayersVisibility] = useState<LayerVisibility[]>([]);
|
||||
const mapContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [hamburgerOpen, setHamburgerOpen] = useState<boolean>(true);
|
||||
const [showAttributes, setShowAttributes] = useState<boolean>(false);
|
||||
const [showTileBoundaries, setShowTileBoundaries] = useState<boolean>(false);
|
||||
const [layersVisibility, setLayersVisibility] = useState<LayerVisibility[]>(
|
||||
[]
|
||||
);
|
||||
const mapRef = useRef<maplibregl.Map | null>(null);
|
||||
const hoveredFeaturesRef = useRef<Set<MapGeoJSONFeature>>(new Set());
|
||||
|
||||
@@ -383,7 +383,7 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let protocol = new Protocol();
|
||||
const protocol = new Protocol();
|
||||
maplibregl.addProtocol("pmtiles", protocol.tile);
|
||||
protocol.add(props.file); // this is necessary for non-HTTP sources
|
||||
|
||||
@@ -423,7 +423,7 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
|
||||
|
||||
const { x, y } = e.point;
|
||||
const r = 2; // radius around the point
|
||||
var features = map.queryRenderedFeatures([
|
||||
let features = map.queryRenderedFeatures([
|
||||
[x - r, y - r],
|
||||
[x + r, y + r],
|
||||
]);
|
||||
@@ -436,7 +436,9 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
|
||||
hoveredFeatures.add(feature);
|
||||
}
|
||||
|
||||
let content = renderToString(<FeaturesProperties features={features} />);
|
||||
const content = renderToString(
|
||||
<FeaturesProperties features={features} />
|
||||
);
|
||||
if (!features.length) {
|
||||
popup.remove();
|
||||
} else {
|
||||
@@ -452,10 +454,10 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let initStyle = async () => {
|
||||
const initStyle = async () => {
|
||||
if (mapRef.current) {
|
||||
let map = mapRef.current;
|
||||
let header = await props.file.getHeader();
|
||||
const map = mapRef.current;
|
||||
const header = await props.file.getHeader();
|
||||
if (!props.mapHashPassed) {
|
||||
// the map hash was not passed, so auto-detect the initial viewport based on metadata
|
||||
map.fitBounds(
|
||||
@@ -471,12 +473,12 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
|
||||
if (
|
||||
header.tileType === TileType.Png ||
|
||||
header.tileType === TileType.Webp ||
|
||||
header.tileType == TileType.Jpeg
|
||||
header.tileType === TileType.Jpeg
|
||||
) {
|
||||
let style = await rasterStyle(props.file);
|
||||
const style = await rasterStyle(props.file);
|
||||
map.setStyle(style);
|
||||
} else {
|
||||
let { style, layersVisibility } = await vectorStyle(props.file);
|
||||
const { style, layersVisibility } = await vectorStyle(props.file);
|
||||
map.setStyle(style);
|
||||
setLayersVisibility(layersVisibility);
|
||||
}
|
||||
@@ -488,7 +490,7 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
|
||||
|
||||
return (
|
||||
<MapContainer ref={mapContainerRef}>
|
||||
<div ref={mapContainerRef}></div>
|
||||
<div ref={mapContainerRef} />
|
||||
<Hamburger onClick={toggleHamburger}>menu</Hamburger>
|
||||
{hamburgerOpen ? (
|
||||
<Options>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { PMTiles, Header } from "../../js/index";
|
||||
import { styled } from "./stitches.config";
|
||||
import { JsonViewer } from "@textea/json-viewer";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Header, PMTiles } from "../../js/index";
|
||||
import { styled } from "./stitches.config";
|
||||
|
||||
const Padded = styled("div", {
|
||||
padding: "2rem",
|
||||
@@ -13,11 +13,11 @@ const Heading = styled("div", {
|
||||
});
|
||||
|
||||
function Metadata(props: { file: PMTiles }) {
|
||||
let [metadata, setMetadata] = useState<any>();
|
||||
let [header, setHeader] = useState<Header | null>(null);
|
||||
const [metadata, setMetadata] = useState<unknown>();
|
||||
const [header, setHeader] = useState<Header | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let pmtiles = props.file;
|
||||
const pmtiles = props.file;
|
||||
const fetchData = async () => {
|
||||
setMetadata(await pmtiles.getMetadata());
|
||||
setHeader(await pmtiles.getHeader());
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useState, Dispatch, SetStateAction, useCallback } from "react";
|
||||
import maplibregl from "maplibre-gl";
|
||||
import { PMTiles, FileSource } from "../../js/index";
|
||||
import { styled } from "./stitches.config";
|
||||
import { Dispatch, SetStateAction, useCallback, useState } from "react";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { FileSource, PMTiles } from "../../js/index";
|
||||
import { styled } from "./stitches.config";
|
||||
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
|
||||
@@ -13,7 +12,7 @@ const Input = styled("input", {
|
||||
justifyContent: "center",
|
||||
fontSize: "$3",
|
||||
fontFamily: "$sans",
|
||||
"&:focus": { boxShadow: `0 0 0 1px black` },
|
||||
"&:focus": { boxShadow: "0 0 0 1px black" },
|
||||
width: "100%",
|
||||
border: "1px solid $white",
|
||||
padding: "$1",
|
||||
@@ -115,8 +114,8 @@ function Start(props: {
|
||||
onDrop,
|
||||
});
|
||||
|
||||
let [remoteUrl, setRemoteUrl] = useState<string>("");
|
||||
let [selectedExample, setSelectedExample] = useState<number | null>(1);
|
||||
const [remoteUrl, setRemoteUrl] = useState<string>("");
|
||||
const [selectedExample, setSelectedExample] = useState<number | null>(1);
|
||||
|
||||
const onRemoteUrlChangeHandler = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
@@ -142,7 +141,7 @@ function Start(props: {
|
||||
id="remoteUrl"
|
||||
placeholder="https://example.com/my_archive.pmtiles"
|
||||
onChange={onRemoteUrlChangeHandler}
|
||||
></Input>
|
||||
/>
|
||||
<Button color="gray" onClick={onSubmit} disabled={!remoteUrl.trim()}>
|
||||
Load URL
|
||||
</Button>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import reactDom from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
const root = document.getElementById("root");
|
||||
if (root) {
|
||||
reactDom.createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user