@goway.to/maps 0.0.0-stage → 0.1.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 (125) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +676 -0
  3. package/NOTICE +25 -0
  4. package/README.md +59 -2
  5. package/app.plugin.js +19 -0
  6. package/dist/CameraBinding.web.d.ts +24 -0
  7. package/dist/CameraBinding.web.js +46 -0
  8. package/dist/DefaultMapMarker.d.ts +8 -0
  9. package/dist/DefaultMapMarker.js +30 -0
  10. package/dist/GeoJSONSource.d.ts +2 -0
  11. package/dist/GeoJSONSource.js +17 -0
  12. package/dist/GeoJSONSource.web.d.ts +2 -0
  13. package/dist/GeoJSONSource.web.js +68 -0
  14. package/dist/InteractionBinding.web.d.ts +7 -0
  15. package/dist/InteractionBinding.web.js +35 -0
  16. package/dist/Layer.d.ts +2 -0
  17. package/dist/Layer.js +21 -0
  18. package/dist/Layer.web.d.ts +2 -0
  19. package/dist/Layer.web.js +46 -0
  20. package/dist/LayerStyle.web.d.ts +10 -0
  21. package/dist/LayerStyle.web.js +45 -0
  22. package/dist/LightBinding.web.d.ts +5 -0
  23. package/dist/LightBinding.web.js +19 -0
  24. package/dist/MapAttribution.d.ts +27 -0
  25. package/dist/MapAttribution.js +149 -0
  26. package/dist/MapBrand.d.ts +22 -0
  27. package/dist/MapBrand.js +91 -0
  28. package/dist/MapErrorState.d.ts +9 -0
  29. package/dist/MapErrorState.js +58 -0
  30. package/dist/MapPlugin.d.ts +6 -0
  31. package/dist/MapPlugin.js +3 -0
  32. package/dist/MapPlugin.web.d.ts +2 -0
  33. package/dist/MapPlugin.web.js +43 -0
  34. package/dist/MapView.d.ts +2 -0
  35. package/dist/MapView.js +420 -0
  36. package/dist/MapView.web.d.ts +5 -0
  37. package/dist/MapView.web.js +413 -0
  38. package/dist/Marker.d.ts +13 -0
  39. package/dist/Marker.js +11 -0
  40. package/dist/Marker.web.d.ts +15 -0
  41. package/dist/Marker.web.js +51 -0
  42. package/dist/Markers.d.ts +2 -0
  43. package/dist/Markers.js +13 -0
  44. package/dist/Markers.web.d.ts +2 -0
  45. package/dist/Markers.web.js +13 -0
  46. package/dist/PluginProps.web.d.ts +7 -0
  47. package/dist/PluginProps.web.js +19 -0
  48. package/dist/RasterDEMSource.d.ts +2 -0
  49. package/dist/RasterDEMSource.js +7 -0
  50. package/dist/RasterDEMSource.web.d.ts +2 -0
  51. package/dist/RasterDEMSource.web.js +44 -0
  52. package/dist/SourceData.web.d.ts +6 -0
  53. package/dist/SourceData.web.js +14 -0
  54. package/dist/UserLocation.d.ts +2 -0
  55. package/dist/UserLocation.js +19 -0
  56. package/dist/UserLocation.web.d.ts +2 -0
  57. package/dist/UserLocation.web.js +21 -0
  58. package/dist/brand/BrandedChrome.d.ts +47 -0
  59. package/dist/brand/BrandedChrome.js +51 -0
  60. package/dist/brand/GowayLogo.d.ts +28 -0
  61. package/dist/brand/GowayLogo.js +50 -0
  62. package/dist/brand/artwork.d.ts +130 -0
  63. package/dist/brand/artwork.js +176 -0
  64. package/dist/cameraReports.web.d.ts +50 -0
  65. package/dist/cameraReports.web.js +117 -0
  66. package/dist/cameraTarget.d.ts +14 -0
  67. package/dist/cameraTarget.js +8 -0
  68. package/dist/chromeLift.d.ts +15 -0
  69. package/dist/chromeLift.js +15 -0
  70. package/dist/clusters.d.ts +57 -0
  71. package/dist/clusters.js +102 -0
  72. package/dist/commands.web.d.ts +16 -0
  73. package/dist/commands.web.js +158 -0
  74. package/dist/compactCredit.d.ts +52 -0
  75. package/dist/compactCredit.js +40 -0
  76. package/dist/creditHeight.d.ts +3 -0
  77. package/dist/creditHeight.js +12 -0
  78. package/dist/creditLabels.d.ts +30 -0
  79. package/dist/creditLabels.js +30 -0
  80. package/dist/creditRise.d.ts +28 -0
  81. package/dist/creditRise.js +33 -0
  82. package/dist/dragAxes.web.d.ts +65 -0
  83. package/dist/dragAxes.web.js +70 -0
  84. package/dist/engineContext.web.d.ts +13 -0
  85. package/dist/engineContext.web.js +12 -0
  86. package/dist/index.d.ts +12 -0
  87. package/dist/index.js +12 -0
  88. package/dist/index.web.d.ts +13 -0
  89. package/dist/index.web.js +13 -0
  90. package/dist/labels.d.ts +202 -0
  91. package/dist/labels.js +451 -0
  92. package/dist/loadedCamera.d.ts +64 -0
  93. package/dist/loadedCamera.js +56 -0
  94. package/dist/locationStore.d.ts +16 -0
  95. package/dist/locationStore.js +132 -0
  96. package/dist/mapRef.d.ts +4 -0
  97. package/dist/mapRef.js +13 -0
  98. package/dist/moveMarkers.d.ts +27 -0
  99. package/dist/moveMarkers.js +22 -0
  100. package/dist/nativeCameraReports.d.ts +44 -0
  101. package/dist/nativeCameraReports.js +73 -0
  102. package/dist/plugin.d.ts +63 -0
  103. package/dist/plugin.js +2 -0
  104. package/dist/queryLayers.d.ts +19 -0
  105. package/dist/queryLayers.js +54 -0
  106. package/dist/shared-exports.d.ts +17 -0
  107. package/dist/shared-exports.js +13 -0
  108. package/dist/shared.d.ts +170 -0
  109. package/dist/shared.js +337 -0
  110. package/dist/sourceContext.d.ts +2 -0
  111. package/dist/sourceContext.js +9 -0
  112. package/dist/styleUrl.d.ts +19 -0
  113. package/dist/styleUrl.js +22 -0
  114. package/dist/testing.d.ts +41 -0
  115. package/dist/testing.js +80 -0
  116. package/dist/types.d.ts +348 -0
  117. package/dist/types.js +1 -0
  118. package/dist/useMarkerSlots.d.ts +6 -0
  119. package/dist/useMarkerSlots.js +56 -0
  120. package/dist/useUserLocation.d.ts +44 -0
  121. package/dist/useUserLocation.js +247 -0
  122. package/dist/zoom.d.ts +2 -0
  123. package/dist/zoom.js +10 -0
  124. package/metro.cjs +70 -0
  125. package/package.json +115 -4
package/dist/shared.js ADDED
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Translation helpers shared by the two engine forks.
3
+ *
4
+ * This is the ONLY place GoWay's `{ latitude, longitude }` becomes MapLibre's
5
+ * `[longitude, latitude]` — once, tested by both forks using it, rather than at
6
+ * every call site where a swapped pair renders a plausible map of the wrong
7
+ * place and nothing errors.
8
+ *
9
+ * It is also the gate. `isDrawableCoordinate` / `drawableMarkers` /
10
+ * `isDrawableBounds` / `resolvePadding` / `asFinite` keep a `NaN` from reaching
11
+ * an engine, which is a THROW rather than a bad frame — see the block comment
12
+ * above {@link isDrawableCoordinate}. `isDrawableGeoJSON` covers the one path
13
+ * that fails the OTHER way: raw GeoJSON does not throw, it renders nowhere and
14
+ * says nothing. `optionalScalar` covers the third: a key that is PRESENT and
15
+ * `undefined` is not the same as an absent one, and an engine that defaults the
16
+ * absent one does not default this one.
17
+ *
18
+ * And `runEngineCommand` is the floor under all of it: whatever still gets
19
+ * through, an engine call that throws costs the CALL, not the application.
20
+ */
21
+ import { unwrappedEast } from '@goway.to/sdk/geo';
22
+ export function toLngLat(coordinate) {
23
+ return [coordinate.longitude, coordinate.latitude];
24
+ }
25
+ export function fromLngLat(position) {
26
+ return { longitude: position[0], latitude: position[1] };
27
+ }
28
+ /**
29
+ * A box in engine order, its east edge carried past 180 when it crosses the
30
+ * antimeridian so both engines frame the strip it describes.
31
+ */
32
+ export function toBoundsArray(bounds) {
33
+ return [bounds.west, bounds.south, unwrappedEast(bounds), bounds.north];
34
+ }
35
+ /** Every interaction is on unless a caller turns it off. */
36
+ export function resolveInteraction(options) {
37
+ return {
38
+ pan: options?.pan ?? true,
39
+ zoom: options?.zoom ?? true,
40
+ rotate: options?.rotate ?? true,
41
+ pitch: options?.pitch ?? true,
42
+ };
43
+ }
44
+ /**
45
+ * Normalise a `moveTo` target: a bare coordinate leaves the camera's zoom alone.
46
+ *
47
+ * `Number.isFinite`, not `typeof === 'number'` — `typeof NaN` IS `'number'`, so
48
+ * the old test accepted `{ latitude, longitude, zoom: NaN }` as a viewport and
49
+ * passed that `NaN` on to the engine. See {@link asFinite} for what that costs.
50
+ * A viewport whose zoom is not a number is treated as the bare coordinate it
51
+ * usefully is.
52
+ */
53
+ export function isViewport(target) {
54
+ return Number.isFinite(target.zoom);
55
+ }
56
+ /**
57
+ * A camera scalar the engine can use, or `undefined`.
58
+ *
59
+ * Zoom, bearing, pitch, duration and maxZoom are the seam's unchecked numbers,
60
+ * and a `NaN` in any of them is not a bad frame — it is the SAME crash a bad
61
+ * coordinate is, one step later. `transform.setZoom(NaN)` survives MapLibre's
62
+ * own `clamp` (every comparison against `NaN` is false), so `_scale` and
63
+ * `worldSize` become `NaN`; the next unprojection — `map.getBounds()`, which
64
+ * this canvas calls on every `move` event, and which `jumpTo` fires
65
+ * SYNCHRONOUSLY — builds a `LngLat` out of two `NaN`s and throws
66
+ * `Invalid LngLat object: (NaN, NaN)` from inside the caller's own React
67
+ * handler. The camera simply not moving is the better failure.
68
+ */
69
+ export function asFinite(value) {
70
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
71
+ }
72
+ /**
73
+ * A scalar an engine's options object may carry — or NOTHING AT ALL.
74
+ *
75
+ * ## The defect this exists to remove
76
+ *
77
+ * `{ maxZoom: undefined }` and `{}` look interchangeable in TypeScript, and to
78
+ * MapLibre GL JS they are opposites. `Map._cameraForBoxAndBearing` fills in its
79
+ * own defaults with
80
+ *
81
+ * ```
82
+ * options = extend({ padding, offset: [0, 0], maxZoom: this.transform.maxZoom }, options);
83
+ * ```
84
+ *
85
+ * and `extend` is `for (const k in src) dest[k] = src[k]` — an OWN key wins
86
+ * even when its value is `undefined`. So passing the key at all replaces the
87
+ * engine's maximum zoom with `undefined`, and the very next line is
88
+ *
89
+ * ```
90
+ * const zoom = Math.min(scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom);
91
+ * ```
92
+ *
93
+ * `Math.min(anything, undefined)` is `NaN`. That `NaN` divides into
94
+ * `offsetAtFinalZoom`, reaches the centre `cameraForBoxAndBearing` unprojects,
95
+ * and `new LngLat(NaN, NaN)` THROWS — from inside the React effect that asked
96
+ * for the fit, which is the error boundary and the whole app. Omitting the key
97
+ * is the only way to mean "use yours".
98
+ *
99
+ * Every optional scalar this seam forwards therefore goes through here, so
100
+ * "the caller did not say" can never arrive as "the caller said nothing".
101
+ */
102
+ export function optionalScalar(key, value) {
103
+ const finite = asFinite(value);
104
+ return finite === undefined ? {} : { [key]: finite };
105
+ }
106
+ /**
107
+ * Fit padding, expanded to the four-sided form both engines want — or `null`
108
+ * when it is not a number the engine can use.
109
+ *
110
+ * See {@link isDrawableCoordinate} for why a non-finite number is refused here
111
+ * rather than passed on: MapLibre's `cameraForBoxAndBearing` divides the free
112
+ * viewport by the padding, and `NaN` propagates all the way to the LngLat it
113
+ * constructs for the new centre, which throws. A NEGATIVE padding does not
114
+ * throw, but it is never what a caller meant (it frames content OUTSIDE the
115
+ * viewport), and a negative pair is one of the ways the free space lands on
116
+ * exactly zero, which does throw. So negatives clamp to zero and non-finite
117
+ * values refuse the fit outright.
118
+ */
119
+ export function resolvePadding(padding, fallback) {
120
+ const raw = padding == null
121
+ ? { top: fallback, right: fallback, bottom: fallback, left: fallback }
122
+ : typeof padding === 'number'
123
+ ? { top: padding, right: padding, bottom: padding, left: padding }
124
+ : padding;
125
+ if (!Number.isFinite(raw.top) ||
126
+ !Number.isFinite(raw.right) ||
127
+ !Number.isFinite(raw.bottom) ||
128
+ !Number.isFinite(raw.left)) {
129
+ reportMapDefect('padding:non-finite', `Refusing a fit: padding is not finite (${describeNumbers(raw)}).`);
130
+ return null;
131
+ }
132
+ return {
133
+ top: Math.max(0, raw.top),
134
+ right: Math.max(0, raw.right),
135
+ bottom: Math.max(0, raw.bottom),
136
+ left: Math.max(0, raw.left),
137
+ };
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // The garbage gate
141
+ // ---------------------------------------------------------------------------
142
+ /**
143
+ * Both MapLibre engines build a `LngLat` out of whatever they are handed, and
144
+ * `LngLat` THROWS on a non-number — `Invalid LngLat object: (NaN, NaN)`. Thrown
145
+ * from a React effect (setting a marker's position, framing a route) that
146
+ * reaches the nearest error boundary, so one bad pin takes the whole screen
147
+ * down and the user sees "Something went wrong" instead of a map.
148
+ *
149
+ * `@goway.to/maps` is the single seam every coordinate crosses on its way to
150
+ * an engine, so it is where that stops being possible. A marker whose
151
+ * coordinate is not drawable is DROPPED (a map missing one pin is a degraded
152
+ * map; a map that throws is no map) and a camera move whose target is not
153
+ * drawable is a no-op.
154
+ *
155
+ * ## Why this is not `isValidCoordinate`
156
+ *
157
+ * `@goway.to/sdk/geo`'s `isValidCoordinate` is the PRODUCT's rule — a real point
158
+ * on the earth, longitude within ±180. This is the ENGINE's rule, which is
159
+ * looser on longitude on purpose: MapLibre reports unwrapped longitudes across
160
+ * the antimeridian (a click at 181°E comes back as 181, not −179), and feeding
161
+ * one of those back in is legal and does the right thing. Rejecting it here
162
+ * would break panning past the date line in the name of safety. Latitude is
163
+ * bounded, because `LngLat` throws outside ±90 too.
164
+ */
165
+ export function isDrawableCoordinate(value) {
166
+ if (!value || typeof value !== 'object')
167
+ return false;
168
+ const { latitude, longitude } = value;
169
+ return (Number.isFinite(longitude) && Number.isFinite(latitude) && latitude >= -90 && latitude <= 90);
170
+ }
171
+ /** The same rule as {@link isDrawableCoordinate}, applied to a box's corners. */
172
+ export function isDrawableBounds(bounds) {
173
+ if (!bounds)
174
+ return false;
175
+ return (isDrawableCoordinate({ latitude: bounds.south, longitude: bounds.west }) &&
176
+ isDrawableCoordinate({ latitude: bounds.north, longitude: bounds.east }));
177
+ }
178
+ /**
179
+ * The markers an engine may be given, with the undrawable ones removed.
180
+ *
181
+ * Returns the INPUT ARRAY when nothing is wrong, so the common path adds one
182
+ * pass and no allocation, and the forks' effect dependencies do not change
183
+ * identity every render.
184
+ */
185
+ export function drawableMarkers(markers) {
186
+ if (!markers || markers.length === 0)
187
+ return EMPTY_MARKERS;
188
+ let firstBad = -1;
189
+ for (let index = 0; index < markers.length; index += 1) {
190
+ if (!isDrawableCoordinate(markers[index].coordinate)) {
191
+ firstBad = index;
192
+ break;
193
+ }
194
+ }
195
+ if (firstBad < 0)
196
+ return markers;
197
+ const kept = markers.slice(0, firstBad);
198
+ for (let index = firstBad; index < markers.length; index += 1) {
199
+ const marker = markers[index];
200
+ if (isDrawableCoordinate(marker.coordinate)) {
201
+ kept.push(marker);
202
+ continue;
203
+ }
204
+ reportMapDefect(`marker:${marker.id}`, `Dropped marker "${marker.id}": coordinate is not drawable (${describeNumbers(marker.coordinate)}).`);
205
+ }
206
+ return kept;
207
+ }
208
+ const EMPTY_MARKERS = [];
209
+ /**
210
+ * Every position in a GeoJSON document is a point the engine can project, and
211
+ * every geometry has enough of them to BE that geometry.
212
+ *
213
+ * The arity check is not pedantry about RFC 7946: a one-point `LineString` and
214
+ * an empty ring are exactly what a mis-sliced route produces, they draw
215
+ * nothing, and they are indistinguishable from "the route did not arrive"
216
+ * unless something says so.
217
+ *
218
+ * A composite geometry needs the same check one level up, for the same reason
219
+ * and one that is easier to miss: `[].every()` is `true`, so an empty
220
+ * `coordinates` array satisfies every per-part rule vacuously. An empty
221
+ * `Polygon` has no ring that can fail `ring()`, and a `MultiPolygon` of `[[]]`
222
+ * has no polygon that can. Both are what a slice that matched nothing
223
+ * produces, both reach the engine, and both draw exactly nothing — so every
224
+ * composite array is required to be non-empty BEFORE its parts are checked.
225
+ */
226
+ export function isDrawableGeoJSON(data) {
227
+ if (!data || typeof data !== 'object')
228
+ return false;
229
+ switch (data.type) {
230
+ case 'FeatureCollection':
231
+ return Array.isArray(data.features) && data.features.every((f) => isDrawableGeoJSON(f));
232
+ case 'Feature':
233
+ // A null geometry is legal GeoJSON and draws nothing, which for an
234
+ // overlay is the same as not being there.
235
+ return data.geometry != null && isDrawableGeoJSON(data.geometry);
236
+ case 'GeometryCollection':
237
+ return (Array.isArray(data.geometries) &&
238
+ data.geometries.length > 0 &&
239
+ data.geometries.every((g) => isDrawableGeoJSON(g)));
240
+ case 'Point':
241
+ return isDrawablePosition(data.coordinates);
242
+ case 'MultiPoint':
243
+ return ring(data.coordinates, 1);
244
+ case 'LineString':
245
+ return ring(data.coordinates, 2);
246
+ case 'MultiLineString':
247
+ return nonEmpty(data.coordinates) && data.coordinates.every((l) => ring(l, 2));
248
+ case 'Polygon':
249
+ return nonEmpty(data.coordinates) && data.coordinates.every((r) => ring(r, 4));
250
+ case 'MultiPolygon':
251
+ return (nonEmpty(data.coordinates) &&
252
+ data.coordinates.every((p) => nonEmpty(p) && p.every((r) => ring(r, 4))));
253
+ default:
254
+ return false;
255
+ }
256
+ }
257
+ function ring(positions, minimum) {
258
+ return (Array.isArray(positions) && positions.length >= minimum && positions.every(isDrawablePosition));
259
+ }
260
+ /**
261
+ * An array with something in it.
262
+ *
263
+ * `[].every()` is `true`, so a composite geometry whose only check is `every`
264
+ * accepts the empty case: `{ type: 'Polygon', coordinates: [] }` has no ring to
265
+ * fail `ring()` and passed every test above. That is the exact silent failure
266
+ * this module exists to catch — a geometry the engine accepts, tiles, and draws
267
+ * nothing for — so the outer array is checked before the inner ones are.
268
+ */
269
+ function nonEmpty(value) {
270
+ return Array.isArray(value) && value.length > 0;
271
+ }
272
+ /** A GeoJSON position is `[longitude, latitude]`, altitude optional. */
273
+ function isDrawablePosition(position) {
274
+ if (!Array.isArray(position) || position.length < 2)
275
+ return false;
276
+ return isDrawableCoordinate({ longitude: position[0], latitude: position[1] });
277
+ }
278
+ /**
279
+ * Say it ONCE per defect.
280
+ *
281
+ * Markers are re-applied on every camera frame a parent reacts to, so a
282
+ * per-occurrence warning would be thousands of identical lines during a single
283
+ * drag — which is how a real signal becomes noise somebody mutes. The key is
284
+ * the marker id (or the kind of camera defect), so a second, different bad pin
285
+ * is still reported.
286
+ */
287
+ const reported = new Set();
288
+ export function reportMapDefect(key, message) {
289
+ if (reported.has(key))
290
+ return;
291
+ reported.add(key);
292
+ console.warn(`[goway/maps] ${message}`);
293
+ }
294
+ /**
295
+ * Run one engine command, and let a throw cost the COMMAND rather than the app.
296
+ *
297
+ * This is the BACKSTOP, deliberately separate from the checks above. Those
298
+ * refuse the bad values we know about; this one is for the ones we do not. Every
299
+ * guard in this file was written after a crash, which is the evidence that the
300
+ * next non-finite number will arrive from somewhere nobody predicted — a
301
+ * renderer upgrade that starts rejecting an input it used to accept, a new
302
+ * option forwarded through an unexamined path, a device API that changes shape.
303
+ *
304
+ * What makes that catastrophic rather than cosmetic is WHERE these calls run:
305
+ * a camera move issued from a `useEffect` throws inside React's commit phase,
306
+ * so the nearest error boundary unmounts the tree and the user gets "Something
307
+ * went wrong" instead of a map. A camera that did not move is a map. That is
308
+ * the whole trade, and it is not close.
309
+ *
310
+ * It is not a licence to stop validating: a swallowed throw is a `console.warn`
311
+ * nobody reads, so anything we can name is still refused BEFORE it gets here,
312
+ * by a check that says which value was wrong and why.
313
+ *
314
+ * Answers whether the command ran: `false` is a dropped command, which a
315
+ * caller waiting on its outcome (a camera move's settle) must not wait for.
316
+ */
317
+ export function runEngineCommand(command, run) {
318
+ try {
319
+ run();
320
+ return true;
321
+ }
322
+ catch (cause) {
323
+ reportMapDefect(`engine:${command}`, `The map engine threw on "${command}" and the command was dropped: ${String(cause)}`);
324
+ return false;
325
+ }
326
+ }
327
+ /**
328
+ * `key NaN` — never `JSON.stringify`, which prints `NaN` and `Infinity` as
329
+ * `null` and so erases the one detail the reader needs.
330
+ */
331
+ export function describeNumbers(value) {
332
+ if (!value || typeof value !== 'object')
333
+ return String(value);
334
+ return Object.entries(value)
335
+ .map(([key, entry]) => `${key} ${String(entry)}`)
336
+ .join(', ');
337
+ }
@@ -0,0 +1,2 @@
1
+ export declare const SourceIdContext: import("react").Context<string | null>;
2
+ export declare function useSourceId(): string;
@@ -0,0 +1,9 @@
1
+ /** The id of the source a `<Layer>` draws: the `<GeoJSONSource>` / `<RasterDEMSource>` around it. */
2
+ import { createContext, useContext } from 'react';
3
+ export const SourceIdContext = createContext(null);
4
+ export function useSourceId() {
5
+ const id = useContext(SourceIdContext);
6
+ if (!id)
7
+ throw new Error('A <Layer> must be rendered inside a source (<GeoJSONSource>, <RasterDEMSource>).');
8
+ return id;
9
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Which style document a map draws.
3
+ *
4
+ * `goway://styles/<id>` is GoWay's name for a published style, as
5
+ * `mapbox://styles/...` is Mapbox's; `@goway.to/sdk` resolves it to its
6
+ * document on goway.to. A GoWay style that follows the light is drawn in the
7
+ * app's theme — day in light mode, night in dark — unless the screen pins a
8
+ * light. Any other URL is used as it is.
9
+ */
10
+ import { type MapLightPreset } from '@goway.to/sdk';
11
+ /** The style a `MapView` draws when it is given none: GoWay Standard. */
12
+ export declare const DEFAULT_MAP_STYLE: string;
13
+ /**
14
+ * The document URL for `mapStyle`, in `lightPreset` or the theme's light.
15
+ * Throws `GoWayValidationError` for a `goway://` name that is not a published
16
+ * style — the map reports that as a style failure rather than drawing a
17
+ * different map than the one asked for.
18
+ */
19
+ export declare function resolveStyleUrl(mapStyle: string, lightPreset: MapLightPreset | undefined, dark: boolean): string;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Which style document a map draws.
3
+ *
4
+ * `goway://styles/<id>` is GoWay's name for a published style, as
5
+ * `mapbox://styles/...` is Mapbox's; `@goway.to/sdk` resolves it to its
6
+ * document on goway.to. A GoWay style that follows the light is drawn in the
7
+ * app's theme — day in light mode, night in dark — unless the screen pins a
8
+ * light. Any other URL is used as it is.
9
+ */
10
+ import { createGoWayClient, gowayStyleUrl } from '@goway.to/sdk';
11
+ /** The style a `MapView` draws when it is given none: GoWay Standard. */
12
+ export const DEFAULT_MAP_STYLE = gowayStyleUrl('standard');
13
+ const links = createGoWayClient().links;
14
+ /**
15
+ * The document URL for `mapStyle`, in `lightPreset` or the theme's light.
16
+ * Throws `GoWayValidationError` for a `goway://` name that is not a published
17
+ * style — the map reports that as a style failure rather than drawing a
18
+ * different map than the one asked for.
19
+ */
20
+ export function resolveStyleUrl(mapStyle, lightPreset, dark) {
21
+ return links.styleUrl(mapStyle, lightPreset ?? (dark ? 'night' : 'day'));
22
+ }
@@ -0,0 +1,41 @@
1
+ import type { MapCameraChange } from './types';
2
+ export { PROGRAMMATIC, PROGRAMMATIC_TRANSIENT } from './cameraReports.web';
3
+ export interface FakeCamera {
4
+ lat: number;
5
+ lng: number;
6
+ zoom: number;
7
+ bearing: number;
8
+ pitch: number;
9
+ /** Half the box's height and width, in degrees: a bigger container, a bigger box. */
10
+ span: number;
11
+ }
12
+ export declare function fakeCameraEngine(start?: Partial<FakeCamera>): {
13
+ camera: FakeCamera;
14
+ fire(type: string, event?: object): void;
15
+ /** A whole camera move, as MapLibre fires it, ending wherever `to` says. */
16
+ move(event: object, to?: Partial<FakeCamera>): void;
17
+ listenerCount: () => number;
18
+ on(type: string, listener: (event: unknown) => void): /*elided*/ any;
19
+ off(type: string, listener: (event: unknown) => void): /*elided*/ any;
20
+ getCenter: () => {
21
+ lat: number;
22
+ lng: number;
23
+ };
24
+ getBounds: () => {
25
+ getWest: () => number;
26
+ getSouth: () => number;
27
+ getEast: () => number;
28
+ getNorth: () => number;
29
+ };
30
+ getZoom: () => number;
31
+ getBearing: () => number;
32
+ getPitch: () => number;
33
+ };
34
+ export type FakeCameraEngine = ReturnType<typeof fakeCameraEngine>;
35
+ /** Attach the real reports to a fake engine; `withListener: false` is the embed. */
36
+ export declare function attachToFake(engine: FakeCameraEngine, withListener?: boolean): {
37
+ changes: MapCameraChange[];
38
+ detach: () => void;
39
+ finals: () => MapCameraChange[];
40
+ advance(ms: number): void;
41
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `@goway.to/maps/testing` — drive the map's REAL camera reports without a GPU.
3
+ *
4
+ * What a screen does with `onCameraChanged` (arm "Search this area", commit a
5
+ * box, pause a tour) is the screen's logic, and the sequences that matter —
6
+ * the opening camera, a programmatic frame, the reader's drag — are the ones a
7
+ * loading map produces. `fakeCameraEngine` is a MapLibre map as far as the
8
+ * reports see it (the events it fires and the camera it reads; the real one
9
+ * needs WebGL), and `attachToFake` runs the SDK's own web reporting on it.
10
+ *
11
+ * const engine = fakeCameraEngine({ lat: 41.39, lng: 2.17, zoom: 11 });
12
+ * const reports = attachToFake(engine);
13
+ * engine.fire('load');
14
+ * engine.move({ originalEvent: {} }, { lng: 2.2 }); // the reader drags
15
+ * engine.move(PROGRAMMATIC, { zoom: 14 }); // the app frames
16
+ */
17
+ import { attachCameraReports } from './cameraReports.web';
18
+ export { PROGRAMMATIC, PROGRAMMATIC_TRANSIENT } from './cameraReports.web';
19
+ export function fakeCameraEngine(start = {}) {
20
+ const listeners = new Map();
21
+ const camera = {
22
+ lat: 41.4036,
23
+ lng: 2.1744,
24
+ zoom: 16.5,
25
+ bearing: 30,
26
+ pitch: 60,
27
+ span: 0.01,
28
+ ...start,
29
+ };
30
+ const engine = {
31
+ camera,
32
+ fire(type, event = {}) {
33
+ for (const listener of listeners.get(type) ?? [])
34
+ listener({ type, ...event });
35
+ },
36
+ /** A whole camera move, as MapLibre fires it, ending wherever `to` says. */
37
+ move(event, to = {}) {
38
+ engine.fire('movestart', event);
39
+ Object.assign(camera, to);
40
+ engine.fire('move', event);
41
+ engine.fire('moveend', event);
42
+ },
43
+ listenerCount: () => [...listeners.values()].reduce((sum, set) => sum + set.size, 0),
44
+ on(type, listener) {
45
+ if (!listeners.has(type))
46
+ listeners.set(type, new Set());
47
+ listeners.get(type)?.add(listener);
48
+ return engine;
49
+ },
50
+ off(type, listener) {
51
+ listeners.get(type)?.delete(listener);
52
+ return engine;
53
+ },
54
+ getCenter: () => ({ lat: camera.lat, lng: camera.lng }),
55
+ getBounds: () => ({
56
+ getWest: () => camera.lng - camera.span,
57
+ getSouth: () => camera.lat - camera.span,
58
+ getEast: () => camera.lng + camera.span,
59
+ getNorth: () => camera.lat + camera.span,
60
+ }),
61
+ getZoom: () => camera.zoom,
62
+ getBearing: () => camera.bearing,
63
+ getPitch: () => camera.pitch,
64
+ };
65
+ return engine;
66
+ }
67
+ /** Attach the real reports to a fake engine; `withListener: false` is the embed. */
68
+ export function attachToFake(engine, withListener = true) {
69
+ const changes = [];
70
+ let clock = 0;
71
+ const detach = attachCameraReports(engine, () => (withListener ? (change) => changes.push(change) : undefined), () => clock);
72
+ return {
73
+ changes,
74
+ detach,
75
+ finals: () => changes.filter((change) => change.isFinal),
76
+ advance(ms) {
77
+ clock += ms;
78
+ },
79
+ };
80
+ }