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.
Files changed (93) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +8 -4
  3. package/dist/animation.d.ts +18 -0
  4. package/dist/animation.js +84 -0
  5. package/dist/animation.js.map +1 -0
  6. package/dist/basemap.worker.d.ts +26 -4
  7. package/dist/basemap.worker.js +2 -2
  8. package/dist/basemap.worker.js.map +9 -5
  9. package/dist/crs.d.ts +72 -0
  10. package/dist/crs.js +243 -0
  11. package/dist/crs.js.map +1 -0
  12. package/dist/decoder.worker.js +4 -4
  13. package/dist/decoder.worker.js.map +4 -4
  14. package/dist/engine-loader.d.ts +28 -0
  15. package/dist/engine-loader.js +112 -0
  16. package/dist/engine-loader.js.map +1 -0
  17. package/dist/features.d.ts +8 -0
  18. package/dist/features.js +17 -10
  19. package/dist/features.js.map +1 -1
  20. package/dist/geo.d.ts +16 -0
  21. package/dist/geo.js +32 -0
  22. package/dist/geo.js.map +1 -0
  23. package/dist/geoplateforme.d.ts +21 -5
  24. package/dist/geoplateforme.js +47 -12
  25. package/dist/geoplateforme.js.map +1 -1
  26. package/dist/glyphs.d.ts +13 -3
  27. package/dist/glyphs.js +26 -4
  28. package/dist/glyphs.js.map +1 -1
  29. package/dist/gpx.d.ts +11 -0
  30. package/dist/gpx.js +74 -0
  31. package/dist/gpx.js.map +1 -0
  32. package/dist/grid.d.ts +81 -0
  33. package/dist/grid.js +270 -0
  34. package/dist/grid.js.map +1 -0
  35. package/dist/icons.d.ts +146 -0
  36. package/dist/icons.js +333 -0
  37. package/dist/icons.js.map +1 -0
  38. package/dist/index.d.ts +11 -4
  39. package/dist/index.js +6 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/interaction.d.ts +0 -4
  42. package/dist/interaction.js +8 -4
  43. package/dist/interaction.js.map +1 -1
  44. package/dist/isard_bg.wasm +0 -0
  45. package/dist/isard_core_bg.wasm +0 -0
  46. package/dist/kml.d.ts +12 -0
  47. package/dist/kml.js +159 -0
  48. package/dist/kml.js.map +1 -0
  49. package/dist/layers.d.ts +89 -2
  50. package/dist/layers.js +171 -15
  51. package/dist/layers.js.map +1 -1
  52. package/dist/lru.d.ts +22 -0
  53. package/dist/lru.js +52 -0
  54. package/dist/lru.js.map +1 -0
  55. package/dist/map.d.ts +45 -7
  56. package/dist/map.js +138 -310
  57. package/dist/map.js.map +1 -1
  58. package/dist/resample.d.ts +41 -0
  59. package/dist/resample.js +164 -0
  60. package/dist/resample.js.map +1 -0
  61. package/dist/settings.d.ts +39 -0
  62. package/dist/settings.js +104 -0
  63. package/dist/settings.js.map +1 -0
  64. package/dist/symbols.d.ts +54 -9
  65. package/dist/symbols.js +278 -55
  66. package/dist/symbols.js.map +1 -1
  67. package/dist/terrain.d.ts +19 -7
  68. package/dist/terrain.js +40 -28
  69. package/dist/terrain.js.map +1 -1
  70. package/dist/topojson.d.ts +29 -0
  71. package/dist/topojson.js +66 -0
  72. package/dist/topojson.js.map +1 -0
  73. package/dist/types.d.ts +14 -6
  74. package/dist/types.js.map +1 -1
  75. package/dist/vectorstyle.d.ts +32 -3
  76. package/dist/vectorstyle.js.map +1 -1
  77. package/dist/vectortiles.d.ts +19 -7
  78. package/dist/vectortiles.js +117 -69
  79. package/dist/vectortiles.js.map +1 -1
  80. package/dist/wasm/isard.d.ts +55 -7
  81. package/dist/wasm/isard.js +117 -6
  82. package/dist/wasm/isard_core.d.ts +29 -4
  83. package/dist/wasm/isard_core.js +84 -5
  84. package/dist/wmts.d.ts +14 -0
  85. package/dist/wmts.js +76 -0
  86. package/dist/wmts.js.map +1 -0
  87. package/dist/xml.d.ts +26 -0
  88. package/dist/xml.js +109 -0
  89. package/dist/xml.js.map +1 -0
  90. package/package.json +1 -1
  91. package/dist/equirectangular.d.ts +0 -35
  92. package/dist/equirectangular.js +0 -143
  93. package/dist/equirectangular.js.map +0 -1
package/dist/xml.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * A small XML reader for the formats read here (KML, GPX, WMTS capabilities): elements, their attributes and text,
3
+ * CDATA, comments, entities. The same in the page, in workers and in tests (which have no `DOMParser`), and lenient:
4
+ * a closing tag without its opening one is ignored, an unclosed element ends with its parent. Names are kept without
5
+ * their namespace prefix (`ows:Identifier` is `Identifier`): the formats read here never mix two meanings of a name.
6
+ * No DTD is read (no external entity, no entity expansion).
7
+ */
8
+ const ENTITIES = { lt: '<', gt: '>', amp: '&', quot: '"', apos: "'" };
9
+ /** Text with its entities decoded (unknown ones kept as they are) */
10
+ function decode(text) {
11
+ if (!text.includes('&'))
12
+ return text;
13
+ return text.replace(/&(#x[\da-f]+|#\d+|\w+);/gi, (whole, entity) => {
14
+ if (entity[0] === '#') {
15
+ const code = entity[1] === 'x' || entity[1] === 'X' ? Number.parseInt(entity.slice(2), 16) : Number(entity.slice(1));
16
+ return Number.isFinite(code) && code >= 0 && code <= 0x10ffff ? String.fromCodePoint(code) : whole;
17
+ }
18
+ return ENTITIES[entity] ?? whole;
19
+ });
20
+ }
21
+ const local = (name) => name.slice(name.indexOf(':') + 1);
22
+ /** A start tag at the reader's position: its name, its attributes (quoted values may hold '>'), whether it closes */
23
+ const START = /<([^\s/>]+)((?:\s+[^\s=/>]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*(\/?)>/y;
24
+ /** The document's root element; throws if there is none */
25
+ export function parseXml(source) {
26
+ const root = { name: '#document', attributes: {}, children: [], text: '' };
27
+ const stack = [root];
28
+ const top = () => stack[stack.length - 1];
29
+ let i = 0;
30
+ while (i < source.length) {
31
+ const lt = source.indexOf('<', i);
32
+ const end = lt < 0 ? source.length : lt;
33
+ if (end > i)
34
+ top().text += decode(source.slice(i, end));
35
+ if (lt < 0)
36
+ break;
37
+ if (source.startsWith('<!--', lt)) {
38
+ const close = source.indexOf('-->', lt + 4);
39
+ i = close < 0 ? source.length : close + 3;
40
+ }
41
+ else if (source.startsWith('<![CDATA[', lt)) {
42
+ const close = source.indexOf(']]>', lt + 9);
43
+ top().text += source.slice(lt + 9, close < 0 ? source.length : close);
44
+ i = close < 0 ? source.length : close + 3;
45
+ }
46
+ else if (source[lt + 1] === '?' || source[lt + 1] === '!') {
47
+ // A declaration, a processing instruction, a DOCTYPE (its internal subset skipped whole)
48
+ const bracket = source.indexOf('[', lt);
49
+ const gt = source.indexOf('>', lt);
50
+ const subset = source[lt + 1] === '!' && bracket >= 0 && bracket < gt ? source.indexOf(']', bracket) : lt;
51
+ const close = source.indexOf('>', subset);
52
+ i = close < 0 ? source.length : close + 1;
53
+ }
54
+ else if (source[lt + 1] === '/') {
55
+ const gt = source.indexOf('>', lt);
56
+ const name = local(source.slice(lt + 2, gt < 0 ? source.length : gt).trim());
57
+ let at = stack.length - 1;
58
+ while (at > 0 && stack[at]?.name !== name)
59
+ at -= 1;
60
+ if (at > 0)
61
+ stack.length = at;
62
+ i = gt < 0 ? source.length : gt + 1;
63
+ }
64
+ else {
65
+ START.lastIndex = lt;
66
+ const tag = START.exec(source);
67
+ if (!tag) {
68
+ top().text += '<';
69
+ i = lt + 1;
70
+ continue;
71
+ }
72
+ const element = { name: local(tag[1]), attributes: {}, children: [], text: '' };
73
+ for (const [, key, double, single] of tag[2].matchAll(/([^\s=]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
74
+ element.attributes[local(key)] = decode(double ?? single ?? '');
75
+ }
76
+ top().children.push(element);
77
+ if (!tag[3])
78
+ stack.push(element);
79
+ i = START.lastIndex;
80
+ }
81
+ }
82
+ const first = root.children[0];
83
+ if (!first)
84
+ throw new Error('isard: no XML element');
85
+ return first;
86
+ }
87
+ /** The first child element of a name */
88
+ export function child(element, name) {
89
+ return element?.children.find((c) => c.name === name);
90
+ }
91
+ /** The child elements of a name */
92
+ export function children(element, name) {
93
+ return element?.children.filter((c) => c.name === name) ?? [];
94
+ }
95
+ /** The trimmed text of the first child of a name; undefined without one */
96
+ export function childText(element, name) {
97
+ return child(element, name)?.text.trim();
98
+ }
99
+ /** Every element of a name under an element, in document order (not inside a match) */
100
+ export function descendants(element, name, out = []) {
101
+ for (const c of element.children) {
102
+ if (c.name === name)
103
+ out.push(c);
104
+ else
105
+ descendants(c, name, out);
106
+ }
107
+ return out;
108
+ }
109
+ //# sourceMappingURL=xml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.js","sourceRoot":"","sources":["../src/xml.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A small XML reader for the formats read here (KML, GPX, WMTS capabilities): elements, their attributes and text,\n * CDATA, comments, entities. The same in the page, in workers and in tests (which have no `DOMParser`), and lenient:\n * a closing tag without its opening one is ignored, an unclosed element ends with its parent. Names are kept without\n * their namespace prefix (`ows:Identifier` is `Identifier`): the formats read here never mix two meanings of a name.\n * No DTD is read (no external entity, no entity expansion).\n */\n\nexport interface XmlElement {\n /** Local name, without its prefix */\n name: string;\n /** Attributes by local name */\n attributes: Record<string, string>;\n children: XmlElement[];\n /** Text directly inside, CDATA included, entities decoded */\n text: string;\n}\n\nconst ENTITIES: Record<string, string> = { lt: '<', gt: '>', amp: '&', quot: '\"', apos: \"'\" };\n\n/** Text with its entities decoded (unknown ones kept as they are) */\nfunction decode(text: string): string {\n if (!text.includes('&')) return text;\n return text.replace(/&(#x[\\da-f]+|#\\d+|\\w+);/gi, (whole, entity: string) => {\n if (entity[0] === '#') {\n const code =\n entity[1] === 'x' || entity[1] === 'X' ? Number.parseInt(entity.slice(2), 16) : Number(entity.slice(1));\n return Number.isFinite(code) && code >= 0 && code <= 0x10ffff ? String.fromCodePoint(code) : whole;\n }\n return ENTITIES[entity] ?? whole;\n });\n}\n\nconst local = (name: string) => name.slice(name.indexOf(':') + 1);\n\n/** A start tag at the reader's position: its name, its attributes (quoted values may hold '>'), whether it closes */\nconst START = /<([^\\s/>]+)((?:\\s+[^\\s=/>]+\\s*=\\s*(?:\"[^\"]*\"|'[^']*'))*)\\s*(\\/?)>/y;\n\n/** The document's root element; throws if there is none */\nexport function parseXml(source: string): XmlElement {\n const root: XmlElement = { name: '#document', attributes: {}, children: [], text: '' };\n const stack = [root];\n const top = () => stack[stack.length - 1] as XmlElement;\n let i = 0;\n while (i < source.length) {\n const lt = source.indexOf('<', i);\n const end = lt < 0 ? source.length : lt;\n if (end > i) top().text += decode(source.slice(i, end));\n if (lt < 0) break;\n if (source.startsWith('<!--', lt)) {\n const close = source.indexOf('-->', lt + 4);\n i = close < 0 ? source.length : close + 3;\n } else if (source.startsWith('<![CDATA[', lt)) {\n const close = source.indexOf(']]>', lt + 9);\n top().text += source.slice(lt + 9, close < 0 ? source.length : close);\n i = close < 0 ? source.length : close + 3;\n } else if (source[lt + 1] === '?' || source[lt + 1] === '!') {\n // A declaration, a processing instruction, a DOCTYPE (its internal subset skipped whole)\n const bracket = source.indexOf('[', lt);\n const gt = source.indexOf('>', lt);\n const subset = source[lt + 1] === '!' && bracket >= 0 && bracket < gt ? source.indexOf(']', bracket) : lt;\n const close = source.indexOf('>', subset);\n i = close < 0 ? source.length : close + 1;\n } else if (source[lt + 1] === '/') {\n const gt = source.indexOf('>', lt);\n const name = local(source.slice(lt + 2, gt < 0 ? source.length : gt).trim());\n let at = stack.length - 1;\n while (at > 0 && stack[at]?.name !== name) at -= 1;\n if (at > 0) stack.length = at;\n i = gt < 0 ? source.length : gt + 1;\n } else {\n START.lastIndex = lt;\n const tag = START.exec(source);\n if (!tag) {\n top().text += '<';\n i = lt + 1;\n continue;\n }\n const element: XmlElement = { name: local(tag[1] as string), attributes: {}, children: [], text: '' };\n for (const [, key, double, single] of (tag[2] as string).matchAll(/([^\\s=]+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/g)) {\n element.attributes[local(key as string)] = decode(double ?? single ?? '');\n }\n top().children.push(element);\n if (!tag[3]) stack.push(element);\n i = START.lastIndex;\n }\n }\n const first = root.children[0];\n if (!first) throw new Error('isard: no XML element');\n return first;\n}\n\n/** The first child element of a name */\nexport function child(element: XmlElement | undefined, name: string): XmlElement | undefined {\n return element?.children.find((c) => c.name === name);\n}\n\n/** The child elements of a name */\nexport function children(element: XmlElement | undefined, name: string): XmlElement[] {\n return element?.children.filter((c) => c.name === name) ?? [];\n}\n\n/** The trimmed text of the first child of a name; undefined without one */\nexport function childText(element: XmlElement | undefined, name: string): string | undefined {\n return child(element, name)?.text.trim();\n}\n\n/** Every element of a name under an element, in document order (not inside a match) */\nexport function descendants(element: XmlElement, name: string, out: XmlElement[] = []): XmlElement[] {\n for (const c of element.children) {\n if (c.name === name) out.push(c);\n else descendants(c, name, out);\n }\n return out;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isard",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "A WebGL2 map engine for large, interactive data layers, labels and 3D Tiles over raster or vector basemaps (Rust core in WebAssembly)",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Graff",
@@ -1,35 +0,0 @@
1
- /**
2
- * Equirectangular tiles (longitudes and latitudes, two 256 px tiles side by side at level 0, rows from the north)
3
- * reprojected into the Web Mercator tiles the engine draws: a raster loader that fetches the source tiles a Mercator
4
- * tile spans and draws them into it, one row of pixels at a time (along a row the latitude is constant, so each row is
5
- * a horizontal strip of the source, scaled). The planetary services (NASA's Trek, USGS) serve this grid only.
6
- */
7
- /** One strip drawn: from source tile (`tx`, `ty`) at the plan's level, the source rectangle (`sx`, `sy`, `sw`, one
8
- * row high) to the destination rectangle (`dx`, `dy`, `dw`, one row high) */
9
- export interface Strip {
10
- tx: number;
11
- ty: number;
12
- sx: number;
13
- sy: number;
14
- sw: number;
15
- dx: number;
16
- dy: number;
17
- dw: number;
18
- }
19
- /** Where the pixels of a Mercator tile come from: the source level, and a strip per row and source column */
20
- export interface Plan {
21
- level: number;
22
- strips: Strip[];
23
- }
24
- /**
25
- * The strips filling Mercator tile `z`/`x`/`y` (`size` pixels a side) from equirectangular tiles of levels `minLevel`
26
- * to `maxLevel`. The source level has the same pixel size along the parallels (one less than `z`: the source is twice
27
- * as wide at level 0), finer towards the poles, where the Mercator pixels shrink along the meridians.
28
- */
29
- export declare function plan(z: number, x: number, y: number, size: number, maxLevel: number, minLevel?: number): Plan;
30
- /**
31
- * A raster loader drawing Web Mercator tiles of `size` pixels from the equirectangular tiles of `url` ({z}, {x}, {y};
32
- * levels `minLevel` to `maxLevel`). A missing source tile (404) leaves its part empty; a tile with no source at all is
33
- * missing; other errors reject (retried by the engine).
34
- */
35
- export declare function equirectangularLoader(url: string, maxLevel: number, size: number, minLevel?: number): (z: number, x: number, y: number, signal: AbortSignal) => Promise<ImageBitmap | null>;
@@ -1,143 +0,0 @@
1
- /**
2
- * Equirectangular tiles (longitudes and latitudes, two 256 px tiles side by side at level 0, rows from the north)
3
- * reprojected into the Web Mercator tiles the engine draws: a raster loader that fetches the source tiles a Mercator
4
- * tile spans and draws them into it, one row of pixels at a time (along a row the latitude is constant, so each row is
5
- * a horizontal strip of the source, scaled). The planetary services (NASA's Trek, USGS) serve this grid only.
6
- */
7
- /** Side of the source tiles, pixels */
8
- const SOURCE = 256;
9
- /** Source levels added at most towards the poles, where Web Mercator stretches the latitudes */
10
- const MAX_BOOST = 3;
11
- /** Source tiles kept decoded: the neighbours of a Mercator tile share them */
12
- const CACHE = 96;
13
- /** Latitude (degrees) of a Mercator y in [0, 1] (towards the south) */
14
- const latitude = (y) => (Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180) / Math.PI;
15
- /**
16
- * The strips filling Mercator tile `z`/`x`/`y` (`size` pixels a side) from equirectangular tiles of levels `minLevel`
17
- * to `maxLevel`. The source level has the same pixel size along the parallels (one less than `z`: the source is twice
18
- * as wide at level 0), finer towards the poles, where the Mercator pixels shrink along the meridians.
19
- */
20
- export function plan(z, x, y, size, maxLevel, minLevel = 0) {
21
- const n = 2 ** z;
22
- const center = latitude((y + 0.5) / n);
23
- const boost = Math.min(MAX_BOOST, Math.max(0, Math.round(-Math.log2(Math.cos((center * Math.PI) / 180)))));
24
- const level = Math.max(0, minLevel, Math.min(maxLevel, z - 1 + boost));
25
- // The source in pixels at that level: 360° across, 180° down
26
- const width = 2 * SOURCE * 2 ** level;
27
- const height = SOURCE * 2 ** level;
28
- const west = (x / n) * width;
29
- const east = ((x + 1) / n) * width;
30
- const scale = size / (east - west);
31
- const strips = [];
32
- for (let row = 0; row < size; row += 1) {
33
- const lat = latitude((y + (row + 0.5) / size) / n);
34
- const v = Math.max(0, Math.min(height - 1e-6, ((90 - lat) / 180) * height));
35
- const ty = Math.floor(v / SOURCE);
36
- // The source row centered on the destination row's latitude, kept inside its tile (no bleeding into the next)
37
- const sy = Math.max(0, Math.min(SOURCE - 1, v - ty * SOURCE - 0.5));
38
- for (let tx = Math.floor(west / SOURCE); tx * SOURCE < east; tx += 1) {
39
- const from = Math.max(west, tx * SOURCE);
40
- const to = Math.min(east, (tx + 1) * SOURCE);
41
- strips.push({
42
- tx,
43
- ty,
44
- sx: from - tx * SOURCE,
45
- sy,
46
- sw: to - from,
47
- dx: (from - west) * scale,
48
- dy: row,
49
- dw: (to - from) * scale,
50
- });
51
- }
52
- }
53
- return { level, strips };
54
- }
55
- /** A tile URL of the source: {z}, {x} and {y} replaced */
56
- const tileUrl = (template, z, x, y) => template.replaceAll('{z}', String(z)).replaceAll('{x}', String(x)).replaceAll('{y}', String(y));
57
- /**
58
- * A raster loader drawing Web Mercator tiles of `size` pixels from the equirectangular tiles of `url` ({z}, {x}, {y};
59
- * levels `minLevel` to `maxLevel`). A missing source tile (404) leaves its part empty; a tile with no source at all is
60
- * missing; other errors reject (retried by the engine).
61
- */
62
- export function equirectangularLoader(url, maxLevel, size, minLevel = 0) {
63
- // Source tiles by key, oldest first: a request is shared by the tiles that need it, its signal is not theirs
64
- const cache = new Map();
65
- /** Tiles being drawn from each source tile: an evicted one is closed once none is */
66
- const users = new Map();
67
- const evicted = new Set();
68
- const close = (tile) => {
69
- evicted.delete(tile);
70
- tile.then((image) => image?.close(), () => undefined);
71
- };
72
- const release = (tile) => {
73
- const count = (users.get(tile) ?? 1) - 1;
74
- if (count > 0)
75
- users.set(tile, count);
76
- else {
77
- users.delete(tile);
78
- if (evicted.has(tile))
79
- close(tile);
80
- }
81
- };
82
- const source = (z, x, y) => {
83
- const key = `${z}/${x}/${y}`;
84
- let tile = cache.get(key);
85
- if (tile) {
86
- cache.delete(key);
87
- }
88
- else {
89
- tile = (async () => {
90
- const response = await fetch(tileUrl(url, z, x, y));
91
- if (response.status === 404 || response.status === 204 || response.status === 410)
92
- return null;
93
- if (!response.ok)
94
- throw new Error(`isard: ${response.status} for an equirectangular tile`);
95
- return createImageBitmap(await response.blob());
96
- })();
97
- // A failure is not kept: the engine's retry fetches again
98
- tile.catch(() => cache.delete(key));
99
- }
100
- cache.set(key, tile);
101
- users.set(tile, (users.get(tile) ?? 0) + 1);
102
- for (const [old, image] of cache) {
103
- if (cache.size <= CACHE)
104
- break;
105
- cache.delete(old);
106
- // Its image freed now, or once the tiles drawing from it are done
107
- if (users.has(image))
108
- evicted.add(image);
109
- else
110
- close(image);
111
- }
112
- return tile;
113
- };
114
- return async (z, x, y, signal) => {
115
- const { level, strips } = plan(z, x, y, size, maxLevel, minLevel);
116
- const keys = [...new Set(strips.map((s) => `${s.tx}/${s.ty}`))];
117
- const tiles = keys.map((key) => {
118
- const [tx, ty] = key.split('/').map(Number);
119
- return [key, source(level, tx, ty)];
120
- });
121
- try {
122
- const images = new Map(await Promise.all(tiles.map(async ([key, tile]) => [key, await tile])));
123
- signal.throwIfAborted();
124
- if (![...images.values()].some(Boolean))
125
- return null;
126
- const canvas = new OffscreenCanvas(size, size);
127
- const context = canvas.getContext('2d');
128
- if (!context)
129
- throw new Error('isard: no 2D canvas to reproject the tiles');
130
- for (const s of strips) {
131
- const image = images.get(`${s.tx}/${s.ty}`);
132
- if (image)
133
- context.drawImage(image, s.sx, s.sy, s.sw, 1, s.dx, s.dy, s.dw, 1);
134
- }
135
- return canvas.transferToImageBitmap();
136
- }
137
- finally {
138
- for (const [, tile] of tiles)
139
- release(tile);
140
- }
141
- };
142
- }
143
- //# sourceMappingURL=equirectangular.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"equirectangular.js","sourceRoot":"","sources":["../src/equirectangular.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Equirectangular tiles (longitudes and latitudes, two 256 px tiles side by side at level 0, rows from the north)\n * reprojected into the Web Mercator tiles the engine draws: a raster loader that fetches the source tiles a Mercator\n * tile spans and draws them into it, one row of pixels at a time (along a row the latitude is constant, so each row is\n * a horizontal strip of the source, scaled). The planetary services (NASA's Trek, USGS) serve this grid only.\n */\n\n/** Side of the source tiles, pixels */\nconst SOURCE = 256;\n/** Source levels added at most towards the poles, where Web Mercator stretches the latitudes */\nconst MAX_BOOST = 3;\n/** Source tiles kept decoded: the neighbours of a Mercator tile share them */\nconst CACHE = 96;\n\n/** One strip drawn: from source tile (`tx`, `ty`) at the plan's level, the source rectangle (`sx`, `sy`, `sw`, one\n * row high) to the destination rectangle (`dx`, `dy`, `dw`, one row high) */\nexport interface Strip {\n tx: number;\n ty: number;\n sx: number;\n sy: number;\n sw: number;\n dx: number;\n dy: number;\n dw: number;\n}\n\n/** Where the pixels of a Mercator tile come from: the source level, and a strip per row and source column */\nexport interface Plan {\n level: number;\n strips: Strip[];\n}\n\n/** Latitude (degrees) of a Mercator y in [0, 1] (towards the south) */\nconst latitude = (y: number) => (Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180) / Math.PI;\n\n/**\n * The strips filling Mercator tile `z`/`x`/`y` (`size` pixels a side) from equirectangular tiles of levels `minLevel`\n * to `maxLevel`. The source level has the same pixel size along the parallels (one less than `z`: the source is twice\n * as wide at level 0), finer towards the poles, where the Mercator pixels shrink along the meridians.\n */\nexport function plan(z: number, x: number, y: number, size: number, maxLevel: number, minLevel = 0): Plan {\n const n = 2 ** z;\n const center = latitude((y + 0.5) / n);\n const boost = Math.min(MAX_BOOST, Math.max(0, Math.round(-Math.log2(Math.cos((center * Math.PI) / 180)))));\n const level = Math.max(0, minLevel, Math.min(maxLevel, z - 1 + boost));\n // The source in pixels at that level: 360° across, 180° down\n const width = 2 * SOURCE * 2 ** level;\n const height = SOURCE * 2 ** level;\n const west = (x / n) * width;\n const east = ((x + 1) / n) * width;\n const scale = size / (east - west);\n const strips: Strip[] = [];\n for (let row = 0; row < size; row += 1) {\n const lat = latitude((y + (row + 0.5) / size) / n);\n const v = Math.max(0, Math.min(height - 1e-6, ((90 - lat) / 180) * height));\n const ty = Math.floor(v / SOURCE);\n // The source row centered on the destination row's latitude, kept inside its tile (no bleeding into the next)\n const sy = Math.max(0, Math.min(SOURCE - 1, v - ty * SOURCE - 0.5));\n for (let tx = Math.floor(west / SOURCE); tx * SOURCE < east; tx += 1) {\n const from = Math.max(west, tx * SOURCE);\n const to = Math.min(east, (tx + 1) * SOURCE);\n strips.push({\n tx,\n ty,\n sx: from - tx * SOURCE,\n sy,\n sw: to - from,\n dx: (from - west) * scale,\n dy: row,\n dw: (to - from) * scale,\n });\n }\n }\n return { level, strips };\n}\n\n/** A tile URL of the source: {z}, {x} and {y} replaced */\nconst tileUrl = (template: string, z: number, x: number, y: number) =>\n template.replaceAll('{z}', String(z)).replaceAll('{x}', String(x)).replaceAll('{y}', String(y));\n\n/**\n * A raster loader drawing Web Mercator tiles of `size` pixels from the equirectangular tiles of `url` ({z}, {x}, {y};\n * levels `minLevel` to `maxLevel`). A missing source tile (404) leaves its part empty; a tile with no source at all is\n * missing; other errors reject (retried by the engine).\n */\nexport function equirectangularLoader(\n url: string,\n maxLevel: number,\n size: number,\n minLevel = 0,\n): (z: number, x: number, y: number, signal: AbortSignal) => Promise<ImageBitmap | null> {\n // Source tiles by key, oldest first: a request is shared by the tiles that need it, its signal is not theirs\n const cache = new Map<string, Promise<ImageBitmap | null>>();\n /** Tiles being drawn from each source tile: an evicted one is closed once none is */\n const users = new Map<Promise<ImageBitmap | null>, number>();\n const evicted = new Set<Promise<ImageBitmap | null>>();\n const close = (tile: Promise<ImageBitmap | null>) => {\n evicted.delete(tile);\n tile.then(\n (image) => image?.close(),\n () => undefined,\n );\n };\n const release = (tile: Promise<ImageBitmap | null>) => {\n const count = (users.get(tile) ?? 1) - 1;\n if (count > 0) users.set(tile, count);\n else {\n users.delete(tile);\n if (evicted.has(tile)) close(tile);\n }\n };\n const source = (z: number, x: number, y: number): Promise<ImageBitmap | null> => {\n const key = `${z}/${x}/${y}`;\n let tile = cache.get(key);\n if (tile) {\n cache.delete(key);\n } else {\n tile = (async () => {\n const response = await fetch(tileUrl(url, z, x, y));\n if (response.status === 404 || response.status === 204 || response.status === 410) return null;\n if (!response.ok) throw new Error(`isard: ${response.status} for an equirectangular tile`);\n return createImageBitmap(await response.blob());\n })();\n // A failure is not kept: the engine's retry fetches again\n tile.catch(() => cache.delete(key));\n }\n cache.set(key, tile);\n users.set(tile, (users.get(tile) ?? 0) + 1);\n for (const [old, image] of cache) {\n if (cache.size <= CACHE) break;\n cache.delete(old);\n // Its image freed now, or once the tiles drawing from it are done\n if (users.has(image)) evicted.add(image);\n else close(image);\n }\n return tile;\n };\n return async (z, x, y, signal) => {\n const { level, strips } = plan(z, x, y, size, maxLevel, minLevel);\n const keys = [...new Set(strips.map((s) => `${s.tx}/${s.ty}`))];\n const tiles = keys.map((key) => {\n const [tx, ty] = key.split('/').map(Number) as [number, number];\n return [key, source(level, tx, ty)] as const;\n });\n try {\n const images = new Map(await Promise.all(tiles.map(async ([key, tile]) => [key, await tile] as const)));\n signal.throwIfAborted();\n if (![...images.values()].some(Boolean)) return null;\n const canvas = new OffscreenCanvas(size, size);\n const context = canvas.getContext('2d');\n if (!context) throw new Error('isard: no 2D canvas to reproject the tiles');\n for (const s of strips) {\n const image = images.get(`${s.tx}/${s.ty}`);\n if (image) context.drawImage(image, s.sx, s.sy, s.sw, 1, s.dx, s.dy, s.dw, 1);\n }\n return canvas.transferToImageBitmap();\n } finally {\n for (const [, tile] of tiles) release(tile);\n }\n };\n}\n"]}