diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index a56f9fd6..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: protomaps diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index f8860183..478f3530 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -18,13 +18,15 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 18.x - - run: cd js && npm install && npm run build + - run: cd js && npm ci && npm run build - run: echo "VITE_GIT_SHA=$(git rev-parse --short HEAD)" >> app/.env - - run: cd app && npm install && ./node_modules/.bin/tsc && npm run prettier-check && ./node_modules/.bin/vite build --base=/PMTiles/ - - run: cd serverless/aws && npm install && npx tsc && npm run build && cp dist/lambda_function.zip ../../app/dist - - run: cd serverless/cloudflare && cp wrangler.toml.example wrangler.toml && npm install && npx tsc && npm run build && cp dist/index.js ../../app/dist + - run: cd app && npm ci && ./node_modules/.bin/tsc && npm run prettier-check && ./node_modules/.bin/vite build + - run: cd serverless/aws && npm ci && npx tsc && npm run biome-check && npm run build-zip && cp dist/lambda_function.zip ../../app/dist && npm run build-cloudformation-stack && cp dist/cloudformation-stack.yaml ../../app/dist + - run: cd serverless/cloudflare && cp wrangler.toml.example wrangler.toml && npm ci && npx tsc && npm run biome-check && npm run build && cp dist/index.js ../../app/dist - run: cd spec/v3 && cp *.pmtiles ../../app/dist - run: cd js/examples && mkdir ../../app/dist/examples && cp *.html ../../app/dist/examples/ + - run: cd js && npm ci && npx typedoc src/index.ts --out ../app/dist/typedoc + - run : cd openlayers && npm ci && npm run tsc - run: cd openlayers/examples && mkdir ../../app/dist/examples/openlayers && cp *.html ../../app/dist/examples/openlayers - name: build_app uses: peaceiris/actions-gh-pages@v3 @@ -33,6 +35,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./app/dist force_orphan: true + cname: pmtiles.io test: runs-on: ubuntu-latest @@ -44,9 +47,9 @@ jobs: with: node-version: 18.x - run: python .github/check_examples.py - - run: cd js && npm install && npm test - - run: cd js && npm run prettier-check + - run: cd js && npm ci && npm test + - run: cd js && npm run biome-check - run: cd python && python -m unittest test/test_* - run: cd cpp && make - - run: cd serverless/cloudflare && npm install && npm test - - run: cd serverless/vtfilter && npm install && npm test + - run: cd serverless/cloudflare && npm ci && npm test + - run: cd serverless/vtfilter && npm ci && npm test diff --git a/README.md b/README.md index 4708dbe2..cfa96e98 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ [![npm](https://img.shields.io/npm/v/pmtiles)](https://www.npmjs.com/package/pmtiles) -[![js minzipped size](https://img.shields.io/bundlephobia/minzip/pmtiles)](https://bundlephobia.com/package/pmtiles) [![pypi](https://img.shields.io/pypi/v/pmtiles)](https://pypi.org/project/pmtiles/) -๐Ÿ”Ž **PMTiles Viewer:** [https://protomaps.github.io/PMTiles/](https://protomaps.github.io/PMTiles/) ๐Ÿ”Ž +๐Ÿ”Ž **PMTiles Viewer:** [https://pmtiles.io/](https://pmtiles.io) ๐Ÿ”Ž # PMTiles @@ -10,10 +9,10 @@ PMTiles is a single-file archive format for tiled data. A PMTiles archive can be * [Protomaps Blog: Dynamic Maps, Static Storage](http://protomaps.com/blog/dynamic-maps-static-storage) -* [PMTiles Viewer](https://protomaps.github.io/PMTiles/) - inspect and preview PMTiles local or remote PMTiles archives. +* [PMTiles Viewer](https://pmtiles.io) - inspect and preview PMTiles local or remote PMTiles archives. * Archives on cloud storage may require CORS for the origin `https://protomaps.github.io` -* [Vector Tiles Example (US Zip Codes)](https://protomaps.github.io/PMTiles/?url=https%3A%2F%2Fr2-public.protomaps.com%2Fprotomaps-sample-datasets%2Fcb_2018_us_zcta510_500k.pmtiles) +* [Vector Tiles Example (US Zip Codes)](https://pmtiles.io/?url=https%3A%2F%2Fr2-public.protomaps.com%2Fprotomaps-sample-datasets%2Fcb_2018_us_zcta510_500k.pmtiles) Demos require MapLibre GL JS v1.15 or later. @@ -26,7 +25,7 @@ See also: Download the `pmtiles` binary for your system at [go-pmtiles/Releases](https://github.com/protomaps/go-pmtiles/releases). pmtiles convert INPUT.mbtiles OUTPUT.pmtiles - pmtiles upload OUTPUT.mbtiles s3://my-bucket?region=us-west-2 // requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY env vars to be set + pmtiles upload OUTPUT.pmtiles s3://my-bucket?region=us-west-2 // requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY env vars to be set ## Consuming PMTiles diff --git a/app/biome.json b/app/biome.json new file mode 100644 index 00000000..a3c305d0 --- /dev/null +++ b/app/biome.json @@ -0,0 +1,20 @@ +{ + "javascript": { + "formatter": { + "trailingComma": "es5" + } + }, + "formatter": { + "indentStyle": "space" + }, + "linter": { + "rules": { + "style": { + "useNamingConvention": {} + }, + "nursery": { + "noUnusedImports": {} + } + } + } +} diff --git a/app/index.html b/app/index.html index b0fddd7c..2cb05b7f 100644 --- a/app/index.html +++ b/app/index.html @@ -8,6 +8,6 @@
- + diff --git a/app/package-lock.json b/app/package-lock.json index 1e0ffef6..3232fa5e 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -20,7 +20,7 @@ "fflate": "^0.7.3", "maplibre-gl": "3.3.1", "pbf": "^3.2.1", - "protomaps-themes-base": "2.0.0-alpha.1", + "protomaps-themes-base": "3.0.1", "react": "^18.0.0", "react-dom": "^18.0.0", "react-dropzone": "^14.1.1", @@ -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", @@ -41,7 +42,7 @@ "@vitejs/plugin-react": "^1.3.0", "prettier": "^2.8.4", "typescript": "^4.6.3", - "vite": "^2.9.16" + "vite": "^3.2.11" } }, "node_modules/@ampproject/remapping": { @@ -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", @@ -604,10 +760,26 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/linux-loong64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", - "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", "cpu": [ "loong64" ], @@ -1867,9 +2039,9 @@ } }, "node_modules/esbuild": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", - "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", "dev": true, "hasInstallScript": true, "bin": { @@ -1879,33 +2051,34 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/linux-loong64": "0.14.54", - "esbuild-android-64": "0.14.54", - "esbuild-android-arm64": "0.14.54", - "esbuild-darwin-64": "0.14.54", - "esbuild-darwin-arm64": "0.14.54", - "esbuild-freebsd-64": "0.14.54", - "esbuild-freebsd-arm64": "0.14.54", - "esbuild-linux-32": "0.14.54", - "esbuild-linux-64": "0.14.54", - "esbuild-linux-arm": "0.14.54", - "esbuild-linux-arm64": "0.14.54", - "esbuild-linux-mips64le": "0.14.54", - "esbuild-linux-ppc64le": "0.14.54", - "esbuild-linux-riscv64": "0.14.54", - "esbuild-linux-s390x": "0.14.54", - "esbuild-netbsd-64": "0.14.54", - "esbuild-openbsd-64": "0.14.54", - "esbuild-sunos-64": "0.14.54", - "esbuild-windows-32": "0.14.54", - "esbuild-windows-64": "0.14.54", - "esbuild-windows-arm64": "0.14.54" + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" } }, "node_modules/esbuild-android-64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", - "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", "cpu": [ "x64" ], @@ -1919,9 +2092,9 @@ } }, "node_modules/esbuild-android-arm64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", - "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", "cpu": [ "arm64" ], @@ -1935,9 +2108,9 @@ } }, "node_modules/esbuild-darwin-64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", - "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", "cpu": [ "x64" ], @@ -1951,9 +2124,9 @@ } }, "node_modules/esbuild-darwin-arm64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", - "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", "cpu": [ "arm64" ], @@ -1967,9 +2140,9 @@ } }, "node_modules/esbuild-freebsd-64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", - "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", "cpu": [ "x64" ], @@ -1983,9 +2156,9 @@ } }, "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", - "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", "cpu": [ "arm64" ], @@ -1999,9 +2172,9 @@ } }, "node_modules/esbuild-linux-32": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", - "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", "cpu": [ "ia32" ], @@ -2015,9 +2188,9 @@ } }, "node_modules/esbuild-linux-64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", - "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", "cpu": [ "x64" ], @@ -2031,9 +2204,9 @@ } }, "node_modules/esbuild-linux-arm": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", - "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", "cpu": [ "arm" ], @@ -2047,9 +2220,9 @@ } }, "node_modules/esbuild-linux-arm64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", - "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", "cpu": [ "arm64" ], @@ -2063,9 +2236,9 @@ } }, "node_modules/esbuild-linux-mips64le": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", - "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", "cpu": [ "mips64el" ], @@ -2079,9 +2252,9 @@ } }, "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", - "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", "cpu": [ "ppc64" ], @@ -2095,9 +2268,9 @@ } }, "node_modules/esbuild-linux-riscv64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", - "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", "cpu": [ "riscv64" ], @@ -2111,9 +2284,9 @@ } }, "node_modules/esbuild-linux-s390x": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", - "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", "cpu": [ "s390x" ], @@ -2127,9 +2300,9 @@ } }, "node_modules/esbuild-netbsd-64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", - "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", "cpu": [ "x64" ], @@ -2143,9 +2316,9 @@ } }, "node_modules/esbuild-openbsd-64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", - "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", "cpu": [ "x64" ], @@ -2159,9 +2332,9 @@ } }, "node_modules/esbuild-sunos-64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", - "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", "cpu": [ "x64" ], @@ -2175,9 +2348,9 @@ } }, "node_modules/esbuild-windows-32": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", - "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", "cpu": [ "ia32" ], @@ -2191,9 +2364,9 @@ } }, "node_modules/esbuild-windows-64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", - "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", "cpu": [ "x64" ], @@ -2207,9 +2380,9 @@ } }, "node_modules/esbuild-windows-arm64": { - "version": "0.14.54", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", - "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", "cpu": [ "arm64" ], @@ -2261,9 +2434,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-loops": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.3.tgz", - "integrity": "sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz", + "integrity": "sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg==" }, "node_modules/fast-shallow-equal": { "version": "1.0.0", @@ -2302,9 +2475,9 @@ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -2316,9 +2489,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -2397,17 +2573,6 @@ "npm": ">=7" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -2416,6 +2581,17 @@ "node": ">=4" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -2491,11 +2667,14 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2858,9 +3037,9 @@ "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" }, "node_modules/protomaps-themes-base": { - "version": "2.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/protomaps-themes-base/-/protomaps-themes-base-2.0.0-alpha.1.tgz", - "integrity": "sha512-eGAiUpBPAohnMvEHoF7NRWp7YuTNk/JsAVJ4733jqNw+/EF6Q5TMjqCOZScG3YSri5NStJg+9Upb95M2AQ3pjw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/protomaps-themes-base/-/protomaps-themes-base-3.0.1.tgz", + "integrity": "sha512-itF0zqLYzEc/fxKdxZyc6D9GFxSnFQi53Hp+vIguuMCrHSveH9ixeRqh8Wv02woM7dRNbbbfOCeHbSWxlCdcxw==" }, "node_modules/quickselect": { "version": "2.0.0", @@ -3083,11 +3262,11 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { - "is-core-module": "^2.8.1", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -3115,9 +3294,9 @@ } }, "node_modules/rollup": { - "version": "2.77.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz", - "integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -3532,31 +3711,37 @@ } }, "node_modules/vite": { - "version": "2.9.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz", - "integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz", + "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", "dev": true, "dependencies": { - "esbuild": "^0.14.27", - "postcss": "^8.4.13", - "resolve": "^1.22.0", - "rollup": ">=2.59.0 <2.78.0" + "esbuild": "^0.15.9", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": ">=12.2.0" + "node": "^14.18.0 || >=16.0.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { + "@types/node": ">= 14", "less": "*", "sass": "*", - "stylus": "*" + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" }, "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, "less": { "optional": true }, @@ -3565,6 +3750,12 @@ }, "stylus": { "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true } } }, diff --git a/app/package.json b/app/package.json index 7d4b1dfa..dbbf75ad 100644 --- a/app/package.json +++ b/app/package.json @@ -22,7 +22,7 @@ "fflate": "^0.7.3", "maplibre-gl": "3.3.1", "pbf": "^3.2.1", - "protomaps-themes-base": "2.0.0-alpha.1", + "protomaps-themes-base": "3.0.1", "react": "^18.0.0", "react-dom": "^18.0.0", "react-dropzone": "^14.1.1", @@ -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", @@ -43,7 +44,7 @@ "@vitejs/plugin-react": "^1.3.0", "prettier": "^2.8.4", "typescript": "^4.6.3", - "vite": "^2.9.16" + "vite": "^3.2.11" }, "overrides": { "react": "$react", diff --git a/app/src/Inspector.tsx b/app/src/Inspector.tsx index b25f3600..5c89edad 100644 --- a/app/src/Inspector.tsx +++ b/app/src/Inspector.tsx @@ -1,13 +1,18 @@ -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/src/index"; +import { styled } from "./stitches.config"; const TableContainer = styled("div", { height: "calc(100vh - $4 - $4)", @@ -15,7 +20,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 +50,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 ( { @@ -59,7 +64,7 @@ const TileRow = (props: { {props.entry.offset} {props.entry.length} - {props.entry.runLength == 0 + {props.entry.runLength === 0 ? "directory" : `tile(${props.entry.runLength})`} @@ -80,7 +85,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 +98,7 @@ const FeatureSvg = (props: { feature: Feature; setSelectedFeature: Dispatch>; }) => { - let [highlighted, setHighlighted] = useState(false); + const [highlighted, setHighlighted] = useState(false); let fill = "none"; let stroke = ""; @@ -103,15 +108,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 +130,7 @@ const FeatureSvg = (props: { onMouseOver={mouseOver} onMouseOut={mouseOut} onMouseDown={mouseDown} - > + /> ); }; @@ -134,12 +139,12 @@ const LayerSvg = (props: { color: string; setSelectedFeature: Dispatch>; }) => { - let elems = props.layer.features.map((f, i) => ( + const elems = props.layer.features.map((f, i) => ( + /> )); return {elems}; }; @@ -153,8 +158,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 +185,44 @@ const VectorPreview = (props: { entry: Entry; tileType: TileType; }) => { - let [layers, setLayers] = useState([]); - let [maxExtent, setMaxExtent] = useState(0); - let [selectedFeature, setSelectedFeature] = useState(null); - const Viewer = useRef(null); + const [layers, setLayers] = useState([]); + const [maxExtent, setMaxExtent] = useState(0); + const [selectedFeature, setSelectedFeature] = useState(null); + const viewer = useRef(null); const [ref, { width, height }] = useMeasure(); 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); - - 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 fn = async (entry: Entry) => { + const [z, x, y] = tileIdToZxy(entry.tileId); + const resp = await props.file.getZxy(z, x, y); + + 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 +240,7 @@ const VectorPreview = (props: { } newLayers.push({ features: features, name: name }); } - setMaxExtent(max_extent); + setMaxExtent(maxExtent); newLayers.sort(smartCompare); setLayers(newLayers); }; @@ -245,19 +250,19 @@ const VectorPreview = (props: { } }, [props.entry]); - let elems = layers.map((l, i) => ( + const elems = layers.map((l, i) => ( + /> )); return ( - + {elems} {selectedFeature ? : null} - + ); }; const RasterPreview = (props: { file: PMTiles; entry: Entry }) => { - let [imgSrc, setImageSrc] = useState(""); + const [imgSrc, setImageSrc] = useState(""); 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 +295,12 @@ const RasterPreview = (props: { file: PMTiles; entry: Entry }) => { } }, [props.entry]); - return ; + return 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 +323,9 @@ function getHashString(entry: Entry) { } function Inspector(props: { file: PMTiles }) { - let [entryRows, setEntryRows] = useState([]); - let [selectedEntry, setSelectedEntryRaw] = useState(null); - let [header, setHeader] = useState
(null); + const [entryRows, setEntryRows] = useState([]); + const [selectedEntry, setSelectedEntryRaw] = useState(null); + const [header, setHeader] = useState
(null); function setSelectedEntry(val: Entry | null) { if (val && val.runLength > 0) { @@ -330,13 +335,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 +349,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 +362,11 @@ function Inspector(props: { file: PMTiles }) { fn(); }, [props.file, selectedEntry]); - let rows = entryRows.map((e, i) => ( - + const rows = entryRows.map((e, i) => ( + )); - let tilePreview =
; + let tilePreview =
; if (selectedEntry && header?.tileType) { if (selectedEntry.runLength === 0) { // do nothing diff --git a/app/src/Loader.tsx b/app/src/Loader.tsx index 53e69582..4c10c9ce 100644 --- a/app/src/Loader.tsx +++ b/app/src/Loader.tsx @@ -1,12 +1,11 @@ -import { useState, useEffect } from "react"; -import { PMTiles } from "../../js/index"; +import { useState } from "react"; +import { PMTiles } from "../../js/src/index"; import { styled } from "./stitches.config"; -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 +27,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,15 +70,13 @@ 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 = ( ); - } else if (tab === "inspector") { - view = ; } else { view = ; } @@ -97,13 +94,13 @@ function Loader(props: { file: PMTiles; mapHashPassed: boolean }) { Map View - - Tile Inspector - Metadata + + ๐Ÿ”Ž Tile Inspector + {props.file.source.getKey()} diff --git a/app/src/MapView.tsx b/app/src/MapView.tsx new file mode 100644 index 00000000..1695416f --- /dev/null +++ b/app/src/MapView.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import reactDom from "react-dom/client"; +import MapViewComponent from "./MapViewComponent"; + +const root = document.getElementById("root"); +if (root) { + reactDom.createRoot(root).render( + + + + ); +} diff --git a/app/src/App.tsx b/app/src/MapViewComponent.tsx similarity index 84% rename from app/src/App.tsx rename to app/src/MapViewComponent.tsx index 494b3511..2596d10f 100644 --- a/app/src/App.tsx +++ b/app/src/MapViewComponent.tsx @@ -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/src/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", @@ -54,18 +54,18 @@ const StyledContent = styled(DialogPrimitive.Content, { const GIT_SHA = (import.meta.env.VITE_GIT_SHA || "").substr(0, 8); -function App() { +function MapViewComponent() { globalStyles(); - let [errorDisplay, setErrorDisplay] = useState(); - let [file, setFile] = useState(); - let [mapHashPassed, setMapHashPassed] = useState(false); + const [errorDisplay, setErrorDisplay] = useState(); + const [file, setFile] = useState(); + const [mapHashPassed, setMapHashPassed] = useState(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) => { + const clear = (event: React.MouseEvent) => { event.preventDefault(); setFile(undefined); }; @@ -136,4 +136,4 @@ function App() { ); } -export default App; +export default MapViewComponent; diff --git a/app/src/MaplibreMap.tsx b/app/src/MaplibreMap.tsx index 50411185..42075020 100644 --- a/app/src/MaplibreMap.tsx +++ b/app/src/MaplibreMap.tsx @@ -1,17 +1,19 @@ -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 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, + 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 baseTheme from "protomaps-themes-base"; +import React, { useState, useEffect, useRef } from "react"; +import { renderToString } from "react-dom/server"; +import { Protocol } from "../../js/src/adapters"; +import { PMTiles, TileType } from "../../js/src/index"; +import { styled } from "./stitches.config"; + +const BASEMAP_THEME = "black"; const INITIAL_ZOOM = 0; const INITIAL_LNG = 0; @@ -33,6 +35,8 @@ const MapContainer = styled("div", { const PopupContainer = styled("div", { color: "black", + maxHeight: "400px", + overflowY: "scroll", }); const FeatureRow = styled("div", { @@ -102,6 +106,13 @@ interface LayerVisibility { visible: boolean; } +interface Metadata { + name?: string; + type?: string; + tilestats?: unknown; + vector_layers: LayerSpecification[]; +} + const LayersVisibilityController = (props: { layers: LayerVisibility[]; onChange: (layers: LayerVisibility[]) => void; @@ -180,12 +191,12 @@ const LayersVisibilityController = (props: { }; const rasterStyle = async (file: PMTiles): Promise => { - let header = await file.getHeader(); - let metadata = await file.getMetadata(); + 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", BASEMAP_THEME); } layers.push({ @@ -199,7 +210,7 @@ const rasterStyle = async (file: PMTiles): Promise => { 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, }, @@ -211,6 +222,7 @@ const rasterStyle = async (file: PMTiles): Promise => { }, }, glyphs: "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf", + sprite: `https://protomaps.github.io/basemaps-assets/sprites/v3/${BASEMAP_THEME}`, layers: layers, }; }; @@ -221,31 +233,23 @@ const vectorStyle = async ( style: StyleSpecification; layersVisibility: LayerVisibility[]; }> => { - let header = await file.getHeader(); - let metadata = await file.getMetadata(); + 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", BASEMAP_THEME); baseOpacity = 0.9; } - var tilestats: any; - var vector_layers: LayerSpecification[]; - if (metadata.json) { - let j = JSON.parse(metadata.json); - tilestats = j.tilestats; - vector_layers = j.vector_layers; - } else { - 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, @@ -267,7 +271,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, @@ -283,7 +287,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, @@ -314,7 +318,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, @@ -330,7 +334,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, })), @@ -338,11 +342,17 @@ const vectorStyle = async ( }; function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) { - let mapContainerRef = useRef(null); - let [hamburgerOpen, setHamburgerOpen] = useState(true); - let [showAttributes, setShowAttributes] = useState(false); - let [showTileBoundaries, setShowTileBoundaries] = useState(false); - let [layersVisibility, setLayersVisibility] = useState([]); + const mapContainerRef = useRef(null); + const [hamburgerOpen, setHamburgerOpen] = useState(true); + const [showAttributes, setShowAttributes] = useState(false); + const [showTileBoundaries, setShowTileBoundaries] = useState(false); + const [layersVisibility, setLayersVisibility] = useState( + [] + ); + const [popupFrozen, setPopupFrozen] = useState(false); + const popupFrozenRef = useRef(); + popupFrozenRef.current = popupFrozen; + const mapRef = useRef(null); const hoveredFeaturesRef = useRef>(new Set()); @@ -382,7 +392,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 @@ -409,6 +419,9 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) { mapRef.current = map; map.on("mousemove", (e) => { + if (popupFrozenRef.current) { + return; + } const hoveredFeatures = hoveredFeaturesRef.current; for (const feature of hoveredFeatures) { map.setFeatureState(feature, { hover: false }); @@ -422,7 +435,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], ]); @@ -435,7 +448,9 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) { hoveredFeatures.add(feature); } - let content = renderToString(); + const content = renderToString( + + ); if (!features.length) { popup.remove(); } else { @@ -445,16 +460,20 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) { } }); + map.on("click", (e) => { + setPopupFrozen((p) => !p); + }); + return () => { map.remove(); }; }, []); 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( @@ -470,12 +489,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); } @@ -487,7 +506,7 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) { return ( -
+
menu {hamburgerOpen ? ( diff --git a/app/src/Metadata.tsx b/app/src/Metadata.tsx index 2fb91091..f184409d 100644 --- a/app/src/Metadata.tsx +++ b/app/src/Metadata.tsx @@ -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/src/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(); - let [header, setHeader] = useState
(null); + const [metadata, setMetadata] = useState(); + const [header, setHeader] = useState
(null); useEffect(() => { - let pmtiles = props.file; + const pmtiles = props.file; const fetchData = async () => { setMetadata(await pmtiles.getMetadata()); setHeader(await pmtiles.getHeader()); diff --git a/app/src/Start.tsx b/app/src/Start.tsx index 4c469f4d..1209e666 100644 --- a/app/src/Start.tsx +++ b/app/src/Start.tsx @@ -1,8 +1,7 @@ -import { useState, Dispatch, SetStateAction, useCallback } from "react"; -import maplibregl from "maplibre-gl"; -import { PMTiles, FileAPISource } 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/src/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", @@ -97,26 +96,26 @@ const ExampleList = styled("div", { }); const EXAMPLE_FILES = [ - "https://r2-public.protomaps.com/protomaps-sample-datasets/protomaps-basemap-opensource-20230408.pmtiles", - "https://protomaps.github.io/PMTiles/protomaps(vector)ODbL_firenze.pmtiles", - "https://protomaps.github.io/PMTiles/stamen_toner(raster)CC-BY+ODbL_z3.pmtiles", + "https://demo-bucket.protomaps.com/v4.pmtiles", + "https://data.source.coop/protomaps/openstreetmap/v4.pmtiles", + "https://pmtiles.io/stamen_toner(raster)CC-BY+ODbL_z3.pmtiles", "https://r2-public.protomaps.com/protomaps-sample-datasets/cb_2018_us_zcta510_500k.pmtiles", - "https://protomaps.github.io/PMTiles/usgs-mt-whitney-8-15-webp-512.pmtiles", + "https://pmtiles.io/usgs-mt-whitney-8-15-webp-512.pmtiles", ]; function Start(props: { setFile: Dispatch>; }) { const onDrop = useCallback((acceptedFiles: File[]) => { - props.setFile(new PMTiles(new FileAPISource(acceptedFiles[0]))); + props.setFile(new PMTiles(new FileSource(acceptedFiles[0]))); }, []); const { acceptedFiles, getRootProps, getInputProps } = useDropzone({ onDrop, }); - let [remoteUrl, setRemoteUrl] = useState(""); - let [selectedExample, setSelectedExample] = useState(1); + const [remoteUrl, setRemoteUrl] = useState(""); + const [selectedExample, setSelectedExample] = useState(1); const onRemoteUrlChangeHandler = ( event: React.ChangeEvent @@ -142,7 +141,7 @@ function Start(props: { id="remoteUrl" placeholder="https://example.com/my_archive.pmtiles" onChange={onRemoteUrlChangeHandler} - > + /> diff --git a/app/src/TileInspect.tsx b/app/src/TileInspect.tsx new file mode 100644 index 00000000..a7def394 --- /dev/null +++ b/app/src/TileInspect.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import reactDom from "react-dom/client"; +import TileInspectComponent from "./TileInspectComponent"; + +const root = document.getElementById("root"); +if (root) { + reactDom.createRoot(root).render( + + + + ); +} diff --git a/app/src/TileInspectComponent.tsx b/app/src/TileInspectComponent.tsx new file mode 100644 index 00000000..3cefcb3a --- /dev/null +++ b/app/src/TileInspectComponent.tsx @@ -0,0 +1,38 @@ +import React, { useState, useEffect } from "react"; +import { PMTiles } from "../../js/src/index"; +import { globalStyles, styled } from "./stitches.config"; +import Inspector from "./Inspector"; +import Start from "./Start"; + +function TileInspectComponent() { + globalStyles(); + + const [file, setFile] = useState(); + + // initial load + useEffect(() => { + const loadUrl = new URLSearchParams(location.search).get("url"); + if (loadUrl) { + const initialValue = new PMTiles(loadUrl); + setFile(initialValue); + } + }, []); + + // maintaining URL state + useEffect(() => { + const url = new URL(window.location.href); + if (file?.source.getKey().startsWith("http")) { + url.searchParams.set("url", file.source.getKey()); + history.pushState(null, "", url.toString()); + } else { + url.searchParams.delete("url"); + history.pushState(null, "", url.toString()); + } + }, [file]); + + return ( +
{file ? : }
+ ); +} + +export default TileInspectComponent; diff --git a/app/src/main.tsx b/app/src/main.tsx deleted file mode 100644 index f46c379c..00000000 --- a/app/src/main.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import App from "./App"; - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - -); diff --git a/app/tileinspect/index.html b/app/tileinspect/index.html new file mode 100644 index 00000000..cf97fa82 --- /dev/null +++ b/app/tileinspect/index.html @@ -0,0 +1,12 @@ + + + + + + Tile Inspect + + +
+ + + diff --git a/app/vite.config.ts b/app/vite.config.ts index eca6e2f3..9d8b149e 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -1,6 +1,15 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { resolve } from "path"; export default defineConfig({ - plugins: [react()] -}) + plugins: [react()], + build: { + rollupOptions: { + input: { + mapview: resolve(__dirname, "index.html"), + tileinspect: resolve(__dirname, "tileinspect/index.html") + }, + }, + }, +}); diff --git a/js/.gitignore b/js/.gitignore index 7d10a01e..8336d142 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -1,4 +1,3 @@ dist +docs node_modules -index.js -index.mjs diff --git a/js/CHANGELOG.md b/js/CHANGELOG.md index 43ccf494..f2933cd8 100644 --- a/js/CHANGELOG.md +++ b/js/CHANGELOG.md @@ -1,3 +1,56 @@ +4.1.0 +* MapLibre `Protocol` constructor takes `errorOnMissingTile` option. [#505] + - Use this only for parity with the overzooming behavior of ZXY tile APIs. + +4.0.1 +* fix iife build via esbuild configuration for fflate browser dependency + +4.0.0 +* remove pmtiles spec v2 support, which reduces bundle size significantly [#287] +* use tsup for creating cjs/esm packages, which fixes typescript usage [#498] +* re-structure files in js project to be more conventional. + +3.2.0 +* MapLibre `Protocol` constructor takes an options object. +* add protocol option `metadata:boolean` that controls whether TileJSON metadata is fetched synchronously on map load. [#247] + * This populates the attribution field and is required for some inspector applications to work. + +3.1.0 +* disable brower caching if Chrome + Windows is detected in user agent to work around https://issues.chromium.org/issues/40542704 [#384, #442, #445] +* add getTileJson to PMTiles [#239] + +3.0.7 +* improve ETag error message [#427] + +3.0.6 +* add CommonJS build fallback for NodeJS projects not using ESM. + +3.0.5 +* fix missing files in dist/ for build systems that bundle .ts files + +3.0.4 +* export DecompressFunc type + +3.0.3 + +* Deprecate `prefetch`-ing the first 16 kb as an option, always true +* Optimize invalidation when etag changes when promises are shared between tile requests. [#90] + +3.0.2 + +* Fix name of script includes (IIFE) name from `index.js` to `pmtiles.js` +* Fix name of ES6 module from `index.mjs` to `index.js`, which fixes bundlers detecting TypeScript types (index.d.ts) + +3.0.1 + +* FileApiSource renamed to FileSource +* package.json defines **ES6 module only** (no CommonJS), fixing issues related to named imports [#317, #248] +* support MapLibre GL v4.x +* `Source` API changed to take ETag, making conditional `If-Match` requests possible [#90] +* FetchSource includes cachebuster logic for browser cache only on ETag change +* Ignore weak ETags, greatly simplify ETag logic +* Internal code has consistent naming and style conventions, change to biome linter [#287] + 2.11.0 * `FetchSource` takes optional 2nd param `Headers` to apply custom headers to all requests. diff --git a/js/README.md b/js/README.md index 05198651..26e88e6f 100644 --- a/js/README.md +++ b/js/README.md @@ -1,27 +1,32 @@ # PMTiles for Browsers + NodeJS +See the [JavaScript API docs](https://pmtiles.io/typedoc/) + the [PMTiles](https://www.npmjs.com/package/pmtiles) package can be included via script tag or ES6 module: ```html - - ``` + +``` + +All the PMTiles exports are available under the global `pmtiles` variable e.g. `pmtiles.PMTiles`. - As an ES6 module: `npm add pmtiles` +As an ES6 module: `npm add pmtiles` - ```js - import * as pmtiles from "pmtiles"; - ``` +```js +import { PMTiles } from "pmtiles"; +``` ### Leaflet: Raster tileset Example of a raster PMTiles archive displayed in Leaflet: ```js -const p = new pmtiles.PMTiles('example.pmtiles') -pmtiles.leafletRasterLayer(p,{attribution:'ยฉ OpenStreetMap'}).addTo(map) +import { PMTiles, leafletRasterLayer } from "pmtiles"; +const p = new PMTiles('example.pmtiles') +leafletRasterLayer(p,{attribution:'ยฉ OpenStreetMap'}).addTo(map) ```` -[Live example](https://protomaps.github.io/PMTiles/examples/leaflet.html) | [Code](https://github.com/protomaps/PMTiles/blob/main/js/examples/leaflet.html) +[Live example](https://pmtiles.io/examples/leaflet.html) | [Code](https://github.com/protomaps/PMTiles/blob/main/js/examples/leaflet.html) ### Leaflet: Vector tileset @@ -32,7 +37,8 @@ See [protomaps-leaflet](https://github.com/protomaps/protomaps-leaflet) Example of a PMTiles archive displayed in MapLibre GL JS: ```js -let protocol = new pmtiles.Protocol(); + import { Protocol } from "pmtiles"; +let protocol = new Protocol(); maplibregl.addProtocol("pmtiles",protocol.tile); var style = { "version": 8, @@ -44,7 +50,7 @@ var style = { ... ``` -[Live example](https://protomaps.github.io/PMTiles/examples/maplibre.html) | [Code](https://github.com/protomaps/PMTiles/blob/main/js/examples/maplibre.html) +[Live example](https://pmtiles.io/examples/maplibre.html) | [Code](https://github.com/protomaps/PMTiles/blob/main/js/examples/maplibre.html) # CORS diff --git a/js/adapters.ts b/js/adapters.ts deleted file mode 100644 index 51a8035a..00000000 --- a/js/adapters.ts +++ /dev/null @@ -1,200 +0,0 @@ -declare const L: any; -declare const window: any; -declare const document: any; - -import { PMTiles, Source, TileType } from "./index"; - -export const leafletRasterLayer = (source: PMTiles, options: any) => { - let loaded = false; - let mimeType: string = ""; - const cls = L.GridLayer.extend({ - createTile: function (coord: any, done: any) { - const el: any = document.createElement("img"); - const controller = new AbortController(); - const signal = controller.signal; - el.cancel = () => { - controller.abort(); - }; - if (!loaded) { - source.getHeader().then((header) => { - if (header.tileType == TileType.Mvt) { - console.error( - "Error: archive contains MVT vector tiles, but leafletRasterLayer is for displaying raster tiles. See https://github.com/protomaps/PMTiles/tree/main/js for details." - ); - } else if (header.tileType == 2) { - mimeType = "image/png"; - } else if (header.tileType == 3) { - mimeType = "image/jpeg"; - } else if (header.tileType == 4) { - mimeType = "image/webp"; - } else if (header.tileType == 5) { - mimeType = "image/avif"; - } - }); - loaded = true; - } - source - .getZxy(coord.z, coord.x, coord.y, signal) - .then((arr) => { - if (arr) { - const blob = new Blob([arr.data], { type: mimeType }); - const imageUrl = window.URL.createObjectURL(blob); - el.src = imageUrl; - el.cancel = null; - done(null, el); - } - }) - .catch((e) => { - if (e.name !== "AbortError") { - throw e; - } - }); - return el; - }, - - _removeTile: function (key: string) { - const tile = this._tiles[key]; - if (!tile) { - return; - } - - if (tile.el.cancel) tile.el.cancel(); - - tile.el.width = 0; - tile.el.height = 0; - tile.el.deleted = true; - L.DomUtil.remove(tile.el); - delete this._tiles[key]; - this.fire("tileunload", { - tile: tile.el, - coords: this._keyToTileCoords(key), - }); - }, - }); - return new cls(options); -}; - -// copied from MapLibre /util/ajax.ts -type RequestParameters = { - url: string; - headers?: any; - method?: "GET" | "POST" | "PUT"; - body?: string; - type?: "string" | "json" | "arrayBuffer" | "image"; - credentials?: "same-origin" | "include"; - collectResourceTiming?: boolean; -}; - -type ResponseCallback = ( - error?: Error | null, - data?: any | null, - cacheControl?: string | null, - expires?: string | null -) => void; - -type Cancelable = { - cancel: () => void; -}; - -export class Protocol { - tiles: Map; - - constructor() { - this.tiles = new Map(); - } - - add(p: PMTiles) { - this.tiles.set(p.source.getKey(), p); - } - - get(url: string) { - return this.tiles.get(url); - } - - tile = ( - params: RequestParameters, - callback: ResponseCallback - ): Cancelable => { - if (params.type == "json") { - const pmtiles_url = params.url.substr(10); - let instance = this.tiles.get(pmtiles_url); - if (!instance) { - instance = new PMTiles(pmtiles_url); - this.tiles.set(pmtiles_url, instance); - } - - instance - .getHeader() - .then((h) => { - const tilejson = { - tiles: [params.url + "/{z}/{x}/{y}"], - minzoom: h.minZoom, - maxzoom: h.maxZoom, - bounds: [h.minLon, h.minLat, h.maxLon, h.maxLat], - }; - callback(null, tilejson, null, null); - }) - .catch((e) => { - callback(e, null, null, null); - }); - - return { - cancel: () => {}, - }; - } else { - const re = new RegExp(/pmtiles:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/); - const result = params.url.match(re); - if (!result) { - throw new Error("Invalid PMTiles protocol URL"); - return { - cancel: () => {}, - }; - } - const pmtiles_url = result[1]; - - let instance = this.tiles.get(pmtiles_url); - if (!instance) { - instance = new PMTiles(pmtiles_url); - this.tiles.set(pmtiles_url, instance); - } - const z = result[2]; - const x = result[3]; - const y = result[4]; - - const controller = new AbortController(); - const signal = controller.signal; - let cancel = () => { - controller.abort(); - }; - - instance.getHeader().then((header) => { - instance! - .getZxy(+z, +x, +y, signal) - .then((resp) => { - if (resp) { - callback( - null, - new Uint8Array(resp.data), - resp.cacheControl, - resp.expires - ); - } else { - if (header.tileType == TileType.Mvt) { - callback(null, new Uint8Array(), null, null); - } else { - callback(null, null, null, null); - } - } - }) - .catch((e) => { - if ((e as Error).name !== "AbortError") { - callback(e, null, null, null); - } - }); - }); - return { - cancel: cancel, - }; - } - }; -} diff --git a/js/biome.json b/js/biome.json new file mode 100644 index 00000000..a3c305d0 --- /dev/null +++ b/js/biome.json @@ -0,0 +1,20 @@ +{ + "javascript": { + "formatter": { + "trailingComma": "es5" + } + }, + "formatter": { + "indentStyle": "space" + }, + "linter": { + "rules": { + "style": { + "useNamingConvention": {} + }, + "nursery": { + "noUnusedImports": {} + } + } + } +} diff --git a/js/examples/leaflet.html b/js/examples/leaflet.html index 3714b570..ad0dad1c 100644 --- a/js/examples/leaflet.html +++ b/js/examples/leaflet.html @@ -2,9 +2,9 @@ PMTiles Leaflet Example - + - + + + +
+ + + diff --git a/js/examples/maplibre_raster_dem.html b/js/examples/maplibre_raster_dem.html index 90a0cfa9..fe5a36f5 100644 --- a/js/examples/maplibre_raster_dem.html +++ b/js/examples/maplibre_raster_dem.html @@ -2,9 +2,9 @@ PMTiles MapLibre Raster DEM Example - - - + + +