@tomtom-org/maps-sdk-plugin-flyover 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,107 @@
1
+ # @tomtom-org/maps-sdk-plugin-flyover
2
+
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2823](https://github.com/tomtom-internal/maps-sdk-js/pull/2823) [`d1b906f`](https://github.com/tomtom-internal/maps-sdk-js/commit/d1b906f460c33607bf9789475ad85bff339b7c3b) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The package ships its `CHANGELOG.md`, as the SDK does, so you can read what changed between two releases from `node_modules`
8
+
9
+ - [#2813](https://github.com/tomtom-internal/maps-sdk-js/pull/2813) [`c186703`](https://github.com/tomtom-internal/maps-sdk-js/commit/c1867038e9a38e300ea72e8812419204bf008e73) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The flyover frames at the pitch MapLibre shows, and a loop keeps its framing and the instruction ahead across its seam
10
+
11
+ - A stop asked for from `onProgress` is honoured
12
+ - A cross-origin marker image warns instead of failing silently
13
+ - Editing the `zoom` or `aim` that `getCamera()` returns no longer changes every flight's defaults
14
+ - A marker given as an SVG data URI with unencoded markup loads
15
+ - After a `seek`, the camera clears the terrain from where it lands rather than from its heading before
16
+
17
+ ## 0.1.1
18
+
19
+ ### Patch Changes
20
+
21
+ - [#2803](https://github.com/tomtom-internal/maps-sdk-js/pull/2803) [`1a62c72`](https://github.com/tomtom-internal/maps-sdk-js/commit/1a62c72eb372d037397bb88485ea731763f3e554) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The README links the repository's security policy page, which sends a vulnerability report to TomTom's HackerOne program
22
+
23
+ ## 0.1.0
24
+
25
+ ### Minor Changes
26
+
27
+ - [#2722](https://github.com/tomtom-internal/maps-sdk-js/pull/2722) [`9eff3f9`](https://github.com/tomtom-internal/maps-sdk-js/commit/9eff3f9f11ae3c358e0df83e9241cfb4a00f7282) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - **Breaking:** the `maplibre-gl` peer dependency moves up to `^6.15.0`
28
+
29
+ ### Patch Changes
30
+
31
+ - [#2757](https://github.com/tomtom-internal/maps-sdk-js/pull/2757) [`2478847`](https://github.com/tomtom-internal/maps-sdk-js/commit/2478847effe8a949fdce057874c236cf20e66dad) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - `Landmarks3D` and the fly-over marker add their layers through the SDK's `map.addCustomLayer`, which keeps them placed through style switches
32
+
33
+ - landmarks-3d: module layers placed at `'poi'` now go under the landmarks, even when added after `Landmarks3D`, where one added later drew over them
34
+ - flyover: module layers placed at `'top'`, such as the default of `CustomGeoJSONModule`, now go under the marker, even when added after it, where one added later drew over it
35
+
36
+ - [#2757](https://github.com/tomtom-internal/maps-sdk-js/pull/2757) [`2478847`](https://github.com/tomtom-internal/maps-sdk-js/commit/2478847effe8a949fdce057874c236cf20e66dad) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Both plugins need `@tomtom-org/maps-sdk` 1.0.0-rc.4 or later, since they line their layers up with the map through its `localFrameMatrix`
37
+
38
+ - [#2742](https://github.com/tomtom-internal/maps-sdk-js/pull/2742) [`2fcc1ab`](https://github.com/tomtom-internal/maps-sdk-js/commit/2fcc1abc07bcdf89aff3942ff1207637ed00f419) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `license` field reads `SEE LICENSE IN LICENSE.txt`, the form npm and license scanners recognise, so they point to the package's own `LICENSE.txt` instead of reporting an unknown license
39
+
40
+ - [#2734](https://github.com/tomtom-internal/maps-sdk-js/pull/2734) [`9a12d95`](https://github.com/tomtom-internal/maps-sdk-js/commit/9a12d95c22e60e69fb1891237344ca259aa236a5) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Installs beside `@tomtom-org/maps-sdk` 1.0.0 and every later 1.x release: the peer range on the SDK runs up to `<2.0.0` instead of stopping below 1.0.0
41
+
42
+ - [#2697](https://github.com/tomtom-internal/maps-sdk-js/pull/2697) [`3d9c9a3`](https://github.com/tomtom-internal/maps-sdk-js/commit/3d9c9a34b897cf8eb791096620ed3e18dead1966) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The package page on npm and the API reference of its entry points say the plugin is in Public Preview until its 1.0.0 release, as its guides already do
43
+
44
+ ## 0.0.7
45
+
46
+ ### Patch Changes
47
+
48
+ - [#2640](https://github.com/tomtom-internal/maps-sdk-js/pull/2640) [`8405521`](https://github.com/tomtom-internal/maps-sdk-js/commit/8405521bdb9a4094c311ad17c422df8c5ede09be) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - `RouteFlyover` can fly a route once, and seek by time or distance
49
+
50
+ - `loop: false` stops the flight at the destination and calls the new `onArrive`. `start()` from there flies the route again from its departure. The flight still loops by default.
51
+ - `seek` also takes a `RouteProgressQuery`, as `getCoordinateAtRouteProgress` does: a time or distance travelled, or a clock time. A point outside the route moves the flight to the nearer end.
52
+
53
+ - [#2693](https://github.com/tomtom-internal/maps-sdk-js/pull/2693) [`db69794`](https://github.com/tomtom-internal/maps-sdk-js/commit/db69794e00011a820e2046a161e5390d07b8bb7a) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Plugins are side-effect free, so your bundler drops every part of a plugin your app doesn't use
54
+
55
+ - Each plugin declares `"sideEffects": false`, and its knob catalogue and other module-level tables are dropped when nothing reads them
56
+ - flyover takes `@turf/turf` as a peer dependency, the copy the SDK already requires, instead of bundling its own
57
+ - The plugins' bundles keep their module-level names, as the SDK's do, so Sandpack and builds that transpile `node_modules` with Babel run their loops correctly
58
+
59
+ ## 0.0.6
60
+
61
+ ### Patch Changes
62
+
63
+ - [#2627](https://github.com/tomtom-internal/maps-sdk-js/pull/2627) [`e09550e`](https://github.com/tomtom-internal/maps-sdk-js/commit/e09550ebbd9a46d464bb186fb4ec220cafc55170) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `maplibre-gl` peer dependency moves up to `^6.13.0`
64
+
65
+ - [#2590](https://github.com/tomtom-internal/maps-sdk-js/pull/2590) [`75fb3ea`](https://github.com/tomtom-internal/maps-sdk-js/commit/75fb3eab4d38b2aad8f4f671c3fd84f78ffc19f3) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - license: the privacy clause of `LICENSE.txt` now links the TomTom Privacy Policy, https://www.tomtom.com/privacy/
66
+
67
+ - [#2614](https://github.com/tomtom-internal/maps-sdk-js/pull/2614) [`fe596fc`](https://github.com/tomtom-internal/maps-sdk-js/commit/fe596fcb2d1c5a05dddb77547f48debc1b856a12) Thanks [@JulianChinAFoeng-TomTom](https://github.com/JulianChinAFoeng-TomTom)! - tests: each package's unit test run now fails when its coverage drops below the goals in `TESTING.md`
68
+
69
+ ## 0.0.5
70
+
71
+ ### Patch Changes
72
+
73
+ - [#2517](https://github.com/tomtom-internal/maps-sdk-js/pull/2517) [`49611d0`](https://github.com/tomtom-internal/maps-sdk-js/commit/49611d0ac91dc0b584e3d059d1f654208bdead6c) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The README's install line adds the `maplibre-gl` peer dependency, and it links the developer guide and the playground example up front
74
+
75
+ ## 0.0.4
76
+
77
+ ### Patch Changes
78
+
79
+ - [#2500](https://github.com/tomtom-internal/maps-sdk-js/pull/2500) [`d19d11d`](https://github.com/tomtom-internal/maps-sdk-js/commit/d19d11d41546bac2dd870560b9508bb0b3a2ce2a) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `maplibre-gl` peer dependency moves up to `^6.12.0`
80
+
81
+ - [#2506](https://github.com/tomtom-internal/maps-sdk-js/pull/2506) [`12099bd`](https://github.com/tomtom-internal/maps-sdk-js/commit/12099bdc040f3ce2d3990266bfb42024cda7956c) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The package ships without source maps, so a stack trace through the plugin points into its minified bundle
82
+ - An app's own source maps still lead a trace to the plugin's frames (`dist/index.es.js:1:2345`); share such a trace in a report and TomTom maps it back to the source
83
+
84
+ ## 0.0.3
85
+
86
+ ### Patch Changes
87
+
88
+ - [#2477](https://github.com/tomtom-internal/maps-sdk-js/pull/2477) [`5b00d34`](https://github.com/tomtom-internal/maps-sdk-js/commit/5b00d34ea25ef436fe6ae53fa46b986837e4fcf7) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - **Breaking:** map, services: internals that were exported without documentation are no longer exported
89
+ - map: `EventsProxy`, `sharedInstance`, `TomTomMapSource`, `InternalTomTomMapParams`, `poiLayerIDs`, `buildRoutingLayers` and `ROUTE_LINE_OUTLINE_COLOR`, whose value the upgrade guide gives
90
+ - services: the geometry data wire-format types `GeometryDataResponseAPI` and `AdditionalDataAPI`
91
+ - flyover: the arrow marker keeps its default fill, now defined in the plugin rather than imported from the SDK
92
+ - Updated dependencies:
93
+ - @tomtom-org/maps-sdk@0.64.0
94
+
95
+ ## 0.0.2
96
+
97
+ ### Patch Changes
98
+
99
+ - [#2413](https://github.com/tomtom-internal/maps-sdk-js/pull/2413) [`94e5baa`](https://github.com/tomtom-internal/maps-sdk-js/commit/94e5baaf8399ac8acf62266a8b707f45ac6e4db0) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - `LICENSE.txt` is the SDK's current license agreement, the same file `@tomtom-org/maps-sdk` ships, in place of the 29 May 2026 version
100
+ - Updated dependencies:
101
+ - @tomtom-org/maps-sdk@0.61.0
102
+
103
+ ## 0.0.1
104
+
105
+ ### Patch Changes
106
+
107
+ - [#2181](https://github.com/tomtom-internal/maps-sdk-js/pull/2181) [`da4a60d`](https://github.com/tomtom-internal/maps-sdk-js/commit/da4a60d64e59beae7d4b55531f0fa6235b70275f) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - New plugin: `RouteFlyover` flies a camera along a calculated route — it aims ahead, frames the next junctions, keeps clear of the terrain it crosses, eases every change on a critically damped spring, and parks the followed position where you want it on screen (`anchorForPitch`), marked with a themeable arrow or an image of your own (`setMarker`)
package/README.md CHANGED
@@ -57,4 +57,4 @@ See [LICENSE.txt](./LICENSE.txt).
57
57
 
58
58
  ## Security
59
59
 
60
- To report a security vulnerability, follow [SECURITY.md](https://github.com/tomtom-international/maps-sdk-js/blob/main/SECURITY.md), which keeps it private.
60
+ To report a security vulnerability, follow our [security policy](https://github.com/tomtom-international/maps-sdk-js/security/policy), which keeps it private.
package/dist/index.d.ts CHANGED
@@ -29,7 +29,8 @@ export declare const anchorForPitch: (pitchDegrees: number) => FlyoverAnchor;
29
29
  * - `arrow` - The plugin's own arrow, recoloured with the SDK's SVG icon options. Its fill defaults
30
30
  * to `#105287`, the SDK's default route outline colour, not a theme's accent, and its outline to white.
31
31
  * - `custom` - Yours, as a URL, raw SVG markup, a data URI or a loaded `<img>`, as for any SDK
32
- * custom image. A URL on another origin needs CORS, since the image becomes a WebGL texture.
32
+ * custom image. An image from another origin needs CORS, a loaded `<img>` one too, since the image
33
+ * becomes a WebGL texture.
33
34
  *
34
35
  * @group Flyover
35
36
  */
@@ -125,6 +126,8 @@ type FlyoverProgress = {
125
126
  traveledTimeInSeconds: number;
126
127
  };
127
128
  /**
129
+ * What a {@link RouteFlyover} flies, and how: the route, the camera, the marker and the callbacks.
130
+ *
128
131
  * @group Flyover
129
132
  */
130
133
  type RouteFlyoverOptions = {
@@ -134,7 +137,11 @@ type RouteFlyoverOptions = {
134
137
  camera?: Partial<FlyoverCamera>;
135
138
  /** The marker riding the route at the followed position, by default an arrow. */
136
139
  marker?: FlyoverMarkerConfig;
137
- /** Called with the flight's progress, once per survey rather than once per frame. */
140
+ /**
141
+ * Called with the flight's progress five times a second, and each time the camera is placed outright
142
+ * (a `seek`, a `setRoute` while running, a `setCamera` while paused on the route), rather than once
143
+ * per frame.
144
+ */
138
145
  onProgress?: (progress: FlyoverProgress) => void;
139
146
  /**
140
147
  * Whether the flight wraps at the destination and flies on from the departure. Without the loop,
@@ -246,7 +253,8 @@ export declare class RouteFlyover {
246
253
  private roadHeading;
247
254
  private instructions;
248
255
  /**
249
- * The instruction the flight is heading for — the first one still ahead of it. Past the last,
256
+ * The instruction the flight is heading for — the first one still ahead of it — or the `skip`th
257
+ * after that, counted on round the seam in a loop but never past the one behind it. Past the last,
250
258
  * a loop heads for the first of all and a flight that doesn't loop for the last.
251
259
  * `undefined` when the route carries no guidance.
252
260
  */
@@ -275,6 +283,7 @@ export declare class RouteFlyover {
275
283
  * worth asking every frame — see {@link SURVEY_INTERVAL_SECONDS}.
276
284
  */
277
285
  private survey;
286
+ private shownPitch;
278
287
  private targetAnchor;
279
288
  private ease;
280
289
  private settle;
package/dist/index.es.js CHANGED
@@ -1 +1 @@
1
- import{MERCATOR_UNITS_PER_EPSG3857_METER,localFrameMatrix}from"@tomtom-org/maps-sdk/map";import{bearing,bearingToAzimuth,degreesToRadians,destination}from"@turf/turf";import{calculateProgressAtRoutePoint,generateId,getCoordinateAtRouteProgress}from"@tomtom-org/maps-sdk/core";import{MercatorCoordinate}from"maplibre-gl";var headingBetween=(e,t)=>bearingToAzimuth(bearing(e,t)),shortestTurn=(e,t)=>(t-e+540)%360-180,clamp=(e,t,r)=>Math.min(Math.max(e,t),r),springAt=e=>({value:e,velocity:0}),springTowards=(e,t,r,i,a=Number.POSITIVE_INFINITY)=>{const o=4/i,n=2*a/o,s=clamp(e.value-t,-n,n);if(Math.abs(s)<1e-6&&Math.abs(e.velocity)<1e-6)return springAt(t);const h=(e.velocity+o*s)*r,m=Math.exp(-o*r);return{value:e.value-s+(s+h)*m,velocity:(e.velocity-o*h)*m}},springTowardsBearing=(e,t,r,i,a)=>{const o=springTowards(e,e.value+shortestTurn(e.value,t),r,i,a);return{value:(o.value+360)%360,velocity:o.velocity}},clampFraction=e=>clamp(e,.05,.95),anchorForPitch=e=>({x:.5,y:.5+clamp(e,0,60)/60*.16666666666666663}),anchorPadding=(e,t)=>{const r=clampFraction(e.x)-.5,i=clampFraction(e.y)-.5,a=(e,t)=>Math.max(2*e*t,0);return{left:a(r,t.width),right:a(-r,t.width),top:a(i,t.height),bottom:a(-i,t.height)}},EQUATOR_METERS_PER_PIXEL=1/(512*MERCATOR_UNITS_PER_EPSG3857_METER),epsg3857MetersPerPixel=e=>EQUATOR_METERS_PER_PIXEL/2**e,metersPerPixel=(e,t)=>epsg3857MetersPerPixel(e)*Math.cos(degreesToRadians(t)),zoomForMetersPerPixel=(e,t)=>Math.log2(metersPerPixel(0,t)/e),cameraDistanceInPixels=({viewportHeight:e,fieldOfViewDegrees:t})=>.5*e/Math.tan(degreesToRadians(t)/2),svgElement=(e,t)=>{const r=document.createElementNS("http://www.w3.org/2000/svg",e);for(const[i,a]of Object.entries(t))r.setAttribute(i,a);return r},svgDataUri=e=>`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,compile=(e,t,r)=>{const i=e.createShader(t);if(!i)throw new Error("The fly-over marker could not create a shader");return e.shaderSource(i,r),e.compileShader(i),i},PositionMarker=class{map;id=`tomtom-flyover-marker-${generateId()}`;type="custom";renderingMode="2d";config={};placed;raster;gl;drawing;customLayer;constructor(e,t){this.map=e,this.customLayer=e.addCustomLayer(this),this.apply(t)}apply(e){this.config=e;const t=e.size??44;var r;(async(e,t)=>{const r=((e,t)=>{const r="arrow"===e.type?svgDataUri((({fillColor:e="#105287",outlineColor:t="white",outlineOpacity:r=1},i)=>{const a=svgElement("svg",{viewBox:"0 0 24 24",width:String(i),height:String(i)});return a.append(svgElement("path",{d:"M12 3l8 18-8-4-8 4z",fill:e,stroke:t,"stroke-opacity":String(r),"stroke-width":"3","stroke-linejoin":"round","paint-order":"stroke"})),(new XMLSerializer).serializeToString(a)})(e,t)):e.image;if("string"!=typeof r)return r;const i=new Image;return i.crossOrigin="anonymous",i.src=r.includes("<svg")?svgDataUri(r):r,i})(e,t);await r.decode();const i=document.createElement("canvas");i.width=t,i.height=t;const a=t/Math.max(r.naturalWidth,r.naturalHeight,1),[o,n]=[r.naturalWidth*a,r.naturalHeight*a];return i.getContext("2d")?.drawImage(r,(t-o)/2,(t-n)/2,o,n),i})(e.image??{type:"arrow"},(r=t,Math.min(2**Math.ceil(Math.log2(Math.max(r*devicePixelRatio,1))),512))).then(t=>{this.config===e&&(this.raster=t,this.releaseTexture(),this.map.mapLibreMap.triggerRepaint())}).catch(e=>console.warn("The fly-over marker image could not be loaded",e)),this.map.mapLibreMap.triggerRepaint()}place(e,t){this.placed={position:e,heading:t},this.map.mapLibreMap.triggerRepaint()}remove(){this.customLayer.remove()}onAdd(e,t){this.gl=t,this.drawing=(e=>{const t=e.createProgram(),r=e.createBuffer();if(!t||!r)throw new Error("The fly-over marker could not create its program");return e.attachShader(t,compile(e,e.VERTEX_SHADER,"\nuniform mat4 u_matrix;\nuniform vec2 u_right;\nuniform vec2 u_forward;\nattribute vec2 a_corner;\nvarying vec2 v_uv;\nvoid main() {\n v_uv = vec2(a_corner.x, -a_corner.y) * 0.5 + 0.5;\n gl_Position = u_matrix * vec4(u_right * a_corner.x + u_forward * a_corner.y, 0.0, 1.0);\n}")),e.attachShader(t,compile(e,e.FRAGMENT_SHADER,"\nprecision mediump float;\nuniform sampler2D u_image;\nvarying vec2 v_uv;\nvoid main() {\n gl_FragColor = texture2D(u_image, v_uv);\n}")),e.linkProgram(t),e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),e.STATIC_DRAW),{program:t,corners:r,texture:void 0,uniforms:{matrix:e.getUniformLocation(t,"u_matrix"),right:e.getUniformLocation(t,"u_right"),forward:e.getUniformLocation(t,"u_forward"),image:e.getUniformLocation(t,"u_image")},corner:e.getAttribLocation(t,"a_corner")}})(t)}onRemove(e,t){this.releaseTexture(),this.drawing&&(t.deleteProgram(this.drawing.program),t.deleteBuffer(this.drawing.corners)),this.drawing=void 0,this.gl=void 0}render(e,t){const{drawing:r,placed:i,raster:a}=this;if(!r||!i||!a||!1===this.config.visible)return;r.texture??=((e,t)=>{const r=e.createTexture()??void 0;return e.bindTexture(e.TEXTURE_2D,r??null),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),e.generateMipmap(e.TEXTURE_2D),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR_MIPMAP_LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),r})(e,a);const o=this.map.mapLibreMap,[n,s]=i.position,h=o.queryTerrainElevation([n,s])??0,m=localFrameMatrix(t.defaultProjectionData.mainMatrix,[n,s],{altitude:MercatorCoordinate.fromLngLat([0,0],h).z/MERCATOR_UNITS_PER_EPSG3857_METER,units:"mercatorMeters",centerLatitude:o.getCenter().lat}),c=(this.config.size??44)/2*epsg3857MetersPerPixel(o.getZoom()),g=degreesToRadians(i.heading),[d,l]=[Math.sin(g)*c,Math.cos(g)*c];e.useProgram(r.program),e.uniformMatrix4fv(r.uniforms.matrix,!1,Float32Array.from(m)),e.uniform2f(r.uniforms.right,l,-d),e.uniform2f(r.uniforms.forward,d,l),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,r.texture??null),e.uniform1i(r.uniforms.image,0),e.bindBuffer(e.ARRAY_BUFFER,r.corners),e.enableVertexAttribArray(r.corner),e.vertexAttribPointer(r.corner,2,e.FLOAT,!1,0,0),e.disable(e.DEPTH_TEST),e.enable(e.BLEND),e.blendFunc(e.ONE,e.ONE_MINUS_SRC_ALPHA),e.drawArrays(e.TRIANGLE_STRIP,0,4)}releaseTexture(){this.drawing?.texture&&this.gl?.deleteTexture(this.drawing.texture),this.drawing&&(this.drawing.texture=void 0)}},DEFAULT_CAMERA={speedKMH:200,pitch:70,aim:{mode:"instruction",fallbackMetersAhead:250},zoom:{mode:"instructions",instructionsAhead:2}},HEADING_SPAN_METERS=15,clampZoom=e=>clamp(e,11,15),RouteFlyover=class{map;route;camera;onProgress;loop;onArrive;marker;animationFrame;lastFrameTime=0;traveledDistanceInMeters=0;speedKMH=springAt(0);motion;target;framedZoom;secondsSinceSurvey=0;viewport={width:1,height:1};flying=!1;pageCenterClampedToGround=!0;pagePadding;constructor(e,t){this.map=e,this.route=t.route,this.camera={...DEFAULT_CAMERA,...t.camera},this.onProgress=t.onProgress??(()=>{}),this.loop=t.loop??!0,this.onArrive=t.onArrive??(()=>{});const r=e.mapLibreMap,i=this.targetAnchor();this.motion={bearing:springAt(r.getBearing()),zoom:springAt(r.getZoom()),pitch:springAt(r.getPitch()),anchorX:springAt(i.x),anchorY:springAt(i.y),elevation:springAt(r.getCenterElevation()),markerHeading:springAt(0)},this.target={bearing:this.motion.bearing.value,zoom:this.motion.zoom.value,elevation:this.motion.elevation.value,markerHeading:0},this.framedZoom=this.target.zoom,this.readViewport(),r.on("resize",this.readViewport),this.marker=new PositionMarker(e,t.marker??{}),this.placeMarkerOutright()}get running(){return void 0!==this.animationFrame}start(){if(this.running)return;this.hasArrived()&&(this.traveledDistanceInMeters=0,this.flying=!1);const e=this.map.mapLibreMap;this.pageCenterClampedToGround=e.getCenterClampedToGround(),e.setCenterClampedToGround(!1),this.speedKMH=springAt(0),this.lastFrameTime=performance.now(),this.animationFrame=requestAnimationFrame(this.step)}stop(){void 0!==this.animationFrame&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=void 0,this.map.mapLibreMap.setCenterClampedToGround(this.pageCenterClampedToGround))}setRoute(e){this.route=e,this.traveledDistanceInMeters=0,this.flying=!1,this.running?this.placeCamera():this.placeMarkerOutright()}setCamera(e){this.camera={...this.camera,...e},this.flying&&!this.running&&this.placeCamera()}getCamera(){return{...this.camera}}setMarker(e){this.marker.apply(e)}seek(e){const t=this.routeLengthInMeters();this.traveledDistanceInMeters="number"==typeof e?t*clamp(e,0,1):clamp(this.distanceAt(e),0,t),this.placeCamera()}remove(){this.stop(),this.marker.remove();const e=this.map.mapLibreMap;e.off("resize",this.readViewport),this.pagePadding&&e.setPadding(this.pagePadding)}readViewport=()=>{const e=this.map.mapLibreMap.getCanvas();this.viewport={width:Math.max(e.clientWidth,1),height:Math.max(e.clientHeight,1)}};routeLengthInMeters(){return this.route.properties.summary.lengthInMeters}distanceAt(e){return"traveledDistanceInMeters"in e?e.traveledDistanceInMeters:getCoordinateAtRouteProgress(this.route,e)?.distanceInMeters??0}hasArrived(){return!this.loop&&this.traveledDistanceInMeters>=this.routeLengthInMeters()}step=e=>{const t=clamp((e-this.lastFrameTime)/1e3,0,.25);this.lastFrameTime=e;const r=this.speedKMH.value;this.speedKMH=springTowards(this.speedKMH,this.camera.speedKMH,t,2);const i=(r+this.speedKMH.value)/2/3.6;this.traveledDistanceInMeters=this.alongRoute(this.traveledDistanceInMeters+i*t);const a=this.hasArrived();if(this.placeCamera(t,a),a)return this.stop(),void this.onArrive();this.animationFrame=requestAnimationFrame(this.step)};coordinateAt(e){return getCoordinateAtRouteProgress(this.route,{traveledDistanceInMeters:e})}positionAt(e){return this.coordinateAt(this.alongRoute(this.traveledDistanceInMeters+e))?.position}metersAheadTo(e){const t=e-this.traveledDistanceInMeters;return this.loop?this.alongRoute(t):Math.max(t,0)}alongRoute(e){const t=this.routeLengthInMeters();return this.loop?t>0?(e%t+t)%t:0:clamp(e,0,t)}roadHeading(){const[e,t]=[-15,HEADING_SPAN_METERS].map(e=>this.coordinateAt(this.traveledDistanceInMeters+e)?.position);return e&&t?headingBetween(e,t):void 0}instructions(){return this.route.properties.guidance?.instructions??[]}instructionAhead(e=0){const t=this.instructions();if(!t.length)return;const r=t.findIndex(e=>e.routeOffsetInMeters>this.traveledDistanceInMeters),i=this.loop?0:t.length-1;return t[Math.min((-1===r?i:r)+e,t.length-1)]}pointIndexHere(){let e=0,t=this.route.geometry.coordinates.length-1;for(;e<t;){const r=Math.floor((e+t)/2),i=calculateProgressAtRoutePoint(this.route,r);void 0!==i&&i.distanceInMeters<this.traveledDistanceInMeters?e=r+1:t=r}return e}aimPosition(){const e=this.camera.aim;switch(e.mode){case"distance":return this.positionAt(e.metersAhead);case"points":{const t=this.route.geometry.coordinates,r=this.pointIndexHere()+e.pointsAhead;return t[this.loop?r%t.length:Math.min(r,t.length-1)]}case"instruction":{const t=this.instructionAhead(),r=t&&this.metersAheadTo(t.routeOffsetInMeters);return void 0===r?this.positionAt(e.fallbackMetersAhead):this.positionAt(r)}}}modeZoom(e){const t=this.camera.zoom;if("fixed"===t.mode)return t.zoom;if("speed"===t.mode){const e=Math.log2(Math.max(this.camera.speedKMH,1)/90);return clampZoom(14-1*e)}const r=this.instructionAhead(Math.max(t.instructionsAhead,1)-1);if(!r)return clampZoom(14);const i=this.metersAheadTo(r.routeOffsetInMeters)*Math.cos(degreesToRadians(Math.min(this.camera.pitch,85)));return clampZoom(((e,t,r)=>zoomForMetersPerPixel(Math.max(e,1)/Math.max(t,1),r))(i,.6*this.viewport.height,e))}clearanceZoom(e,t,r){const i=degreesToRadians(Math.min(this.camera.pitch,85)),a=((e,t)=>cameraDistanceInPixels(t)*metersPerPixel(e,t.latitudeDegrees))(this.motion.zoom.value,r)*Math.sin(i)/1e3,o=this.motion.bearing.value+180;let n=200;for(let s=0;s<4;s++){const r=s/4,i=destination(e,a*(1-r),o).geometry.coordinates,h=this.groundAt(i);void 0!==h&&(n=Math.max(n,200+(h-t)/(1-r)))}return((e,t)=>zoomForMetersPerPixel(Math.max(e,1)/cameraDistanceInPixels(t),t.latitudeDegrees))(n/Math.cos(i),r)}groundAt([e,t]){return this.map.mapLibreMap.queryTerrainElevation([e,t])??void 0}survey(e){const t={latitudeDegrees:e[1],viewportHeight:this.viewport.height,fieldOfViewDegrees:this.map.mapLibreMap.getVerticalFieldOfView()},r=this.aimPosition();!r||r[0]===e[0]&&r[1]===e[1]||(this.target.bearing=((e,t)=>Math.abs(shortestTurn(e,t))>2.5?t:e)(this.target.bearing,headingBetween(e,r))),this.target.markerHeading=this.roadHeading()??this.target.markerHeading;const i=this.modeZoom(t.latitudeDegrees),a="instructions"===this.camera.zoom.mode?.2:0;Math.abs(i-this.framedZoom)>a&&(this.framedZoom=i);const o=this.groundAt(e);void 0===o?(this.map.mapLibreMap.getTerrain()||(this.target.elevation=0),this.target.zoom=this.framedZoom):(this.target.elevation=o,this.target.zoom=Math.min(this.framedZoom,this.clearanceZoom(e,o,t)))}targetAnchor(){return this.camera.anchor??anchorForPitch(this.camera.pitch)}ease(e){const{motion:t,target:r}=this,i=this.targetAnchor(),a=(t,r,i)=>springTowards(t,r,e,i);this.motion={bearing:springTowardsBearing(t.bearing,r.bearing,e,2,22),markerHeading:springTowardsBearing(t.markerHeading,r.markerHeading,e,.5),zoom:a(t.zoom,r.zoom,3),elevation:a(t.elevation,r.elevation,.6),pitch:a(t.pitch,this.camera.pitch,1.5),anchorX:a(t.anchorX,i.x,1.5),anchorY:a(t.anchorY,i.y,1.5)}}settle(){const e=this.targetAnchor();this.motion={bearing:springAt(this.target.bearing),markerHeading:springAt(this.target.markerHeading),zoom:springAt(this.target.zoom),elevation:springAt(this.target.elevation),pitch:springAt(this.camera.pitch),anchorX:springAt(e.x),anchorY:springAt(e.y)}}placeMarkerOutright(){const e=this.positionAt(0);this.target.markerHeading=this.roadHeading()??this.target.markerHeading,this.motion.markerHeading=springAt(this.target.markerHeading),e&&this.marker.place(e,this.target.markerHeading)}placeCamera(e,t=!1){const r=this.coordinateAt(this.traveledDistanceInMeters);if(!r)return;const i=void 0!==e&&this.flying;this.secondsSinceSurvey+=e??0;const a=!i||t||this.secondsSinceSurvey>=.2;a&&(this.secondsSinceSurvey=0,this.survey(r.position)),i?this.ease(e):this.settle(),this.flying=!0;const[o,n]=r.position,{bearing:s,zoom:h,pitch:m,elevation:c,anchorX:g,anchorY:d,markerHeading:l}=this.motion,p=this.map.mapLibreMap;this.pagePadding??=p.getPadding(),p.jumpTo({center:[o,n],elevation:c.value,bearing:s.value,pitch:m.value,zoom:h.value,padding:anchorPadding({x:g.value,y:d.value},this.viewport)}),this.marker.place(r.position,l.value),a&&this.onProgress({traveledDistanceInMeters:r.distanceInMeters,traveledTimeInSeconds:r.travelTimeInSeconds})}};export{RouteFlyover,anchorForPitch};
1
+ import{MERCATOR_UNITS_PER_EPSG3857_METER,localFrameMatrix}from"@tomtom-org/maps-sdk/map";import{bearing,bearingToAzimuth,degreesToRadians,destination}from"@turf/turf";import{calculateProgressAtRoutePoint,generateId,getCoordinateAtRouteProgress}from"@tomtom-org/maps-sdk/core";import{MercatorCoordinate}from"maplibre-gl";var headingBetween=(e,t)=>bearingToAzimuth(bearing(e,t)),shortestTurn=(e,t)=>(t-e+540)%360-180,clamp=(e,t,r)=>Math.min(Math.max(e,t),r),springAt=e=>({value:e,velocity:0}),springTowards=(e,t,r,i,a=Number.POSITIVE_INFINITY)=>{const o=4/i,n=2*a/o,s=clamp(e.value-t,-n,n);if(Math.abs(s)<1e-6&&Math.abs(e.velocity)<1e-6)return springAt(t);const h=(e.velocity+o*s)*r,m=Math.exp(-o*r);return{value:e.value-s+(s+h)*m,velocity:(e.velocity-o*h)*m}},springTowardsBearing=(e,t,r,i,a)=>{const o=springTowards(e,e.value+shortestTurn(e.value,t),r,i,a);return{value:(o.value+360)%360,velocity:o.velocity}},clampFraction=e=>clamp(e,.05,.95),anchorForPitch=e=>({x:.5,y:.5+clamp(e,0,60)/60*.16666666666666663}),anchorPadding=(e,t)=>{const r=clampFraction(e.x)-.5,i=clampFraction(e.y)-.5,a=(e,t)=>Math.max(2*e*t,0);return{left:a(r,t.width),right:a(-r,t.width),top:a(i,t.height),bottom:a(-i,t.height)}},EQUATOR_METERS_PER_PIXEL=1/(512*MERCATOR_UNITS_PER_EPSG3857_METER),epsg3857MetersPerPixel=e=>EQUATOR_METERS_PER_PIXEL/2**e,metersPerPixel=(e,t)=>epsg3857MetersPerPixel(e)*Math.cos(degreesToRadians(t)),zoomForMetersPerPixel=(e,t)=>Math.log2(metersPerPixel(0,t)/e),cameraDistanceInPixels=({viewportHeight:e,fieldOfViewDegrees:t})=>.5*e/Math.tan(degreesToRadians(t)/2),svgElement=(e,t)=>{const r=document.createElementNS("http://www.w3.org/2000/svg",e);for(const[i,a]of Object.entries(t))r.setAttribute(i,a);return r},svgDataUri=e=>`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,compile=(e,t,r)=>{const i=e.createShader(t);if(!i)throw new Error("The fly-over marker could not create a shader");return e.shaderSource(i,r),e.compileShader(i),i},PositionMarker=class{map;id=`tomtom-flyover-marker-${generateId()}`;type="custom";renderingMode="2d";config={};placed;raster;gl;drawing;customLayer;constructor(e,t){this.map=e,this.customLayer=e.addCustomLayer(this),this.apply(t)}apply(e){this.config=e;const t=e.size??44;var r;(async(e,t)=>{const r=((e,t)=>{const r="arrow"===e.type?svgDataUri((({fillColor:e="#105287",outlineColor:t="white",outlineOpacity:r=1},i)=>{const a=svgElement("svg",{viewBox:"0 0 24 24",width:String(i),height:String(i)});return a.append(svgElement("path",{d:"M12 3l8 18-8-4-8 4z",fill:e,stroke:t,"stroke-opacity":String(r),"stroke-width":"3","stroke-linejoin":"round","paint-order":"stroke"})),(new XMLSerializer).serializeToString(a)})(e,t)):e.image;if("string"!=typeof r)return r;const i=new Image;return i.crossOrigin="anonymous",i.src=!/^data:image\/svg\+xml[;,]/i.test(r)&&r.includes("<svg")?svgDataUri(r):r,i})(e,t);await r.decode();const i=document.createElement("canvas");i.width=t,i.height=t;const a=t/Math.max(r.naturalWidth,r.naturalHeight,1),[o,n]=[r.naturalWidth*a,r.naturalHeight*a],s=i.getContext("2d");return s?.drawImage(r,(t-o)/2,(t-n)/2,o,n),s?.getImageData(0,0,1,1),i})(e.image??{type:"arrow"},(r=t,Math.min(2**Math.ceil(Math.log2(Math.max(r*devicePixelRatio,1))),512))).then(t=>{this.config===e&&(this.raster=t,this.releaseTexture(),this.map.mapLibreMap.triggerRepaint())}).catch(e=>console.warn("The fly-over marker image could not be loaded",e)),this.map.mapLibreMap.triggerRepaint()}place(e,t){this.placed={position:e,heading:t},this.map.mapLibreMap.triggerRepaint()}remove(){this.customLayer.remove()}onAdd(e,t){this.gl=t,this.drawing=(e=>{const t=e.createProgram(),r=e.createBuffer();if(!t||!r)throw new Error("The fly-over marker could not create its program");const i=[compile(e,e.VERTEX_SHADER,"\nuniform mat4 u_matrix;\nuniform vec2 u_right;\nuniform vec2 u_forward;\nattribute vec2 a_corner;\nvarying vec2 v_uv;\nvoid main(){\nv_uv=vec2(a_corner.x,-a_corner.y)*.5+.5;\ngl_Position=u_matrix*vec4(u_right*a_corner.x+u_forward*a_corner.y,0.,1.);\n}"),compile(e,e.FRAGMENT_SHADER,"\nprecision mediump float;\nuniform sampler2D u_image;\nvarying vec2 v_uv;\nvoid main(){\ngl_FragColor=texture2D(u_image,v_uv);\n}")];for(const a of i)e.attachShader(t,a);e.linkProgram(t);for(const a of i)e.deleteShader(a);return e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),e.STATIC_DRAW),{program:t,corners:r,texture:void 0,uniforms:{matrix:e.getUniformLocation(t,"u_matrix"),right:e.getUniformLocation(t,"u_right"),forward:e.getUniformLocation(t,"u_forward"),image:e.getUniformLocation(t,"u_image")},corner:e.getAttribLocation(t,"a_corner")}})(t)}onRemove(e,t){this.releaseTexture(),this.drawing&&(t.deleteProgram(this.drawing.program),t.deleteBuffer(this.drawing.corners)),this.drawing=void 0,this.gl=void 0}render(e,t){const{drawing:r,placed:i,raster:a}=this;if(!r||!i||!a||!1===this.config.visible)return;r.texture??=((e,t)=>{const r=e.createTexture()??void 0;return e.bindTexture(e.TEXTURE_2D,r??null),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),e.generateMipmap(e.TEXTURE_2D),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR_MIPMAP_LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),r})(e,a);const o=this.map.mapLibreMap,[n,s]=i.position,h=o.queryTerrainElevation([n,s])??0,m=localFrameMatrix(t.defaultProjectionData.mainMatrix,[n,s],{altitude:MercatorCoordinate.fromLngLat([0,0],h).z/MERCATOR_UNITS_PER_EPSG3857_METER,units:"mercatorMeters",centerLatitude:o.getCenter().lat}),c=(this.config.size??44)/2*epsg3857MetersPerPixel(o.getZoom()),g=degreesToRadians(i.heading),[d,l]=[Math.sin(g)*c,Math.cos(g)*c];e.useProgram(r.program),e.uniformMatrix4fv(r.uniforms.matrix,!1,Float32Array.from(m)),e.uniform2f(r.uniforms.right,l,-d),e.uniform2f(r.uniforms.forward,d,l),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,r.texture??null),e.uniform1i(r.uniforms.image,0),e.bindBuffer(e.ARRAY_BUFFER,r.corners),e.enableVertexAttribArray(r.corner),e.vertexAttribPointer(r.corner,2,e.FLOAT,!1,0,0),e.disable(e.DEPTH_TEST),e.enable(e.BLEND),e.blendFunc(e.ONE,e.ONE_MINUS_SRC_ALPHA),e.drawArrays(e.TRIANGLE_STRIP,0,4)}releaseTexture(){this.drawing?.texture&&this.gl?.deleteTexture(this.drawing.texture),this.drawing&&(this.drawing.texture=void 0)}},DEFAULT_CAMERA={speedKMH:200,pitch:70,aim:{mode:"instruction",fallbackMetersAhead:250},zoom:{mode:"instructions",instructionsAhead:2}},HEADING_SPAN_METERS=15,clampZoom=e=>clamp(e,11,15),RouteFlyover=class{map;route;camera;onProgress;loop;onArrive;marker;animationFrame;lastFrameTime=0;traveledDistanceInMeters=0;speedKMH=springAt(0);motion;target;framedZoom;secondsSinceSurvey=0;viewport={width:1,height:1};flying=!1;pageCenterClampedToGround=!0;pagePadding;constructor(e,t){this.map=e,this.route=t.route,this.camera={...DEFAULT_CAMERA,...t.camera},this.onProgress=t.onProgress??(()=>{}),this.loop=t.loop??!0,this.onArrive=t.onArrive??(()=>{});const r=e.mapLibreMap,i=this.targetAnchor();this.motion={bearing:springAt(r.getBearing()),zoom:springAt(r.getZoom()),pitch:springAt(r.getPitch()),anchorX:springAt(i.x),anchorY:springAt(i.y),elevation:springAt(r.getCenterElevation()),markerHeading:springAt(0)},this.target={bearing:this.motion.bearing.value,zoom:this.motion.zoom.value,elevation:this.motion.elevation.value,markerHeading:0},this.framedZoom=this.target.zoom,this.readViewport(),r.on("resize",this.readViewport),this.marker=new PositionMarker(e,t.marker??{}),this.placeMarkerOutright()}get running(){return void 0!==this.animationFrame}start(){if(this.running)return;this.hasArrived()&&(this.traveledDistanceInMeters=0,this.flying=!1);const e=this.map.mapLibreMap;this.pageCenterClampedToGround=e.getCenterClampedToGround(),e.setCenterClampedToGround(!1),this.speedKMH=springAt(0),this.lastFrameTime=performance.now(),this.animationFrame=requestAnimationFrame(this.step)}stop(){void 0!==this.animationFrame&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=void 0,this.map.mapLibreMap.setCenterClampedToGround(this.pageCenterClampedToGround))}setRoute(e){this.route=e,this.traveledDistanceInMeters=0,this.flying=!1,this.running?this.placeCamera():this.placeMarkerOutright()}setCamera(e){this.camera={...this.camera,...e},this.flying&&!this.running&&this.placeCamera()}getCamera(){const{aim:e,zoom:t,anchor:r}=this.camera;return{...this.camera,aim:{...e},zoom:{...t},...r&&{anchor:{...r}}}}setMarker(e){this.marker.apply(e)}seek(e){const t=this.routeLengthInMeters();this.traveledDistanceInMeters="number"==typeof e?t*clamp(e,0,1):clamp(this.distanceAt(e),0,t),this.placeCamera()}remove(){this.stop(),this.marker.remove();const e=this.map.mapLibreMap;e.off("resize",this.readViewport),this.pagePadding&&e.setPadding(this.pagePadding)}readViewport=()=>{const e=this.map.mapLibreMap.getCanvas();this.viewport={width:Math.max(e.clientWidth,1),height:Math.max(e.clientHeight,1)}};routeLengthInMeters(){return this.route.properties.summary.lengthInMeters}distanceAt(e){return"traveledDistanceInMeters"in e?e.traveledDistanceInMeters:getCoordinateAtRouteProgress(this.route,e)?.distanceInMeters??0}hasArrived(){return!this.loop&&this.traveledDistanceInMeters>=this.routeLengthInMeters()}step=e=>{const t=this.animationFrame,r=clamp((e-this.lastFrameTime)/1e3,0,.25);this.lastFrameTime=e;const i=this.speedKMH.value;this.speedKMH=springTowards(this.speedKMH,this.camera.speedKMH,r,2);const a=(i+this.speedKMH.value)/2/3.6;this.traveledDistanceInMeters=this.alongRoute(this.traveledDistanceInMeters+a*r);const o=this.hasArrived();if(this.placeCamera(r,o),o)return this.stop(),void this.onArrive();this.animationFrame===t&&(this.animationFrame=requestAnimationFrame(this.step))};coordinateAt(e){return getCoordinateAtRouteProgress(this.route,{traveledDistanceInMeters:e})}positionAt(e){return this.coordinateAt(this.alongRoute(this.traveledDistanceInMeters+e))?.position}metersAheadTo(e){const t=e-this.traveledDistanceInMeters;return this.loop?this.alongRoute(t)||this.routeLengthInMeters():Math.max(t,0)}alongRoute(e){const t=this.routeLengthInMeters();return this.loop?t>0?(e%t+t)%t:0:clamp(e,0,t)}roadHeading(){const[e,t]=[-15,HEADING_SPAN_METERS].map(e=>this.coordinateAt(this.traveledDistanceInMeters+e)?.position);return e&&t?headingBetween(e,t):void 0}instructions(){return this.route.properties.guidance?.instructions??[]}instructionAhead(e=0){const t=this.instructions();if(!t.length)return;const r=t.findIndex(e=>e.routeOffsetInMeters>this.traveledDistanceInMeters),i=this.loop?0:t.length-1,a=-1===r?i:r,o=t.length-1;return t[this.loop?(a+Math.min(e,o))%t.length:Math.min(a+e,o)]}pointIndexHere(){let e=0,t=this.route.geometry.coordinates.length-1;for(;e<t;){const r=Math.floor((e+t)/2),i=calculateProgressAtRoutePoint(this.route,r);void 0!==i&&i.distanceInMeters<this.traveledDistanceInMeters?e=r+1:t=r}return e}aimPosition(){const e=this.camera.aim;switch(e.mode){case"distance":return this.positionAt(e.metersAhead);case"points":{const t=this.route.geometry.coordinates,r=this.pointIndexHere()+e.pointsAhead;return t[this.loop?r%t.length:Math.min(r,t.length-1)]}case"instruction":{const t=this.instructionAhead(),r=t&&this.metersAheadTo(t.routeOffsetInMeters);return void 0===r?this.positionAt(e.fallbackMetersAhead):this.positionAt(r)}}}modeZoom(e){const t=this.camera.zoom;if("fixed"===t.mode)return t.zoom;if("speed"===t.mode){const e=Math.log2(Math.max(this.camera.speedKMH,1)/90);return clampZoom(14-1*e)}const r=this.instructionAhead(Math.max(t.instructionsAhead,1)-1);if(!r)return clampZoom(14);const i=this.metersAheadTo(r.routeOffsetInMeters)*Math.cos(degreesToRadians(Math.min(this.shownPitch(),85)));return clampZoom(((e,t,r)=>zoomForMetersPerPixel(Math.max(e,1)/Math.max(t,1),r))(i,.6*this.viewport.height,e))}clearanceZoom(e,t,r,i){const a=degreesToRadians(Math.min(this.shownPitch(),85)),o=i?this.framedZoom:this.motion.zoom.value,n=i?this.target.bearing:this.motion.bearing.value,s=((e,t)=>cameraDistanceInPixels(t)*metersPerPixel(e,t.latitudeDegrees))(o,r)*Math.sin(a)/1e3,h=n+180;let m=200;for(let c=0;c<4;c++){const r=c/4,i=destination(e,s*(1-r),h).geometry.coordinates,a=this.groundAt(i);void 0!==a&&(m=Math.max(m,200+(a-t)/(1-r)))}return((e,t)=>zoomForMetersPerPixel(Math.max(e,1)/cameraDistanceInPixels(t),t.latitudeDegrees))(m/Math.cos(a),r)}groundAt([e,t]){return this.map.mapLibreMap.queryTerrainElevation([e,t])??void 0}survey(e,t){const r={latitudeDegrees:e[1],viewportHeight:this.viewport.height,fieldOfViewDegrees:this.map.mapLibreMap.getVerticalFieldOfView()},i=this.aimPosition();!i||i[0]===e[0]&&i[1]===e[1]||(this.target.bearing=((e,t)=>Math.abs(shortestTurn(e,t))>2.5?t:e)(this.target.bearing,headingBetween(e,i))),this.target.markerHeading=this.roadHeading()??this.target.markerHeading;const a=this.modeZoom(r.latitudeDegrees),o="instructions"===this.camera.zoom.mode?.2:0;Math.abs(a-this.framedZoom)>o&&(this.framedZoom=a);const n=this.groundAt(e);void 0===n?(this.map.mapLibreMap.getTerrain()||(this.target.elevation=0),this.target.zoom=this.framedZoom):(this.target.elevation=n,this.target.zoom=Math.min(this.framedZoom,this.clearanceZoom(e,n,r,t)))}shownPitch(){return Math.min(this.camera.pitch,this.map.mapLibreMap.getMaxPitch())}targetAnchor(){return this.camera.anchor??anchorForPitch(this.shownPitch())}ease(e){const{motion:t,target:r}=this,i=this.targetAnchor(),a=(t,r,i)=>springTowards(t,r,e,i);this.motion={bearing:springTowardsBearing(t.bearing,r.bearing,e,2,22),markerHeading:springTowardsBearing(t.markerHeading,r.markerHeading,e,.5),zoom:a(t.zoom,r.zoom,3),elevation:a(t.elevation,r.elevation,.6),pitch:a(t.pitch,this.shownPitch(),1.5),anchorX:a(t.anchorX,i.x,1.5),anchorY:a(t.anchorY,i.y,1.5)}}settle(){const e=this.targetAnchor();this.motion={bearing:springAt(this.target.bearing),markerHeading:springAt(this.target.markerHeading),zoom:springAt(this.target.zoom),elevation:springAt(this.target.elevation),pitch:springAt(this.shownPitch()),anchorX:springAt(e.x),anchorY:springAt(e.y)}}placeMarkerOutright(){const e=this.positionAt(0);this.target.markerHeading=this.roadHeading()??this.target.markerHeading,this.motion.markerHeading=springAt(this.target.markerHeading),e&&this.marker.place(e,this.target.markerHeading)}placeCamera(e,t=!1){const r=this.coordinateAt(this.traveledDistanceInMeters);if(!r)return;const i=void 0!==e&&this.flying;this.secondsSinceSurvey+=e??0;const a=!i||t||this.secondsSinceSurvey>=.2;a&&(this.secondsSinceSurvey=0,this.survey(r.position,!i)),i?this.ease(e):this.settle(),this.flying=!0;const[o,n]=r.position,{bearing:s,zoom:h,pitch:m,elevation:c,anchorX:g,anchorY:d,markerHeading:l}=this.motion,p=this.map.mapLibreMap;this.pagePadding??=p.getPadding(),p.jumpTo({center:[o,n],elevation:c.value,bearing:s.value,pitch:m.value,zoom:h.value,padding:anchorPadding({x:g.value,y:d.value},this.viewport)}),this.marker.place(r.position,l.value),a&&this.onProgress({traveledDistanceInMeters:r.distanceInMeters,traveledTimeInSeconds:r.travelTimeInSeconds})}};export{RouteFlyover,anchorForPitch};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tomtom-org/maps-sdk-plugin-flyover",
3
3
  "author": "TomTom International B.V.",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
- "version": "0.1.0",
5
+ "version": "0.1.2",
6
6
  "type": "module",
7
7
  "description": "A camera that flies a route on a TomTomMap: it follows the line, aims at the junctions ahead, frames them, clears the terrain it crosses, and parks the followed position where you want it on screen",
8
8
  "types": "dist/index.d.ts",
@@ -22,12 +22,13 @@
22
22
  "typescript": "^6.0.3",
23
23
  "vite": "^8.3.4",
24
24
  "vitest": "^5.0.5",
25
- "@tomtom-org/maps-sdk": "1.0.0-rc.4"
25
+ "@tomtom-org/maps-sdk": "1.0.0-rc.6"
26
26
  },
27
27
  "files": [
28
28
  "dist",
29
29
  "package.json",
30
30
  "README.md",
31
+ "CHANGELOG.md",
31
32
  "LICENSE.txt",
32
33
  "THIRD_PARTY.txt",
33
34
  "!**/*.map"