@faclon-labs/app-sdk 0.1.0 → 0.2.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 (178) hide show
  1. package/README.md +34 -1
  2. package/dist/SignIn.js +2 -2
  3. package/dist/config.d.ts +5 -0
  4. package/dist/config.js +1 -0
  5. package/dist/map/globe.d.ts +5 -0
  6. package/dist/map/globe.js +5 -0
  7. package/dist/map/index.d.ts +21 -0
  8. package/dist/map/index.js +21 -0
  9. package/dist/map/source.d.ts +46 -0
  10. package/dist/map/source.js +54 -0
  11. package/dist/map/testing.d.ts +5 -0
  12. package/dist/map/testing.js +5 -0
  13. package/dist/session.d.ts +1 -1
  14. package/dist/session.js +1 -1
  15. package/package.json +55 -5
  16. package/vendor/SOURCE.txt +1 -0
  17. package/vendor/map-engine/cesium/camera.d.ts +10 -0
  18. package/vendor/map-engine/cesium/camera.js +25 -0
  19. package/vendor/map-engine/cesium/index.d.ts +2 -0
  20. package/vendor/map-engine/cesium/index.js +2 -0
  21. package/vendor/map-engine/cesium/renderer.d.ts +149 -0
  22. package/vendor/map-engine/cesium/renderer.js +837 -0
  23. package/vendor/map-engine/commands/index.d.ts +2 -0
  24. package/vendor/map-engine/commands/index.js +2 -0
  25. package/vendor/map-engine/commands/post-message.d.ts +32 -0
  26. package/vendor/map-engine/commands/post-message.js +77 -0
  27. package/vendor/map-engine/commands/webmcp.d.ts +17 -0
  28. package/vendor/map-engine/commands/webmcp.js +59 -0
  29. package/vendor/map-engine/core/commands.d.ts +82 -0
  30. package/vendor/map-engine/core/commands.js +46 -0
  31. package/vendor/map-engine/core/emitter.d.ts +9 -0
  32. package/vendor/map-engine/core/emitter.js +38 -0
  33. package/vendor/map-engine/core/equal.d.ts +2 -0
  34. package/vendor/map-engine/core/equal.js +28 -0
  35. package/vendor/map-engine/core/errors.d.ts +20 -0
  36. package/vendor/map-engine/core/errors.js +34 -0
  37. package/vendor/map-engine/core/geometry-family.d.ts +3 -0
  38. package/vendor/map-engine/core/geometry-family.js +13 -0
  39. package/vendor/map-engine/core/index.d.ts +4 -0
  40. package/vendor/map-engine/core/index.js +3 -0
  41. package/vendor/map-engine/core/types.d.ts +709 -0
  42. package/vendor/map-engine/core/types.js +7 -0
  43. package/vendor/map-engine/data/annotations.d.ts +3 -0
  44. package/vendor/map-engine/data/annotations.js +63 -0
  45. package/vendor/map-engine/data/contracts.d.ts +172 -0
  46. package/vendor/map-engine/data/contracts.js +12 -0
  47. package/vendor/map-engine/data/index.d.ts +4 -0
  48. package/vendor/map-engine/data/index.js +3 -0
  49. package/vendor/map-engine/data/schema.d.ts +7 -0
  50. package/vendor/map-engine/data/schema.js +40 -0
  51. package/vendor/map-engine/geofence/evaluator.d.ts +35 -0
  52. package/vendor/map-engine/geofence/evaluator.js +172 -0
  53. package/vendor/map-engine/geofence/history.d.ts +9 -0
  54. package/vendor/map-engine/geofence/history.js +19 -0
  55. package/vendor/map-engine/geofence/index.d.ts +2 -0
  56. package/vendor/map-engine/geofence/index.js +2 -0
  57. package/vendor/map-engine/index.d.ts +27 -0
  58. package/vendor/map-engine/index.js +14 -0
  59. package/vendor/map-engine/layers/index.d.ts +1 -0
  60. package/vendor/map-engine/layers/index.js +1 -0
  61. package/vendor/map-engine/layers/layers.d.ts +49 -0
  62. package/vendor/map-engine/layers/layers.js +316 -0
  63. package/vendor/map-engine/maplibre/deck-host.d.ts +105 -0
  64. package/vendor/map-engine/maplibre/deck-host.js +401 -0
  65. package/vendor/map-engine/maplibre/icons.d.ts +17 -0
  66. package/vendor/map-engine/maplibre/icons.js +65 -0
  67. package/vendor/map-engine/maplibre/index.d.ts +3 -0
  68. package/vendor/map-engine/maplibre/index.js +2 -0
  69. package/vendor/map-engine/maplibre/map-like.d.ts +56 -0
  70. package/vendor/map-engine/maplibre/map-like.js +1 -0
  71. package/vendor/map-engine/maplibre/renderer.d.ts +173 -0
  72. package/vendor/map-engine/maplibre/renderer.js +889 -0
  73. package/vendor/map-engine/maplibre/style.d.ts +84 -0
  74. package/vendor/map-engine/maplibre/style.js +493 -0
  75. package/vendor/map-engine/present/decorations.d.ts +25 -0
  76. package/vendor/map-engine/present/decorations.js +115 -0
  77. package/vendor/map-engine/present/index.d.ts +2 -0
  78. package/vendor/map-engine/present/index.js +2 -0
  79. package/vendor/map-engine/present/present.d.ts +47 -0
  80. package/vendor/map-engine/present/present.js +149 -0
  81. package/vendor/map-engine/query/index.d.ts +1 -0
  82. package/vendor/map-engine/query/index.js +1 -0
  83. package/vendor/map-engine/query/planner.d.ts +26 -0
  84. package/vendor/map-engine/query/planner.js +47 -0
  85. package/vendor/map-engine/react/MapView.d.ts +87 -0
  86. package/vendor/map-engine/react/MapView.js +270 -0
  87. package/vendor/map-engine/react/context.d.ts +39 -0
  88. package/vendor/map-engine/react/context.js +94 -0
  89. package/vendor/map-engine/react/index.d.ts +2 -0
  90. package/vendor/map-engine/react/index.js +2 -0
  91. package/vendor/map-engine/react/store.d.ts +19 -0
  92. package/vendor/map-engine/react/store.js +18 -0
  93. package/vendor/map-engine/realtime/apply.d.ts +74 -0
  94. package/vendor/map-engine/realtime/apply.js +155 -0
  95. package/vendor/map-engine/realtime/index.d.ts +1 -0
  96. package/vendor/map-engine/realtime/index.js +1 -0
  97. package/vendor/map-engine/render/index.d.ts +1 -0
  98. package/vendor/map-engine/render/index.js +1 -0
  99. package/vendor/map-engine/render/offline.d.ts +8 -0
  100. package/vendor/map-engine/render/offline.js +47 -0
  101. package/vendor/map-engine/render/renderer.d.ts +74 -0
  102. package/vendor/map-engine/render/renderer.js +1 -0
  103. package/vendor/map-engine/render/terrarium.d.ts +48 -0
  104. package/vendor/map-engine/render/terrarium.js +110 -0
  105. package/vendor/map-engine/runtime/drawing.d.ts +28 -0
  106. package/vendor/map-engine/runtime/drawing.js +58 -0
  107. package/vendor/map-engine/runtime/index.d.ts +5 -0
  108. package/vendor/map-engine/runtime/index.js +2 -0
  109. package/vendor/map-engine/runtime/offline.d.ts +24 -0
  110. package/vendor/map-engine/runtime/offline.js +37 -0
  111. package/vendor/map-engine/runtime/overlays.d.ts +25 -0
  112. package/vendor/map-engine/runtime/overlays.js +65 -0
  113. package/vendor/map-engine/runtime/runtime.d.ts +4 -0
  114. package/vendor/map-engine/runtime/runtime.js +2105 -0
  115. package/vendor/map-engine/runtime/types.d.ts +407 -0
  116. package/vendor/map-engine/runtime/types.js +1 -0
  117. package/vendor/map-engine/scene/index.d.ts +1 -0
  118. package/vendor/map-engine/scene/index.js +1 -0
  119. package/vendor/map-engine/scene/scene.d.ts +31 -0
  120. package/vendor/map-engine/scene/scene.js +216 -0
  121. package/vendor/map-engine/selection/index.d.ts +1 -0
  122. package/vendor/map-engine/selection/index.js +1 -0
  123. package/vendor/map-engine/selection/selection.d.ts +12 -0
  124. package/vendor/map-engine/selection/selection.js +28 -0
  125. package/vendor/map-engine/spatial/geojson.d.ts +15 -0
  126. package/vendor/map-engine/spatial/geojson.js +83 -0
  127. package/vendor/map-engine/spatial/geometry.d.ts +13 -0
  128. package/vendor/map-engine/spatial/geometry.js +206 -0
  129. package/vendor/map-engine/spatial/import.d.ts +27 -0
  130. package/vendor/map-engine/spatial/import.js +96 -0
  131. package/vendor/map-engine/spatial/index.d.ts +38 -0
  132. package/vendor/map-engine/spatial/index.js +37 -0
  133. package/vendor/map-engine/spatial/line-of-sight.d.ts +35 -0
  134. package/vendor/map-engine/spatial/line-of-sight.js +43 -0
  135. package/vendor/map-engine/spatial/measure.d.ts +28 -0
  136. package/vendor/map-engine/spatial/measure.js +90 -0
  137. package/vendor/map-engine/spatial/operations.d.ts +21 -0
  138. package/vendor/map-engine/spatial/operations.js +171 -0
  139. package/vendor/map-engine/spatial/query.d.ts +17 -0
  140. package/vendor/map-engine/spatial/query.js +88 -0
  141. package/vendor/map-engine/spatial/spatial-index.d.ts +24 -0
  142. package/vendor/map-engine/spatial/spatial-index.js +36 -0
  143. package/vendor/map-engine/spatial/volume-mesh.d.ts +19 -0
  144. package/vendor/map-engine/spatial/volume-mesh.js +56 -0
  145. package/vendor/map-engine/temporal/index.d.ts +3 -0
  146. package/vendor/map-engine/temporal/index.js +3 -0
  147. package/vendor/map-engine/temporal/journal.d.ts +20 -0
  148. package/vendor/map-engine/temporal/journal.js +34 -0
  149. package/vendor/map-engine/temporal/playback.d.ts +41 -0
  150. package/vendor/map-engine/temporal/playback.js +80 -0
  151. package/vendor/map-engine/temporal/tracks.d.ts +33 -0
  152. package/vendor/map-engine/temporal/tracks.js +119 -0
  153. package/vendor/map-engine/testing/annotation-store.d.ts +24 -0
  154. package/vendor/map-engine/testing/annotation-store.js +43 -0
  155. package/vendor/map-engine/testing/contracts.d.ts +41 -0
  156. package/vendor/map-engine/testing/contracts.js +328 -0
  157. package/vendor/map-engine/testing/fixtures-monitoring.d.ts +22 -0
  158. package/vendor/map-engine/testing/fixtures-monitoring.js +103 -0
  159. package/vendor/map-engine/testing/fixtures.d.ts +18 -0
  160. package/vendor/map-engine/testing/fixtures.js +28 -0
  161. package/vendor/map-engine/testing/index.d.ts +7 -0
  162. package/vendor/map-engine/testing/index.js +7 -0
  163. package/vendor/map-engine/testing/mock-ontology.d.ts +13 -0
  164. package/vendor/map-engine/testing/mock-ontology.js +24 -0
  165. package/vendor/map-engine/testing/mock-provider.d.ts +97 -0
  166. package/vendor/map-engine/testing/mock-provider.js +369 -0
  167. package/vendor/map-engine/testing/test-renderer.d.ts +72 -0
  168. package/vendor/map-engine/testing/test-renderer.js +153 -0
  169. package/vendor/map-engine/viewport/index.d.ts +1 -0
  170. package/vendor/map-engine/viewport/index.js +1 -0
  171. package/vendor/map-engine/viewport/viewport.d.ts +19 -0
  172. package/vendor/map-engine/viewport/viewport.js +102 -0
  173. package/vendor/map-provider-uns/client.d.ts +75 -0
  174. package/vendor/map-provider-uns/client.js +111 -0
  175. package/vendor/map-provider-uns/index.d.ts +2 -0
  176. package/vendor/map-provider-uns/index.js +2 -0
  177. package/vendor/map-provider-uns/provider.d.ts +131 -0
  178. package/vendor/map-provider-uns/provider.js +561 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Realtime (doc 14, §39–40, §62): how one frame of incremental changes is applied. Pure functions over the data
3
+ * the runtime hands in — no scene, no renderer, no events of its own — so the rules (coalescing, stale and
4
+ * out-of-order updates, clock skew, leaving the view, incremental index updates, trail growth) are testable alone.
5
+ */
6
+ import { MapEngineError } from '../core/errors.js';
7
+ import { layerQuery } from '../layers/layers.js';
8
+ import { validateGeometry } from '../spatial/geometry.js';
9
+ import { matchesQuery } from '../spatial/query.js';
10
+ import { appendPoint, toIso, toMs } from '../temporal/tracks.js';
11
+ /**
12
+ * Is `next` older than what is held? A held time beyond `trustUntil` (clock skew) cannot order anything: a real
13
+ * update must not be refused because a source once stamped the future.
14
+ */
15
+ export function isOlder(next, prev, trustUntil) {
16
+ if (!next.observedAt || !prev.observedAt)
17
+ return false;
18
+ const a = Date.parse(next.observedAt);
19
+ const b = Date.parse(prev.observedAt);
20
+ if (!Number.isFinite(a) || !Number.isFinite(b) || b > trustUntil)
21
+ return false;
22
+ return a < b;
23
+ }
24
+ /** Applies one frame of stream events. Many events for one object collapse into its newest state. */
25
+ export function applyFrame(batch, ctx) {
26
+ const touched = new Map();
27
+ const working = (layerId) => {
28
+ let m = touched.get(layerId);
29
+ if (!m)
30
+ touched.set(layerId, (m = new Map(ctx.current(layerId).map((o) => [o.objectId, o]))));
31
+ return m;
32
+ };
33
+ const changes = new Map();
34
+ const changeOf = (layerId) => {
35
+ let c = changes.get(layerId);
36
+ if (!c)
37
+ changes.set(layerId, (c = { upserted: new Map(), removed: new Set() }));
38
+ return c;
39
+ };
40
+ const leave = (layerId, id) => {
41
+ const c = changeOf(layerId);
42
+ c.upserted.delete(id);
43
+ c.removed.add(id);
44
+ };
45
+ const trails = new Set();
46
+ const fresh = [];
47
+ for (const { layerIds, patch, trail } of batch) {
48
+ if (trail) {
49
+ for (const layerId of layerIds)
50
+ if (ctx.trail(layerId, patch))
51
+ trails.add(layerId);
52
+ continue;
53
+ }
54
+ for (const layerId of layerIds) {
55
+ const layer = ctx.layer(layerId);
56
+ if (!layer || !layer.visible)
57
+ continue;
58
+ const objects = working(layerId);
59
+ const query = layerQuery(layer, ctx.bounds);
60
+ for (const id of patch.removed ?? []) {
61
+ if (objects.delete(id)) {
62
+ ctx.counters.applied++;
63
+ leave(layerId, id);
64
+ }
65
+ }
66
+ for (const o of patch.upserted ?? []) {
67
+ if (!o || typeof o.objectId !== 'string' || !validateGeometry(o.geometry).valid) {
68
+ ctx.warn(new MapEngineError('INVALID_GEOMETRY', `realtime: invalid object skipped`, { layerId, objectId: o?.objectId }));
69
+ continue;
70
+ }
71
+ const prev = objects.get(o.objectId);
72
+ ctx.saw(o, layerId);
73
+ if (prev && isOlder(o, prev, ctx.trustUntil)) {
74
+ ctx.counters.stale++; // out-of-order or stale: the newer state wins
75
+ continue;
76
+ }
77
+ // Geofences see every fresh reading, including the one that takes an object out of view.
78
+ if (o.geometry.type === 'Point')
79
+ fresh.push({ layerId, object: o });
80
+ if (!matchesQuery(o, query)) {
81
+ // Moved out of view or no longer matches the layer: it leaves the layer.
82
+ if (objects.delete(o.objectId)) {
83
+ ctx.counters.outOfView++;
84
+ leave(layerId, o.objectId);
85
+ }
86
+ continue;
87
+ }
88
+ objects.set(o.objectId, o);
89
+ const c = changeOf(layerId);
90
+ c.removed.delete(o.objectId);
91
+ c.upserted.set(o.objectId, o);
92
+ ctx.counters.applied++;
93
+ }
94
+ }
95
+ }
96
+ return { touched, changes, fresh, trails };
97
+ }
98
+ /**
99
+ * Applies one frame's changes to the client index instead of rebuilding it. The index holds each object once
100
+ * however many layers show it, so an object leaves only when no other layer still shows it.
101
+ *
102
+ * `displayed` is what every layer shows; `touched` holds the frame's new state for the layers it changed;
103
+ * `skip` names layers kept out of the index (trails).
104
+ */
105
+ export function patchIndex(index, changes, touched, displayed, skip) {
106
+ const ids = new Map();
107
+ const shownElsewhere = (layerId, id) => {
108
+ for (const [other, list] of displayed) {
109
+ if (other === layerId || skip(other))
110
+ continue;
111
+ const t = touched.get(other);
112
+ if (t) {
113
+ if (t.has(id))
114
+ return true;
115
+ continue;
116
+ }
117
+ let set = ids.get(other);
118
+ if (!set)
119
+ ids.set(other, (set = new Set(list.map((o) => o.objectId))));
120
+ if (set.has(id))
121
+ return true;
122
+ }
123
+ return false;
124
+ };
125
+ for (const [layerId, c] of changes) {
126
+ if (skip(layerId))
127
+ continue;
128
+ for (const id of c.removed)
129
+ if (!shownElsewhere(layerId, id))
130
+ index.remove(id);
131
+ for (const o of c.upserted.values())
132
+ index.insert(o);
133
+ }
134
+ }
135
+ /**
136
+ * Grows trails from live point updates and drops points older than `keepFrom`. Returns how many points were added
137
+ * and whether anything changed (a removal changes a trail without adding points).
138
+ */
139
+ export function growTrails(tracks, patch, keepFrom, now) {
140
+ let changed = false;
141
+ let added = 0;
142
+ for (const id of patch.removed ?? [])
143
+ changed = tracks.delete(id) || changed;
144
+ for (const o of patch.upserted ?? []) {
145
+ if (!o || o.geometry?.type !== 'Point' || !validateGeometry(o.geometry).valid)
146
+ continue;
147
+ const ts = o.observedAt && Number.isFinite(toMs(o.observedAt)) ? o.observedAt : toIso(now);
148
+ const p = o.geometry.coordinates;
149
+ const heading = typeof o.properties?.heading === 'number' ? { heading: o.properties.heading } : {};
150
+ tracks.set(o.objectId, appendPoint(tracks.get(o.objectId) ?? { objectId: o.objectId, points: [] }, { timestamp: ts, latitude: p.latitude, longitude: p.longitude, ...heading }, keepFrom));
151
+ added++;
152
+ changed = true;
153
+ }
154
+ return { changed, added };
155
+ }
@@ -0,0 +1 @@
1
+ export { applyFrame, growTrails, isOlder, patchIndex, type AppliedFrame, type ApplyContext, type LayerChanges, type PendingPatch } from './apply.js';
@@ -0,0 +1 @@
1
+ export { applyFrame, growTrails, isOlder, patchIndex } from './apply.js';
@@ -0,0 +1 @@
1
+ export type { MapClickEvent, PickEvent, Renderer, RendererCapabilities, RendererOptions } from './renderer.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Whether a URL stays on the site (doc 28 G14): relative URLs, `data:`, `blob:` and `pmtiles://` packs on the
3
+ * site do; anything else needs one of the allowed origins. Shared by the runtime (config) and renderers (what a
4
+ * style file points at).
5
+ */
6
+ export declare function isLocalUrl(url: string, allowed: string[]): boolean;
7
+ /** Every URL a style JSON points at (sources, glyphs, sprite), for the offline check. */
8
+ export declare function styleUrls(style: Record<string, unknown>): string[];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Whether a URL stays on the site (doc 28 G14): relative URLs, `data:`, `blob:` and `pmtiles://` packs on the
3
+ * site do; anything else needs one of the allowed origins. Shared by the runtime (config) and renderers (what a
4
+ * style file points at).
5
+ */
6
+ export function isLocalUrl(url, allowed) {
7
+ if (/^(data:|blob:)/i.test(url))
8
+ return true;
9
+ const pm = /^pmtiles:\/\/(.+)$/i.exec(url);
10
+ if (pm)
11
+ return isLocalUrl(pm[1], allowed);
12
+ if (!/^[a-z][a-z0-9+.-]*:/i.test(url) && !url.startsWith('//'))
13
+ return true; // relative
14
+ try {
15
+ const u = new URL(url.startsWith('//') ? `https:${url}` : url);
16
+ return allowed.some((o) => {
17
+ try {
18
+ return new URL(o).origin === u.origin;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ });
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ /** Every URL a style JSON points at (sources, glyphs, sprite), for the offline check. */
30
+ export function styleUrls(style) {
31
+ const out = [];
32
+ const add = (u) => typeof u === 'string' && u && out.push(u.replace(/\{[a-z]+\}/gi, '0'));
33
+ add(style.glyphs);
34
+ if (Array.isArray(style.sprite))
35
+ for (const s of style.sprite)
36
+ add(s?.url);
37
+ else
38
+ add(style.sprite);
39
+ for (const src of Object.values((style.sources ?? {}))) {
40
+ add(src?.url);
41
+ for (const t of src?.tiles ?? [])
42
+ add(t);
43
+ if (typeof src?.data === 'string')
44
+ add(src.data);
45
+ }
46
+ return out;
47
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The renderer boundary (ADR-011, Decision 10). Bidirectional: the engine pushes scenes and patches in;
3
+ * the renderer reports camera moves, picks, clicks, hover and errors back — always as stable objectIds.
4
+ * A renderer holds no business logic and never queries data.
5
+ */
6
+ import type { BasemapConfig, MapConfig, MapViewport, Position, RendererCapabilities, ScenePatch, SceneState, Size, TerrainConfig, TilesetConfig, Unsubscribe } from '../core/types.js';
7
+ export type { RendererCapabilities };
8
+ export interface RendererOptions {
9
+ basemap: BasemapConfig;
10
+ viewport: MapViewport;
11
+ projection?: MapConfig['projection'];
12
+ terrain?: TerrainConfig;
13
+ tilesets?: TilesetConfig[];
14
+ /** The offline profile (doc 28 G14): a renderer refuses anything it fetches that would leave these origins. */
15
+ offline?: {
16
+ allowedOrigins: string[];
17
+ };
18
+ }
19
+ export interface PickEvent {
20
+ objectId: string;
21
+ layerId: string;
22
+ /** Where the pointer was. Renderers provide it for clicks; hover may omit it. */
23
+ position?: Position;
24
+ }
25
+ export interface MapClickEvent {
26
+ position: Position;
27
+ }
28
+ /**
29
+ * Lifecycle (doc §65): initialize → render/update → destroy. destroy() releases everything (GPU context, DOM,
30
+ * listeners) and cancels an initialize still in flight; initialize() may then be called again — the runtime
31
+ * unmounts and remounts with the same renderer.
32
+ */
33
+ export interface Renderer {
34
+ initialize(container: HTMLElement, options: RendererOptions): Promise<void>;
35
+ /**
36
+ * Swaps the basemap and keeps the map (camera, GPU context). Engine layers are gone afterwards: the runtime
37
+ * calls render() with the full scene once this resolves. Optional; without it the runtime remounts.
38
+ */
39
+ setBasemap?(basemap: BasemapConfig): Promise<void>;
40
+ readonly capabilities?: RendererCapabilities;
41
+ /** Flat map or globe. Optional; a renderer without it cannot draw a globe (see capabilities). */
42
+ setProjection?(projection: NonNullable<MapConfig['projection']>): void;
43
+ /** Turns terrain relief on (a config) or off (null). */
44
+ setTerrain?(terrain: TerrainConfig | null): void;
45
+ /** Replaces the 3D Tiles scenery (needs `tiles3d`). */
46
+ setTilesets?(tilesets: TilesetConfig[]): void;
47
+ /** Ground heights (metres above sea level) under positions, from the renderer's terrain; null where unknown. */
48
+ sampleElevation?(positions: Position[]): Promise<Array<number | null>>;
49
+ setViewport(viewport: MapViewport, options?: {
50
+ animate?: boolean;
51
+ }): void;
52
+ /** Full scene (first draw, or after a reset). */
53
+ render(scene: SceneState): void;
54
+ /** Incremental change; must not rebuild the whole scene (ADR-014). */
55
+ update(patch: ScenePatch): void;
56
+ resize(): void;
57
+ /** Drawing surface size in CSS pixels; the engine uses it to compute viewport bounds. */
58
+ getSize(): Size;
59
+ destroy(): void;
60
+ /** Camera settled (user or programmatic). Includes the renderer's real visible bounds. */
61
+ onViewportChange(listener: (viewport: MapViewport) => void): Unsubscribe;
62
+ onPick(listener: (event: PickEvent) => void): Unsubscribe;
63
+ /** Click on the map that did not hit an object. */
64
+ onMapClick(listener: (event: MapClickEvent) => void): Unsubscribe;
65
+ onHover(listener: (event: PickEvent | null) => void): Unsubscribe;
66
+ onError(listener: (error: Error) => void): Unsubscribe;
67
+ /** Screen pixel of a position inside the map container (for overlays such as popups). Optional. */
68
+ project?(position: Position): {
69
+ x: number;
70
+ y: number;
71
+ } | null;
72
+ /** Every camera frame while it moves (pan, zoom, animation), for overlays that follow the map. Optional. */
73
+ onCameraMove?(listener: () => void): Unsubscribe;
74
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ /**
2
+ * DEM tiles (Terrarium or Mapbox RGB encoding, web-mercator tiles), decoded by the engine itself: Cesium's terrain
3
+ * comes from the same tiles MapLibre draws (no Cesium Ion, self-hostable like the basemap, §25), and heights are
4
+ * sampled anywhere, not only where the map happens to have tiles loaded.
5
+ */
6
+ import type { TerrainConfig } from '../core/types.js';
7
+ /** Heights (metres) packed in one pixel's colour. */
8
+ export declare function decodeHeight(r: number, g: number, b: number, encoding: TerrainConfig['encoding']): number;
9
+ /**
10
+ * A `size`×`size` RGBA tile resampled to an `out`×`out` heightmap (edges included, as Cesium expects), row by row
11
+ * from north to south.
12
+ */
13
+ export declare function heightsFromTile(rgba: Uint8ClampedArray | Uint8Array, size: number, out: number, encoding: TerrainConfig['encoding']): Float32Array;
14
+ /** Fetches a DEM tile and decodes it to an `out`×`out` heightmap. Undefined when the tile is missing. */
15
+ export declare function loadHeights(url: string, out: number, encoding: TerrainConfig['encoding']): Promise<Float32Array | undefined>;
16
+ /** Where a point falls in the web-mercator tile grid at zoom `z`: tile x/y and the fraction (0…1) inside it. */
17
+ export declare function tilePoint(longitude: number, latitude: number, z: number): {
18
+ x: number;
19
+ y: number;
20
+ fx: number;
21
+ fy: number;
22
+ };
23
+ /** Height at a fraction inside a decoded `size`×`size` tile, between its four nearest pixels. */
24
+ export declare function heightAt(heights: Float32Array, size: number, fx: number, fy: number): number;
25
+ /**
26
+ * True heights (metres, no exaggeration) from the DEM tiles at one detail level, anywhere on Earth. Tiles are
27
+ * fetched once per batch and kept (a small cache, oldest dropped). A missing or failed tile gives null: unknown,
28
+ * never sea level.
29
+ */
30
+ export declare class DemSampler {
31
+ private readonly terrain;
32
+ private readonly zoom;
33
+ private readonly maxTiles;
34
+ private readonly load;
35
+ private readonly tiles;
36
+ constructor(terrain: TerrainConfig, zoom?: number, maxTiles?: number, load?: (url: string) => Promise<{
37
+ rgba: Uint8ClampedArray | Uint8Array;
38
+ size: number;
39
+ } | undefined>);
40
+ /** Tiles are shared between calls, so a caller's cancel is checked by the caller, not passed to the fetch. */
41
+ sample(positions: Array<{
42
+ longitude: number;
43
+ latitude: number;
44
+ }>): Promise<Array<number | null>>;
45
+ private tile;
46
+ }
47
+ /** The tile URL for x/y/z. */
48
+ export declare const tileUrl: (template: string, x: number, y: number, z: number) => string;
@@ -0,0 +1,110 @@
1
+ /** Heights (metres) packed in one pixel's colour. */
2
+ export function decodeHeight(r, g, b, encoding) {
3
+ return encoding === 'mapbox' ? -10_000 + (r * 65_536 + g * 256 + b) * 0.1 : r * 256 + g + b / 256 - 32_768;
4
+ }
5
+ /**
6
+ * A `size`×`size` RGBA tile resampled to an `out`×`out` heightmap (edges included, as Cesium expects), row by row
7
+ * from north to south.
8
+ */
9
+ export function heightsFromTile(rgba, size, out, encoding) {
10
+ const heights = new Float32Array(out * out);
11
+ for (let row = 0; row < out; row++) {
12
+ const y = Math.round((row / (out - 1)) * (size - 1));
13
+ for (let col = 0; col < out; col++) {
14
+ const x = Math.round((col / (out - 1)) * (size - 1));
15
+ const i = (y * size + x) * 4;
16
+ heights[row * out + col] = decodeHeight(rgba[i], rgba[i + 1], rgba[i + 2], encoding);
17
+ }
18
+ }
19
+ return heights;
20
+ }
21
+ /** Fetches a DEM tile's pixels (createImageBitmap + canvas). Undefined when the tile is missing. */
22
+ async function loadPixels(url) {
23
+ const res = await fetch(url);
24
+ if (!res.ok)
25
+ return undefined;
26
+ const bitmap = await createImageBitmap(await res.blob());
27
+ const size = bitmap.width;
28
+ const canvas = typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(size, size) : Object.assign(document.createElement('canvas'), { width: size, height: size });
29
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
30
+ ctx.drawImage(bitmap, 0, 0);
31
+ bitmap.close?.();
32
+ return { rgba: ctx.getImageData(0, 0, size, size).data, size };
33
+ }
34
+ /** Fetches a DEM tile and decodes it to an `out`×`out` heightmap. Undefined when the tile is missing. */
35
+ export async function loadHeights(url, out, encoding) {
36
+ const px = await loadPixels(url);
37
+ return px && heightsFromTile(px.rgba, px.size, out, encoding);
38
+ }
39
+ /** Where a point falls in the web-mercator tile grid at zoom `z`: tile x/y and the fraction (0…1) inside it. */
40
+ export function tilePoint(longitude, latitude, z) {
41
+ const n = 2 ** z;
42
+ const lat = Math.max(-85.0511, Math.min(85.0511, latitude)) * (Math.PI / 180);
43
+ const gx = ((((longitude + 180) / 360) % 1) + 1) % 1 * n;
44
+ const gy = ((1 - Math.log(Math.tan(lat) + 1 / Math.cos(lat)) / Math.PI) / 2) * n;
45
+ const x = Math.min(n - 1, Math.floor(gx));
46
+ const y = Math.min(n - 1, Math.max(0, Math.floor(gy)));
47
+ return { x, y, fx: gx - x, fy: gy - y };
48
+ }
49
+ /** Height at a fraction inside a decoded `size`×`size` tile, between its four nearest pixels. */
50
+ export function heightAt(heights, size, fx, fy) {
51
+ const px = Math.max(0, Math.min(size - 1, fx * size - 0.5));
52
+ const py = Math.max(0, Math.min(size - 1, fy * size - 0.5));
53
+ const x0 = Math.floor(px);
54
+ const y0 = Math.floor(py);
55
+ const x1 = Math.min(size - 1, x0 + 1);
56
+ const y1 = Math.min(size - 1, y0 + 1);
57
+ const tx = px - x0;
58
+ const ty = py - y0;
59
+ const h = (x, y) => heights[y * size + x];
60
+ return (h(x0, y0) * (1 - tx) + h(x1, y0) * tx) * (1 - ty) + (h(x0, y1) * (1 - tx) + h(x1, y1) * tx) * ty;
61
+ }
62
+ /**
63
+ * True heights (metres, no exaggeration) from the DEM tiles at one detail level, anywhere on Earth. Tiles are
64
+ * fetched once per batch and kept (a small cache, oldest dropped). A missing or failed tile gives null: unknown,
65
+ * never sea level.
66
+ */
67
+ export class DemSampler {
68
+ terrain;
69
+ zoom;
70
+ maxTiles;
71
+ load;
72
+ tiles = new Map();
73
+ constructor(terrain, zoom = Math.min(14, terrain.maxZoom ?? 14), maxTiles = 64, load = loadPixels) {
74
+ this.terrain = terrain;
75
+ this.zoom = zoom;
76
+ this.maxTiles = maxTiles;
77
+ this.load = load;
78
+ }
79
+ /** Tiles are shared between calls, so a caller's cancel is checked by the caller, not passed to the fetch. */
80
+ async sample(positions) {
81
+ const points = positions.map((p) => tilePoint(p.longitude, p.latitude, this.zoom));
82
+ const tiles = await Promise.all(points.map((t) => this.tile(t.x, t.y)));
83
+ return points.map((t, i) => {
84
+ const tile = tiles[i];
85
+ return tile ? heightAt(tile.heights, tile.size, t.fx, t.fy) : null;
86
+ });
87
+ }
88
+ tile(x, y) {
89
+ const key = `${x}/${y}`;
90
+ const known = this.tiles.get(key);
91
+ if (known) {
92
+ this.tiles.delete(key); // most recent last
93
+ this.tiles.set(key, known);
94
+ return known;
95
+ }
96
+ const encoding = this.terrain.encoding;
97
+ const pending = this.load(tileUrl(this.terrain.tileUrl, x, y, this.zoom)).then((px) => (px ? { heights: heightsFromTile(px.rgba, px.size, px.size, encoding), size: px.size } : null), () => null);
98
+ // A failure is not kept: the next request tries again.
99
+ void pending.then((t) => {
100
+ if (!t && this.tiles.get(key) === pending)
101
+ this.tiles.delete(key);
102
+ });
103
+ this.tiles.set(key, pending);
104
+ while (this.tiles.size > this.maxTiles)
105
+ this.tiles.delete(this.tiles.keys().next().value);
106
+ return pending;
107
+ }
108
+ }
109
+ /** The tile URL for x/y/z. */
110
+ export const tileUrl = (template, x, y, z) => template.replace('{z}', String(z)).replace('{x}', String(x)).replace('{y}', String(y));
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Drawing (doc §37): clicks become generic geometry. The engine owns the interaction and the draft; the application
3
+ * receives a Geometry and decides what it means (an annotation, a search area, a geofence).
4
+ */
5
+ import type { Geometry, MapLayer, Position, SpatialObject } from '../core/types.js';
6
+ export type DrawKind = 'point' | 'line' | 'polygon' | 'rectangle' | 'circle';
7
+ export interface DrawingState {
8
+ kind: DrawKind;
9
+ /** Clicked positions so far. Rectangle: two corners. Circle: centre, then a point on the edge. */
10
+ points: Position[];
11
+ /** Enough points to finish, and the shape is valid. */
12
+ canFinish: boolean;
13
+ /** Why the shape cannot be finished as it is (e.g. a polygon whose edges cross), for the application to show. */
14
+ problem?: string;
15
+ }
16
+ export declare const minPoints: (kind: DrawKind) => number;
17
+ export declare const autoFinishes: (kind: DrawKind, points: number) => boolean;
18
+ /** The geometry a finished drawing produces, or null if there are not enough points. */
19
+ export declare function drawnGeometry(kind: DrawKind, pts: Position[]): Geometry | null;
20
+ /** Internal scene layers that show the draft. They are never returned by getLayers() and cannot be selected. */
21
+ export declare const DRAFT_LAYER_IDS: {
22
+ readonly area: "__draft-area";
23
+ readonly line: "__draft-line";
24
+ readonly vertices: "__draft-vertices";
25
+ };
26
+ export declare const DRAFT_LAYERS: MapLayer[];
27
+ /** Draft objects per internal layer: the shape so far, its outline, and the clicked vertices. */
28
+ export declare function draftObjects(state: DrawingState | null): Record<string, SpatialObject[]>;
@@ -0,0 +1,58 @@
1
+ import { distanceMeters } from '../spatial/operations.js';
2
+ /** Clicks after which a drawing finishes by itself. Lines and polygons finish when the application says so. */
3
+ const AUTO_FINISH = { point: 1, rectangle: 2, circle: 2 };
4
+ const MIN_POINTS = { point: 1, line: 2, polygon: 3, rectangle: 2, circle: 2 };
5
+ export const minPoints = (kind) => MIN_POINTS[kind];
6
+ export const autoFinishes = (kind, points) => AUTO_FINISH[kind] !== undefined && points >= AUTO_FINISH[kind];
7
+ /** The geometry a finished drawing produces, or null if there are not enough points. */
8
+ export function drawnGeometry(kind, pts) {
9
+ if (pts.length < MIN_POINTS[kind])
10
+ return null;
11
+ switch (kind) {
12
+ case 'point':
13
+ return { type: 'Point', coordinates: pts[0] };
14
+ case 'line':
15
+ return { type: 'LineString', coordinates: [...pts] };
16
+ case 'polygon':
17
+ return { type: 'Polygon', coordinates: [[...pts, pts[0]]] };
18
+ case 'rectangle': {
19
+ const [a, b] = [pts[0], pts[1]];
20
+ const s = Math.min(a.latitude, b.latitude);
21
+ const n = Math.max(a.latitude, b.latitude);
22
+ const w = Math.min(a.longitude, b.longitude);
23
+ const e = Math.max(a.longitude, b.longitude);
24
+ if (s === n || w === e)
25
+ return null;
26
+ const sw = { latitude: s, longitude: w };
27
+ return { type: 'Polygon', coordinates: [[sw, { latitude: s, longitude: e }, { latitude: n, longitude: e }, { latitude: n, longitude: w }, sw]] };
28
+ }
29
+ case 'circle': {
30
+ const radiusMeters = distanceMeters(pts[0], pts[1]);
31
+ return radiusMeters > 0 ? { type: 'Circle', center: pts[0], radiusMeters } : null;
32
+ }
33
+ }
34
+ }
35
+ /** Internal scene layers that show the draft. They are never returned by getLayers() and cannot be selected. */
36
+ export const DRAFT_LAYER_IDS = { area: '__draft-area', line: '__draft-line', vertices: '__draft-vertices' };
37
+ const DRAFT = '#D99A1C';
38
+ const quiet = { selectable: false, hoverable: false };
39
+ export const DRAFT_LAYERS = [
40
+ { id: DRAFT_LAYER_IDS.area, source: {}, visible: true, zIndex: 10_000, interaction: quiet, visualization: { type: 'polygon', style: [{ style: { color: DRAFT, opacity: 0.15, strokeColor: DRAFT } }] } },
41
+ { id: DRAFT_LAYER_IDS.line, source: {}, visible: true, zIndex: 10_001, interaction: quiet, visualization: { type: 'line', style: [{ style: { color: DRAFT, size: 2 } }] } },
42
+ { id: DRAFT_LAYER_IDS.vertices, source: {}, visible: true, zIndex: 10_002, interaction: quiet, visualization: { type: 'marker', style: [{ style: { color: DRAFT, size: 4, strokeColor: '#FFFFFF', strokeWidth: 1.5 } }] } },
43
+ ];
44
+ /** Draft objects per internal layer: the shape so far, its outline, and the clicked vertices. */
45
+ export function draftObjects(state) {
46
+ const out = { [DRAFT_LAYER_IDS.area]: [], [DRAFT_LAYER_IDS.line]: [], [DRAFT_LAYER_IDS.vertices]: [] };
47
+ if (!state)
48
+ return out;
49
+ const pts = state.points;
50
+ out[DRAFT_LAYER_IDS.vertices] = pts.map((p, i) => ({ objectId: `__draft-v${i}`, geometry: { type: 'Point', coordinates: p } }));
51
+ const shape = state.kind === 'line' ? null : drawnGeometry(state.kind, pts);
52
+ if (shape && shape.type !== 'Point')
53
+ out[DRAFT_LAYER_IDS.area] = [{ objectId: '__draft-shape', geometry: shape }];
54
+ if ((state.kind === 'line' || state.kind === 'polygon') && pts.length >= 2) {
55
+ out[DRAFT_LAYER_IDS.line] = [{ objectId: '__draft-path', geometry: { type: 'LineString', coordinates: state.kind === 'polygon' && pts.length >= 3 ? [...pts, pts[0]] : [...pts] } }];
56
+ }
57
+ return out;
58
+ }
@@ -0,0 +1,5 @@
1
+ export { createMapRuntime } from './runtime.js';
2
+ export type { DrawingState, DrawKind } from './drawing.js';
3
+ export type { FlyToOptions, LayerFilter, LineOfSightOptions, MapRuntime, MapRuntimeDeps, MapRuntimeEvents, MapRuntimeOptions, MapRuntimeStats, ObjectInspection, RuntimeState } from './types.js';
4
+ export type { ResolvedReference } from './types.js';
5
+ export { offlineViolations, isLocalUrl } from './offline.js';
@@ -0,0 +1,2 @@
1
+ export { createMapRuntime } from './runtime.js';
2
+ export { offlineViolations, isLocalUrl } from './offline.js';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The offline profile (doc 28 G14): a map for a disconnected site may only use URLs that stay on the site —
3
+ * relative ones, or the origins the deployment lists. Every URL in the config is checked when it is set; a
4
+ * violation is refused loudly (OFFLINE_VIOLATION) instead of failing quietly in the field.
5
+ */
6
+ import type { BasemapConfig, MapConfig, MapLayer, TerrainConfig, TilesetConfig } from '../core/types.js';
7
+ import { isLocalUrl } from '../render/offline.js';
8
+ export { isLocalUrl };
9
+ export declare const allowedOrigins: (offline: MapConfig["offline"]) => string[] | null;
10
+ /** Every URL a part of the config uses, with where it is, for the error message. */
11
+ export declare function configUrls(parts: {
12
+ basemap?: BasemapConfig;
13
+ terrain?: TerrainConfig | null;
14
+ tilesets?: TilesetConfig[];
15
+ layers?: MapLayer[];
16
+ }): Array<{
17
+ where: string;
18
+ url: string;
19
+ }>;
20
+ /** The URLs that would leave the site, or none (also none when the profile is not offline). */
21
+ export declare function offlineViolations(offline: MapConfig['offline'], parts: Parameters<typeof configUrls>[0]): Array<{
22
+ where: string;
23
+ url: string;
24
+ }>;
@@ -0,0 +1,37 @@
1
+ import { isLocalUrl } from '../render/offline.js';
2
+ export { isLocalUrl };
3
+ export const allowedOrigins = (offline) => (!offline ? null : offline === true ? [] : offline.allowedOrigins ?? []);
4
+ /** Every URL a part of the config uses, with where it is, for the error message. */
5
+ export function configUrls(parts) {
6
+ const out = [];
7
+ const add = (where, url) => typeof url === 'string' && url && out.push({ where, url: url.replace(/\{[xyz]\}/g, '0') });
8
+ const b = parts.basemap;
9
+ if (b) {
10
+ add('basemap.styleUrl', b.styleUrl);
11
+ add('basemap.tileUrl', b.tileUrl);
12
+ add('basemap.rasterFallback', b.rasterFallback);
13
+ add('basemap.font.url', b.font?.url);
14
+ }
15
+ if (parts.terrain)
16
+ add('terrain.tileUrl', parts.terrain.tileUrl);
17
+ for (const t of parts.tilesets ?? [])
18
+ add(`tilesets.${t.id}.url`, t.url);
19
+ for (const l of parts.layers ?? []) {
20
+ const vz = l.visualization;
21
+ add(`layers.${l.id}.icon.imageUrl`, vz.icon?.imageUrl);
22
+ add(`layers.${l.id}.model.url`, vz.model?.url);
23
+ add(`layers.${l.id}.image.url`, vz.image?.url);
24
+ for (const [i, r] of (vz.style ?? []).entries()) {
25
+ add(`layers.${l.id}.style[${i}].imageUrl`, r.style.imageUrl);
26
+ add(`layers.${l.id}.style[${i}].modelUrl`, r.style.modelUrl);
27
+ }
28
+ }
29
+ return out;
30
+ }
31
+ /** The URLs that would leave the site, or none (also none when the profile is not offline). */
32
+ export function offlineViolations(offline, parts) {
33
+ const allowed = allowedOrigins(offline);
34
+ if (!allowed)
35
+ return [];
36
+ return configUrls(parts).filter((u) => !isLocalUrl(u.url, allowed));
37
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Engine-owned layers on top of the application's (doc 28 G7, G10; doc 29 §5): annotations, proposals and the
3
+ * highlight. Plain polygons, lines and points styled through rules and per-object `$s:` values, so every renderer
4
+ * draws them with what it already has. Like the draft layers, they are never returned by getLayers().
5
+ */
6
+ import type { Annotation, Highlight, MapLayer, Proposal, SpatialObject } from '../core/types.js';
7
+ type Kind = 'areas' | 'lines' | 'points';
8
+ export declare const ANNOTATION_LAYER: (k: Kind) => string;
9
+ export declare const PROPOSAL_LAYER: (k: Kind) => string;
10
+ export declare const HIGHLIGHT_LAYER: (k: Kind) => string;
11
+ export declare const ANNOTATION_COLOR = "#7A3FD6";
12
+ export declare const PROPOSAL_COLOR = "#D97706";
13
+ export declare const HIGHLIGHT_COLOR = "#F59E0B";
14
+ export declare const ANNOTATION_LAYERS: MapLayer[];
15
+ export declare const PROPOSAL_LAYERS: MapLayer[];
16
+ /** A wide translucent halo under nothing but the draft: the objects keep their own look, the halo marks them. */
17
+ export declare function highlightLayers(color: string): MapLayer[];
18
+ /** The engine layer ids that carry objects people can select (annotations, proposals). */
19
+ export declare const SELECTABLE_OVERLAYS: Set<string>;
20
+ export declare const isOverlayLayer: (id: string) => boolean;
21
+ export declare function annotationObjects(list: Iterable<Annotation>): Record<string, SpatialObject[]>;
22
+ export declare function proposalObjects(list: Iterable<Proposal>): Record<string, SpatialObject[]>;
23
+ /** Halo objects for the highlighted ids that are on the map now (they follow their objects). */
24
+ export declare function highlightObjects(h: Highlight | null, lookup: (id: string) => SpatialObject | undefined): Record<string, SpatialObject[]>;
25
+ export {};