protomaps-leaflet
Version:
Vector tile rendering and labeling for [Leaflet](https://github.com/Leaflet/Leaflet).
236 lines (235 loc) • 8.7 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import Point from "@mapbox/point-geometry";
import { VectorTile } from "@mapbox/vector-tile";
import Protobuf from "pbf";
import { PMTiles } from "pmtiles";
export var GeomType;
(function (GeomType) {
GeomType[GeomType["Point"] = 1] = "Point";
GeomType[GeomType["Line"] = 2] = "Line";
GeomType[GeomType["Polygon"] = 3] = "Polygon";
})(GeomType || (GeomType = {}));
export function toIndex(c) {
return `${c.x}:${c.y}:${c.z}`;
}
// reimplement loadGeometry with a scalefactor
// so the general tile rendering case does not need rescaling.
const loadGeomAndBbox = (pbf, geometry, scale) => {
pbf.pos = geometry;
const end = pbf.readVarint() + pbf.pos;
let cmd = 1;
let length = 0;
let x = 0;
let y = 0;
let x1 = Infinity;
let x2 = -Infinity;
let y1 = Infinity;
let y2 = -Infinity;
const lines = [];
let line = [];
while (pbf.pos < end) {
if (length <= 0) {
const cmdLen = pbf.readVarint();
cmd = cmdLen & 0x7;
length = cmdLen >> 3;
}
length--;
if (cmd === 1 || cmd === 2) {
x += pbf.readSVarint() * scale;
y += pbf.readSVarint() * scale;
if (x < x1)
x1 = x;
if (x > x2)
x2 = x;
if (y < y1)
y1 = y;
if (y > y2)
y2 = y;
if (cmd === 1) {
if (line.length > 0)
lines.push(line);
line = [];
}
line.push(new Point(x, y));
}
else if (cmd === 7) {
if (line)
line.push(line[0].clone());
}
else
throw new Error(`unknown command ${cmd}`);
}
if (line)
lines.push(line);
return { geom: lines, bbox: { minX: x1, minY: y1, maxX: x2, maxY: y2 } };
};
function parseTile(buffer, tileSize) {
const v = new VectorTile(new Protobuf(buffer));
const result = new Map();
for (const [key, value] of Object.entries(v.layers)) {
const features = [];
// biome-ignore lint: need to use private fields of vector-tile
const layer = value;
for (let i = 0; i < layer.length; i++) {
const loaded = loadGeomAndBbox(layer.feature(i)._pbf, layer.feature(i)._geometry, tileSize / layer.extent);
let numVertices = 0;
for (const part of loaded.geom)
numVertices += part.length;
features.push({
id: layer.feature(i).id,
geomType: layer.feature(i).type,
geom: loaded.geom,
numVertices: numVertices,
bbox: loaded.bbox,
props: layer.feature(i).properties,
});
}
result.set(key, features);
}
return result;
}
export class PmtilesSource {
constructor(url, shouldCancelZooms) {
if (typeof url === "string") {
this.p = new PMTiles(url);
}
else {
this.p = url;
}
this.zoomaborts = [];
this.shouldCancelZooms = shouldCancelZooms;
}
get(c, tileSize) {
return __awaiter(this, void 0, void 0, function* () {
if (this.shouldCancelZooms) {
this.zoomaborts = this.zoomaborts.filter((za) => {
if (za.z !== c.z) {
za.controller.abort();
return false;
}
return true;
});
}
const controller = new AbortController();
this.zoomaborts.push({ z: c.z, controller: controller });
const signal = controller.signal;
const result = yield this.p.getZxy(c.z, c.x, c.y, signal);
if (result) {
return parseTile(result.data, tileSize);
}
return new Map();
});
}
}
export class ZxySource {
constructor(url, shouldCancelZooms) {
this.url = url;
this.zoomaborts = [];
this.shouldCancelZooms = shouldCancelZooms;
}
get(c, tileSize) {
return __awaiter(this, void 0, void 0, function* () {
if (this.shouldCancelZooms) {
this.zoomaborts = this.zoomaborts.filter((za) => {
if (za.z !== c.z) {
za.controller.abort();
return false;
}
return true;
});
}
const url = this.url
.replace("{z}", c.z.toString())
.replace("{x}", c.x.toString())
.replace("{y}", c.y.toString());
const controller = new AbortController();
this.zoomaborts.push({ z: c.z, controller: controller });
const signal = controller.signal;
return new Promise((resolve, reject) => {
fetch(url, { signal: signal })
.then((resp) => {
return resp.arrayBuffer();
})
.then((buffer) => {
const result = parseTile(buffer, tileSize);
resolve(result);
})
.catch((e) => {
reject(e);
});
});
});
}
}
export class TileCache {
constructor(source, tileSize) {
this.source = source;
this.cache = new Map();
this.inflight = new Map();
this.tileSize = tileSize;
}
get(c) {
return __awaiter(this, void 0, void 0, function* () {
const idx = toIndex(c);
return new Promise((resolve, reject) => {
const entry = this.cache.get(idx);
if (entry) {
entry.used = performance.now();
resolve(entry.data);
}
else {
const ifentry = this.inflight.get(idx);
if (ifentry) {
ifentry.push({ resolve: resolve, reject: reject });
}
else {
this.inflight.set(idx, []);
this.source
.get(c, this.tileSize)
.then((tile) => {
this.cache.set(idx, { used: performance.now(), data: tile });
const ifentry2 = this.inflight.get(idx);
if (ifentry2) {
for (const f of ifentry2) {
f.resolve(tile);
}
}
this.inflight.delete(idx);
resolve(tile);
if (this.cache.size >= 64) {
let minUsed = +Infinity;
let minKey = undefined;
this.cache.forEach((value, key) => {
if (value.used < minUsed) {
minUsed = value.used;
minKey = key;
}
});
if (minKey)
this.cache.delete(minKey);
}
})
.catch((e) => {
const ifentry2 = this.inflight.get(idx);
if (ifentry2) {
for (const f of ifentry2) {
f.reject(e);
}
}
this.inflight.delete(idx);
reject(e);
});
}
}
});
});
}
}