@perspective-dev/viewer-charts 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +193 -0
- package/dist/cdn/perspective-viewer-charts.js +3 -0
- package/dist/cdn/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/axis/axis-primitives.d.ts +24 -0
- package/dist/esm/axis/bar-axis.d.ts +51 -0
- package/dist/esm/axis/canvas.d.ts +24 -0
- package/dist/esm/axis/categorical-axis-core.d.ts +42 -0
- package/dist/esm/axis/categorical-axis.d.ts +27 -0
- package/dist/esm/axis/facet-chrome.d.ts +13 -0
- package/dist/esm/axis/label-geometry.d.ts +41 -0
- package/dist/esm/axis/legend.d.ts +44 -0
- package/dist/esm/axis/numeric-axis.d.ts +20 -0
- package/dist/esm/charts/candlestick/candlestick-build.d.ts +129 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +10 -0
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +24 -0
- package/dist/esm/charts/candlestick/candlestick.d.ts +144 -0
- package/dist/esm/charts/candlestick/glyphs/draw-candlesticks.d.ts +36 -0
- package/dist/esm/charts/candlestick/glyphs/draw-ohlc.d.ts +33 -0
- package/dist/esm/charts/canvas-types.d.ts +15 -0
- package/dist/esm/charts/cartesian/cartesian-build.d.ts +14 -0
- package/dist/esm/charts/cartesian/cartesian-interact.d.ts +20 -0
- package/dist/esm/charts/cartesian/cartesian-render.d.ts +26 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +239 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +53 -0
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +142 -0
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +23 -0
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +24 -0
- package/dist/esm/charts/cartesian/label-interner.d.ts +21 -0
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +11 -0
- package/dist/esm/charts/chart-base.d.ts +402 -0
- package/dist/esm/charts/chart.d.ts +338 -0
- package/dist/esm/charts/common/band-layout.d.ts +32 -0
- package/dist/esm/charts/common/categorical-y-chart.d.ts +53 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +90 -0
- package/dist/esm/charts/common/chrome-cache.d.ts +18 -0
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +9 -0
- package/dist/esm/charts/common/leaf-color.d.ts +33 -0
- package/dist/esm/charts/common/node-store.d.ts +81 -0
- package/dist/esm/charts/common/tree-chart.d.ts +48 -0
- package/dist/esm/charts/common/tree-chrome.d.ts +31 -0
- package/dist/esm/charts/common/tree-data.d.ts +54 -0
- package/dist/esm/charts/common/visible-extent.d.ts +51 -0
- package/dist/esm/charts/heatmap/heatmap-build.d.ts +86 -0
- package/dist/esm/charts/heatmap/heatmap-interact.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-render.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-y-axis.d.ts +46 -0
- package/dist/esm/charts/heatmap/heatmap.d.ts +117 -0
- package/dist/esm/charts/map/map.d.ts +67 -0
- package/dist/esm/charts/registry.d.ts +14 -0
- package/dist/esm/charts/series/glyphs/draw-areas.d.ts +30 -0
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +15 -0
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +34 -0
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +33 -0
- package/dist/esm/charts/series/series-build.d.ts +228 -0
- package/dist/esm/charts/series/series-interact.d.ts +35 -0
- package/dist/esm/charts/series/series-render.d.ts +41 -0
- package/dist/esm/charts/series/series-type.d.ts +49 -0
- package/dist/esm/charts/series/series.d.ts +317 -0
- package/dist/esm/charts/sunburst/sunburst-interact.d.ts +7 -0
- package/dist/esm/charts/sunburst/sunburst-layout.d.ts +33 -0
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +22 -0
- package/dist/esm/charts/sunburst/sunburst.d.ts +85 -0
- package/dist/esm/charts/treemap/treemap-interact.d.ts +12 -0
- package/dist/esm/charts/treemap/treemap-layout.d.ts +28 -0
- package/dist/esm/charts/treemap/treemap-render.d.ts +18 -0
- package/dist/esm/charts/treemap/treemap.d.ts +74 -0
- package/dist/esm/config.d.ts +27 -0
- package/dist/esm/data/lazy-row.d.ts +32 -0
- package/dist/esm/data/split-groups.d.ts +20 -0
- package/dist/esm/data/view-reader.d.ts +35 -0
- package/dist/esm/event-detail.d.ts +28 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/interaction/hit-test.d.ts +30 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +19 -0
- package/dist/esm/interaction/host-sink-message.d.ts +46 -0
- package/dist/esm/interaction/lazy-tooltip.d.ts +61 -0
- package/dist/esm/interaction/raw-event-forwarder.d.ts +27 -0
- package/dist/esm/interaction/spatial-grid.d.ts +15 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +193 -0
- package/dist/esm/interaction/zoom-controller.d.ts +106 -0
- package/dist/esm/interaction/zoom-router.d.ts +48 -0
- package/dist/esm/layout/facet-grid.d.ts +126 -0
- package/dist/esm/layout/plot-layout.d.ts +104 -0
- package/dist/esm/layout/ticks.d.ts +17 -0
- package/dist/esm/map/mercator.d.ts +102 -0
- package/dist/esm/map/tile-cache.d.ts +38 -0
- package/dist/esm/map/tile-layer.d.ts +66 -0
- package/dist/esm/map/tile-loader.d.ts +52 -0
- package/dist/esm/map/tile-source.d.ts +66 -0
- package/dist/esm/perspective-viewer-charts.js +3 -0
- package/dist/esm/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/plugin/charts.d.ts +40 -0
- package/dist/esm/plugin/plugin.d.ts +95 -0
- package/dist/esm/render/scheduler.d.ts +41 -0
- package/dist/esm/theme/gradient.d.ts +48 -0
- package/dist/esm/theme/palette.d.ts +13 -0
- package/dist/esm/theme/theme-snapshot.d.ts +7 -0
- package/dist/esm/theme/theme.d.ts +53 -0
- package/dist/esm/transport/protocol.d.ts +430 -0
- package/dist/esm/transport/renderer-transport.d.ts +201 -0
- package/dist/esm/utils/css.d.ts +1 -0
- package/dist/esm/utils/font-snapshot.d.ts +50 -0
- package/dist/esm/webgl/buffer-pool.d.ts +62 -0
- package/dist/esm/webgl/context-manager.d.ts +184 -0
- package/dist/esm/webgl/gradient-texture.d.ts +17 -0
- package/dist/esm/webgl/instanced-attrs.d.ts +44 -0
- package/dist/esm/webgl/plot-frame.d.ts +39 -0
- package/dist/esm/webgl/program-cache.d.ts +13 -0
- package/dist/esm/webgl/shader-manifest.d.ts +53 -0
- package/dist/esm/webgl/shader-registry.d.ts +22 -0
- package/dist/esm/worker/boot.d.ts +0 -0
- package/dist/esm/worker/dispatch.d.ts +9 -0
- package/dist/esm/worker/font-loader.d.ts +2 -0
- package/dist/esm/worker/renderer.worker.d.ts +115 -0
- package/dist/esm/worker/session-host.d.ts +26 -0
- package/package.json +47 -0
- package/src/css/perspective-viewer-charts.css +95 -0
- package/src/ts/axis/axis-primitives.ts +125 -0
- package/src/ts/axis/bar-axis.ts +345 -0
- package/src/ts/axis/canvas.ts +64 -0
- package/src/ts/axis/categorical-axis-core.ts +125 -0
- package/src/ts/axis/categorical-axis.ts +716 -0
- package/src/ts/axis/facet-chrome.ts +42 -0
- package/src/ts/axis/label-geometry.ts +188 -0
- package/src/ts/axis/legend.ts +218 -0
- package/src/ts/axis/numeric-axis.ts +353 -0
- package/src/ts/charts/candlestick/candlestick-build.ts +516 -0
- package/src/ts/charts/candlestick/candlestick-interact.ts +256 -0
- package/src/ts/charts/candlestick/candlestick-render.ts +387 -0
- package/src/ts/charts/candlestick/candlestick.ts +367 -0
- package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +432 -0
- package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +317 -0
- package/src/ts/charts/canvas-types.ts +30 -0
- package/src/ts/charts/cartesian/cartesian-build.ts +616 -0
- package/src/ts/charts/cartesian/cartesian-interact.ts +355 -0
- package/src/ts/charts/cartesian/cartesian-render.ts +948 -0
- package/src/ts/charts/cartesian/cartesian.ts +469 -0
- package/src/ts/charts/cartesian/glyph.ts +81 -0
- package/src/ts/charts/cartesian/glyphs/density.ts +1263 -0
- package/src/ts/charts/cartesian/glyphs/lines.ts +320 -0
- package/src/ts/charts/cartesian/glyphs/points.ts +239 -0
- package/src/ts/charts/cartesian/label-interner.ts +56 -0
- package/src/ts/charts/cartesian/tooltip-lines.ts +80 -0
- package/src/ts/charts/chart-base.ts +840 -0
- package/src/ts/charts/chart.ts +427 -0
- package/src/ts/charts/common/band-layout.ts +63 -0
- package/src/ts/charts/common/categorical-y-chart.ts +81 -0
- package/src/ts/charts/common/category-axis-resolver.ts +314 -0
- package/src/ts/charts/common/chrome-cache.ts +79 -0
- package/src/ts/charts/common/draw-tooltip-box.ts +84 -0
- package/src/ts/charts/common/leaf-color.ts +92 -0
- package/src/ts/charts/common/node-store.ts +235 -0
- package/src/ts/charts/common/tree-chart.ts +76 -0
- package/src/ts/charts/common/tree-chrome.ts +123 -0
- package/src/ts/charts/common/tree-data.ts +623 -0
- package/src/ts/charts/common/visible-extent.ts +112 -0
- package/src/ts/charts/heatmap/heatmap-build.ts +426 -0
- package/src/ts/charts/heatmap/heatmap-interact.ts +274 -0
- package/src/ts/charts/heatmap/heatmap-render.ts +815 -0
- package/src/ts/charts/heatmap/heatmap-y-axis.ts +351 -0
- package/src/ts/charts/heatmap/heatmap.ts +368 -0
- package/src/ts/charts/map/map.ts +201 -0
- package/src/ts/charts/registry.ts +65 -0
- package/src/ts/charts/series/glyphs/draw-areas.ts +331 -0
- package/src/ts/charts/series/glyphs/draw-bars.ts +113 -0
- package/src/ts/charts/series/glyphs/draw-lines.ts +320 -0
- package/src/ts/charts/series/glyphs/draw-scatter.ts +328 -0
- package/src/ts/charts/series/series-build.ts +848 -0
- package/src/ts/charts/series/series-interact.ts +604 -0
- package/src/ts/charts/series/series-render.ts +1109 -0
- package/src/ts/charts/series/series-type.ts +99 -0
- package/src/ts/charts/series/series.ts +794 -0
- package/src/ts/charts/sunburst/sunburst-interact.ts +460 -0
- package/src/ts/charts/sunburst/sunburst-layout.ts +238 -0
- package/src/ts/charts/sunburst/sunburst-render.ts +887 -0
- package/src/ts/charts/sunburst/sunburst.ts +248 -0
- package/src/ts/charts/treemap/treemap-interact.ts +445 -0
- package/src/ts/charts/treemap/treemap-layout.ts +328 -0
- package/src/ts/charts/treemap/treemap-render.ts +886 -0
- package/src/ts/charts/treemap/treemap.ts +247 -0
- package/src/ts/config.ts +41 -0
- package/src/ts/data/lazy-row.ts +140 -0
- package/src/ts/data/split-groups.ts +97 -0
- package/src/ts/data/view-reader.ts +107 -0
- package/src/ts/event-detail.ts +44 -0
- package/src/ts/index.ts +53 -0
- package/src/ts/interaction/hit-test.ts +106 -0
- package/src/ts/interaction/host-sink-dom.ts +85 -0
- package/src/ts/interaction/host-sink-message.ts +75 -0
- package/src/ts/interaction/lazy-tooltip.ts +102 -0
- package/src/ts/interaction/raw-event-forwarder.ts +175 -0
- package/src/ts/interaction/spatial-grid.ts +100 -0
- package/src/ts/interaction/tooltip-controller.ts +407 -0
- package/src/ts/interaction/zoom-controller.ts +468 -0
- package/src/ts/interaction/zoom-router.ts +230 -0
- package/src/ts/layout/facet-grid.ts +346 -0
- package/src/ts/layout/plot-layout.ts +277 -0
- package/src/ts/layout/ticks.ts +168 -0
- package/src/ts/map/mercator.ts +204 -0
- package/src/ts/map/tile-cache.ts +96 -0
- package/src/ts/map/tile-layer.ts +382 -0
- package/src/ts/map/tile-loader.ts +143 -0
- package/src/ts/map/tile-source.ts +156 -0
- package/src/ts/plugin/charts.ts +286 -0
- package/src/ts/plugin/plugin.ts +668 -0
- package/src/ts/render/scheduler.ts +339 -0
- package/src/ts/shaders/area.frag.glsl +20 -0
- package/src/ts/shaders/area.vert.glsl +19 -0
- package/src/ts/shaders/bar.frag.glsl +25 -0
- package/src/ts/shaders/bar.vert.glsl +60 -0
- package/src/ts/shaders/candlestick-body.frag.glsl +19 -0
- package/src/ts/shaders/candlestick-body.vert.glsl +34 -0
- package/src/ts/shaders/density-extreme.frag.glsl +30 -0
- package/src/ts/shaders/density-mrt.frag.glsl +44 -0
- package/src/ts/shaders/density-mrt.vert.glsl +48 -0
- package/src/ts/shaders/density-resolve.frag.glsl +89 -0
- package/src/ts/shaders/density-resolve.vert.glsl +23 -0
- package/src/ts/shaders/density-splat.frag.glsl +34 -0
- package/src/ts/shaders/density-splat.vert.glsl +52 -0
- package/src/ts/shaders/gridline.frag.glsl +18 -0
- package/src/ts/shaders/gridline.vert.glsl +18 -0
- package/src/ts/shaders/heatmap.frag.glsl +23 -0
- package/src/ts/shaders/heatmap.vert.glsl +42 -0
- package/src/ts/shaders/line-uniform.frag.glsl +26 -0
- package/src/ts/shaders/line-uniform.vert.glsl +54 -0
- package/src/ts/shaders/line.frag.glsl +28 -0
- package/src/ts/shaders/line.vert.glsl +87 -0
- package/src/ts/shaders/scatter.frag.glsl +39 -0
- package/src/ts/shaders/scatter.vert.glsl +67 -0
- package/src/ts/shaders/sunburst-arc.frag.glsl +19 -0
- package/src/ts/shaders/sunburst-arc.vert.glsl +79 -0
- package/src/ts/shaders/tile.frag.glsl +27 -0
- package/src/ts/shaders/tile.vert.glsl +35 -0
- package/src/ts/shaders/treemap.frag.glsl +19 -0
- package/src/ts/shaders/treemap.vert.glsl +25 -0
- package/src/ts/shaders/y-scatter.frag.glsl +30 -0
- package/src/ts/shaders/y-scatter.vert.glsl +31 -0
- package/src/ts/theme/gradient.ts +312 -0
- package/src/ts/theme/palette.ts +64 -0
- package/src/ts/theme/theme-snapshot.ts +66 -0
- package/src/ts/theme/theme.ts +166 -0
- package/src/ts/transport/protocol.ts +497 -0
- package/src/ts/transport/renderer-transport.ts +788 -0
- package/src/ts/utils/css.ts +36 -0
- package/src/ts/utils/font-snapshot.ts +159 -0
- package/src/ts/webgl/buffer-pool.ts +163 -0
- package/src/ts/webgl/context-manager.ts +414 -0
- package/src/ts/webgl/gradient-texture.ts +84 -0
- package/src/ts/webgl/instanced-attrs.ts +139 -0
- package/src/ts/webgl/plot-frame.ts +91 -0
- package/src/ts/webgl/program-cache.ts +46 -0
- package/src/ts/webgl/shader-manifest.ts +148 -0
- package/src/ts/webgl/shader-registry.ts +97 -0
- package/src/ts/worker/boot.ts +22 -0
- package/src/ts/worker/dispatch.ts +99 -0
- package/src/ts/worker/font-loader.ts +89 -0
- package/src/ts/worker/renderer.worker.ts +734 -0
- package/src/ts/worker/session-host.ts +118 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
2
|
+
// β ββββββ ββββββ ββββββ β β β β β ββ ββββ β β
|
|
3
|
+
// β ββββββ ββββββ ββββββ βββββββββββ β ββββββ βββββββββββββ ββββ ββ β βββββ β
|
|
4
|
+
// β ββββββ ββββββ ββββββ βββββ β ββββββ ββββββ βββββββββββββ ββββββ β βββββ β
|
|
5
|
+
// β β ββββββ β βββ β ββββββ β ββββββββ ββββββββ β β
|
|
6
|
+
// β£ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ«
|
|
7
|
+
// β Copyright (c) 2017, the Perspective Authors. β
|
|
8
|
+
// β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
9
|
+
// β This file is part of the Perspective library, distributed under the terms β
|
|
10
|
+
// β of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). β
|
|
11
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
12
|
+
|
|
13
|
+
import type { WebGLContextManager } from "../webgl/context-manager";
|
|
14
|
+
import type { PlotLayout } from "../layout/plot-layout";
|
|
15
|
+
import { pickZoom, tilesForExtent, tileExtent, type TileId } from "./mercator";
|
|
16
|
+
import { TileCache } from "./tile-cache";
|
|
17
|
+
import { TileLoader, tileKey } from "./tile-loader";
|
|
18
|
+
import type { TileSource } from "./tile-source";
|
|
19
|
+
import tileVert from "../shaders/tile.vert.glsl";
|
|
20
|
+
import tileFrag from "../shaders/tile.frag.glsl";
|
|
21
|
+
|
|
22
|
+
type GL = WebGL2RenderingContext | WebGLRenderingContext;
|
|
23
|
+
|
|
24
|
+
interface TileProgramCache {
|
|
25
|
+
program: WebGLProgram;
|
|
26
|
+
u_projection: WebGLUniformLocation | null;
|
|
27
|
+
u_extent_min: WebGLUniformLocation | null;
|
|
28
|
+
u_extent_max: WebGLUniformLocation | null;
|
|
29
|
+
u_uv_min: WebGLUniformLocation | null;
|
|
30
|
+
u_uv_max: WebGLUniformLocation | null;
|
|
31
|
+
u_tile: WebGLUniformLocation | null;
|
|
32
|
+
u_alpha: WebGLUniformLocation | null;
|
|
33
|
+
a_corner: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Renders an XYZ raster tile basemap into the chart's webgl canvas
|
|
38
|
+
* inside the plot rect's scissor. Used by `MapChart` from inside
|
|
39
|
+
* `renderInPlotFrame`, before the glyph draw, so chart glyphs (point,
|
|
40
|
+
* line, density) composite naturally on top of the basemap.
|
|
41
|
+
*
|
|
42
|
+
* The layer owns:
|
|
43
|
+
* - The tile shader program (compiled once per WebGL context).
|
|
44
|
+
* - A unit-quad corner buffer (one 2-byte attribute, reused per
|
|
45
|
+
* tile).
|
|
46
|
+
* - A `TileCache` (LRU of `WebGLTexture`).
|
|
47
|
+
* - A `TileLoader` (async fetch + dedup).
|
|
48
|
+
*
|
|
49
|
+
* On each render: pick the integer zoom that matches the requested
|
|
50
|
+
* meters-per-pixel, enumerate visible tiles at that zoom, and draw
|
|
51
|
+
* each one β either with the loaded texture or with a parent
|
|
52
|
+
* texture's sub-rect while the target is in flight.
|
|
53
|
+
*/
|
|
54
|
+
export class TileLayer {
|
|
55
|
+
private _program: TileProgramCache | null = null;
|
|
56
|
+
private _cornerBuffer: WebGLBuffer | null = null;
|
|
57
|
+
private _cache = new TileCache();
|
|
58
|
+
private _loader = new TileLoader();
|
|
59
|
+
private _source: TileSource | null = null;
|
|
60
|
+
private _alpha = 1.0;
|
|
61
|
+
private _onTileLoad: () => void = () => {};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Hook the "tile arrived" notification through to the chart's
|
|
65
|
+
* render scheduler. Called once when the layer is constructed
|
|
66
|
+
* by `MapChart`.
|
|
67
|
+
*/
|
|
68
|
+
setOnTileLoad(cb: () => void): void {
|
|
69
|
+
this._onTileLoad = cb;
|
|
70
|
+
this._loader.setOnLoad(cb);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Swap the tile source (e.g. light β dark theme). Drops the cache
|
|
75
|
+
* because the cached textures came from the prior source's URLs.
|
|
76
|
+
*/
|
|
77
|
+
setSource(gl: GL, source: TileSource): void {
|
|
78
|
+
if (this._source?.id === source.id) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this._cache.dispose(gl);
|
|
83
|
+
this._loader.cancelAll();
|
|
84
|
+
this._source = source;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setAlpha(alpha: number): void {
|
|
88
|
+
this._alpha = Math.max(0, Math.min(1, alpha));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get source(): TileSource | null {
|
|
92
|
+
return this._source;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Render the basemap for the current visible Mercator extent.
|
|
97
|
+
* Caller is responsible for binding the chart's main framebuffer
|
|
98
|
+
* (the plot-frame scissor is already in place by the time we get
|
|
99
|
+
* here). The same `projection` matrix the glyph draw uses is
|
|
100
|
+
* passed straight through.
|
|
101
|
+
*/
|
|
102
|
+
render(
|
|
103
|
+
glManager: WebGLContextManager,
|
|
104
|
+
layout: PlotLayout,
|
|
105
|
+
projection: Float32Array,
|
|
106
|
+
domain: { xMin: number; xMax: number; yMin: number; yMax: number },
|
|
107
|
+
xOrigin: number,
|
|
108
|
+
yOrigin: number,
|
|
109
|
+
): void {
|
|
110
|
+
const source = this._source;
|
|
111
|
+
if (!source) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this._ensureProgram(glManager);
|
|
116
|
+
const prog = this._program;
|
|
117
|
+
const cornerBuf = this._cornerBuffer;
|
|
118
|
+
if (!prog || !cornerBuf) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const gl = glManager.gl;
|
|
123
|
+
const dpr = glManager.dpr;
|
|
124
|
+
const plotWidth = Math.max(1, layout.plotRect.width * dpr);
|
|
125
|
+
const xRange = domain.xMax - domain.xMin;
|
|
126
|
+
if (!isFinite(xRange) || xRange <= 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const mpp = xRange / plotWidth;
|
|
131
|
+
const z = pickZoom(mpp, source.tileSize, source.maxZoom);
|
|
132
|
+
const visible = tilesForExtent(domain, z);
|
|
133
|
+
|
|
134
|
+
// Cancel any in-flight fetches for old zooms / off-screen
|
|
135
|
+
// tiles. We compute the live key set up-front so the loader
|
|
136
|
+
// doesn't keep tickling `requestRender` after the user pans
|
|
137
|
+
// past a slow-loading region.
|
|
138
|
+
const liveKeys = new Set<string>();
|
|
139
|
+
for (const t of visible) {
|
|
140
|
+
liveKeys.add(tileKey(source.id, t.z, t.x, t.y));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this._loader.cancelExcept(liveKeys);
|
|
144
|
+
|
|
145
|
+
// Setup the shader program + static unit-quad buffer once per
|
|
146
|
+
// frame. Inside the loop only the per-tile uniforms change.
|
|
147
|
+
gl.useProgram(prog.program);
|
|
148
|
+
gl.uniformMatrix4fv(prog.u_projection, false, projection);
|
|
149
|
+
gl.uniform1f(prog.u_alpha, this._alpha);
|
|
150
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, cornerBuf);
|
|
151
|
+
gl.enableVertexAttribArray(prog.a_corner);
|
|
152
|
+
gl.vertexAttribPointer(prog.a_corner, 2, gl.FLOAT, false, 0, 0);
|
|
153
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
154
|
+
gl.uniform1i(prog.u_tile, 0);
|
|
155
|
+
|
|
156
|
+
// Tiles are opaque; use the simplest blend mode so the
|
|
157
|
+
// glyph layer (drawn next in `_fullRender`) lands on top
|
|
158
|
+
// naturally without weird premultiplied tricks.
|
|
159
|
+
const wasBlend = gl.isEnabled(gl.BLEND);
|
|
160
|
+
gl.enable(gl.BLEND);
|
|
161
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
162
|
+
|
|
163
|
+
for (const tile of visible) {
|
|
164
|
+
this._drawTile(gl, prog, source, tile, xOrigin, yOrigin);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!wasBlend) {
|
|
168
|
+
gl.disable(gl.BLEND);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Free every GPU resource. Called from `MapChart.destroyInternal`.
|
|
174
|
+
*/
|
|
175
|
+
destroy(gl: GL): void {
|
|
176
|
+
this._loader.cancelAll();
|
|
177
|
+
this._cache.dispose(gl);
|
|
178
|
+
if (this._cornerBuffer) {
|
|
179
|
+
gl.deleteBuffer(this._cornerBuffer);
|
|
180
|
+
this._cornerBuffer = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this._program = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private _drawTile(
|
|
187
|
+
gl: GL,
|
|
188
|
+
prog: TileProgramCache,
|
|
189
|
+
source: TileSource,
|
|
190
|
+
tile: TileId,
|
|
191
|
+
xOrigin: number,
|
|
192
|
+
yOrigin: number,
|
|
193
|
+
): void {
|
|
194
|
+
// Subtract the chart's rebase origin so the position matches
|
|
195
|
+
// the convention glyphs use (`_xData = absX - xOrigin`). The
|
|
196
|
+
// shared projection matrix bakes in `xOrigin/yOrigin` and
|
|
197
|
+
// would otherwise shift tiles by `sx*xOrigin` clip units β
|
|
198
|
+
// for Mercator-scale origins (~1e7 m), well off-screen.
|
|
199
|
+
const rawExtent = tileExtent(tile.z, tile.x, tile.y);
|
|
200
|
+
const extent = {
|
|
201
|
+
xMin: rawExtent.xMin - xOrigin,
|
|
202
|
+
xMax: rawExtent.xMax - xOrigin,
|
|
203
|
+
yMin: rawExtent.yMin - yOrigin,
|
|
204
|
+
yMax: rawExtent.yMax - yOrigin,
|
|
205
|
+
};
|
|
206
|
+
const key = tileKey(source.id, tile.z, tile.x, tile.y);
|
|
207
|
+
|
|
208
|
+
const tex = this._cache.get(key);
|
|
209
|
+
if (tex) {
|
|
210
|
+
this._issueDraw(gl, prog, tex, extent, [0, 0], [1, 1]);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Cache miss: kick off async load (idempotent β loader dedups).
|
|
215
|
+
// No await β we paint a fallback this frame and the chart will
|
|
216
|
+
// re-render when the texture arrives.
|
|
217
|
+
this._kickLoad(gl, source, tile);
|
|
218
|
+
|
|
219
|
+
// Walk up the pyramid up to 6 levels looking for any loaded
|
|
220
|
+
// ancestor. Each level halves the UV sub-rect that the target
|
|
221
|
+
// tile occupies inside the ancestor; the math is direct from
|
|
222
|
+
// the tile coordinate bit-shift, no recursive accumulation.
|
|
223
|
+
for (let dz = 1; dz <= 6; dz++) {
|
|
224
|
+
const az = tile.z - dz;
|
|
225
|
+
if (az < 0) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const ax = tile.x >> dz;
|
|
230
|
+
const ay = tile.y >> dz;
|
|
231
|
+
const ancestorKey = tileKey(source.id, az, ax, ay);
|
|
232
|
+
const ancestorTex = this._cache.get(ancestorKey);
|
|
233
|
+
if (!ancestorTex) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const n = 1 << dz;
|
|
238
|
+
const localX = tile.x - ax * n;
|
|
239
|
+
const localY = tile.y - ay * n;
|
|
240
|
+
const span = 1 / n;
|
|
241
|
+
const uvMin: [number, number] = [localX * span, localY * span];
|
|
242
|
+
const uvMax: [number, number] = [uvMin[0] + span, uvMin[1] + span];
|
|
243
|
+
this._issueDraw(gl, prog, ancestorTex, extent, uvMin, uvMax);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private _issueDraw(
|
|
249
|
+
gl: GL,
|
|
250
|
+
prog: TileProgramCache,
|
|
251
|
+
tex: WebGLTexture,
|
|
252
|
+
extent: { xMin: number; yMin: number; xMax: number; yMax: number },
|
|
253
|
+
uvMin: [number, number],
|
|
254
|
+
uvMax: [number, number],
|
|
255
|
+
): void {
|
|
256
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
257
|
+
gl.uniform2f(prog.u_extent_min, extent.xMin, extent.yMin);
|
|
258
|
+
gl.uniform2f(prog.u_extent_max, extent.xMax, extent.yMax);
|
|
259
|
+
gl.uniform2f(prog.u_uv_min, uvMin[0], uvMin[1]);
|
|
260
|
+
gl.uniform2f(prog.u_uv_max, uvMax[0], uvMax[1]);
|
|
261
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private _kickLoad(gl: GL, source: TileSource, tile: TileId): void {
|
|
265
|
+
const key = tileKey(source.id, tile.z, tile.x, tile.y);
|
|
266
|
+
if (this._cache.has(key)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this._loader.load(source, tile.z, tile.x, tile.y).then((bmp) => {
|
|
271
|
+
if (!bmp) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// The chart may have switched sources between launch and
|
|
276
|
+
// resolve; drop the bitmap if so.
|
|
277
|
+
if (this._source?.id !== source.id) {
|
|
278
|
+
bmp.close();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const tex = gl.createTexture();
|
|
283
|
+
if (!tex) {
|
|
284
|
+
bmp.close();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Anchor the upload to a known texture unit. Without
|
|
289
|
+
// this the upload binds to whatever unit was last
|
|
290
|
+
// active (gradient LUT lives at TEXTURE2, etc.), which
|
|
291
|
+
// is harmless for the upload itself but easy to confuse
|
|
292
|
+
// with the sampling path during debugging.
|
|
293
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
294
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
295
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
296
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
297
|
+
gl.texParameteri(
|
|
298
|
+
gl.TEXTURE_2D,
|
|
299
|
+
gl.TEXTURE_WRAP_S,
|
|
300
|
+
gl.CLAMP_TO_EDGE,
|
|
301
|
+
);
|
|
302
|
+
gl.texParameteri(
|
|
303
|
+
gl.TEXTURE_2D,
|
|
304
|
+
gl.TEXTURE_WRAP_T,
|
|
305
|
+
gl.CLAMP_TO_EDGE,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// Pin pixel-store flags. Workers and main threads start
|
|
309
|
+
// with the WebGL spec defaults, but other parts of the
|
|
310
|
+
// chart (or future extensions) may flip
|
|
311
|
+
// `UNPACK_PREMULTIPLY_ALPHA_WEBGL` or
|
|
312
|
+
// `UNPACK_FLIP_Y_WEBGL` and not restore them. Set them
|
|
313
|
+
// explicitly so the upload result is deterministic.
|
|
314
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
315
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
316
|
+
gl.texImage2D(
|
|
317
|
+
gl.TEXTURE_2D,
|
|
318
|
+
0,
|
|
319
|
+
gl.RGBA,
|
|
320
|
+
gl.RGBA,
|
|
321
|
+
gl.UNSIGNED_BYTE,
|
|
322
|
+
bmp,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// Do *not* `bmp.close()` here. The WebGL spec says
|
|
326
|
+
// `texImage2D(ImageBitmap)` consumes the source at call
|
|
327
|
+
// time, but several browser/driver combinations defer
|
|
328
|
+
// the actual GPU copy until the next draw; closing the
|
|
329
|
+
// bitmap before that copy lands leaves the texture
|
|
330
|
+
// valid-but-empty and the sampler returns (0,0,0,1) β
|
|
331
|
+
// i.e. solid black β for every tile after the first one
|
|
332
|
+
// or two whose upload happened to drain in time. The
|
|
333
|
+
// ImageBitmap is small (β€256Γ256Γ4 β 256 KB) and will
|
|
334
|
+
// be garbage-collected once the .then closure is
|
|
335
|
+
// released.
|
|
336
|
+
this._cache.set(gl, key, tex);
|
|
337
|
+
this._onTileLoad();
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private _ensureProgram(glManager: WebGLContextManager): void {
|
|
342
|
+
if (this._program && this._cornerBuffer) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const gl = glManager.gl;
|
|
347
|
+
const program = glManager.shaders.getOrCreate(
|
|
348
|
+
"map-tile",
|
|
349
|
+
tileVert,
|
|
350
|
+
tileFrag,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
this._program = {
|
|
354
|
+
program,
|
|
355
|
+
u_projection: gl.getUniformLocation(program, "u_projection"),
|
|
356
|
+
u_extent_min: gl.getUniformLocation(program, "u_extent_min"),
|
|
357
|
+
u_extent_max: gl.getUniformLocation(program, "u_extent_max"),
|
|
358
|
+
u_uv_min: gl.getUniformLocation(program, "u_uv_min"),
|
|
359
|
+
u_uv_max: gl.getUniformLocation(program, "u_uv_max"),
|
|
360
|
+
u_tile: gl.getUniformLocation(program, "u_tile"),
|
|
361
|
+
u_alpha: gl.getUniformLocation(program, "u_alpha"),
|
|
362
|
+
a_corner: gl.getAttribLocation(program, "a_corner"),
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const buf = gl.createBuffer();
|
|
366
|
+
if (!buf) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
371
|
+
// Unit-quad corners in TRIANGLE_STRIP order:
|
|
372
|
+
// (0,0) (1,0) (0,1) (1,1)
|
|
373
|
+
// Stretched into Mercator space by `u_extent_*` uniforms in
|
|
374
|
+
// the vertex shader; UV picked by `u_uv_*`.
|
|
375
|
+
gl.bufferData(
|
|
376
|
+
gl.ARRAY_BUFFER,
|
|
377
|
+
new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
|
|
378
|
+
gl.STATIC_DRAW,
|
|
379
|
+
);
|
|
380
|
+
this._cornerBuffer = buf;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
2
|
+
// β ββββββ ββββββ ββββββ β β β β β ββ ββββ β β
|
|
3
|
+
// β ββββββ ββββββ ββββββ βββββββββββ β ββββββ βββββββββββββ ββββ ββ β βββββ β
|
|
4
|
+
// β ββββββ ββββββ ββββββ βββββ β ββββββ ββββββ βββββββββββββ ββββββ β βββββ β
|
|
5
|
+
// β β ββββββ β βββ β ββββββ β ββββββββ ββββββββ β β
|
|
6
|
+
// β£ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ«
|
|
7
|
+
// β Copyright (c) 2017, the Perspective Authors. β
|
|
8
|
+
// β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
9
|
+
// β This file is part of the Perspective library, distributed under the terms β
|
|
10
|
+
// β of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). β
|
|
11
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
12
|
+
|
|
13
|
+
import type { TileSource } from "./tile-source";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One in-flight tile fetch. The same key (z/x/y under one source) only
|
|
17
|
+
* launches one fetch; concurrent requesters share the promise.
|
|
18
|
+
*/
|
|
19
|
+
interface InFlight {
|
|
20
|
+
promise: Promise<ImageBitmap | null>;
|
|
21
|
+
abort: AbortController;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Async tile fetcher with in-flight dedup and abort-on-zoom-change.
|
|
26
|
+
*
|
|
27
|
+
* Runs inside the renderer worker (the chart's host process). `fetch`
|
|
28
|
+
* and `createImageBitmap` are both available in workers, and the
|
|
29
|
+
* resulting `ImageBitmap` can be uploaded straight to a WebGL2 texture
|
|
30
|
+
* via `gl.texImage2D(..., ImageBitmap)` β no main-thread bounce.
|
|
31
|
+
*
|
|
32
|
+
* The loader does not own GPU resources. Upload to texture happens in
|
|
33
|
+
* the tile layer once an `ImageBitmap` resolves; the layer then drops
|
|
34
|
+
* the bitmap (calling `close()` to free the decoded pixels).
|
|
35
|
+
*/
|
|
36
|
+
export class TileLoader {
|
|
37
|
+
private _inFlight = new Map<string, InFlight>();
|
|
38
|
+
private _onLoad: () => void = () => {};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register the "tile arrived" callback. The layer wires this to
|
|
42
|
+
* `chart.requestRender(glManager)` so a newly-loaded tile triggers
|
|
43
|
+
* exactly one extra frame.
|
|
44
|
+
*/
|
|
45
|
+
setOnLoad(cb: () => void): void {
|
|
46
|
+
this._onLoad = cb;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Kick off a fetch (or return the in-flight one). The same
|
|
51
|
+
* (source.id, z, x, y) tuple only ever has one outstanding
|
|
52
|
+
* fetch; multiple callers share the result. Rejected fetches
|
|
53
|
+
* (network error, abort) resolve to `null` so callers can skip
|
|
54
|
+
* the tile without try/catch noise on every miss.
|
|
55
|
+
*/
|
|
56
|
+
load(
|
|
57
|
+
source: TileSource,
|
|
58
|
+
z: number,
|
|
59
|
+
x: number,
|
|
60
|
+
y: number,
|
|
61
|
+
): Promise<ImageBitmap | null> {
|
|
62
|
+
const key = tileKey(source.id, z, x, y);
|
|
63
|
+
const existing = this._inFlight.get(key);
|
|
64
|
+
if (existing) {
|
|
65
|
+
return existing.promise;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const abort = new AbortController();
|
|
69
|
+
const url = source.urlFor(z, x, y);
|
|
70
|
+
const promise = this._fetchAndDecode(url, abort.signal)
|
|
71
|
+
.then((bmp) => {
|
|
72
|
+
this._inFlight.delete(key);
|
|
73
|
+
if (bmp) {
|
|
74
|
+
this._onLoad();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return bmp;
|
|
78
|
+
})
|
|
79
|
+
.catch(() => {
|
|
80
|
+
this._inFlight.delete(key);
|
|
81
|
+
return null;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this._inFlight.set(key, { promise, abort });
|
|
85
|
+
return promise;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Abort every in-flight fetch. Called on view teardown / chart
|
|
90
|
+
* destroy. Fetches whose `Response` has already arrived but
|
|
91
|
+
* haven't yet decoded will still complete the decode and resolve
|
|
92
|
+
* to `null` (because `_onLoad` is replaced or the cache is gone)
|
|
93
|
+
* β harmless, no resource leak.
|
|
94
|
+
*/
|
|
95
|
+
cancelAll(): void {
|
|
96
|
+
for (const entry of this._inFlight.values()) {
|
|
97
|
+
entry.abort.abort();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this._inFlight.clear();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Abort just the fetches whose key isn't in the supplied set.
|
|
105
|
+
* The layer calls this on every render with the currently-visible
|
|
106
|
+
* tile set so old-zoom requests don't keep arriving and triggering
|
|
107
|
+
* spurious re-renders after the user has moved on.
|
|
108
|
+
*/
|
|
109
|
+
cancelExcept(liveKeys: Set<string>): void {
|
|
110
|
+
for (const [key, entry] of this._inFlight) {
|
|
111
|
+
if (!liveKeys.has(key)) {
|
|
112
|
+
entry.abort.abort();
|
|
113
|
+
this._inFlight.delete(key);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private async _fetchAndDecode(
|
|
119
|
+
url: string,
|
|
120
|
+
signal: AbortSignal,
|
|
121
|
+
): Promise<ImageBitmap | null> {
|
|
122
|
+
const resp = await fetch(url, { signal });
|
|
123
|
+
if (!resp.ok) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const blob = await resp.blob();
|
|
128
|
+
return await createImageBitmap(blob);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Stable cache key for a tile under a given source. Embedded source id
|
|
134
|
+
* so swapping sources (light/dark) doesn't surface stale tiles.
|
|
135
|
+
*/
|
|
136
|
+
export function tileKey(
|
|
137
|
+
sourceId: string,
|
|
138
|
+
z: number,
|
|
139
|
+
x: number,
|
|
140
|
+
y: number,
|
|
141
|
+
): string {
|
|
142
|
+
return `${sourceId}/${z}/${x}/${y}`;
|
|
143
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
2
|
+
// β ββββββ ββββββ ββββββ β β β β β ββ ββββ β β
|
|
3
|
+
// β ββββββ ββββββ ββββββ βββββββββββ β ββββββ βββββββββββββ ββββ ββ β βββββ β
|
|
4
|
+
// β ββββββ ββββββ ββββββ βββββ β ββββββ ββββββ βββββββββββββ ββββββ β βββββ β
|
|
5
|
+
// β β ββββββ β βββ β ββββββ β ββββββββ ββββββββ β β
|
|
6
|
+
// β£ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ«
|
|
7
|
+
// β Copyright (c) 2017, the Perspective Authors. β
|
|
8
|
+
// β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
9
|
+
// β This file is part of the Perspective library, distributed under the terms β
|
|
10
|
+
// β of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). β
|
|
11
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A tile source describes *where* to fetch raster XYZ tiles and what
|
|
15
|
+
* attribution text the renderer must display in the chrome canvas.
|
|
16
|
+
* Implementations are stateless β the tile loader handles caching and
|
|
17
|
+
* in-flight dedup.
|
|
18
|
+
*/
|
|
19
|
+
export interface TileSource {
|
|
20
|
+
/**
|
|
21
|
+
* Build the URL for tile (z, x, y). Implementations typically
|
|
22
|
+
* substitute a template like `{z}/{x}/{y}` and may rotate
|
|
23
|
+
* subdomains for browsers that throttle concurrent connections
|
|
24
|
+
* per host.
|
|
25
|
+
*/
|
|
26
|
+
urlFor(z: number, x: number, y: number): string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Plain-text attribution shown in the bottom-right of the chrome
|
|
30
|
+
* canvas. Required by every major tile provider's terms of use β
|
|
31
|
+
* do not suppress it without provider-side opt-out.
|
|
32
|
+
*/
|
|
33
|
+
readonly attribution: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Side length of one tile in pixels. Tile providers ship 256 by
|
|
37
|
+
* default; a few offer 512 (`@2x`) variants. Used by the zoom-
|
|
38
|
+
* level picker to convert meters-per-pixel into a zoom level.
|
|
39
|
+
*/
|
|
40
|
+
readonly tileSize: number;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Maximum zoom level the provider serves. Tiles requested above
|
|
44
|
+
* this fall back to the deepest available level with sub-tile
|
|
45
|
+
* UVs β same trick used during async loads.
|
|
46
|
+
*/
|
|
47
|
+
readonly maxZoom: number;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Stable identifier for caching. Two `TileSource` instances with
|
|
51
|
+
* the same `id` share a tile cache; switching source (e.g. theme
|
|
52
|
+
* change) invalidates and re-fetches.
|
|
53
|
+
*/
|
|
54
|
+
readonly id: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Subdomain-rotated URL template. Replaces `{s}` with one of the
|
|
59
|
+
* provided subdomains hashed by `(x + y)`, and `{z}`, `{x}`, `{y}`
|
|
60
|
+
* with the tile address. Most major tile providers fit this shape.
|
|
61
|
+
*/
|
|
62
|
+
export class TemplatedTileSource implements TileSource {
|
|
63
|
+
constructor(
|
|
64
|
+
readonly id: string,
|
|
65
|
+
private readonly template: string,
|
|
66
|
+
readonly attribution: string,
|
|
67
|
+
readonly tileSize = 256,
|
|
68
|
+
readonly maxZoom = 19,
|
|
69
|
+
private readonly subdomains: readonly string[] = [],
|
|
70
|
+
) {}
|
|
71
|
+
|
|
72
|
+
urlFor(z: number, x: number, y: number): string {
|
|
73
|
+
let url = this.template
|
|
74
|
+
.replace("{z}", String(z))
|
|
75
|
+
.replace("{x}", String(x))
|
|
76
|
+
.replace("{y}", String(y));
|
|
77
|
+
if (this.subdomains.length > 0) {
|
|
78
|
+
const idx = (x + y) % this.subdomains.length;
|
|
79
|
+
url = url.replace("{s}", this.subdomains[idx]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return url;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Identifier of the default tile providers shipped with `viewer-charts`.
|
|
88
|
+
* Surfaced as the `map_tile_provider` PluginConfig enum so users can
|
|
89
|
+
* pick light vs. dark vs. labels-only without writing a custom source.
|
|
90
|
+
*/
|
|
91
|
+
export type TileProviderId =
|
|
92
|
+
| "carto-positron"
|
|
93
|
+
| "carto-dark-matter"
|
|
94
|
+
| "carto-voyager";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* CartoDB's "Positron" basemap β light, low-contrast, designed to sit
|
|
98
|
+
* behind a chart overlay. Default for light themes.
|
|
99
|
+
*/
|
|
100
|
+
function cartoPositron(): TileSource {
|
|
101
|
+
return new TemplatedTileSource(
|
|
102
|
+
"carto-positron",
|
|
103
|
+
"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
|
|
104
|
+
"Β© OpenStreetMap contributors Β© CARTO",
|
|
105
|
+
256,
|
|
106
|
+
19,
|
|
107
|
+
["a", "b", "c", "d"],
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* CartoDB's "Dark Matter" basemap β dark, low-contrast. Default for
|
|
113
|
+
* dark themes.
|
|
114
|
+
*/
|
|
115
|
+
function cartoDarkMatter(): TileSource {
|
|
116
|
+
return new TemplatedTileSource(
|
|
117
|
+
"carto-dark-matter",
|
|
118
|
+
"https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
|
|
119
|
+
"Β© OpenStreetMap contributors Β© CARTO",
|
|
120
|
+
256,
|
|
121
|
+
19,
|
|
122
|
+
["a", "b", "c", "d"],
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* CartoDB's "Voyager" basemap β full color, more land/water contrast
|
|
128
|
+
* than Positron. Good when the chart glyphs are translucent.
|
|
129
|
+
*/
|
|
130
|
+
function cartoVoyager(): TileSource {
|
|
131
|
+
return new TemplatedTileSource(
|
|
132
|
+
"carto-voyager",
|
|
133
|
+
"https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png",
|
|
134
|
+
"Β© OpenStreetMap contributors Β© CARTO",
|
|
135
|
+
256,
|
|
136
|
+
19,
|
|
137
|
+
["a", "b", "c", "d"],
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Resolve a `TileProviderId` (from PluginConfig) to a concrete
|
|
143
|
+
* `TileSource`. Unknown ids fall back to Positron so a misconfigured
|
|
144
|
+
* `_pluginConfig` never produces a blank map.
|
|
145
|
+
*/
|
|
146
|
+
export function tileSourceFor(id: TileProviderId | string): TileSource {
|
|
147
|
+
switch (id) {
|
|
148
|
+
case "carto-dark-matter":
|
|
149
|
+
return cartoDarkMatter();
|
|
150
|
+
case "carto-voyager":
|
|
151
|
+
return cartoVoyager();
|
|
152
|
+
case "carto-positron":
|
|
153
|
+
default:
|
|
154
|
+
return cartoPositron();
|
|
155
|
+
}
|
|
156
|
+
}
|