import DataTile from "ol/source/DataTile.js"; import VectorTile from "ol/source/VectorTile.js"; import TileState from "ol/TileState.js"; import { MVT } from "ol/format.js"; import * as pmtiles from "pmtiles"; export 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({ ...options, ...{ state: "loading", }, }); const fetchSource = new pmtiles.FetchSource( options.url, new Headers(options.headers), ); const p = new pmtiles.PMTiles(fetchSource); 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 src = URL.createObjectURL(new Blob([response.data])); const image = await this.loadImage(src); URL.revokeObjectURL(src); return image; }); this.setState("ready"); }); } } export 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) => { this.pmtiles_ .getZxy(z, x, y) .then((tile_result) => { if (tile_result) { const format = tile.getFormat(); tile.setFeatures( format.readFeatures(tile_result.data, { extent: extent, featureProjection: projection, }), ); tile.setState(TileState.LOADED); } else { tile.setFeatures([]); tile.setState(TileState.EMPTY); } }) .catch((err) => { tile.setFeatures([]); tile.setState(TileState.ERROR); }); }); }; constructor(options) { super({ ...options, ...{ state: "loading", url: "pmtiles://" + options.url + "/{z}/{x}/{y}", format: new MVT(), }, }); const fetchSource = new pmtiles.FetchSource( options.url, new Headers(options.headers), ); this.pmtiles_ = new pmtiles.PMTiles(fetchSource); this.pmtiles_.getHeader().then((h) => { this.tileGrid.minZoom = h.minZoom; this.tileGrid.maxZoom = h.maxZoom; this.setTileLoadFunction(this.tileLoadFunction); this.setState("ready"); }); } }