UNPKG

protomaps-leaflet

Version:

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

173 lines (172 loc) 7.97 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 { labelRules, paintRules } from "../default_style/style"; import themes from "../default_style/themes"; import { Labeler } from "../labeler"; import { paint } from "../painter"; import { sourcesToViews } from "../view"; const R = 6378137; const MAX_LATITUDE = 85.0511287798; const MAXCOORD = R * Math.PI; const project = (latlng) => { const d = Math.PI / 180; const constrainedLat = Math.max(Math.min(MAX_LATITUDE, latlng.y), -MAX_LATITUDE); const sin = Math.sin(constrainedLat * d); return new Point(R * latlng.x * d, (R * Math.log((1 + sin) / (1 - sin))) / 2); }; const unproject = (point) => { const d = 180 / Math.PI; return { lat: (2 * Math.atan(Math.exp(point.y / R)) - Math.PI / 2) * d, lng: (point.x * d) / R, }; }; const instancedProject = (origin, displayZoom) => { return (latlng) => { const projected = project(latlng); const normalized = new Point((projected.x + MAXCOORD) / (MAXCOORD * 2), 1 - (projected.y + MAXCOORD) / (MAXCOORD * 2)); return normalized.mult((1 << displayZoom) * 256).sub(origin); }; }; const instancedUnproject = (origin, displayZoom) => { return (point) => { const normalized = new Point(point.x, point.y) .add(origin) .div((1 << displayZoom) * 256); const projected = new Point(normalized.x * (MAXCOORD * 2) - MAXCOORD, (1 - normalized.y) * (MAXCOORD * 2) - MAXCOORD); return unproject(projected); }; }; export const getZoom = (degreesLng, cssPixels) => { const d = cssPixels * (360 / degreesLng); return Math.log2(d / 256); }; export class Static { constructor(options) { if (options.theme) { const theme = themes[options.theme]; this.paintRules = paintRules(theme); this.labelRules = labelRules(theme); this.backgroundColor = theme.background; } else { this.paintRules = options.paintRules || []; this.labelRules = options.labelRules || []; this.backgroundColor = options.backgroundColor; } this.views = sourcesToViews(options); this.debug = options.debug || ""; } drawContext(ctx, width, height, latlng, displayZoom) { return __awaiter(this, void 0, void 0, function* () { const center = project(latlng); const normalizedCenter = new Point((center.x + MAXCOORD) / (MAXCOORD * 2), 1 - (center.y + MAXCOORD) / (MAXCOORD * 2)); // the origin of the painter call in global Z coordinates const origin = normalizedCenter .clone() .mult(Math.pow(2, displayZoom) * 256) .sub(new Point(width / 2, height / 2)); // the bounds of the painter call in global Z coordinates const bbox = { minX: origin.x, minY: origin.y, maxX: origin.x + width, maxY: origin.y + height, }; const promises = []; for (const [k, v] of this.views) { const promise = v.getBbox(displayZoom, bbox); promises.push({ key: k, promise: promise }); } const tileResponses = yield Promise.all(promises.map((o) => { return o.promise.then((v) => { return { status: "fulfilled", value: v, key: o.key }; }, (error) => { return { status: "rejected", value: [], reason: error, key: o.key }; }); })); const preparedTilemap = new Map(); for (const tileResponse of tileResponses) { if (tileResponse.status === "fulfilled") { preparedTilemap.set(tileResponse.key, tileResponse.value); } } const start = performance.now(); const labeler = new Labeler(displayZoom, ctx, this.labelRules, 16, undefined); // because need ctx to measure const layoutTime = labeler.add(preparedTilemap); if (this.backgroundColor) { ctx.save(); ctx.fillStyle = this.backgroundColor; ctx.fillRect(0, 0, width, height); ctx.restore(); } const paintRules = this.paintRules; const p = paint(ctx, displayZoom, preparedTilemap, labeler.index, paintRules, bbox, origin, true, this.debug); if (this.debug) { ctx.save(); ctx.translate(-origin.x, -origin.y); ctx.strokeStyle = this.debug; ctx.fillStyle = this.debug; ctx.font = "12px sans-serif"; let idx = 0; for (const [k, v] of preparedTilemap) { for (const preparedTile of v) { ctx.strokeRect(preparedTile.origin.x, preparedTile.origin.y, preparedTile.dim, preparedTile.dim); const dt = preparedTile.dataTile; ctx.fillText(`${k + (k ? " " : "") + dt.z} ${dt.x} ${dt.y}`, preparedTile.origin.x + 4, preparedTile.origin.y + 14 * (1 + idx)); } idx++; } ctx.restore(); } // TODO this API isn't so elegant return { elapsed: performance.now() - start, project: instancedProject(origin, displayZoom), unproject: instancedUnproject(origin, displayZoom), }; }); } drawCanvas(canvas, latlng, displayZoom, options = {}) { return __awaiter(this, void 0, void 0, function* () { const dpr = window.devicePixelRatio; const width = canvas.clientWidth; const height = canvas.clientHeight; if (!(canvas.width === width * dpr && canvas.height === height * dpr)) { canvas.width = width * dpr; canvas.height = height * dpr; } if (options.lang) canvas.lang = options.lang; const ctx = canvas.getContext("2d"); if (!ctx) { console.error("Failed to initialize canvas2d context."); return; } ctx.setTransform(dpr, 0, 0, dpr, 0, 0); return this.drawContext(ctx, width, height, latlng, displayZoom); }); } drawContextBounds(ctx, topLeft, bottomRight, width, height) { return __awaiter(this, void 0, void 0, function* () { const deltaDegrees = bottomRight.x - topLeft.x; const center = new Point((topLeft.x + bottomRight.x) / 2, (topLeft.y + bottomRight.y) / 2); return this.drawContext(ctx, width, height, center, getZoom(deltaDegrees, width)); }); } drawCanvasBounds(canvas, topLeft, bottomRight, width, options = {}) { return __awaiter(this, void 0, void 0, function* () { const deltaDegrees = bottomRight.x - topLeft.x; const center = new Point((topLeft.x + bottomRight.x) / 2, (topLeft.y + bottomRight.y) / 2); return this.drawCanvas(canvas, center, getZoom(deltaDegrees, width), options); }); } }