isard 0.2.0 → 0.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/CHANGELOG.md +134 -14
- package/README.md +2 -2
- package/dist/animation.d.ts +18 -0
- package/dist/animation.js +84 -0
- package/dist/animation.js.map +1 -0
- package/dist/basemap.worker.d.ts +26 -1
- package/dist/basemap.worker.js +2 -2
- package/dist/basemap.worker.js.map +8 -4
- package/dist/basemaps.d.ts +5 -3
- package/dist/basemaps.js +5 -3
- package/dist/basemaps.js.map +1 -1
- package/dist/crs.d.ts +72 -0
- package/dist/crs.js +243 -0
- package/dist/crs.js.map +1 -0
- package/dist/decoder.worker.js +4 -4
- package/dist/decoder.worker.js.map +4 -4
- package/dist/engine-loader.d.ts +27 -0
- package/dist/engine-loader.js +112 -0
- package/dist/engine-loader.js.map +1 -0
- package/dist/extrusions.js +11 -2
- package/dist/extrusions.js.map +1 -1
- package/dist/features.d.ts +8 -0
- package/dist/features.js +17 -10
- package/dist/features.js.map +1 -1
- package/dist/geo.d.ts +16 -0
- package/dist/geo.js +32 -0
- package/dist/geo.js.map +1 -0
- package/dist/geoplateforme.d.ts +21 -5
- package/dist/geoplateforme.js +47 -12
- package/dist/geoplateforme.js.map +1 -1
- package/dist/glyphs.d.ts +10 -3
- package/dist/glyphs.js +14 -2
- package/dist/glyphs.js.map +1 -1
- package/dist/gpx.d.ts +11 -0
- package/dist/gpx.js +74 -0
- package/dist/gpx.js.map +1 -0
- package/dist/grid.d.ts +81 -0
- package/dist/grid.js +270 -0
- package/dist/grid.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +3 -4
- package/dist/interaction.js +27 -7
- package/dist/interaction.js.map +1 -1
- package/dist/isard_bg.wasm +0 -0
- package/dist/isard_core_bg.wasm +0 -0
- package/dist/kml.d.ts +12 -0
- package/dist/kml.js +159 -0
- package/dist/kml.js.map +1 -0
- package/dist/layers.d.ts +5 -2
- package/dist/layers.js +22 -18
- package/dist/layers.js.map +1 -1
- package/dist/lru.d.ts +22 -0
- package/dist/lru.js +52 -0
- package/dist/lru.js.map +1 -0
- package/dist/map.d.ts +26 -6
- package/dist/map.js +196 -287
- package/dist/map.js.map +1 -1
- package/dist/resample.d.ts +41 -0
- package/dist/resample.js +164 -0
- package/dist/resample.js.map +1 -0
- package/dist/settings.d.ts +39 -0
- package/dist/settings.js +104 -0
- package/dist/settings.js.map +1 -0
- package/dist/symbols.d.ts +12 -1
- package/dist/symbols.js +124 -26
- package/dist/symbols.js.map +1 -1
- package/dist/terrain.d.ts +19 -7
- package/dist/terrain.js +50 -26
- package/dist/terrain.js.map +1 -1
- package/dist/topojson.d.ts +29 -0
- package/dist/topojson.js +66 -0
- package/dist/topojson.js.map +1 -0
- package/dist/types.d.ts +23 -7
- package/dist/types.js.map +1 -1
- package/dist/vectortiles.d.ts +13 -3
- package/dist/vectortiles.js +85 -46
- package/dist/vectortiles.js.map +1 -1
- package/dist/wasm/isard.d.ts +14 -7
- package/dist/wasm/isard.js +33 -10
- package/dist/wasm/isard_core.d.ts +6 -5
- package/dist/wasm/isard_core.js +41 -10
- package/dist/wmts.d.ts +14 -0
- package/dist/wmts.js +76 -0
- package/dist/wmts.js.map +1 -0
- package/dist/xml.d.ts +26 -0
- package/dist/xml.js +109 -0
- package/dist/xml.js.map +1 -0
- package/package.json +1 -1
- package/dist/equirectangular.d.ts +0 -35
- package/dist/equirectangular.js +0 -113
- package/dist/equirectangular.js.map +0 -1
package/dist/resample.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raster reprojection, the drawing half: a Web Mercator tile drawn from the source images it spans (tiles of another
|
|
3
|
+
* grid, or one image of a WMS), in a worker of the map (OffscreenCanvas) or in the page when the map has no workers.
|
|
4
|
+
* The page plans each tile (`grid.ts`): which images, where they go in a mosaic, and a mesh of control points giving,
|
|
5
|
+
* for the corners of 16 × 16 cells of the tile, the mosaic pixel under them. Here the images are fetched (kept for the
|
|
6
|
+
* neighbouring tiles), drawn into the mosaic, and every pixel of the tile sampled from it, its source position
|
|
7
|
+
* interpolated within its cell (bilinear), its color between the four nearest source pixels (bilinear, alpha
|
|
8
|
+
* premultiplied). This code knows no projection.
|
|
9
|
+
*/
|
|
10
|
+
import { Lru } from "./lru.js";
|
|
11
|
+
/** Source images kept decoded (by the page, or shared by the workers of a pool): the neighbours of a tile share them */
|
|
12
|
+
export const SOURCE_CACHE = 96;
|
|
13
|
+
/**
|
|
14
|
+
* Samples the pixels of a `size` × `size` tile (RGBA, straight alpha) from a mosaic of `width` × `height` (RGBA,
|
|
15
|
+
* straight alpha) through the mesh of a job. Pixels whose position falls outside the mosaic, or on a NaN, stay
|
|
16
|
+
* transparent; the mosaic's edge fades over half a pixel
|
|
17
|
+
*/
|
|
18
|
+
export function resample(source, width, height, mesh, n, size) {
|
|
19
|
+
const out = new Uint8ClampedArray(size * size * 4);
|
|
20
|
+
const cell = size / n;
|
|
21
|
+
const stride = n + 1;
|
|
22
|
+
for (let row = 0; row < size; row += 1) {
|
|
23
|
+
const fy = (row + 0.5) / cell;
|
|
24
|
+
const j = Math.min(n - 1, Math.floor(fy));
|
|
25
|
+
const ty = fy - j;
|
|
26
|
+
for (let col = 0; col < size; col += 1) {
|
|
27
|
+
const fx = (col + 0.5) / cell;
|
|
28
|
+
const i = Math.min(n - 1, Math.floor(fx));
|
|
29
|
+
const tx = fx - i;
|
|
30
|
+
const a = 2 * (j * stride + i);
|
|
31
|
+
const b = a + 2 * stride;
|
|
32
|
+
// The corners of the cell, then the position in it
|
|
33
|
+
const w00 = (1 - tx) * (1 - ty);
|
|
34
|
+
const w10 = tx * (1 - ty);
|
|
35
|
+
const w01 = (1 - tx) * ty;
|
|
36
|
+
const w11 = tx * ty;
|
|
37
|
+
const u = w00 * mesh[a] +
|
|
38
|
+
w10 * mesh[a + 2] +
|
|
39
|
+
w01 * mesh[b] +
|
|
40
|
+
w11 * mesh[b + 2];
|
|
41
|
+
const v = w00 * mesh[a + 1] +
|
|
42
|
+
w10 * mesh[a + 3] +
|
|
43
|
+
w01 * mesh[b + 1] +
|
|
44
|
+
w11 * mesh[b + 3];
|
|
45
|
+
// Pixel centers at half pixels: the four around (u, v)
|
|
46
|
+
const sx = u - 0.5;
|
|
47
|
+
const sy = v - 0.5;
|
|
48
|
+
if (!(sx > -1 && sy > -1 && sx < width && sy < height))
|
|
49
|
+
continue;
|
|
50
|
+
const x0 = Math.floor(sx);
|
|
51
|
+
const y0 = Math.floor(sy);
|
|
52
|
+
const kx = sx - x0;
|
|
53
|
+
const ky = sy - y0;
|
|
54
|
+
let r = 0;
|
|
55
|
+
let g = 0;
|
|
56
|
+
let bl = 0;
|
|
57
|
+
let alpha = 0;
|
|
58
|
+
for (let k = 0; k < 4; k += 1) {
|
|
59
|
+
const x = x0 + (k & 1);
|
|
60
|
+
const y = y0 + (k >> 1);
|
|
61
|
+
if (x < 0 || y < 0 || x >= width || y >= height)
|
|
62
|
+
continue;
|
|
63
|
+
const p = 4 * (y * width + x);
|
|
64
|
+
const wa = (k & 1 ? kx : 1 - kx) * (k >> 1 ? ky : 1 - ky) * source[p + 3];
|
|
65
|
+
r += wa * source[p];
|
|
66
|
+
g += wa * source[p + 1];
|
|
67
|
+
bl += wa * source[p + 2];
|
|
68
|
+
alpha += wa;
|
|
69
|
+
}
|
|
70
|
+
if (alpha <= 0)
|
|
71
|
+
continue;
|
|
72
|
+
const q = 4 * (row * size + col);
|
|
73
|
+
out[q] = r / alpha;
|
|
74
|
+
out[q + 1] = g / alpha;
|
|
75
|
+
out[q + 2] = bl / alpha;
|
|
76
|
+
out[q + 3] = alpha;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Draws planned tiles, keeping `cached` source images decoded. A missing source image (404, 204, 410) leaves its part
|
|
83
|
+
* empty; a tile with no source at all is missing; other errors reject (retried by the engine). In the page, or in a
|
|
84
|
+
* worker of the map (`close` frees the images kept)
|
|
85
|
+
*/
|
|
86
|
+
export function resampler(cached = SOURCE_CACHE) {
|
|
87
|
+
/** Tiles being drawn from each source image: an evicted one is closed once none is */
|
|
88
|
+
const users = new Map();
|
|
89
|
+
const evicted = new Set();
|
|
90
|
+
const close = (image) => {
|
|
91
|
+
evicted.delete(image);
|
|
92
|
+
image.then((bitmap) => bitmap?.close(), () => undefined);
|
|
93
|
+
};
|
|
94
|
+
/** Freed now, or once the tiles drawing from it are done */
|
|
95
|
+
const drop = (image) => {
|
|
96
|
+
if (users.has(image))
|
|
97
|
+
evicted.add(image);
|
|
98
|
+
else
|
|
99
|
+
close(image);
|
|
100
|
+
};
|
|
101
|
+
// Source images by URL: a request is shared by the tiles that need it, its signal is not theirs
|
|
102
|
+
const cache = new Lru(cached, drop);
|
|
103
|
+
const release = (image) => {
|
|
104
|
+
const count = (users.get(image) ?? 1) - 1;
|
|
105
|
+
if (count > 0)
|
|
106
|
+
users.set(image, count);
|
|
107
|
+
else {
|
|
108
|
+
users.delete(image);
|
|
109
|
+
if (evicted.has(image))
|
|
110
|
+
close(image);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const fetchImage = (url) => {
|
|
114
|
+
let image = cache.get(url);
|
|
115
|
+
if (!image) {
|
|
116
|
+
image = (async () => {
|
|
117
|
+
const response = await fetch(url);
|
|
118
|
+
if (response.status === 404 || response.status === 204 || response.status === 410)
|
|
119
|
+
return null;
|
|
120
|
+
if (!response.ok)
|
|
121
|
+
throw new Error(`isard: ${response.status} for a raster to reproject`);
|
|
122
|
+
return createImageBitmap(await response.blob());
|
|
123
|
+
})();
|
|
124
|
+
// A failure is not kept: the engine's retry fetches again
|
|
125
|
+
image.catch(() => cache.delete(url));
|
|
126
|
+
cache.set(url, image);
|
|
127
|
+
}
|
|
128
|
+
users.set(image, (users.get(image) ?? 0) + 1);
|
|
129
|
+
return image;
|
|
130
|
+
};
|
|
131
|
+
const draw = async (job, signal) => {
|
|
132
|
+
const pending = job.sources.map((s) => [s, fetchImage(s.url)]);
|
|
133
|
+
try {
|
|
134
|
+
const images = await Promise.all(pending.map(async ([s, image]) => [s, await image]));
|
|
135
|
+
signal.throwIfAborted();
|
|
136
|
+
if (!images.some(([, image]) => image))
|
|
137
|
+
return null;
|
|
138
|
+
const canvas = new OffscreenCanvas(job.width, job.height);
|
|
139
|
+
const context = canvas.getContext('2d', { willReadFrequently: true });
|
|
140
|
+
if (!context)
|
|
141
|
+
throw new Error('isard: no 2D canvas to reproject the tiles');
|
|
142
|
+
for (const [s, image] of images)
|
|
143
|
+
if (image)
|
|
144
|
+
context.drawImage(image, s.x, s.y, s.w, s.h);
|
|
145
|
+
const mosaic = context.getImageData(0, 0, job.width, job.height).data;
|
|
146
|
+
const pixels = resample(mosaic, job.width, job.height, job.mesh, job.n, job.size);
|
|
147
|
+
return createImageBitmap(new ImageData(pixels, job.size, job.size), { premultiplyAlpha: 'none' });
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
for (const [, image] of pending)
|
|
151
|
+
release(image);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
draw,
|
|
156
|
+
cache,
|
|
157
|
+
close: () => {
|
|
158
|
+
for (const image of cache.values())
|
|
159
|
+
drop(image);
|
|
160
|
+
cache.clear();
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=resample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resample.js","sourceRoot":"","sources":["../src/resample.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Raster reprojection, the drawing half: a Web Mercator tile drawn from the source images it spans (tiles of another\n * grid, or one image of a WMS), in a worker of the map (OffscreenCanvas) or in the page when the map has no workers.\n * The page plans each tile (`grid.ts`): which images, where they go in a mosaic, and a mesh of control points giving,\n * for the corners of 16 × 16 cells of the tile, the mosaic pixel under them. Here the images are fetched (kept for the\n * neighbouring tiles), drawn into the mosaic, and every pixel of the tile sampled from it, its source position\n * interpolated within its cell (bilinear), its color between the four nearest source pixels (bilinear, alpha\n * premultiplied). This code knows no projection.\n */\nimport { Lru } from './lru.ts';\n\n/** Source images kept decoded (by the page, or shared by the workers of a pool): the neighbours of a tile share them */\nexport const SOURCE_CACHE = 96;\n\n/** A source image and the rectangle of the mosaic it covers (pixels) */\nexport interface Placed {\n url: string;\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** How to draw a Mercator tile of `size` pixels: the mosaic of the source images, and the mesh of `n` × `n` cells\n * ((n + 1)² points, rows from the north, x then y): the mosaic position of each corner, NaN where the source CRS has\n * none */\nexport interface Job {\n size: number;\n width: number;\n height: number;\n sources: Placed[];\n n: number;\n mesh: Float32Array;\n}\n\n/**\n * Samples the pixels of a `size` × `size` tile (RGBA, straight alpha) from a mosaic of `width` × `height` (RGBA,\n * straight alpha) through the mesh of a job. Pixels whose position falls outside the mosaic, or on a NaN, stay\n * transparent; the mosaic's edge fades over half a pixel\n */\nexport function resample(\n source: Uint8ClampedArray,\n width: number,\n height: number,\n mesh: Float32Array,\n n: number,\n size: number,\n): Uint8ClampedArray<ArrayBuffer> {\n const out = new Uint8ClampedArray(size * size * 4);\n const cell = size / n;\n const stride = n + 1;\n for (let row = 0; row < size; row += 1) {\n const fy = (row + 0.5) / cell;\n const j = Math.min(n - 1, Math.floor(fy));\n const ty = fy - j;\n for (let col = 0; col < size; col += 1) {\n const fx = (col + 0.5) / cell;\n const i = Math.min(n - 1, Math.floor(fx));\n const tx = fx - i;\n const a = 2 * (j * stride + i);\n const b = a + 2 * stride;\n // The corners of the cell, then the position in it\n const w00 = (1 - tx) * (1 - ty);\n const w10 = tx * (1 - ty);\n const w01 = (1 - tx) * ty;\n const w11 = tx * ty;\n const u =\n w00 * (mesh[a] as number) +\n w10 * (mesh[a + 2] as number) +\n w01 * (mesh[b] as number) +\n w11 * (mesh[b + 2] as number);\n const v =\n w00 * (mesh[a + 1] as number) +\n w10 * (mesh[a + 3] as number) +\n w01 * (mesh[b + 1] as number) +\n w11 * (mesh[b + 3] as number);\n // Pixel centers at half pixels: the four around (u, v)\n const sx = u - 0.5;\n const sy = v - 0.5;\n if (!(sx > -1 && sy > -1 && sx < width && sy < height)) continue;\n const x0 = Math.floor(sx);\n const y0 = Math.floor(sy);\n const kx = sx - x0;\n const ky = sy - y0;\n let r = 0;\n let g = 0;\n let bl = 0;\n let alpha = 0;\n for (let k = 0; k < 4; k += 1) {\n const x = x0 + (k & 1);\n const y = y0 + (k >> 1);\n if (x < 0 || y < 0 || x >= width || y >= height) continue;\n const p = 4 * (y * width + x);\n const wa = (k & 1 ? kx : 1 - kx) * (k >> 1 ? ky : 1 - ky) * (source[p + 3] as number);\n r += wa * (source[p] as number);\n g += wa * (source[p + 1] as number);\n bl += wa * (source[p + 2] as number);\n alpha += wa;\n }\n if (alpha <= 0) continue;\n const q = 4 * (row * size + col);\n out[q] = r / alpha;\n out[q + 1] = g / alpha;\n out[q + 2] = bl / alpha;\n out[q + 3] = alpha;\n }\n }\n return out;\n}\n\n/** Draws a planned tile: its image, null where the source has nothing; rejects on errors */\nexport type DrawJob = (job: Job, signal: AbortSignal) => Promise<ImageBitmap | null>;\n\n/**\n * Draws planned tiles, keeping `cached` source images decoded. A missing source image (404, 204, 410) leaves its part\n * empty; a tile with no source at all is missing; other errors reject (retried by the engine). In the page, or in a\n * worker of the map (`close` frees the images kept)\n */\nexport function resampler(cached = SOURCE_CACHE): { draw: DrawJob; close: () => void; cache: { capacity: number } } {\n type Image = Promise<ImageBitmap | null>;\n /** Tiles being drawn from each source image: an evicted one is closed once none is */\n const users = new Map<Image, number>();\n const evicted = new Set<Image>();\n const close = (image: Image) => {\n evicted.delete(image);\n image.then(\n (bitmap) => bitmap?.close(),\n () => undefined,\n );\n };\n /** Freed now, or once the tiles drawing from it are done */\n const drop = (image: Image) => {\n if (users.has(image)) evicted.add(image);\n else close(image);\n };\n // Source images by URL: a request is shared by the tiles that need it, its signal is not theirs\n const cache = new Lru<string, Image>(cached, drop);\n const release = (image: Image) => {\n const count = (users.get(image) ?? 1) - 1;\n if (count > 0) users.set(image, count);\n else {\n users.delete(image);\n if (evicted.has(image)) close(image);\n }\n };\n const fetchImage = (url: string): Image => {\n let image = cache.get(url);\n if (!image) {\n image = (async () => {\n const response = await fetch(url);\n if (response.status === 404 || response.status === 204 || response.status === 410) return null;\n if (!response.ok) throw new Error(`isard: ${response.status} for a raster to reproject`);\n return createImageBitmap(await response.blob());\n })();\n // A failure is not kept: the engine's retry fetches again\n image.catch(() => cache.delete(url));\n cache.set(url, image);\n }\n users.set(image, (users.get(image) ?? 0) + 1);\n return image;\n };\n const draw: DrawJob = async (job, signal) => {\n const pending = job.sources.map((s) => [s, fetchImage(s.url)] as const);\n try {\n const images = await Promise.all(pending.map(async ([s, image]) => [s, await image] as const));\n signal.throwIfAborted();\n if (!images.some(([, image]) => image)) return null;\n const canvas = new OffscreenCanvas(job.width, job.height);\n const context = canvas.getContext('2d', { willReadFrequently: true });\n if (!context) throw new Error('isard: no 2D canvas to reproject the tiles');\n for (const [s, image] of images) if (image) context.drawImage(image, s.x, s.y, s.w, s.h);\n const mosaic = context.getImageData(0, 0, job.width, job.height).data;\n const pixels = resample(mosaic, job.width, job.height, job.mesh, job.n, job.size);\n return createImageBitmap(new ImageData(pixels, job.size, job.size), { premultiplyAlpha: 'none' });\n } finally {\n for (const [, image] of pending) release(image);\n }\n };\n return {\n draw,\n cache,\n close: () => {\n for (const image of cache.values()) drop(image);\n cache.clear();\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PngEncoding } from './terrain.ts';
|
|
2
|
+
import type { AtmosphereOptions, BodyOptions, CelestialBody, LoadingMode, TerrainModel, TerrainOptions } from './types.ts';
|
|
3
|
+
/** Decodes the relief tiles of a URL for the engine: 256 × 256 altitudes, null where there is no tile */
|
|
4
|
+
export type TerrainLoader = (z: number, x: number, y: number, signal: AbortSignal) => Promise<Float32Array | null>;
|
|
5
|
+
/** Settings replayed on a new engine after a WebGL context loss */
|
|
6
|
+
export interface Settings {
|
|
7
|
+
body: Required<BodyOptions>;
|
|
8
|
+
zoomRange: [min: number, max: number];
|
|
9
|
+
maxPitch: number;
|
|
10
|
+
globe: boolean;
|
|
11
|
+
sky: boolean;
|
|
12
|
+
atmosphere: Float32Array | undefined;
|
|
13
|
+
terrain: {
|
|
14
|
+
url: string;
|
|
15
|
+
exaggeration: number;
|
|
16
|
+
maxZoom: number;
|
|
17
|
+
geoid: boolean;
|
|
18
|
+
/** Decodes the tiles of `url` for the engine (an encoding other than raw floats) */
|
|
19
|
+
loader?: TerrainLoader;
|
|
20
|
+
/** Credits of the model */
|
|
21
|
+
attribution?: string;
|
|
22
|
+
} | undefined;
|
|
23
|
+
loading: LoadingMode;
|
|
24
|
+
background: [r: number, g: number, b: number];
|
|
25
|
+
globeColor: [r: number, g: number, b: number];
|
|
26
|
+
}
|
|
27
|
+
/** A body from the option, its defaults filled in */
|
|
28
|
+
export declare function bodySettings(body: CelestialBody | undefined): Required<BodyOptions>;
|
|
29
|
+
/** The halo of an atmosphere for the engine (RGB 0-1 and its strength, the color's alpha): undefined for none */
|
|
30
|
+
export declare function haloSettings(atmosphere: boolean | string): Float32Array | undefined;
|
|
31
|
+
/** Relief settings from the option: undefined when off. On the Earth the Terrain Tiles by default (worldwide,
|
|
32
|
+
* Terrarium PNGs decoded by `terrainLoader`) or the IGN terrain model (France, raised by the geoid); NASA's on the Moon
|
|
33
|
+
* and Mars; an ArcGIS image service asked for in the body's Mercator. `terrainLoader`: the loader of PNG tiles in an
|
|
34
|
+
* encoding, the same for the same tiles (the engine keeps its tiles when only the exaggeration changes) */
|
|
35
|
+
export declare function terrainSettings(terrain: boolean | TerrainModel | TerrainOptions | undefined, body: CelestialBody | undefined, radius: number, terrainLoader: (url: string, encoding: PngEncoding, floor?: number) => TerrainLoader): Settings['terrain'];
|
|
36
|
+
/** Sky settings for the engine: colors (RGB 0-1) and blends, MapLibre's defaults */
|
|
37
|
+
export declare function atmosphereSettings(options: AtmosphereOptions | undefined): Float32Array | undefined;
|
|
38
|
+
/** A CSS color to RGB 0-1 (its alpha dropped); black if unknown */
|
|
39
|
+
export declare function rgb(color: string): [r: number, g: number, b: number];
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings of a map's engine from its options: the body, its halo, its relief, the sky. Kept by the map and
|
|
3
|
+
* replayed on a new engine (after a context loss, or on the engine with 3D Tiles).
|
|
4
|
+
*/
|
|
5
|
+
import { BODY_TERRAINS, IGN_ATTRIBUTION, IMAGE_SERVER, imageServerTerrain, TERRAIN_TILES, TERRAIN_TILES_ATTRIBUTION, TERRAIN_URL, } from "./basemaps.js";
|
|
6
|
+
import { parseCssColor } from "./color.js";
|
|
7
|
+
/** The named bodies; a body's `color` is that of its globe under the tiles (the Earth's follows its basemap), its
|
|
8
|
+
* `relief` the range of its ground (the Earth's dry land, from the Dead Sea to Everest). Mars: the sphere of its
|
|
9
|
+
* equatorial radius (its data take planetocentric latitudes), a faint butterscotch halo of dust */
|
|
10
|
+
const BODIES = {
|
|
11
|
+
earth: { radius: 6378137, flattening: 1 / 298.257223563, atmosphere: true, color: '#cacaca', relief: [-450, 8900] },
|
|
12
|
+
moon: { radius: 1737400, flattening: 0, atmosphere: false, color: '#77756f', relief: [-9200, 10800] },
|
|
13
|
+
mars: {
|
|
14
|
+
radius: 3396190,
|
|
15
|
+
flattening: 0,
|
|
16
|
+
atmosphere: 'rgba(232, 168, 120, 0.6)',
|
|
17
|
+
color: '#b5714a',
|
|
18
|
+
relief: [-8300, 21300],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
/** The Earth's halo, RGB 0-1 and its strength: what `atmosphere: true` draws */
|
|
22
|
+
const EARTH_HALO = Float32Array.of(0.42, 0.66, 1, 1);
|
|
23
|
+
/** A body from the option, its defaults filled in */
|
|
24
|
+
export function bodySettings(body) {
|
|
25
|
+
if (body === undefined || typeof body === 'string')
|
|
26
|
+
return BODIES[body ?? 'earth'];
|
|
27
|
+
const relief = [-0.01 * body.radius, 0.01 * body.radius];
|
|
28
|
+
return { flattening: 0, atmosphere: false, color: '#808080', relief, ...body };
|
|
29
|
+
}
|
|
30
|
+
/** The halo of an atmosphere for the engine (RGB 0-1 and its strength, the color's alpha): undefined for none */
|
|
31
|
+
export function haloSettings(atmosphere) {
|
|
32
|
+
if (atmosphere === true)
|
|
33
|
+
return EARTH_HALO;
|
|
34
|
+
const color = atmosphere === false ? undefined : parseCssColor(atmosphere);
|
|
35
|
+
return color && Float32Array.from(color);
|
|
36
|
+
}
|
|
37
|
+
/** Relief settings from the option: undefined when off. On the Earth the Terrain Tiles by default (worldwide,
|
|
38
|
+
* Terrarium PNGs decoded by `terrainLoader`) or the IGN terrain model (France, raised by the geoid); NASA's on the Moon
|
|
39
|
+
* and Mars; an ArcGIS image service asked for in the body's Mercator. `terrainLoader`: the loader of PNG tiles in an
|
|
40
|
+
* encoding, the same for the same tiles (the engine keeps its tiles when only the exaggeration changes) */
|
|
41
|
+
export function terrainSettings(terrain, body, radius, terrainLoader) {
|
|
42
|
+
if (!terrain)
|
|
43
|
+
return undefined;
|
|
44
|
+
let options = {};
|
|
45
|
+
if (typeof terrain === 'string')
|
|
46
|
+
options = { model: terrain };
|
|
47
|
+
else if (terrain !== true)
|
|
48
|
+
options = terrain;
|
|
49
|
+
const earth = (body ?? 'earth') === 'earth';
|
|
50
|
+
const exaggeration = options.exaggeration ?? 1;
|
|
51
|
+
if (options.url === undefined && earth) {
|
|
52
|
+
if (options.model === 'ign') {
|
|
53
|
+
const geoid = options.geoid ?? true;
|
|
54
|
+
return { url: TERRAIN_URL, exaggeration, maxZoom: options.maxZoom ?? 15, geoid, attribution: IGN_ATTRIBUTION };
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
url: TERRAIN_TILES.url,
|
|
58
|
+
exaggeration,
|
|
59
|
+
maxZoom: options.maxZoom ?? TERRAIN_TILES.maxZoom,
|
|
60
|
+
geoid: options.geoid ?? false,
|
|
61
|
+
// The sea floor of the model at sea level: the oceans stay flat under the basemap's water
|
|
62
|
+
loader: terrainLoader(TERRAIN_TILES.url, 'terrarium', 0),
|
|
63
|
+
attribution: TERRAIN_TILES_ATTRIBUTION,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const preset = body === 'moon' || body === 'mars' ? BODY_TERRAINS[body] : undefined;
|
|
67
|
+
const url = options.url ?? preset?.url;
|
|
68
|
+
if (url === undefined)
|
|
69
|
+
throw new Error('isard: the relief of this body needs a url');
|
|
70
|
+
const encoding = options.encoding ?? 'float32';
|
|
71
|
+
// PNG encodings are fetched tile by tile: a {bbox} or an image service would be fetched as it is
|
|
72
|
+
if (encoding !== 'float32' &&
|
|
73
|
+
(IMAGE_SERVER.test(url) || !/\{z\}/.test(url) || !/\{x\}/.test(url) || !/\{y\}/.test(url))) {
|
|
74
|
+
throw new Error(`isard: the '${encoding}' terrain encoding takes a tile URL with {z}, {x} and {y} (${url})`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
url: IMAGE_SERVER.test(url) ? imageServerTerrain(url, earth, radius) : url,
|
|
78
|
+
exaggeration,
|
|
79
|
+
maxZoom: options.maxZoom ?? (options.url === undefined && preset ? preset.maxZoom : 15),
|
|
80
|
+
geoid: options.geoid ?? false,
|
|
81
|
+
...(encoding === 'float32' ? {} : { loader: terrainLoader(url, encoding) }),
|
|
82
|
+
// NASA's credits for its models
|
|
83
|
+
...(options.url === undefined && preset ? { attribution: preset.attribution } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Sky settings for the engine: colors (RGB 0-1) and blends, MapLibre's defaults */
|
|
87
|
+
export function atmosphereSettings(options) {
|
|
88
|
+
if (!options)
|
|
89
|
+
return undefined;
|
|
90
|
+
return Float32Array.from([
|
|
91
|
+
...rgb(options.skyColor ?? '#9cc7ec'),
|
|
92
|
+
...rgb(options.horizonColor ?? '#e8f1f8'),
|
|
93
|
+
...rgb(options.fogColor ?? '#f2f5f8'),
|
|
94
|
+
options.skyHorizonBlend ?? 0.6,
|
|
95
|
+
options.horizonFogBlend ?? 0.7,
|
|
96
|
+
options.fogGroundBlend ?? 0.4,
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
/** A CSS color to RGB 0-1 (its alpha dropped); black if unknown */
|
|
100
|
+
export function rgb(color) {
|
|
101
|
+
const [r, g, b] = parseCssColor(color) ?? [0, 0, 0, 1];
|
|
102
|
+
return [r, g, b];
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The settings of a map's engine from its options: the body, its halo, its relief, the sky. Kept by the map and\n * replayed on a new engine (after a context loss, or on the engine with 3D Tiles).\n */\nimport {\n BODY_TERRAINS,\n IGN_ATTRIBUTION,\n IMAGE_SERVER,\n imageServerTerrain,\n TERRAIN_TILES,\n TERRAIN_TILES_ATTRIBUTION,\n TERRAIN_URL,\n} from './basemaps.ts';\nimport { parseCssColor } from './color.ts';\nimport type { PngEncoding } from './terrain.ts';\nimport type {\n AtmosphereOptions,\n BodyOptions,\n CelestialBody,\n LoadingMode,\n TerrainModel,\n TerrainOptions,\n} from './types.ts';\n\n/** Decodes the relief tiles of a URL for the engine: 256 × 256 altitudes, null where there is no tile */\nexport type TerrainLoader = (z: number, x: number, y: number, signal: AbortSignal) => Promise<Float32Array | null>;\n\n/** Settings replayed on a new engine after a WebGL context loss */\nexport interface Settings {\n body: Required<BodyOptions>;\n zoomRange: [min: number, max: number];\n maxPitch: number;\n globe: boolean;\n sky: boolean;\n atmosphere: Float32Array | undefined;\n terrain:\n | {\n url: string;\n exaggeration: number;\n maxZoom: number;\n geoid: boolean;\n /** Decodes the tiles of `url` for the engine (an encoding other than raw floats) */\n loader?: TerrainLoader;\n /** Credits of the model */\n attribution?: string;\n }\n | undefined;\n loading: LoadingMode;\n background: [r: number, g: number, b: number];\n globeColor: [r: number, g: number, b: number];\n}\n\n/** The named bodies; a body's `color` is that of its globe under the tiles (the Earth's follows its basemap), its\n * `relief` the range of its ground (the Earth's dry land, from the Dead Sea to Everest). Mars: the sphere of its\n * equatorial radius (its data take planetocentric latitudes), a faint butterscotch halo of dust */\nconst BODIES: Record<Exclude<CelestialBody, BodyOptions>, Required<BodyOptions>> = {\n earth: { radius: 6378137, flattening: 1 / 298.257223563, atmosphere: true, color: '#cacaca', relief: [-450, 8900] },\n moon: { radius: 1737400, flattening: 0, atmosphere: false, color: '#77756f', relief: [-9200, 10800] },\n mars: {\n radius: 3396190,\n flattening: 0,\n atmosphere: 'rgba(232, 168, 120, 0.6)',\n color: '#b5714a',\n relief: [-8300, 21300],\n },\n};\n\n/** The Earth's halo, RGB 0-1 and its strength: what `atmosphere: true` draws */\nconst EARTH_HALO = Float32Array.of(0.42, 0.66, 1, 1);\n\n/** A body from the option, its defaults filled in */\nexport function bodySettings(body: CelestialBody | undefined): Required<BodyOptions> {\n if (body === undefined || typeof body === 'string') return BODIES[body ?? 'earth'];\n const relief: [number, number] = [-0.01 * body.radius, 0.01 * body.radius];\n return { flattening: 0, atmosphere: false, color: '#808080', relief, ...body };\n}\n\n/** The halo of an atmosphere for the engine (RGB 0-1 and its strength, the color's alpha): undefined for none */\nexport function haloSettings(atmosphere: boolean | string): Float32Array | undefined {\n if (atmosphere === true) return EARTH_HALO;\n const color = atmosphere === false ? undefined : parseCssColor(atmosphere);\n return color && Float32Array.from(color);\n}\n\n/** Relief settings from the option: undefined when off. On the Earth the Terrain Tiles by default (worldwide,\n * Terrarium PNGs decoded by `terrainLoader`) or the IGN terrain model (France, raised by the geoid); NASA's on the Moon\n * and Mars; an ArcGIS image service asked for in the body's Mercator. `terrainLoader`: the loader of PNG tiles in an\n * encoding, the same for the same tiles (the engine keeps its tiles when only the exaggeration changes) */\nexport function terrainSettings(\n terrain: boolean | TerrainModel | TerrainOptions | undefined,\n body: CelestialBody | undefined,\n radius: number,\n terrainLoader: (url: string, encoding: PngEncoding, floor?: number) => TerrainLoader,\n): Settings['terrain'] {\n if (!terrain) return undefined;\n let options: TerrainOptions = {};\n if (typeof terrain === 'string') options = { model: terrain };\n else if (terrain !== true) options = terrain;\n const earth = (body ?? 'earth') === 'earth';\n const exaggeration = options.exaggeration ?? 1;\n if (options.url === undefined && earth) {\n if (options.model === 'ign') {\n const geoid = options.geoid ?? true;\n return { url: TERRAIN_URL, exaggeration, maxZoom: options.maxZoom ?? 15, geoid, attribution: IGN_ATTRIBUTION };\n }\n return {\n url: TERRAIN_TILES.url,\n exaggeration,\n maxZoom: options.maxZoom ?? TERRAIN_TILES.maxZoom,\n geoid: options.geoid ?? false,\n // The sea floor of the model at sea level: the oceans stay flat under the basemap's water\n loader: terrainLoader(TERRAIN_TILES.url, 'terrarium', 0),\n attribution: TERRAIN_TILES_ATTRIBUTION,\n };\n }\n const preset = body === 'moon' || body === 'mars' ? BODY_TERRAINS[body] : undefined;\n const url = options.url ?? preset?.url;\n if (url === undefined) throw new Error('isard: the relief of this body needs a url');\n const encoding = options.encoding ?? 'float32';\n // PNG encodings are fetched tile by tile: a {bbox} or an image service would be fetched as it is\n if (\n encoding !== 'float32' &&\n (IMAGE_SERVER.test(url) || !/\\{z\\}/.test(url) || !/\\{x\\}/.test(url) || !/\\{y\\}/.test(url))\n ) {\n throw new Error(`isard: the '${encoding}' terrain encoding takes a tile URL with {z}, {x} and {y} (${url})`);\n }\n return {\n url: IMAGE_SERVER.test(url) ? imageServerTerrain(url, earth, radius) : url,\n exaggeration,\n maxZoom: options.maxZoom ?? (options.url === undefined && preset ? preset.maxZoom : 15),\n geoid: options.geoid ?? false,\n ...(encoding === 'float32' ? {} : { loader: terrainLoader(url, encoding) }),\n // NASA's credits for its models\n ...(options.url === undefined && preset ? { attribution: preset.attribution } : {}),\n };\n}\n\n/** Sky settings for the engine: colors (RGB 0-1) and blends, MapLibre's defaults */\nexport function atmosphereSettings(options: AtmosphereOptions | undefined): Float32Array | undefined {\n if (!options) return undefined;\n return Float32Array.from([\n ...rgb(options.skyColor ?? '#9cc7ec'),\n ...rgb(options.horizonColor ?? '#e8f1f8'),\n ...rgb(options.fogColor ?? '#f2f5f8'),\n options.skyHorizonBlend ?? 0.6,\n options.horizonFogBlend ?? 0.7,\n options.fogGroundBlend ?? 0.4,\n ]);\n}\n\n/** A CSS color to RGB 0-1 (its alpha dropped); black if unknown */\nexport function rgb(color: string): [r: number, g: number, b: number] {\n const [r, g, b] = parseCssColor(color) ?? [0, 0, 0, 1];\n return [r, g, b];\n}\n"]}
|
package/dist/symbols.d.ts
CHANGED
|
@@ -44,16 +44,27 @@ export declare class SymbolLayer<P = Properties> extends Layer {
|
|
|
44
44
|
private readonly host;
|
|
45
45
|
private style;
|
|
46
46
|
private list;
|
|
47
|
+
/** The features in groups that do not change (`setGroups`): each group's labels kept between updates */
|
|
48
|
+
private groups;
|
|
49
|
+
private readonly chunks;
|
|
47
50
|
/** Bumped by each change: a shaping that finished after a newer one began is dropped */
|
|
48
51
|
private generation;
|
|
52
|
+
/** Bumped by each change of style: the labels kept are shaped again */
|
|
53
|
+
private styleGeneration;
|
|
54
|
+
/** No style function takes the index of a feature: a group's labels do not depend on its place */
|
|
55
|
+
private indexFree;
|
|
49
56
|
private labels;
|
|
50
57
|
get features(): readonly Feature<P>[];
|
|
51
58
|
setData(data: FeatureData<P>): void;
|
|
52
59
|
setStyle(style: Partial<SymbolStyle<P>>): void;
|
|
53
60
|
setFilter(filter: ((properties: P, index: number) => boolean) | undefined): void;
|
|
54
61
|
setVisible(visible: boolean): void;
|
|
55
|
-
|
|
62
|
+
private styled;
|
|
63
|
+
/** The runs of text of each feature of a group (none for a feature filtered out) */
|
|
64
|
+
private parts;
|
|
65
|
+
/** Shapes the labels (loading their glyphs first) and sends them; the groups' labels still valid are kept */
|
|
56
66
|
private update;
|
|
67
|
+
/** The labels of a group of features, `base` the index of its first feature */
|
|
57
68
|
private build;
|
|
58
69
|
private send;
|
|
59
70
|
private sendLayer;
|
package/dist/symbols.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* drawn with a halo, standing on the relief.
|
|
5
5
|
*/
|
|
6
6
|
import { evaluate, featuresOf, parseColor, stops, } from "./features.js";
|
|
7
|
+
import { toMercator } from "./geo.js";
|
|
7
8
|
import { codes, DEFAULT_FONT, shape } from "./glyphs.js";
|
|
8
9
|
import { Layer } from "./layers.js";
|
|
9
10
|
/** Where a geometry is labelled: its points, the middle of its longest line (with the direction of the line there),
|
|
@@ -65,17 +66,60 @@ function points(geometry) {
|
|
|
65
66
|
return geometry.geometries.flatMap(points);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/** Numbers per glyph quad: its label's index, its place, its atlas coordinates, its scale */
|
|
70
|
+
const QUAD = 10;
|
|
71
|
+
/** Every character of a run has its glyph */
|
|
72
|
+
function present(part, loaded) {
|
|
73
|
+
const glyphs = loaded.get(part.font ?? DEFAULT_FONT);
|
|
74
|
+
if (!glyphs)
|
|
75
|
+
return false;
|
|
76
|
+
for (const ch of part.text)
|
|
77
|
+
if (ch !== '\n' && !glyphs.has(ch.codePointAt(0) ?? 32))
|
|
78
|
+
return false;
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
/** The labels of chunks one after the other, the quads' label indices moved past those of the chunks before */
|
|
82
|
+
function concat(chunks) {
|
|
83
|
+
if (chunks.length === 1 && chunks[0])
|
|
84
|
+
return chunks[0];
|
|
85
|
+
const length = (key) => chunks.reduce((n, c) => n + c[key].length, 0);
|
|
86
|
+
const out = {
|
|
87
|
+
anchors: new Float64Array(length('anchors')),
|
|
88
|
+
boxes: new Float32Array(length('boxes')),
|
|
89
|
+
styles: new Float32Array(length('styles')),
|
|
90
|
+
quads: new Float32Array(length('quads')),
|
|
91
|
+
};
|
|
92
|
+
const at = { anchors: 0, boxes: 0, styles: 0, quads: 0 };
|
|
93
|
+
let labels = 0;
|
|
94
|
+
for (const c of chunks) {
|
|
95
|
+
out.anchors.set(c.anchors, at.anchors);
|
|
96
|
+
out.boxes.set(c.boxes, at.boxes);
|
|
97
|
+
out.styles.set(c.styles, at.styles);
|
|
98
|
+
out.quads.set(c.quads, at.quads);
|
|
99
|
+
if (labels > 0)
|
|
100
|
+
for (let q = at.quads; q < at.quads + c.quads.length; q += QUAD)
|
|
101
|
+
out.quads[q] = (out.quads[q] ?? 0) + labels;
|
|
102
|
+
at.anchors += c.anchors.length;
|
|
103
|
+
at.boxes += c.boxes.length;
|
|
104
|
+
at.styles += c.styles.length;
|
|
105
|
+
at.quads += c.quads.length;
|
|
106
|
+
labels += c.count;
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
73
110
|
export class SymbolLayer extends Layer {
|
|
74
111
|
host;
|
|
75
112
|
style;
|
|
76
113
|
list;
|
|
114
|
+
/** The features in groups that do not change (`setGroups`): each group's labels kept between updates */
|
|
115
|
+
groups;
|
|
116
|
+
chunks = new WeakMap();
|
|
77
117
|
/** Bumped by each change: a shaping that finished after a newer one began is dropped */
|
|
78
118
|
generation = 0;
|
|
119
|
+
/** Bumped by each change of style: the labels kept are shaped again */
|
|
120
|
+
styleGeneration = 0;
|
|
121
|
+
/** No style function takes the index of a feature: a group's labels do not depend on its place */
|
|
122
|
+
indexFree = true;
|
|
79
123
|
labels;
|
|
80
124
|
/** @internal */
|
|
81
125
|
constructor(host, data, style) {
|
|
@@ -83,16 +127,28 @@ export class SymbolLayer extends Layer {
|
|
|
83
127
|
this.host = host;
|
|
84
128
|
this.style = style;
|
|
85
129
|
this.list = featuresOf(data);
|
|
130
|
+
this.styled();
|
|
86
131
|
}
|
|
87
132
|
get features() {
|
|
88
133
|
return this.list;
|
|
89
134
|
}
|
|
90
135
|
setData(data) {
|
|
91
136
|
this.list = featuresOf(data);
|
|
137
|
+
this.groups = undefined;
|
|
138
|
+
void this.update();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* @internal The features as groups that are never changed once given (the labels of a vector tile and style layer),
|
|
142
|
+
* in order: the labels of a group seen before are not shaped again
|
|
143
|
+
*/
|
|
144
|
+
setGroups(groups) {
|
|
145
|
+
this.groups = groups;
|
|
146
|
+
this.list = groups.flat();
|
|
92
147
|
void this.update();
|
|
93
148
|
}
|
|
94
149
|
setStyle(style) {
|
|
95
150
|
this.style = { ...this.style, ...style };
|
|
151
|
+
this.styled();
|
|
96
152
|
void this.update();
|
|
97
153
|
}
|
|
98
154
|
setFilter(filter) {
|
|
@@ -103,11 +159,16 @@ export class SymbolLayer extends Layer {
|
|
|
103
159
|
this.sendLayer(this.map.engine, this.id);
|
|
104
160
|
this.map.requestRender();
|
|
105
161
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
162
|
+
styled() {
|
|
163
|
+
this.styleGeneration += 1;
|
|
164
|
+
// A function of one parameter reads the properties only
|
|
165
|
+
this.indexFree = Object.values(this.style).every((v) => typeof v !== 'function' || v.length < 2);
|
|
166
|
+
}
|
|
167
|
+
/** The runs of text of each feature of a group (none for a feature filtered out) */
|
|
168
|
+
parts(group, base) {
|
|
109
169
|
const { text, font, filter } = this.style;
|
|
110
|
-
|
|
170
|
+
return group.map((f, k) => {
|
|
171
|
+
const i = base + k;
|
|
111
172
|
if (filter && !filter(f.properties, i))
|
|
112
173
|
return [];
|
|
113
174
|
const t = evaluate(text, f.properties, i);
|
|
@@ -115,35 +176,69 @@ export class SymbolLayer extends Layer {
|
|
|
115
176
|
// Each run in its font, the feature's by default
|
|
116
177
|
return (typeof t === 'string' ? [{ text: t }] : t).map((p) => ({ ...p, font: p.font ?? own }));
|
|
117
178
|
});
|
|
179
|
+
}
|
|
180
|
+
/** Shapes the labels (loading their glyphs first) and sends them; the groups' labels still valid are kept */
|
|
181
|
+
async update() {
|
|
182
|
+
const generation = ++this.generation;
|
|
183
|
+
const groups = this.groups ?? [this.list];
|
|
184
|
+
const keep = this.groups !== undefined;
|
|
185
|
+
const glyphs = this.host.glyphs;
|
|
186
|
+
const todo = [];
|
|
187
|
+
let base = 0;
|
|
188
|
+
for (const group of groups) {
|
|
189
|
+
const c = keep ? this.chunks.get(group) : undefined;
|
|
190
|
+
const valid = c !== undefined &&
|
|
191
|
+
c.style === this.styleGeneration &&
|
|
192
|
+
(c.base === base || this.indexFree) &&
|
|
193
|
+
(c.complete || c.glyphs === glyphs.version);
|
|
194
|
+
if (!valid)
|
|
195
|
+
todo.push({ group, base, parts: this.parts(group, base) });
|
|
196
|
+
base += group.length;
|
|
197
|
+
}
|
|
118
198
|
const byFont = new Map();
|
|
119
|
-
for (const
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
199
|
+
for (const { parts } of todo) {
|
|
200
|
+
for (const p of parts.flat()) {
|
|
201
|
+
const key = p.font ?? DEFAULT_FONT;
|
|
202
|
+
const set = byFont.get(key) ?? new Set();
|
|
203
|
+
for (const code of codes([p]))
|
|
204
|
+
set.add(code);
|
|
205
|
+
byFont.set(key, set);
|
|
206
|
+
}
|
|
125
207
|
}
|
|
126
208
|
const loaded = new Map();
|
|
127
209
|
await Promise.all([...byFont].map(async ([f, set]) => {
|
|
128
|
-
loaded.set(f, await
|
|
210
|
+
loaded.set(f, await glyphs.load(f, set));
|
|
129
211
|
}));
|
|
130
212
|
if (generation !== this.generation)
|
|
131
213
|
return;
|
|
132
|
-
|
|
214
|
+
const built = new Map();
|
|
215
|
+
for (const { group, base: b, parts } of todo) {
|
|
216
|
+
const chunk = this.build(group, b, parts, loaded);
|
|
217
|
+
built.set(group, chunk);
|
|
218
|
+
if (keep)
|
|
219
|
+
this.chunks.set(group, chunk);
|
|
220
|
+
}
|
|
221
|
+
this.labels = concat(groups.map((g) => built.get(g) ?? this.chunks.get(g)));
|
|
133
222
|
this.send(this.map.engine, this.id);
|
|
134
223
|
this.map.requestRender();
|
|
135
224
|
}
|
|
136
|
-
|
|
225
|
+
/** The labels of a group of features, `base` the index of its first feature */
|
|
226
|
+
build(group, base, parts, loaded) {
|
|
137
227
|
const { font, size, anchor, offset, maxWidth, lineHeight, color, haloColor, haloWidth, overlap, placement } = this.style;
|
|
138
228
|
const anchorList = [];
|
|
139
229
|
const boxes = [];
|
|
140
230
|
const styles = [];
|
|
141
231
|
const quads = [];
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
232
|
+
let count = 0;
|
|
233
|
+
let complete = true;
|
|
234
|
+
group.forEach((f, k) => {
|
|
235
|
+
const i = base + k;
|
|
236
|
+
const text = parts[k];
|
|
145
237
|
if (!text?.length || text.every((p) => !p.text))
|
|
146
238
|
return;
|
|
239
|
+
// Glyphs still missing: shaped again once more glyphs are loaded
|
|
240
|
+
if (complete && !text.every((p) => present(p, loaded)))
|
|
241
|
+
complete = false;
|
|
147
242
|
// Shaped at its largest size; the engine scales by the zoom stops
|
|
148
243
|
const s0 = evaluate(size, f.properties, i);
|
|
149
244
|
const shaped = typeof s0 === 'number' ? s0 : Math.max(...s0.map(([, v]) => v), 1);
|
|
@@ -162,14 +257,13 @@ export class SymbolLayer extends Layer {
|
|
|
162
257
|
const fill = parseColor(evaluate(color, f.properties, i));
|
|
163
258
|
const haloRgba = parseColor(evaluate(haloColor, f.properties, i));
|
|
164
259
|
for (const { at, along } of anchors(f.geometry)) {
|
|
165
|
-
const label =
|
|
166
|
-
|
|
167
|
-
const [x, y] = merc(at);
|
|
260
|
+
const label = count++;
|
|
261
|
+
const [x, y] = toMercator(at);
|
|
168
262
|
anchorList.push(x, y);
|
|
169
263
|
// Along the line: its direction in Mercator (y towards the south)
|
|
170
264
|
let direction = [0, 0];
|
|
171
265
|
if (placement === 'line' && along) {
|
|
172
|
-
const [x1, y1] =
|
|
266
|
+
const [x1, y1] = toMercator([at[0] + along[0], at[1] + along[1]]);
|
|
173
267
|
direction = [x1 - x, y1 - y];
|
|
174
268
|
}
|
|
175
269
|
// The collision box: the ink, with the halo and a little room
|
|
@@ -185,7 +279,11 @@ export class SymbolLayer extends Layer {
|
|
|
185
279
|
boxes: Float32Array.from(boxes),
|
|
186
280
|
styles: Float32Array.from(styles),
|
|
187
281
|
quads: Float32Array.from(quads),
|
|
188
|
-
|
|
282
|
+
count,
|
|
283
|
+
style: this.styleGeneration,
|
|
284
|
+
base,
|
|
285
|
+
glyphs: this.host.glyphs.version,
|
|
286
|
+
complete,
|
|
189
287
|
};
|
|
190
288
|
}
|
|
191
289
|
send(engine, id) {
|