From 5209bf5cebe042df4a9198376d63b5b806c23959 Mon Sep 17 00:00:00 2001 From: George Madeley Date: Wed, 31 Jul 2024 11:03:41 +0100 Subject: [PATCH 1/4] fix: added export from themes to index.ts --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index c6180255..f42c5289 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ export * from "./frontends/leaflet"; export * from "./symbolizer"; export * from "./task"; export * from "./default_style/style"; +export * from "./default_style/themes" export * from "./painter"; export * from "./tilecache"; export * from "./view"; From 4472bbe3a0a9d0b81d180c72426ee51db60f7a6e Mon Sep 17 00:00:00 2001 From: George Madeley Date: Wed, 31 Jul 2024 11:08:52 +0100 Subject: [PATCH 2/4] feat: moved LeafletLayer class to global variable and exported it to index.ts --- src/frontends/leaflet.ts | 479 +++++++++++++++++++-------------------- 1 file changed, 239 insertions(+), 240 deletions(-) diff --git a/src/frontends/leaflet.ts b/src/frontends/leaflet.ts index 36f92144..0ecd865f 100644 --- a/src/frontends/leaflet.ts +++ b/src/frontends/leaflet.ts @@ -61,287 +61,286 @@ interface LeafletLayerOptions { backgroundColor?: string; } -const leafletLayer = (options: LeafletLayerOptions = {}): unknown => { - class LeafletLayer extends L.GridLayer { - constructor(options: LeafletLayerOptions = {}) { - if (options.noWrap && !options.bounds) - options.bounds = [ - [-90, -180], - [90, 180], - ]; - if (options.attribution == null) - options.attribution = - 'Protomaps © OpenStreetMap'; - super(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.lastRequestedZ = undefined; - this.tasks = options.tasks || []; +export class LeafletLayer extends L.GridLayer { + constructor(options: LeafletLayerOptions = {}) { + if (options.noWrap && !options.bounds) + options.bounds = [ + [-90, -180], + [90, 180], + ]; + if (options.attribution == null) + options.attribution = + 'Protomaps © OpenStreetMap'; + super(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.lastRequestedZ = undefined; + this.tasks = options.tasks || []; - this.debug = options.debug; - const scratch = document.createElement("canvas").getContext("2d"); - this.scratch = scratch; - this.onTilesInvalidated = (tiles: Set) => { - for (const t of tiles) { - this.rerenderTile(t); - } - }; - this.labelers = new Labelers( - this.scratch, - this.labelRules, - 16, - this.onTilesInvalidated, - ); - this.tileSize = 256 * window.devicePixelRatio; - this.tileDelay = options.tileDelay || 3; - this.lang = options.lang; - } + this.views = sourcesToViews(options); - public async renderTile( - coords: Coords, - element: KeyedHtmlCanvasElement, - key: string, - done = () => {}, - ) { - this.lastRequestedZ = coords.z; - - const promises = []; - for (const [k, v] of this.views) { - const promise = v.getDisplayTile(coords); - promises.push({ key: k, promise: promise }); + this.debug = options.debug; + const scratch = document.createElement("canvas").getContext("2d"); + this.scratch = scratch; + this.onTilesInvalidated = (tiles: Set) => { + for (const t of tiles) { + this.rerenderTile(t); } - const tileResponses = await Promise.all( - promises.map((o) => { - return o.promise.then( - (v: PreparedTile[]) => { - return { status: "fulfilled", value: v, key: o.key }; - }, - (error: Error) => { - return { status: "rejected", reason: error, key: o.key }; - }, - ); - }), - ); + }; + this.labelers = new Labelers( + this.scratch, + this.labelRules, + 16, + this.onTilesInvalidated, + ); + this.tileSize = 256 * window.devicePixelRatio; + this.tileDelay = options.tileDelay || 3; + this.lang = options.lang; + } - const preparedTilemap = new Map(); - for (const tileResponse of tileResponses) { - if (tileResponse.status === "fulfilled") { - preparedTilemap.set(tileResponse.key, [tileResponse.value]); + public async renderTile( + coords: Coords, + element: KeyedHtmlCanvasElement, + key: string, + done = () => {}, + ) { + this.lastRequestedZ = coords.z; + + const promises = []; + for (const [k, v] of this.views) { + const promise = v.getDisplayTile(coords); + promises.push({ key: k, promise: promise }); + } + const tileResponses = await Promise.all( + promises.map((o) => { + return o.promise.then( + (v: PreparedTile[]) => { + return { status: "fulfilled", value: v, key: o.key }; + }, + (error: Error) => { + return { status: "rejected", reason: error, key: o.key }; + }, + ); + }), + ); + + const preparedTilemap = new Map(); + for (const tileResponse of tileResponses) { + if (tileResponse.status === "fulfilled") { + preparedTilemap.set(tileResponse.key, [tileResponse.value]); + } else { + if (tileResponse.reason.name === "AbortError") { + // do nothing } else { - if (tileResponse.reason.name === "AbortError") { - // do nothing - } else { - console.error(tileResponse.reason); - } + console.error(tileResponse.reason); } } + } - if (element.key !== key) return; - if (this.lastRequestedZ !== coords.z) return; - - await Promise.all(this.tasks.map(reflect)); - - if (element.key !== key) return; - if (this.lastRequestedZ !== coords.z) return; + if (element.key !== key) return; + if (this.lastRequestedZ !== coords.z) return; - const layoutTime = this.labelers.add(coords.z, preparedTilemap); + await Promise.all(this.tasks.map(reflect)); - if (element.key !== key) return; - if (this.lastRequestedZ !== coords.z) return; + if (element.key !== key) return; + if (this.lastRequestedZ !== coords.z) return; - const labelData = this.labelers.getIndex(coords.z); + const layoutTime = this.labelers.add(coords.z, preparedTilemap); - if (!this._map) return; // the layer has been removed from the map + if (element.key !== key) return; + if (this.lastRequestedZ !== coords.z) return; - const center = this._map.getCenter().wrap(); - const pixelBounds = this._getTiledPixelBounds(center); - const tileRange = this._pxBoundsToTileRange(pixelBounds); - const tileCenter = tileRange.getCenter(); - const priority = coords.distanceTo(tileCenter) * this.tileDelay; + const labelData = this.labelers.getIndex(coords.z); - await timer(priority); + if (!this._map) return; // the layer has been removed from the map - if (element.key !== key) return; - if (this.lastRequestedZ !== coords.z) return; + const center = this._map.getCenter().wrap(); + const pixelBounds = this._getTiledPixelBounds(center); + const tileRange = this._pxBoundsToTileRange(pixelBounds); + const tileCenter = tileRange.getCenter(); + const priority = coords.distanceTo(tileCenter) * this.tileDelay; - const buf = 16; - const bbox = { - minX: 256 * coords.x - buf, - minY: 256 * coords.y - buf, - maxX: 256 * (coords.x + 1) + buf, - maxY: 256 * (coords.y + 1) + buf, - }; - const origin = new Point(256 * coords.x, 256 * coords.y); + await timer(priority); - element.width = this.tileSize; - element.height = this.tileSize; - const ctx = element.getContext("2d"); - if (!ctx) { - console.error("Failed to get Canvas context"); - return; - } - ctx.setTransform(this.tileSize / 256, 0, 0, this.tileSize / 256, 0, 0); - ctx.clearRect(0, 0, 256, 256); - - if (this.backgroundColor) { - ctx.save(); - ctx.fillStyle = this.backgroundColor; - ctx.fillRect(0, 0, 256, 256); - ctx.restore(); - } + if (element.key !== key) return; + if (this.lastRequestedZ !== coords.z) return; - let paintingTime = 0; - - const paintRules = this.paintRules; - - paintingTime = paint( - ctx, - coords.z, - preparedTilemap, - this.xray ? null : labelData, - paintRules, - bbox, - origin, - false, - this.debug, - ); + const buf = 16; + const bbox = { + minX: 256 * coords.x - buf, + minY: 256 * coords.y - buf, + maxX: 256 * (coords.x + 1) + buf, + maxY: 256 * (coords.y + 1) + buf, + }; + const origin = new Point(256 * coords.x, 256 * coords.y); - if (this.debug) { - ctx.save(); - ctx.fillStyle = this.debug; - ctx.font = "600 12px sans-serif"; - ctx.fillText(`${coords.z} ${coords.x} ${coords.y}`, 4, 14); - - ctx.font = "12px sans-serif"; - let ypos = 28; - for (const [k, v] of preparedTilemap) { - const dt = v[0].dataTile; - ctx.fillText(`${k + (k ? " " : "") + dt.z} ${dt.x} ${dt.y}`, 4, ypos); - ypos += 14; - } + element.width = this.tileSize; + element.height = this.tileSize; + const ctx = element.getContext("2d"); + if (!ctx) { + console.error("Failed to get Canvas context"); + return; + } + ctx.setTransform(this.tileSize / 256, 0, 0, this.tileSize / 256, 0, 0); + ctx.clearRect(0, 0, 256, 256); + + if (this.backgroundColor) { + ctx.save(); + ctx.fillStyle = this.backgroundColor; + ctx.fillRect(0, 0, 256, 256); + ctx.restore(); + } - ctx.font = "600 10px sans-serif"; - if (paintingTime > 8) { - ctx.fillText(`${paintingTime.toFixed()} ms paint`, 4, ypos); - ypos += 14; - } + let paintingTime = 0; + + const paintRules = this.paintRules; + + paintingTime = paint( + ctx, + coords.z, + preparedTilemap, + this.xray ? null : labelData, + paintRules, + bbox, + origin, + false, + this.debug, + ); + + if (this.debug) { + ctx.save(); + ctx.fillStyle = this.debug; + ctx.font = "600 12px sans-serif"; + ctx.fillText(`${coords.z} ${coords.x} ${coords.y}`, 4, 14); + + ctx.font = "12px sans-serif"; + let ypos = 28; + for (const [k, v] of preparedTilemap) { + const dt = v[0].dataTile; + ctx.fillText(`${k + (k ? " " : "") + dt.z} ${dt.x} ${dt.y}`, 4, ypos); + ypos += 14; + } - if (layoutTime > 8) { - ctx.fillText(`${layoutTime.toFixed()} ms layout`, 4, ypos); - } - ctx.strokeStyle = this.debug; + ctx.font = "600 10px sans-serif"; + if (paintingTime > 8) { + ctx.fillText(`${paintingTime.toFixed()} ms paint`, 4, ypos); + ypos += 14; + } - ctx.lineWidth = 0.5; - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.lineTo(0, 256); - ctx.stroke(); + if (layoutTime > 8) { + ctx.fillText(`${layoutTime.toFixed()} ms layout`, 4, ypos); + } + ctx.strokeStyle = this.debug; - ctx.lineWidth = 0.5; - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.lineTo(256, 0); - ctx.stroke(); + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, 256); + ctx.stroke(); - ctx.restore(); - } - done(); - } + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(256, 0); + ctx.stroke(); - public rerenderTile(key: string) { - for (const unwrappedK in this._tiles) { - const wrappedCoord = this._wrapCoords( - this._keyToTileCoords(unwrappedK), - ); - if (key === this._tileCoordsToKey(wrappedCoord)) { - this.renderTile(wrappedCoord, this._tiles[unwrappedK].el, key); - } - } + ctx.restore(); } + done(); + } - // a primitive way to check the features at a certain point. - // it does not support hover states, cursor changes, or changing the style of the selected feature, - // so is only appropriate for debuggging or very basic use cases. - // those features are outside of the scope of this library: - // for fully pickable, interactive features, use MapLibre GL JS instead. - public queryTileFeaturesDebug( - lng: number, - lat: number, - brushSize = 16, - ): Map { - const featuresBySourceName = new Map(); - for (const [sourceName, view] of this.views) { - featuresBySourceName.set( - sourceName, - view.queryFeatures(lng, lat, this._map.getZoom(), brushSize), - ); + public rerenderTile(key: string) { + for (const unwrappedK in this._tiles) { + const wrappedCoord = this._wrapCoords( + this._keyToTileCoords(unwrappedK), + ); + if (key === this._tileCoordsToKey(wrappedCoord)) { + this.renderTile(wrappedCoord, this._tiles[unwrappedK].el, key); } - return featuresBySourceName; } + } - public clearLayout() { - this.labelers = new Labelers( - this.scratch, - this.labelRules, - 16, - this.onTilesInvalidated, + // a primitive way to check the features at a certain point. + // it does not support hover states, cursor changes, or changing the style of the selected feature, + // so is only appropriate for debuggging or very basic use cases. + // those features are outside of the scope of this library: + // for fully pickable, interactive features, use MapLibre GL JS instead. + public queryTileFeaturesDebug( + lng: number, + lat: number, + brushSize = 16, + ): Map { + const featuresBySourceName = new Map(); + for (const [sourceName, view] of this.views) { + featuresBySourceName.set( + sourceName, + view.queryFeatures(lng, lat, this._map.getZoom(), brushSize), ); } + return featuresBySourceName; + } - public rerenderTiles() { - for (const unwrappedK in this._tiles) { - const wrappedCoord = this._wrapCoords( - this._keyToTileCoords(unwrappedK), - ); - const key = this._tileCoordsToKey(wrappedCoord); - this.renderTile(wrappedCoord, this._tiles[unwrappedK].el, key); - } + public clearLayout() { + this.labelers = new Labelers( + this.scratch, + this.labelRules, + 16, + this.onTilesInvalidated, + ); + } + + public rerenderTiles() { + for (const unwrappedK in this._tiles) { + const wrappedCoord = this._wrapCoords( + this._keyToTileCoords(unwrappedK), + ); + const key = this._tileCoordsToKey(wrappedCoord); + this.renderTile(wrappedCoord, this._tiles[unwrappedK].el, key); } + } - public createTile(coords: Coords, showTile: DoneCallback) { - const element = L.DomUtil.create("canvas", "leaflet-tile"); - element.lang = this.lang; + public createTile(coords: Coords, showTile: DoneCallback) { + const element = L.DomUtil.create("canvas", "leaflet-tile"); + element.lang = this.lang; - const key = this._tileCoordsToKey(coords); - element.key = key; + const key = this._tileCoordsToKey(coords); + element.key = key; - this.renderTile(coords, element, key, () => { - showTile(undefined, element); - }); + this.renderTile(coords, element, key, () => { + showTile(undefined, element); + }); - return element; - } + return element; + } - public _removeTile(key: string) { - const tile = this._tiles[key]; - if (!tile) { - return; - } - tile.el.removed = true; - tile.el.key = undefined; - L.DomUtil.removeClass(tile.el, "leaflet-tile-loaded"); - tile.el.width = tile.el.height = 0; - L.DomUtil.remove(tile.el); - delete this._tiles[key]; - this.fire("tileunload", { - tile: tile.el, - coords: this._keyToTileCoords(key), - }); + public _removeTile(key: string) { + const tile = this._tiles[key]; + if (!tile) { + return; } + tile.el.removed = true; + tile.el.key = undefined; + L.DomUtil.removeClass(tile.el, "leaflet-tile-loaded"); + tile.el.width = tile.el.height = 0; + L.DomUtil.remove(tile.el); + delete this._tiles[key]; + this.fire("tileunload", { + tile: tile.el, + coords: this._keyToTileCoords(key), + }); } +} + +export const leafletLayer = (options: LeafletLayerOptions = {}): unknown => { return new LeafletLayer(options); }; - -export { leafletLayer }; From 760dfd74da760bb966100fdaf708f2d797af2679 Mon Sep 17 00:00:00 2001 From: George Madeley Date: Wed, 31 Jul 2024 11:14:50 +0100 Subject: [PATCH 3/4] Fixed diagnostic check --- src/frontends/leaflet.ts | 2 +- src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontends/leaflet.ts b/src/frontends/leaflet.ts index 0ecd865f..24d06100 100644 --- a/src/frontends/leaflet.ts +++ b/src/frontends/leaflet.ts @@ -112,7 +112,7 @@ export class LeafletLayer extends L.GridLayer { coords: Coords, element: KeyedHtmlCanvasElement, key: string, - done = () => {}, + done = () => { }, ) { this.lastRequestedZ = coords.z; diff --git a/src/index.ts b/src/index.ts index f42c5289..294b79f3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ export * from "./frontends/leaflet"; export * from "./symbolizer"; export * from "./task"; export * from "./default_style/style"; -export * from "./default_style/themes" +export * from "./default_style/themes"; export * from "./painter"; export * from "./tilecache"; export * from "./view"; From c316f77beb00cd67f06da9988cae2d256baa5c25 Mon Sep 17 00:00:00 2001 From: George Madeley Date: Wed, 31 Jul 2024 11:20:11 +0100 Subject: [PATCH 4/4] Fixed Formtting Errors --- src/frontends/leaflet.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/frontends/leaflet.ts b/src/frontends/leaflet.ts index 24d06100..51ccaeae 100644 --- a/src/frontends/leaflet.ts +++ b/src/frontends/leaflet.ts @@ -112,7 +112,7 @@ export class LeafletLayer extends L.GridLayer { coords: Coords, element: KeyedHtmlCanvasElement, key: string, - done = () => { }, + done = () => {}, ) { this.lastRequestedZ = coords.z; @@ -261,9 +261,7 @@ export class LeafletLayer extends L.GridLayer { public rerenderTile(key: string) { for (const unwrappedK in this._tiles) { - const wrappedCoord = this._wrapCoords( - this._keyToTileCoords(unwrappedK), - ); + const wrappedCoord = this._wrapCoords(this._keyToTileCoords(unwrappedK)); if (key === this._tileCoordsToKey(wrappedCoord)) { this.renderTile(wrappedCoord, this._tiles[unwrappedK].el, key); } @@ -301,9 +299,7 @@ export class LeafletLayer extends L.GridLayer { public rerenderTiles() { for (const unwrappedK in this._tiles) { - const wrappedCoord = this._wrapCoords( - this._keyToTileCoords(unwrappedK), - ); + const wrappedCoord = this._wrapCoords(this._keyToTileCoords(unwrappedK)); const key = this._tileCoordsToKey(wrappedCoord); this.renderTile(wrappedCoord, this._tiles[unwrappedK].el, key); }