isard 0.2.1 → 0.4.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 +93 -0
- package/README.md +8 -4
- 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 -4
- package/dist/basemap.worker.js +2 -2
- package/dist/basemap.worker.js.map +9 -5
- 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 +28 -0
- package/dist/engine-loader.js +112 -0
- package/dist/engine-loader.js.map +1 -0
- 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 +13 -3
- package/dist/glyphs.js +26 -4
- 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/icons.d.ts +146 -0
- package/dist/icons.js +333 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +0 -4
- package/dist/interaction.js +8 -4
- 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 +89 -2
- package/dist/layers.js +171 -15
- 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 +45 -7
- package/dist/map.js +138 -310
- 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 +54 -9
- package/dist/symbols.js +278 -55
- package/dist/symbols.js.map +1 -1
- package/dist/terrain.d.ts +19 -7
- package/dist/terrain.js +40 -28
- 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 +14 -6
- package/dist/types.js.map +1 -1
- package/dist/vectorstyle.d.ts +32 -3
- package/dist/vectorstyle.js.map +1 -1
- package/dist/vectortiles.d.ts +19 -7
- package/dist/vectortiles.js +117 -69
- package/dist/vectortiles.js.map +1 -1
- package/dist/wasm/isard.d.ts +55 -7
- package/dist/wasm/isard.js +117 -6
- package/dist/wasm/isard_core.d.ts +29 -4
- package/dist/wasm/isard_core.js +84 -5
- 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 -143
- 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":"AAAA;;;GAGG;AACH,OAAO,EACL,aAAa,EACb,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,yBAAyB,EACzB,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAuC3C;;mGAEmG;AACnG,MAAM,MAAM,GAAuE;IACjF,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,GAAG,aAAa,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE;IACnH,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE;IACrG,IAAI,EAAE;QACJ,MAAM,EAAE,OAAO;QACf,UAAU,EAAE,CAAC;QACb,UAAU,EAAE,0BAA0B;QACtC,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC;KACvB;CACF,CAAC;AAEF,gFAAgF;AAChF,MAAM,UAAU,GAAG,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAErD,qDAAqD;AACrD,MAAM,UAAU,YAAY,CAAC,IAA+B;IAC1D,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC;IACnF,MAAM,MAAM,GAAqB,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3E,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;AACjF,CAAC;AAED,iHAAiH;AACjH,MAAM,UAAU,YAAY,CAAC,UAA4B;IACvD,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,UAAU,CAAC;IAC3C,MAAM,KAAK,GAAG,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAC3E,OAAO,KAAK,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED;;;2GAG2G;AAC3G,MAAM,UAAU,eAAe,CAC7B,OAA4D,EAC5D,IAA+B,EAC/B,MAAc,EACd,aAAoF;IAEpF,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,IAAI,OAAO,GAAmB,EAAE,CAAC;IACjC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;SACzD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,GAAG,OAAO,CAAC;IAC7C,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,OAAO,CAAC;IAC5C,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC;IAC/C,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,KAAK,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;YACpC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;QACjH,CAAC;QACD,OAAO;YACL,GAAG,EAAE,aAAa,CAAC,GAAG;YACtB,YAAY;YACZ,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,aAAa,CAAC,OAAO;YACjD,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,KAAK;YAC7B,0FAA0F;YAC1F,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;YACxD,WAAW,EAAE,yBAAyB;SACvC,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,MAAM,EAAE,GAAG,CAAC;IACvC,IAAI,GAAG,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IACrF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC;IAC/C,iGAAiG;IACjG,IACE,QAAQ,KAAK,SAAS;QACtB,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAC1F,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,eAAe,QAAQ,8DAA8D,GAAG,GAAG,CAAC,CAAC;IAC/G,CAAC;IACD,OAAO;QACL,GAAG,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;QAC1E,YAAY;QACZ,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACvF,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,KAAK;QAC7B,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC3E,gCAAgC;QAChC,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpF,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,kBAAkB,CAAC,OAAsC;IACvE,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,OAAO,YAAY,CAAC,IAAI,CAAC;QACvB,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC;QACrC,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,SAAS,CAAC;QACzC,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC;QACrC,OAAO,CAAC,eAAe,IAAI,GAAG;QAC9B,OAAO,CAAC,eAAe,IAAI,GAAG;QAC9B,OAAO,CAAC,cAAc,IAAI,GAAG;KAC9B,CAAC,CAAC;AACL,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,GAAG,CAAC,KAAa;IAC/B,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACnB,CAAC","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
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Text labels from GeoJSON: at points, at the middle of lines, at the center of polygons. Shaped on the page,
|
|
3
|
-
* the engine every frame with collisions shared by all symbol layers (the top layer first, each layer in
|
|
4
|
-
* drawn with a halo, standing on the relief.
|
|
2
|
+
* Text labels and icons from GeoJSON: at points, at the middle of lines, at the center of polygons. Shaped on the page,
|
|
3
|
+
* placed by the engine every frame with collisions shared by all symbol layers (the top layer first, each layer in
|
|
4
|
+
* data order), drawn with a halo, standing on the relief. The icons are the map's images (`map.addImage`, a vector
|
|
5
|
+
* basemap's sprite), beside their text or stretched around it.
|
|
5
6
|
*/
|
|
6
7
|
import { type Feature, type FeatureData, type FeatureLayerStyle, type Placement, type Properties, type Size, type Styled } from './features.ts';
|
|
7
8
|
import { type Glyphs, type TextPart } from './glyphs.ts';
|
|
9
|
+
import { type Icons } from './icons.ts';
|
|
8
10
|
import { Layer } from './layers.ts';
|
|
9
11
|
import type { Anchor } from './overlay.ts';
|
|
10
12
|
import type { LayerHost } from './types.ts';
|
|
@@ -16,7 +18,7 @@ export interface SymbolStyle<P = Properties> extends Omit<FeatureLayerStyle<P>,
|
|
|
16
18
|
/** CSS pixels, or zoom stops (default 12) */
|
|
17
19
|
size: Styled<Size, P>;
|
|
18
20
|
/** `'point'` (default): horizontal text; `'line'`: along the line at its middle, kept upright */
|
|
19
|
-
placement: 'point' | 'line'
|
|
21
|
+
placement: Styled<'point' | 'line', P>;
|
|
20
22
|
color: Styled<string, P>;
|
|
21
23
|
haloColor: Styled<string, P>;
|
|
22
24
|
/** CSS pixels (default 0) */
|
|
@@ -29,31 +31,74 @@ export interface SymbolStyle<P = Properties> extends Omit<FeatureLayerStyle<P>,
|
|
|
29
31
|
maxWidth: Styled<number, P>;
|
|
30
32
|
/** Ems (default 1.2) */
|
|
31
33
|
lineHeight: Styled<number, P>;
|
|
32
|
-
/** Shown even where it overlaps other labels, and hiding none (MapLibre's allow-overlap and ignore-placement)
|
|
33
|
-
*
|
|
34
|
-
|
|
34
|
+
/** Shown even where it overlaps other labels, and hiding none (MapLibre's allow-overlap and ignore-placement);
|
|
35
|
+
* `'allow'`: shown even where it overlaps, still hiding the labels after it; `'ignore'`: shown only where it fits,
|
|
36
|
+
* hiding none (default false) */
|
|
37
|
+
overlap: Styled<Overlap, P>;
|
|
38
|
+
/** Shown without its icon where the icon does not fit (default false) */
|
|
39
|
+
textOptional: Styled<boolean, P>;
|
|
40
|
+
/** An image of the map (`map.addImage`); empty: none (default) */
|
|
41
|
+
icon: Styled<string, P>;
|
|
42
|
+
/** Scale of the image, or zoom stops (default 1) */
|
|
43
|
+
iconSize: Styled<Size, P>;
|
|
44
|
+
/** Side of the icon at the anchor (default `'center'`) */
|
|
45
|
+
iconAnchor: Styled<Anchor, P>;
|
|
46
|
+
/** Shift, CSS pixels at size 1 (default [0, 0]) */
|
|
47
|
+
iconOffset: Styled<[x: number, y: number], P>;
|
|
48
|
+
/** Degrees, clockwise (default 0) */
|
|
49
|
+
iconRotate: Styled<number, P>;
|
|
50
|
+
/** `'viewport'`: facing the screen; `'map'`: turning with the map, along its line when placed on one; `'auto'`
|
|
51
|
+
* (default): `'map'` with the `'line'` placement */
|
|
52
|
+
iconRotationAlignment: Styled<'map' | 'viewport' | 'auto', P>;
|
|
53
|
+
/** As `overlap`, for the icon (default false) */
|
|
54
|
+
iconOverlap: Styled<Overlap, P>;
|
|
55
|
+
/** Shown without its text where the text does not fit (default false) */
|
|
56
|
+
iconOptional: Styled<boolean, P>;
|
|
57
|
+
/** Stretched around its text: its width, its height, both (a road shield; `'none'` by default) */
|
|
58
|
+
iconTextFit: Styled<'none' | 'width' | 'height' | 'both', P>;
|
|
59
|
+
/** Room around the text of a fitted icon: top, right, bottom, left (CSS pixels, default 0) */
|
|
60
|
+
iconTextFitPadding: Styled<[top: number, right: number, bottom: number, left: number], P>;
|
|
61
|
+
/** Color of a signed distance field image (default black) */
|
|
62
|
+
iconColor: Styled<string, P>;
|
|
63
|
+
/** 0 to 1 (default 1) */
|
|
64
|
+
iconOpacity: Styled<number, P>;
|
|
35
65
|
}
|
|
66
|
+
/** How a label's text or icon meets the others: `true` shown wherever and hiding none, `'allow'` shown wherever,
|
|
67
|
+
* `'ignore'` hiding none, `false` neither (MapLibre's allow-overlap and ignore-placement) */
|
|
68
|
+
export type Overlap = boolean | 'allow' | 'ignore';
|
|
36
69
|
export interface SymbolOptions<P = Properties> extends Partial<SymbolStyle<P>>, Placement {
|
|
37
70
|
data: FeatureData<P>;
|
|
38
71
|
}
|
|
39
|
-
/** The map's glyphs, for a layer */
|
|
72
|
+
/** The map's glyphs and icons, for a layer */
|
|
40
73
|
export interface GlyphHost extends LayerHost {
|
|
41
74
|
readonly glyphs: Glyphs;
|
|
75
|
+
readonly icons: Icons;
|
|
42
76
|
}
|
|
43
77
|
export declare class SymbolLayer<P = Properties> extends Layer {
|
|
44
78
|
private readonly host;
|
|
45
79
|
private style;
|
|
46
80
|
private list;
|
|
81
|
+
/** The features in groups that do not change (`setGroups`): each group's labels kept between updates */
|
|
82
|
+
private groups;
|
|
83
|
+
private readonly chunks;
|
|
47
84
|
/** Bumped by each change: a shaping that finished after a newer one began is dropped */
|
|
48
85
|
private generation;
|
|
86
|
+
/** Bumped by each change of style: the labels kept are shaped again */
|
|
87
|
+
private styleGeneration;
|
|
88
|
+
/** No style function takes the index of a feature: a group's labels do not depend on its place */
|
|
89
|
+
private indexFree;
|
|
49
90
|
private labels;
|
|
50
91
|
get features(): readonly Feature<P>[];
|
|
51
92
|
setData(data: FeatureData<P>): void;
|
|
52
93
|
setStyle(style: Partial<SymbolStyle<P>>): void;
|
|
53
94
|
setFilter(filter: ((properties: P, index: number) => boolean) | undefined): void;
|
|
54
95
|
setVisible(visible: boolean): void;
|
|
55
|
-
|
|
96
|
+
private styled;
|
|
97
|
+
/** The runs of text of each feature of a group (none for a feature filtered out) */
|
|
98
|
+
private parts;
|
|
99
|
+
/** Shapes the labels (loading their glyphs first) and sends them; the groups' labels still valid are kept */
|
|
56
100
|
private update;
|
|
101
|
+
/** The labels of a group of features, `base` the index of its first feature */
|
|
57
102
|
private build;
|
|
58
103
|
private send;
|
|
59
104
|
private sendLayer;
|