ol-pmtiles 0.0.2: basic first-class support for OpenLayers

This commit is contained in:
Brandon Liu
2023-05-05 17:07:21 +08:00
parent c418539cd8
commit 23a3056a7f
6 changed files with 504 additions and 0 deletions

75
openlayers/README.md Normal file
View File

@@ -0,0 +1,75 @@
# PMTiles for OpenLayers
## Example Usage
Based on the [OpenLayers Quick Start](https://openlayers.org/doc/quickstart.html)
`npm install ol-pmtiles`
### Raster tiles
```js
import "./style.css";
import { Map, View } from "ol";
import WebGLTile from "ol/layer/WebGLTile";
import { PMTilesRasterSource } from "ol-pmtiles";
import { useGeographic } from 'ol/proj';
const rasterLayer = new WebGLTile({
source: new PMTilesRasterSource({
url:"https://r2-public.protomaps.com/protomaps-sample-datasets/terrarium_z9.pmtiles",
attributions:["https://github.com/tilezen/joerd/blob/master/docs/attribution.md"],
tileSize: [512,512]
})
});
useGeographic();
const map = new Map({
target: "map",
layers: [rasterLayer],
view: new View({
center: [0,0],
zoom: 1,
}),
});
```
### Vector tiles
```js
import "./style.css";
import { Map, View } from "ol";
import VectorTile from "ol/layer/VectorTile";
import { PMTilesVectorSource } from "ol-pmtiles";
import { Style, Stroke, Fill } from 'ol/style';
import { useGeographic } from 'ol/proj';
const vectorLayer = new VectorTile({
declutter: true,
source: new PMTilesVectorSource({
url: "https://r2-public.protomaps.com/protomaps-sample-datasets/nz-buildings-v3.pmtiles",
attributions: ["© Land Information New Zealand"],
}),
style: new Style({
stroke: new Stroke({
color: "gray",
width: 1,
}),
fill: new Fill({
color: "rgba(20,20,20,0.9)",
}),
}),
});
useGeographic();
const map = new Map({
target: "map",
layers: [vectorLayer],
view: new View({
center: [172.606201,-43.556510],
zoom: 7,
}),
});
```

302
openlayers/package-lock.json generated Normal file
View File

@@ -0,0 +1,302 @@
{
"name": "ol-pmtiles",
"version": "0.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ol-pmtiles",
"version": "0.0.2",
"license": "BSD-3-Clause",
"dependencies": {
"pmtiles": "^2.7.0"
},
"devDependencies": {},
"peerDependencies": {
"ol": ">=7.3.0"
}
},
"node_modules/@mapbox/jsonlint-lines-primitives": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
"integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@mapbox/mapbox-gl-style-spec": {
"version": "13.28.0",
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz",
"integrity": "sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==",
"peer": true,
"dependencies": {
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
"@mapbox/point-geometry": "^0.1.0",
"@mapbox/unitbezier": "^0.0.0",
"csscolorparser": "~1.0.2",
"json-stringify-pretty-compact": "^2.0.0",
"minimist": "^1.2.6",
"rw": "^1.3.3",
"sort-object": "^0.3.2"
},
"bin": {
"gl-style-composite": "bin/gl-style-composite.js",
"gl-style-format": "bin/gl-style-format.js",
"gl-style-migrate": "bin/gl-style-migrate.js",
"gl-style-validate": "bin/gl-style-validate.js"
}
},
"node_modules/@mapbox/point-geometry": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
"integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==",
"peer": true
},
"node_modules/@mapbox/unitbezier": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz",
"integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==",
"peer": true
},
"node_modules/@petamoriken/float16": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.0.tgz",
"integrity": "sha512-AhVAm6SQ+zgxIiOzwVdUcDmKlu/qU39FiYD2UD6kQQaVenrn0dGZewIghWAENGQsvC+1avLCuT+T2/3Gsp/W3w==",
"peer": true
},
"node_modules/csscolorparser": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz",
"integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==",
"peer": true
},
"node_modules/earcut": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
"peer": true
},
"node_modules/fflate": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="
},
"node_modules/geotiff": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.0.7.tgz",
"integrity": "sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q==",
"peer": true,
"dependencies": {
"@petamoriken/float16": "^3.4.7",
"lerc": "^3.0.0",
"pako": "^2.0.4",
"parse-headers": "^2.0.2",
"quick-lru": "^6.1.1",
"web-worker": "^1.2.0",
"xml-utils": "^1.0.2"
},
"engines": {
"node": ">=10.19"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"peer": true
},
"node_modules/json-stringify-pretty-compact": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz",
"integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==",
"peer": true
},
"node_modules/lerc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz",
"integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==",
"peer": true
},
"node_modules/mapbox-to-css-font": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.2.tgz",
"integrity": "sha512-f+NBjJJY4T3dHtlEz1wCG7YFlkODEjFIYlxDdLIDMNpkSksqTt+l/d4rjuwItxuzkuMFvPyrjzV2lxRM4ePcIA==",
"peer": true
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ol": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/ol/-/ol-7.3.0.tgz",
"integrity": "sha512-08vJE4xITKPazQ9qJjeqYjRngnM9s+1eSv219Pdlrjj3LpLqjEH386ncq+76Dw1oGPGR8eLVEePk7FEd9XqqMw==",
"peer": true,
"dependencies": {
"earcut": "^2.2.3",
"geotiff": "^2.0.7",
"ol-mapbox-style": "^9.2.0",
"pbf": "3.2.1",
"rbush": "^3.0.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/openlayers"
}
},
"node_modules/ol-mapbox-style": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-9.7.0.tgz",
"integrity": "sha512-YX3u8FBJHsRHaoGxmd724Mp5WPTuV7wLQW6zZhcihMuInsSdCX1EiZfU+8IAL7jG0pbgl5YgC0aWE/MXJcUXxg==",
"peer": true,
"dependencies": {
"@mapbox/mapbox-gl-style-spec": "^13.23.1",
"mapbox-to-css-font": "^2.4.1"
}
},
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"peer": true
},
"node_modules/parse-headers": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz",
"integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==",
"peer": true
},
"node_modules/pbf": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
"integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
"peer": true,
"dependencies": {
"ieee754": "^1.1.12",
"resolve-protobuf-schema": "^2.1.0"
},
"bin": {
"pbf": "bin/pbf"
}
},
"node_modules/pmtiles": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-2.7.2.tgz",
"integrity": "sha512-YRdfLlvN5z94F5/e3cnEFE0TymKx6egi0vDx6NJF83rYGNLV6Lr2PsdpWkSo3oVl4e6LVj8RvaWLfpT874QH+Q==",
"dependencies": {
"fflate": "^0.7.3"
}
},
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
"peer": true
},
"node_modules/quick-lru": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz",
"integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==",
"peer": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/quickselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==",
"peer": true
},
"node_modules/rbush": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
"integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
"peer": true,
"dependencies": {
"quickselect": "^2.0.0"
}
},
"node_modules/resolve-protobuf-schema": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
"peer": true,
"dependencies": {
"protocol-buffers-schema": "^3.3.1"
}
},
"node_modules/rw": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"peer": true
},
"node_modules/sort-asc": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz",
"integrity": "sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sort-desc": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.1.1.tgz",
"integrity": "sha512-jfZacW5SKOP97BF5rX5kQfJmRVZP5/adDUTY8fCSPvNcXDVpUEe2pr/iKGlcyZzchRJZrswnp68fgk3qBXgkJw==",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sort-object": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/sort-object/-/sort-object-0.3.2.tgz",
"integrity": "sha512-aAQiEdqFTTdsvUFxXm3umdo04J7MRljoVGbBlkH7BgNsMvVNAJyGj7C/wV1A8wHWAJj/YikeZbfuCKqhggNWGA==",
"peer": true,
"dependencies": {
"sort-asc": "^0.1.0",
"sort-desc": "^0.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/web-worker": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
"integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==",
"peer": true
},
"node_modules/xml-utils": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.7.0.tgz",
"integrity": "sha512-bWB489+RQQclC7A9OW8e5BzbT8Tu//jtAOvkYwewFr+Q9T9KDGvfzC1lp0pYPEQPEoPQLDkmxkepSC/2gIAZGw==",
"peer": true
}
}
}

View File

@@ -0,0 +1,34 @@
{
"name": "ol-pmtiles",
"version": "0.0.2",
"description": "PMTiles sources for OpenLayers",
"type": "module",
"main": "src/index.js",
"files": [
"src/*"
],
"repository": {
"type": "git",
"url": "git://github.com/protomaps/PMTiles.git"
},
"bugs": {
"url": "https://github.com/protomaps/PMTiles/issues"
},
"keywords": [
"openlayers",
"pmtiles"
],
"license": "BSD-3-Clause",
"scripts": {
"prettier": "prettier --write *.js",
"prettier-check": "prettier --check *.js"
},
"dependencies": {
"pmtiles": "^2.7.0"
},
"devDependencies": {
},
"peerDependencies": {
"ol": ">=7.3.0"
}
}

View File

@@ -0,0 +1,38 @@
import DataTile from "ol/source/DataTile";
import * as pmtiles from "pmtiles";
class PMTilesRasterSource extends DataTile {
loadImage = (src) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener("load", () => resolve(img));
img.addEventListener("error", () => reject(new Error("load failed")));
img.src = src;
});
};
constructor(options) {
super({
state: "loading",
attributions: options.attributions,
tileSize: options.tileSize,
});
const p = new pmtiles.PMTiles(options.url);
p.getHeader().then((h) => {
this.tileGrid.minZoom = h.minZoom;
this.tileGrid.maxZoom = h.maxZoom;
this.setLoader(async (z, x, y) => {
const response = await p.getZxy(z, x, y);
const blob = new Blob([response.data]);
const src = URL.createObjectURL(blob);
const image = await this.loadImage(src);
URL.revokeObjectURL(src);
return image;
});
this.setState("ready");
});
}
}
export default PMTilesRasterSource;

View File

@@ -0,0 +1,53 @@
import VectorTile from "ol/source/VectorTile";
import TileState from "ol/TileState";
import { MVT } from "ol/format";
import * as pmtiles from "pmtiles";
class PMTilesVectorSource extends VectorTile {
tileLoadFunction = (tile, url) => {
// the URL construction is done internally by OL, so we need to parse it
// back out here using a hacky regex
const re = new RegExp(/pmtiles:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/);
const result = url.match(re);
const z = +result[2];
const x = +result[3];
const y = +result[4];
tile.setLoader((extent, resolution, projection) => {
tile.setState(TileState.LOADING);
this._p.getZxy(z, x, y).then((tile_result) => {
if (tile_result) {
const format = tile.getFormat();
const features = format.readFeatures(tile_result.data.buffer, {
extent: extent,
featureProjection: projection,
});
tile.setFeatures(features);
tile.setState(TileState.LOADED);
} else {
tile.setFeatures([]);
tile.setState(TileState.EMPTY);
} // TODO error state
});
});
};
constructor(options) {
super({
state: "loading",
url: "pmtiles://" + options.url + "/{z}/{x}/{y}",
format: new MVT(),
attributions: options.attributions,
});
this._p = new pmtiles.PMTiles(options.url);
this._p.getHeader().then((h) => {
this.tileGrid.minZoom = h.minZoom;
this.tileGrid.maxZoom = h.maxZoom;
this.setTileLoadFunction(this.tileLoadFunction);
this.setState("ready");
});
}
}
export default PMTilesVectorSource;

2
openlayers/src/index.js Normal file
View File

@@ -0,0 +1,2 @@
export { default as PMTilesRasterSource } from './PMTilesRasterSource.js';
export { default as PMTilesVectorSource } from './PMTilesVectorSource.js';