UNPKG

protomaps-leaflet

Version:

Vector tile rendering and labeling for [Leaflet](https://github.com/Leaflet/Leaflet).

236 lines (235 loc) 8.7 kB
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); }); } } }); }); } }