Skip to content

Commit

Permalink
Simplify ol-pmtiles
Browse files Browse the repository at this point in the history
  • Loading branch information
simon04 committed Dec 19, 2024
1 parent d6afc98 commit e5ddc1e
Showing 1 changed file with 64 additions and 121 deletions.
185 changes: 64 additions & 121 deletions src/ol-pmtiles.ts
Original file line number Diff line number Diff line change
@@ -1,143 +1,86 @@
// https://github.com/protomaps/PMTiles/blob/9a53ed849517bf486a6362fd8bc521fc51e16d19/openlayers/src/index.ts
// BSD-3-Clause license
// see import style (.js) at https://openlayers.org/en/latest/examples/data-tiles.html
import { type Data } from "ol/DataTile.js";
import { type FeatureLike } from "ol/Feature";
import { MVT } from "ol/format";
import type RenderFeature from "ol/render/Feature";
import {
type Options as DataTileSourceOptions,
default as DataTileSource,
} from "ol/source/DataTile.js";
import TileState from "ol/TileState.js";
import { MVT } from "ol/format.js";
import type TileSource from "ol/source/Tile.js";
import { type Extent } from "ol/extent.js";
import type Projection from "ol/proj/Projection.js";
import type Tile from "ol/Tile.js";
import type VectorTile from "ol/VectorTile.js";
import {
type Options as VectorTileSourceOptions,
default as VectorTileSource,
} from "ol/source/VectorTile.js";
import type RenderFeature from "ol/render/Feature.js";
import { createXYZ, extentFromProjection } from "ol/tilegrid.js";
import { PMTiles, Header, Source } from "pmtiles";
type Options as VectorTileSourceOptions,
} from "ol/source/VectorTile";
import type Tile from "ol/Tile";
import { createXYZ, extentFromProjection } from "ol/tilegrid";
import TileState from "ol/TileState";
import type VectorTile from "ol/VectorTile";
import { Header, PMTiles, Source } from "pmtiles";

export class PMTilesRasterSource extends DataTileSource {
loadImage = (src: string): Promise<HTMLImageElement> => {
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;
});
};
type Options = VectorTileSourceOptions<RenderFeature> & {
url: string | Source;
};

constructor(options: DataTileSourceOptions & { url: string | Source }) {
export class PMTilesVectorSource extends VectorTileSource {
pmtiles: PMTiles;

constructor(options: Options) {
super({
...options,
...{
state: "loading",
url: "pmtiles://{z}/{x}/{y}",
format: options.format || new MVT(),
},
});

const p = new PMTiles(options.url);
p.getHeader().then((h: Header) => {
const projection =
options.projection === undefined ? "EPSG:3857" : options.projection;
this.tileGrid =
options.tileGrid ||
createXYZ({
extent: extentFromProjection(projection),
maxResolution: options.maxResolution,
minZoom: h.minZoom,
maxZoom: h.maxZoom,
tileSize: options.tileSize,
});
this.setLoader(async (z, x, y): Promise<Data> => {
const response = await p.getZxy(z, x, y);
if (!response) {
return new Uint8Array();
}
const src = URL.createObjectURL(new Blob([response.data]));
const image = await this.loadImage(src);
URL.revokeObjectURL(src);
return image;
});
this.setState("ready");
});
this.pmtiles = new PMTiles(options.url);
this.pmtiles.getHeader().then((h: Header) => this.init(options, h));
}
}

export class PMTilesVectorSource extends VectorTileSource {
pmtiles_: PMTiles;

tileLoadFunction = (tile: Tile, url: string) => {
const vtile = tile as VectorTile;
// 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);
private init(options: Options, h: Header) {
const projection = options.projection || "EPSG:3857";
const extent = options.extent || extentFromProjection(projection);
this.tileGrid =
options.tileGrid ||
createXYZ({
extent,
maxResolution: options.maxResolution,
maxZoom: options.maxZoom !== undefined ? options.maxZoom : h.maxZoom,
minZoom: h.minZoom,
tileSize: options.tileSize || 512,
});
this.setTileLoadFunction((tile: Tile, url: string) => {
// 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);

if (!(result && result.length >= 4)) {
throw Error("Could not parse tile URL");
}
const z = +result[1];
const x = +result[2];
const y = +result[3];
if (!(result && result.length >= 4)) {
throw Error("Could not parse tile URL");
}
const z = +result[1];
const x = +result[2];
const y = +result[3];

vtile.setLoader(
(extent: Extent, resolution: number, projection: Projection) => {
this.pmtiles_
.getZxy(z, x, y)
.then((tile_result) => {
if (tile_result) {
const format = vtile.getFormat();
vtile.setFeatures(
format.readFeatures(tile_result.data, {
extent: extent,
featureProjection: projection,
}),
);
vtile.setState(TileState.LOADED);
} else {
vtile.setFeatures([]);
vtile.setState(TileState.EMPTY);
}
})
.catch((err) => {
const vtile = tile as VectorTile<FeatureLike>;
vtile.setLoader(async (extent, _r, projection) => {
try {
const result = await this.pmtiles.getZxy(z, x, y);
if (!result) {
vtile.setFeatures([]);
vtile.setState(TileState.ERROR);
vtile.setState(TileState.EMPTY);
return;
}
const format = vtile.getFormat();
const features = format.readFeatures(result.data, {
extent,
featureProjection: projection,
});
},
);
};

constructor(
options: VectorTileSourceOptions<RenderFeature> & { url: string | Source },
) {
super({
...options,
...{
state: "loading",
url: "pmtiles://{z}/{x}/{y}",
format: options.format || new MVT(),
},
});

this.pmtiles_ = new PMTiles(options.url);
this.pmtiles_.getHeader().then((h: Header) => {
const projection = options.projection || "EPSG:3857";
const extent = options.extent || extentFromProjection(projection);
this.tileGrid =
options.tileGrid ||
createXYZ({
extent: extent,
maxResolution: options.maxResolution,
maxZoom: options.maxZoom !== undefined ? options.maxZoom : h.maxZoom,
minZoom: h.minZoom,
tileSize: options.tileSize || 512,
});
this.setTileLoadFunction(this.tileLoadFunction);
this.setState("ready");
vtile.setFeatures(features);
vtile.setState(TileState.LOADED);
} catch (e) {
vtile.setFeatures([]);
vtile.setState(TileState.ERROR);
}
});
});
this.setState("ready");
}
}

0 comments on commit e5ddc1e

Please sign in to comment.