@tomtom-org/maps-sdk-plugin-flyover 0.0.3 → 0.0.5

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/LICENSE.txt CHANGED
@@ -1,8 +1,8 @@
1
1
  TOMTOM MAPS SDK FOR JAVASCRIPT LICENSE AGREEMENT (“AGREEMENT”)
2
2
 
3
- Last update: September 2026
3
+ Last update: October 2026
4
4
 
5
- By downloading or accessing the “TomTom Maps SDK for JavaScript” (the “SDK”), you are entering into a binding agreement with TomTom Global Content B.V. (“TomTom”). The SDK comprises all software, binaries and source code made available by TomTom for it, whether through the TomTom Developer Portal or public code repositories (e.g. GitHub). This Agreement with TomTom consists of the terms and conditions below together with the Data Processing Schedule. You should not use the SDK if you cannot agree to both.
5
+ By downloading or accessing the “TomTom Maps SDK for JavaScript” (the “SDK”), you are entering into a binding agreement with TomTom Global Content B.V. (“TomTom”). The SDK comprises all software, binaries and source code made available by TomTom for it, whether through the TomTom Developer Portal, package registries (e.g. npm) or otherwise. The example applications that TomTom publishes in public code repositories (e.g. GitHub) are not part of the SDK: they are licensed separately, under the license distributed with them. This Agreement with TomTom consists of the terms and conditions below together with the Data Processing Schedule. You should not use the SDK if you cannot agree to both.
6
6
 
7
7
  You may not use the SDK or accept this Agreement if you are barred from receiving it under the laws of the European Union or of any other country, including the country in which you are resident or from which you use the SDK. If you accept this Agreement on behalf of your employer or another entity, you represent and warrant that you have full legal authority to bind that employer or entity. If you do not have that authority, you may not accept this Agreement or use the SDK on their behalf.
8
8
 
@@ -14,7 +14,7 @@ You may not use the SDK or accept this Agreement if you are barred from receivin
14
14
 
15
15
  3.1. You agree to use the SDK only for purposes permitted by (a) this Agreement and (b) any applicable law, regulation or generally accepted practice or guideline in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).
16
16
 
17
- 3.2. Other than as permitted by applicable legislation, you are prohibited from copying (except for backup purposes), modifying, adapting, (re)distributing, renting, lending, publicly presenting, decompiling, reverse engineering, disassembling or creating derivative works of the SDK or any part of it. Notwithstanding the foregoing, where the SDK is hosted on a public repository (e.g. GitHub, which allows forking under its terms of service), you may: (i) fork the repository solely for the purpose of submitting a pull request to TomTom, provided that such fork remains hosted within the platform and is not used as a basis for a separate or competing product or service; and (ii) download a local copy of the source code solely for the purpose of debugging your Permitted Application, provided that such copy is not shared or redistributed. The availability of the source code in a public repository does not constitute “Open-Source” licensing and grants you no rights under any Open Source Initiative (OSI) approved license. Neither that availability, nor any rights granted by the platform’s terms of service with respect to viewing or forking within the platform, shall be construed as a grant of rights to modify, fork or redistribute the SDK beyond those expressly granted in Clause 1 and this Clause 3.2.
17
+ 3.2. Other than as permitted by applicable legislation, you are prohibited from copying (except for backup purposes), modifying, adapting, (re)distributing, renting, lending, publicly presenting, decompiling, reverse engineering, disassembling or creating derivative works of the SDK or any part of it. Notwithstanding the foregoing, you may inspect the SDK as distributed to you, including any source code embedded in its source maps, solely for the purpose of debugging your Permitted Application, provided that it is not shared or redistributed. The readability of the SDK’s source code in its distribution does not constitute “Open-Source” licensing and grants you no rights under any Open Source Initiative (OSI) approved license, nor shall it be construed as a grant of rights to modify, fork or redistribute the SDK beyond those expressly granted in Clause 1 and this Clause 3.2.
18
18
 
19
19
  3.3. You may not use the SDK in connection with any illegal, fraudulent, dishonest or unethical activity or behaviour, and in particular not to create or develop, or help others create or develop, a product, service or library that competes, directly or indirectly, with the SDK or the TomTom APIs.
20
20
 
@@ -78,7 +78,7 @@ You may not use the SDK or accept this Agreement if you are barred from receivin
78
78
 
79
79
  11.4. The TomTom APIs and the SDK are provided as “Commercial Computer Software” or “restricted computer software”. Use, duplication or disclosure by the U.S. Government or a U.S. Government subcontractor is subject to the restrictions set forth in 48 C.F.R. Section 12.212 or 48 C.F.R. 227.2702, as applicable, or successor provisions. In respect of the Map API and Search API, the manufacturer is TomTom Global Content B.V.
80
80
 
81
- 11.5. By submitting any suggestion, bug fix or code contribution (“Contribution”) to TomTom (e.g. via a GitHub Pull Request), you grant TomTom a perpetual, irrevocable, worldwide, royalty-free and non-exclusive license to use, reproduce, modify and distribute the Contribution as part of the SDK. You represent that you have the legal right to grant this license and that the Contribution does not infringe any third-party intellectual property rights.
81
+ 11.5. By submitting any suggestion, bug fix or code contribution (“Contribution”) to TomTom (e.g. via a GitHub issue or discussion), you grant TomTom a perpetual, irrevocable, worldwide, royalty-free and non-exclusive license to use, reproduce, modify and distribute the Contribution as part of the SDK. You represent that you have the legal right to grant this license and that the Contribution does not infringe any third-party intellectual property rights.
82
82
 
83
83
  THIRD PARTY SOFTWARE NOTICES
84
84
 
package/README.md CHANGED
@@ -1,11 +1,20 @@
1
- # @tomtom-org/maps-sdk-plugin-flyover
1
+ # Flyover Plugin
2
2
 
3
- A camera that flies a route on a [TomTom Maps SDK for JavaScript](https://docs.tomtom.com/maps-sdk-js/)
3
+ A camera that flies a route on a [TomTom Maps SDK for JavaScript](https://docs.tomtom.com/maps-sdk-js/introduction/overview)
4
4
  map: it follows the line, aims at the junctions ahead, frames them, keeps clear of the terrain it
5
5
  crosses, and parks the followed position — marked with an arrow — where you want it on screen.
6
6
 
7
+ ## Docs & examples
8
+
9
+ - Developer guide: https://docs.tomtom.com/maps-sdk-js/guides/plugins/flyover
10
+ - Example: https://docs.tomtom.com/maps-sdk-js/examples/route-flyover-playground
11
+
12
+ ## Quickstart
13
+
14
+ Note: this plugin declares `@tomtom-org/maps-sdk` and `maplibre-gl` as peer dependencies and bundles neither — ensure both are installed in your project.
15
+
7
16
  ```bash
8
- npm install @tomtom-org/maps-sdk @tomtom-org/maps-sdk-plugin-flyover
17
+ npm install @tomtom-org/maps-sdk maplibre-gl @tomtom-org/maps-sdk-plugin-flyover
9
18
  ```
10
19
 
11
20
  ```javascript
@@ -23,6 +32,8 @@ const flyover = new RouteFlyover(map, {
23
32
  flyover.start();
24
33
  ```
25
34
 
35
+ ## Features
36
+
26
37
  The route is the only geometry it needs — no camera keyframes to author, and no animation to
27
38
  re-time when the route changes. The flight loops: it wraps at the destination and never lands.
28
39
 
@@ -37,10 +48,6 @@ re-time when the route changes. The flight loops: it wraps at the destination an
37
48
  - **Smooth by default** — every value the camera changes moves on a critically damped spring, the
38
49
  same at any frame rate.
39
50
 
40
- Full documentation: **[Fly-over plugin guide](https://docs.tomtom.com/maps-sdk-js/guides/plugins/flyover)**.
41
-
42
- `@tomtom-org/maps-sdk` and `maplibre-gl` are peer dependencies; this package bundles neither.
43
-
44
51
  ## License
45
52
 
46
53
  See [LICENSE.txt](./LICENSE.txt).
package/THIRD_PARTY.txt CHANGED
@@ -168,7 +168,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
168
168
  ---
169
169
 
170
170
  Name: maplibre-gl
171
- Version: 6.11.2
171
+ Version: 6.12.0
172
172
  License: BSD-3-Clause
173
173
  Bundled: false (peer dependency)
174
174
  Description: BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
@@ -161,7 +161,7 @@ your application bundles them. The licenses below apply to those copies, and eac
161
161
  these components may in turn install its own dependencies under their own licenses.
162
162
 
163
163
  Name: maplibre-gl
164
- Version: 6.11.2
164
+ Version: 6.12.0
165
165
  License: BSD-3-Clause
166
166
  Bundled: false (peer dependency)
167
167
  Description: BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
package/dist/index.d.ts CHANGED
@@ -12,11 +12,11 @@ type FlyoverAnchor = {
12
12
  y: number;
13
13
  };
14
14
  /**
15
- * Where a camera at this pitch puts the position it follows, absent an anchor of your own.
15
+ * Returns where a camera at this pitch puts the position it follows when you set no anchor.
16
16
  *
17
17
  * A level map is a plan, so the position belongs in the middle. Tilt the camera and the picture
18
18
  * becomes mostly what lies *ahead*, so the position slides down with the pitch, reaching the lower
19
- * third at 60° — and a top-down pass stays centred without switching modes.
19
+ * third at 60°. A top-down pass stays centred without switching modes.
20
20
  *
21
21
  * @group Flyover
22
22
  */
@@ -24,12 +24,12 @@ export declare const anchorForPitch: (pitchDegrees: number) => FlyoverAnchor;
24
24
  //#endregion
25
25
  //#region src/marker.d.ts
26
26
  /**
27
- * The image that rides the route, drawn pointing up: the flight turns it to face the road.
27
+ * The image that rides the route, drawn pointing up. The flight turns it to face the road.
28
28
  *
29
- * - `arrow` — the plugin's own arrow, recoloured with the SDK's SVG icon options. Its fill defaults
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
- * - `custom` — yours: a URL, raw SVG markup, a data URI or a loaded `<img>`, as for any SDK custom
32
- * image. A URL on another origin needs CORS, since the image becomes a WebGL texture.
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.
33
33
  *
34
34
  * @group Flyover
35
35
  */
@@ -55,13 +55,13 @@ type FlyoverMarkerConfig = {
55
55
  //#endregion
56
56
  //#region src/RouteFlyover.d.ts
57
57
  /**
58
- * What the camera points at, which is what decides how early it turns into a bend.
58
+ * What the camera points at, which decides how early it turns into a bend.
59
59
  *
60
- * - `distance` — a fixed distance down the road. Predictable, and blind to what the road does.
61
- * - `points` — a number of the route's own vertices. The line is denser through bends, so the aim
62
- * shortens exactly where the road is turning.
63
- * - `instruction` — the next guidance instruction, so the camera looks at the junction it is
64
- * heading for. Needs a route asked for with `guidance`; without instructions it aims by distance.
60
+ * - `distance` - A fixed distance down the road. It's predictable, but blind to what the road does.
61
+ * - `points` - A number of the route's own vertices. The line is denser through bends, so the aim
62
+ * shortens exactly where the road turns.
63
+ * - `instruction` - The next guidance instruction, so the camera looks at the junction it's
64
+ * heading for. It needs a route requested with `guidance`. Without instructions, it aims by distance.
65
65
  *
66
66
  * @group Flyover
67
67
  */
@@ -79,10 +79,10 @@ type FlyoverAim = {
79
79
  * How high the camera rides. Whatever the mode, it pulls back further where terrain would otherwise
80
80
  * come between the camera and the road.
81
81
  *
82
- * - `fixed` — one MapLibre zoom, whatever the road does.
83
- * - `instructions` — framed on the junctions ahead: the zoom that fits the next N instructions (one
84
- * at least) in the view, so a run of hairpins pulls back and a straight closes in.
85
- * - `speed` — tied to the ground speed, the way a driver's eyes lift with it.
82
+ * - `fixed` - One MapLibre zoom, whatever the road does.
83
+ * - `instructions` - Frames the junctions ahead with the zoom that fits the next N instructions, at
84
+ * least one, in the view. A run of hairpins then pulls back, and a straight closes in.
85
+ * - `speed` - Follows the ground speed, the way a driver's eyes lift with it.
86
86
  *
87
87
  * @group Flyover
88
88
  */
@@ -103,14 +103,14 @@ type FlyoverZoom = {
103
103
  type FlyoverCamera = {
104
104
  /** Ground speed of the camera, in kilometres per hour. @default 200 */
105
105
  speedKMH: number;
106
- /** Camera tilt, in degrees; the map's `maxPitch` is the ceiling. @default 70 */
106
+ /** Camera tilt, in degrees, capped at the map's `maxPitch`. @default 70 */
107
107
  pitch: number;
108
108
  /** What the camera aims at down the route. @default `{ mode: 'instruction', fallbackMetersAhead: 250 }` */
109
109
  aim: FlyoverAim;
110
110
  /** What sets the camera's height. @default `{ mode: 'instructions', instructionsAhead: 2 }` */
111
111
  zoom: FlyoverZoom;
112
112
  /**
113
- * Where the followed position sits on screen. Omitted, it follows the pitch — see
113
+ * Where the followed position sits on screen. By default, it follows the pitch through
114
114
  * {@link anchorForPitch}.
115
115
  */
116
116
  anchor?: FlyoverAnchor;
@@ -130,33 +130,33 @@ type FlyoverProgress = {
130
130
  type RouteFlyoverOptions = {
131
131
  /** The route to fly. Replace it later with {@link RouteFlyover.setRoute}. */
132
132
  route: Route;
133
- /** Overrides for the camera; anything left out takes the default on {@link FlyoverCamera}. */
133
+ /** Overrides for the camera. Anything you leave out takes the default on {@link FlyoverCamera}. */
134
134
  camera?: Partial<FlyoverCamera>;
135
- /** The marker riding the route at the followed position; an arrow by default. */
135
+ /** The marker riding the route at the followed position, by default an arrow. */
136
136
  marker?: FlyoverMarkerConfig;
137
137
  /** Called with the flight's progress, once per survey rather than once per frame. */
138
138
  onProgress?: (progress: FlyoverProgress) => void;
139
139
  };
140
140
  /**
141
- * A camera that flies a route on a loop: it advances a distance along the line every frame, reads
142
- * the coordinate the route has at that distance, and points itself at where it will be next.
141
+ * A camera that flies a route on a loop. Every frame, it advances a distance along the line, reads
142
+ * the route's coordinate at that distance, and points itself at where it will be next.
143
143
  *
144
144
  * @remarks
145
- * The route is the only geometry it needs — no path of camera keyframes to author, and no
145
+ * The route is the only geometry it needs. There's no path of camera keyframes to author and no
146
146
  * animation to re-time when the route changes. What it adds on top of following the line:
147
147
  *
148
- * - **it aims ahead** rather than at its own position, by distance, by route vertices or at the
149
- * next guidance instruction, so the view turns into a bend before the camera reaches it;
150
- * - **it frames** what is coming: the zoom that fits the next junctions, or one tied to the speed;
151
- * - **it clears the ground**, pulling back where terrain would come between the camera and the road;
152
- * - **it parks the position where you want it on screen** — see {@link FlyoverAnchor} — and marks
153
- * it with an arrow, or an image of your own, facing the road;
154
- * - **it moves like a camera**: every value it changes eases on a critically damped spring, so a new
148
+ * - It aims ahead rather than at its own position, by distance, by route vertices or at the next
149
+ * guidance instruction, so the view turns into a bend before the camera reaches it.
150
+ * - It frames what is coming, with the zoom that fits the next junctions or one tied to the speed.
151
+ * - It clears the ground, pulling back where terrain would come between the camera and the road.
152
+ * - It parks the position where you want it on screen (see {@link FlyoverAnchor}), and marks it
153
+ * with an arrow, or an image of your own, facing the road.
154
+ * - It moves like a camera. Every value it changes eases on a critically damped spring, so a new
155
155
  * heading, zoom or setting bends the motion rather than kicking it, and a start ramps the speed.
156
156
  *
157
- * While it runs, the flight owns the altitude of the point the camera looks at — the ground under
158
- * the followed position — so it takes MapLibre off clamping the centre to the ground, and gives
159
- * that back when it stops.
157
+ * While it runs, the flight owns the altitude of the point the camera looks at, which is the ground
158
+ * under the followed position. It therefore turns off MapLibre's clamping of the centre to the
159
+ * ground, and restores it when it stops.
160
160
  *
161
161
  * @example
162
162
  * ```typescript
@@ -193,13 +193,13 @@ export declare class RouteFlyover {
193
193
  /** Starts, or resumes, the flight, ramping up to its speed. */
194
194
  start(): void;
195
195
  /**
196
- * Stops the flight where it is; {@link start} resumes from there. The centre clamp goes back to
196
+ * Stops the flight where it is. {@link start} resumes from there. The centre clamp returns to
197
197
  * what the map had before {@link start}.
198
198
  */
199
199
  stop(): void;
200
200
  /** Puts a new route under the flight, from its departure point. */
201
201
  setRoute(route: Route): void;
202
- /** Applies camera settings — speed, pitch, aim, framing, anchor — paused or under way. */
202
+ /** Applies camera settings (speed, pitch, aim, framing, anchor), whether paused or under way. */
203
203
  setCamera(camera: Partial<FlyoverCamera>): void;
204
204
  /** The camera the flight is using, including the defaults it was not given. */
205
205
  getCamera(): FlyoverCamera;
@@ -208,7 +208,7 @@ export declare class RouteFlyover {
208
208
  /** Moves the flight to a point along the route, as a share of its length within 0..1. */
209
209
  seek(fraction: number): void;
210
210
  /**
211
- * Stops the flight and gives the map back: the marker leaves, and the padding returns to what
211
+ * Stops the flight and gives the map back. The marker leaves, and the padding returns to what
212
212
  * the map had before the flight first placed the camera.
213
213
  */
214
214
  remove(): void;
package/dist/index.es.js CHANGED
@@ -1,2 +1 @@
1
- import{calculateProgressAtRoutePoint as e,generateId as t,getCoordinateAtRouteProgress as r}from"@tomtom-org/maps-sdk/core";import{MercatorCoordinate as i}from"maplibre-gl";var a=6371008.8,n={centimeters:637100880,centimetres:637100880,cm:637100880,degrees:360/(2*Math.PI),deg:360/(2*Math.PI),feet:20902260.511392,ft:20902260.511392,inches:39.37*a,in:39.37*a,kilometers:6371.0088,kilometres:6371.0088,km:6371.0088,meters:a,metres:a,m:a,miles:3958.761333810546,mi:3958.761333810546,millimeters:6371008800,millimetres:6371008800,mm:6371008800,nauticalmiles:a/1852,nmi:a/1852,radians:1,rad:1,yards:6967335.223679999,yd:6967335.223679999};function o(e,t,r={}){if(!e)throw new Error("coordinates is required");if(!Array.isArray(e))throw new Error("coordinates must be an Array");if(e.length<2)throw new Error("coordinates must be at least 2 numbers long");if(!m(e[0])||!m(e[1]))throw new Error("coordinates must contain numbers");return function(e,t,r={}){const i={type:"Feature"};return(0===r.id||r.id)&&(i.id=r.id),r.bbox&&(i.bbox=r.bbox),i.properties=t||{},i.geometry=e,i}({type:"Point",coordinates:e},t,r)}function s(e){return e%(2*Math.PI)*180/Math.PI}function h(e){return e%360*Math.PI/180}function m(e){return!isNaN(e)&&null!==e&&!Array.isArray(e)}function c(e){if(!e)throw new Error("coord is required");if(!Array.isArray(e)){if("Feature"===e.type&&null!==e.geometry&&"Point"===e.geometry.type)return[...e.geometry.coordinates];if("Point"===e.type)return[...e.coordinates]}if(Array.isArray(e)&&e.length>=2&&!Array.isArray(e[0])&&!Array.isArray(e[1]))return[...e];throw new Error("coord must be GeoJSON Point or an Array of numbers")}function d(e,t,r={}){if(!0===r.final)return function(e,t){let r=d(t,e);return r=(r+180)%360,r>180?r-360:r}(e,t);const i=c(e),a=c(t),n=h(i[0]),o=h(a[0]),m=h(i[1]),u=h(a[1]),g=Math.sin(o-n)*Math.cos(u),l=Math.cos(m)*Math.sin(u)-Math.sin(m)*Math.cos(u)*Math.cos(o-n);return s(Math.atan2(g,l))}function u(e,t,r,i={}){const a=c(e),m=h(a[0]),d=h(a[1]),u=h(r),g=function(e,t="kilometers"){const r=n[t];if(!r)throw new Error(t+" units is invalid");return e/r}(t,i.units),l=Math.asin(Math.sin(d)*Math.cos(g)+Math.cos(d)*Math.sin(g)*Math.cos(u)),p=s(m+Math.atan2(Math.sin(u)*Math.sin(g)*Math.cos(d),Math.cos(g)-Math.sin(d)*Math.sin(l))),v=s(l);return void 0!==a[2]?o([p,v,a[2]],i.properties):o([p,v],i.properties)}var g=(e,t)=>function(e){let t=e%360;return t<0&&(t+=360),t}(d(e,t)),l=(e,t)=>(t-e+540)%360-180,p=(e,t,r)=>Math.min(Math.max(e,t),r),v=e=>({value:e,velocity:0}),M=1e-6,f=(e,t,r,i,a=Number.POSITIVE_INFINITY)=>{const n=4/i,o=2*a/n,s=p(e.value-t,-o,o);if(Math.abs(s)<M&&Math.abs(e.velocity)<M)return v(t);const h=(e.velocity+n*s)*r,m=Math.exp(-n*r);return{value:e.value-s+(s+h)*m,velocity:(e.velocity-n*h)*m}},y=(e,t,r,i,a)=>{const n=f(e,e.value+l(e.value,t),r,i,a);return{value:(n.value+360)%360,velocity:n.velocity}},A=e=>p(e,.05,.95),w=e=>({x:.5,y:.5+p(e,0,60)/60*.16666666666666663}),T=(e,t)=>{const r=A(e.x)-.5,i=A(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)}},E=40075016.686/512,b=(e,t)=>Math.log2(E*Math.cos(h(t))/e),x=({viewportHeight:e,fieldOfViewDegrees:t})=>.5*e/Math.tan(h(t)/2),_=(e,t)=>x(t)*((e,t)=>E*Math.cos(h(t))/2**e)(e,t.latitudeDegrees),I=(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},R=e=>`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,L=(e,t)=>{const r="arrow"===e.type?R((({fillColor:e="#105287",outlineColor:t="white",outlineOpacity:r=1},i)=>{const a=I("svg",{viewBox:"0 0 24 24",width:String(i),height:String(i)});return a.append(I("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")?R(r):r,i},P=(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},k=class{map;id=`tomtom-flyover-marker-${t()}`;type="custom";renderingMode="2d";config={};placed;raster;gl;drawing;removed=!1;stopRestoring;constructor(e,t){this.map=e,this.stopRestoring=e.addStyleChangeHandler({onStyleChanged:()=>this.install()}),this.installWhenReady(),this.apply(t)}apply(e){this.config=e;const t=e.size??44;var r;(async(e,t)=>{const r=L(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),[n,o]=[r.naturalWidth*a,r.naturalHeight*a];return i.getContext("2d")?.drawImage(r,(t-n)/2,(t-o)/2,n,o),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.removed=!0,this.stopRestoring(),this.map.mapLibreMap.getLayer(this.id)&&this.map.mapLibreMap.removeLayer(this.id)}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,P(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,P(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:a,raster:n}=this;if(!r||!a||!n||!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,n);const o=this.map.mapLibreMap,[s,m]=a.position,c=o.queryTerrainElevation([s,m])??0,d=i.fromLngLat([s,m],c),u=(this.config.size??44)/2*(1/(512*2**o.getZoom()));const g=h(a.heading),[l,p]=[Math.sin(g)*u,Math.cos(g)*u];e.useProgram(r.program),e.uniformMatrix4fv(r.uniforms.matrix,!1,((e,{x:t,y:r,z:i})=>{const a=Float32Array.from(e);for(let n=0;n<4;n++)a[12+n]=e[n]*t+e[4+n]*r+e[8+n]*i+e[12+n];return a})(t.defaultProjectionData.mainMatrix,d)),e.uniform2f(r.uniforms.right,p,l),e.uniform2f(r.uniforms.forward,l,-p),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)}installWhenReady(){this.map.mapReady?this.install():this.map.mapLibreMap.once("styledata",()=>this.installWhenReady())}install(){this.removed||this.map.mapLibreMap.getLayer(this.id)||this.map.mapLibreMap.addLayer(this)}},D={speedKMH:200,pitch:70,aim:{mode:"instruction",fallbackMetersAhead:250},zoom:{mode:"instructions",instructionsAhead:2}},H=2,S=2,C=3,F=1.5,U=1.5,z=.6,N=.5,X=15,O=e=>p(e,11,15),G=class{map;route;camera;onProgress;marker;animationFrame;lastFrameTime=0;traveledDistanceInMeters=0;speedKMH=v(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={...D,...t.camera},this.onProgress=t.onProgress??(()=>{});const r=e.mapLibreMap,i=this.targetAnchor();this.motion={bearing:v(r.getBearing()),zoom:v(r.getZoom()),pitch:v(r.getPitch()),anchorX:v(i.x),anchorY:v(i.y),elevation:v(r.getCenterElevation()),markerHeading:v(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 k(e,t.marker??{}),this.placeMarkerOutright()}get running(){return void 0!==this.animationFrame}start(){if(this.running)return;const e=this.map.mapLibreMap;this.pageCenterClampedToGround=e.getCenterClampedToGround(),e.setCenterClampedToGround(!1),this.speedKMH=v(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){this.traveledDistanceInMeters=this.routeLengthInMeters()*p(e,0,1),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}step=e=>{const t=p((e-this.lastFrameTime)/1e3,0,.25);this.lastFrameTime=e;const r=this.speedKMH.value;this.speedKMH=f(this.speedKMH,this.camera.speedKMH,t,H);const i=(r+this.speedKMH.value)/2/3.6;this.traveledDistanceInMeters=this.wrapped(this.traveledDistanceInMeters+i*t),this.placeCamera(t),this.animationFrame=requestAnimationFrame(this.step)};coordinateAt(e){return r(this.route,{traveledDistanceInMeters:e})}positionAt(e){return this.coordinateAt(this.wrapped(this.traveledDistanceInMeters+e))?.position}wrapped(e){const t=this.routeLengthInMeters();return t>0?(e%t+t)%t:0}roadHeading(){const[e,t]=[-15,X].map(e=>this.coordinateAt(this.traveledDistanceInMeters+e)?.position);return e&&t?g(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);return t[Math.min((-1===r?0:r)+e,t.length-1)]}pointIndexHere(){let t=0,r=this.route.geometry.coordinates.length-1;for(;t<r;){const i=Math.floor((t+r)/2),a=e(this.route,i);void 0!==a&&a.distanceInMeters<this.traveledDistanceInMeters?t=i+1:r=i}return t}aimPosition(){const e=this.camera.aim;switch(e.mode){case"distance":return this.positionAt(e.metersAhead);case"points":{const t=this.route.geometry.coordinates;return t[(this.pointIndexHere()+e.pointsAhead)%t.length]}case"instruction":{const t=this.instructionAhead(),r=t&&this.wrapped(t.routeOffsetInMeters-this.traveledDistanceInMeters);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 O(14-1*e)}const r=this.instructionAhead(Math.max(t.instructionsAhead,1)-1);if(!r)return O(14);const i=this.wrapped(r.routeOffsetInMeters-this.traveledDistanceInMeters)*Math.cos(h(Math.min(this.camera.pitch,85)));return O(((e,t,r)=>b(Math.max(e,1)/Math.max(t,1),r))(i,.6*this.viewport.height,e))}clearanceZoom(e,t,r){const i=h(Math.min(this.camera.pitch,85)),a=_(this.motion.zoom.value,r)*Math.sin(i)/1e3,n=this.motion.bearing.value+180;let o=200;for(let s=0;s<4;s++){const r=s/4,i=u(e,a*(1-r),n).geometry.coordinates,h=this.groundAt(i);void 0!==h&&(o=Math.max(o,200+(h-t)/(1-r)))}return((e,t)=>b(Math.max(e,1)/x(t),t.latitudeDegrees))(o/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&&(this.target.bearing=((e,t,r)=>Math.abs(l(e,t))>r?t:e)(this.target.bearing,g(e,r),2.5)),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 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,t)))}targetAnchor(){return this.camera.anchor??w(this.camera.pitch)}ease(e){const{motion:t,target:r}=this,i=this.targetAnchor(),a=(t,r,i)=>f(t,r,e,i);this.motion={bearing:y(t.bearing,r.bearing,e,S,22),markerHeading:y(t.markerHeading,r.markerHeading,e,N),zoom:a(t.zoom,r.zoom,C),elevation:a(t.elevation,r.elevation,z),pitch:a(t.pitch,this.camera.pitch,F),anchorX:a(t.anchorX,i.x,U),anchorY:a(t.anchorY,i.y,U)}}settle(){const e=this.targetAnchor();this.motion={bearing:v(this.target.bearing),markerHeading:v(this.target.markerHeading),zoom:v(this.target.zoom),elevation:v(this.target.elevation),pitch:v(this.camera.pitch),anchorX:v(e.x),anchorY:v(e.y)}}placeMarkerOutright(){const e=this.positionAt(0);this.target.markerHeading=this.roadHeading()??this.target.markerHeading,this.motion.markerHeading=v(this.target.markerHeading),e&&this.marker.place(e,this.target.markerHeading)}placeCamera(e){const t=this.coordinateAt(this.traveledDistanceInMeters);if(!t)return;const r=void 0!==e&&this.flying;this.secondsSinceSurvey+=e??0;const i=!r||this.secondsSinceSurvey>=.2;i&&(this.secondsSinceSurvey=0,this.survey(t.position)),r?this.ease(e):this.settle(),this.flying=!0;const[a,n]=t.position,{bearing:o,zoom:s,pitch:h,elevation:m,anchorX:c,anchorY:d,markerHeading:u}=this.motion,g=this.map.mapLibreMap;this.pagePadding??=g.getPadding(),g.jumpTo({center:[a,n],elevation:m.value,bearing:o.value,pitch:h.value,zoom:s.value,padding:T({x:c.value,y:d.value},this.viewport)}),this.marker.place(t.position,u.value),i&&this.onProgress({traveledDistanceInMeters:t.distanceInMeters,traveledTimeInSeconds:t.travelTimeInSeconds})}};export{G as RouteFlyover,w as anchorForPitch};
2
- //# sourceMappingURL=index.es.js.map
1
+ import{calculateProgressAtRoutePoint as e,generateId as t,getCoordinateAtRouteProgress as r}from"@tomtom-org/maps-sdk/core";import{MercatorCoordinate as i}from"maplibre-gl";var a=6371008.8,n={centimeters:637100880,centimetres:637100880,cm:637100880,degrees:360/(2*Math.PI),deg:360/(2*Math.PI),feet:20902260.511392,ft:20902260.511392,inches:39.37*a,in:39.37*a,kilometers:6371.0088,kilometres:6371.0088,km:6371.0088,meters:a,metres:a,m:a,miles:3958.761333810546,mi:3958.761333810546,millimeters:6371008800,millimetres:6371008800,mm:6371008800,nauticalmiles:a/1852,nmi:a/1852,radians:1,rad:1,yards:6967335.223679999,yd:6967335.223679999};function o(e,t,r={}){if(!e)throw new Error("coordinates is required");if(!Array.isArray(e))throw new Error("coordinates must be an Array");if(e.length<2)throw new Error("coordinates must be at least 2 numbers long");if(!m(e[0])||!m(e[1]))throw new Error("coordinates must contain numbers");return function(e,t,r={}){const i={type:"Feature"};return(0===r.id||r.id)&&(i.id=r.id),r.bbox&&(i.bbox=r.bbox),i.properties=t||{},i.geometry=e,i}({type:"Point",coordinates:e},t,r)}function s(e){return e%(2*Math.PI)*180/Math.PI}function h(e){return e%360*Math.PI/180}function m(e){return!isNaN(e)&&null!==e&&!Array.isArray(e)}function c(e){if(!e)throw new Error("coord is required");if(!Array.isArray(e)){if("Feature"===e.type&&null!==e.geometry&&"Point"===e.geometry.type)return[...e.geometry.coordinates];if("Point"===e.type)return[...e.coordinates]}if(Array.isArray(e)&&e.length>=2&&!Array.isArray(e[0])&&!Array.isArray(e[1]))return[...e];throw new Error("coord must be GeoJSON Point or an Array of numbers")}function d(e,t,r={}){if(!0===r.final)return function(e,t){let r=d(t,e);return r=(r+180)%360,r>180?r-360:r}(e,t);const i=c(e),a=c(t),n=h(i[0]),o=h(a[0]),m=h(i[1]),u=h(a[1]),g=Math.sin(o-n)*Math.cos(u),l=Math.cos(m)*Math.sin(u)-Math.sin(m)*Math.cos(u)*Math.cos(o-n);return s(Math.atan2(g,l))}function u(e,t,r,i={}){const a=c(e),m=h(a[0]),d=h(a[1]),u=h(r),g=function(e,t="kilometers"){const r=n[t];if(!r)throw new Error(t+" units is invalid");return e/r}(t,i.units),l=Math.asin(Math.sin(d)*Math.cos(g)+Math.cos(d)*Math.sin(g)*Math.cos(u)),p=s(m+Math.atan2(Math.sin(u)*Math.sin(g)*Math.cos(d),Math.cos(g)-Math.sin(d)*Math.sin(l))),v=s(l);return void 0!==a[2]?o([p,v,a[2]],i.properties):o([p,v],i.properties)}var g=(e,t)=>function(e){let t=e%360;return t<0&&(t+=360),t}(d(e,t)),l=(e,t)=>(t-e+540)%360-180,p=(e,t,r)=>Math.min(Math.max(e,t),r),v=e=>({value:e,velocity:0}),M=1e-6,f=(e,t,r,i,a=Number.POSITIVE_INFINITY)=>{const n=4/i,o=2*a/n,s=p(e.value-t,-o,o);if(Math.abs(s)<M&&Math.abs(e.velocity)<M)return v(t);const h=(e.velocity+n*s)*r,m=Math.exp(-n*r);return{value:e.value-s+(s+h)*m,velocity:(e.velocity-n*h)*m}},y=(e,t,r,i,a)=>{const n=f(e,e.value+l(e.value,t),r,i,a);return{value:(n.value+360)%360,velocity:n.velocity}},A=e=>p(e,.05,.95),w=e=>({x:.5,y:.5+p(e,0,60)/60*.16666666666666663}),T=(e,t)=>{const r=A(e.x)-.5,i=A(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)}},E=40075016.686/512,b=(e,t)=>Math.log2(E*Math.cos(h(t))/e),x=({viewportHeight:e,fieldOfViewDegrees:t})=>.5*e/Math.tan(h(t)/2),_=(e,t)=>x(t)*((e,t)=>E*Math.cos(h(t))/2**e)(e,t.latitudeDegrees),I=(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},R=e=>`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,L=(e,t)=>{const r="arrow"===e.type?R((({fillColor:e="#105287",outlineColor:t="white",outlineOpacity:r=1},i)=>{const a=I("svg",{viewBox:"0 0 24 24",width:String(i),height:String(i)});return a.append(I("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")?R(r):r,i},P=(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},k=class{map;id=`tomtom-flyover-marker-${t()}`;type="custom";renderingMode="2d";config={};placed;raster;gl;drawing;removed=!1;stopRestoring;constructor(e,t){this.map=e,this.stopRestoring=e.addStyleChangeHandler({onStyleChanged:()=>this.install()}),this.installWhenReady(),this.apply(t)}apply(e){this.config=e;const t=e.size??44;var r;(async(e,t)=>{const r=L(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),[n,o]=[r.naturalWidth*a,r.naturalHeight*a];return i.getContext("2d")?.drawImage(r,(t-n)/2,(t-o)/2,n,o),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.removed=!0,this.stopRestoring(),this.map.mapLibreMap.getLayer(this.id)&&this.map.mapLibreMap.removeLayer(this.id)}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,P(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,P(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:a,raster:n}=this;if(!r||!a||!n||!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,n);const o=this.map.mapLibreMap,[s,m]=a.position,c=o.queryTerrainElevation([s,m])??0,d=i.fromLngLat([s,m],c),u=(this.config.size??44)/2*(1/(512*2**o.getZoom()));const g=h(a.heading),[l,p]=[Math.sin(g)*u,Math.cos(g)*u];e.useProgram(r.program),e.uniformMatrix4fv(r.uniforms.matrix,!1,((e,{x:t,y:r,z:i})=>{const a=Float32Array.from(e);for(let n=0;n<4;n++)a[12+n]=e[n]*t+e[4+n]*r+e[8+n]*i+e[12+n];return a})(t.defaultProjectionData.mainMatrix,d)),e.uniform2f(r.uniforms.right,p,l),e.uniform2f(r.uniforms.forward,l,-p),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)}installWhenReady(){this.map.mapReady?this.install():this.map.mapLibreMap.once("styledata",()=>this.installWhenReady())}install(){this.removed||this.map.mapLibreMap.getLayer(this.id)||this.map.mapLibreMap.addLayer(this)}},D={speedKMH:200,pitch:70,aim:{mode:"instruction",fallbackMetersAhead:250},zoom:{mode:"instructions",instructionsAhead:2}},H=2,S=2,C=3,F=1.5,U=1.5,z=.6,N=.5,X=15,O=e=>p(e,11,15),G=class{map;route;camera;onProgress;marker;animationFrame;lastFrameTime=0;traveledDistanceInMeters=0;speedKMH=v(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={...D,...t.camera},this.onProgress=t.onProgress??(()=>{});const r=e.mapLibreMap,i=this.targetAnchor();this.motion={bearing:v(r.getBearing()),zoom:v(r.getZoom()),pitch:v(r.getPitch()),anchorX:v(i.x),anchorY:v(i.y),elevation:v(r.getCenterElevation()),markerHeading:v(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 k(e,t.marker??{}),this.placeMarkerOutright()}get running(){return void 0!==this.animationFrame}start(){if(this.running)return;const e=this.map.mapLibreMap;this.pageCenterClampedToGround=e.getCenterClampedToGround(),e.setCenterClampedToGround(!1),this.speedKMH=v(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){this.traveledDistanceInMeters=this.routeLengthInMeters()*p(e,0,1),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}step=e=>{const t=p((e-this.lastFrameTime)/1e3,0,.25);this.lastFrameTime=e;const r=this.speedKMH.value;this.speedKMH=f(this.speedKMH,this.camera.speedKMH,t,H);const i=(r+this.speedKMH.value)/2/3.6;this.traveledDistanceInMeters=this.wrapped(this.traveledDistanceInMeters+i*t),this.placeCamera(t),this.animationFrame=requestAnimationFrame(this.step)};coordinateAt(e){return r(this.route,{traveledDistanceInMeters:e})}positionAt(e){return this.coordinateAt(this.wrapped(this.traveledDistanceInMeters+e))?.position}wrapped(e){const t=this.routeLengthInMeters();return t>0?(e%t+t)%t:0}roadHeading(){const[e,t]=[-15,X].map(e=>this.coordinateAt(this.traveledDistanceInMeters+e)?.position);return e&&t?g(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);return t[Math.min((-1===r?0:r)+e,t.length-1)]}pointIndexHere(){let t=0,r=this.route.geometry.coordinates.length-1;for(;t<r;){const i=Math.floor((t+r)/2),a=e(this.route,i);void 0!==a&&a.distanceInMeters<this.traveledDistanceInMeters?t=i+1:r=i}return t}aimPosition(){const e=this.camera.aim;switch(e.mode){case"distance":return this.positionAt(e.metersAhead);case"points":{const t=this.route.geometry.coordinates;return t[(this.pointIndexHere()+e.pointsAhead)%t.length]}case"instruction":{const t=this.instructionAhead(),r=t&&this.wrapped(t.routeOffsetInMeters-this.traveledDistanceInMeters);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 O(14-1*e)}const r=this.instructionAhead(Math.max(t.instructionsAhead,1)-1);if(!r)return O(14);const i=this.wrapped(r.routeOffsetInMeters-this.traveledDistanceInMeters)*Math.cos(h(Math.min(this.camera.pitch,85)));return O(((e,t,r)=>b(Math.max(e,1)/Math.max(t,1),r))(i,.6*this.viewport.height,e))}clearanceZoom(e,t,r){const i=h(Math.min(this.camera.pitch,85)),a=_(this.motion.zoom.value,r)*Math.sin(i)/1e3,n=this.motion.bearing.value+180;let o=200;for(let s=0;s<4;s++){const r=s/4,i=u(e,a*(1-r),n).geometry.coordinates,h=this.groundAt(i);void 0!==h&&(o=Math.max(o,200+(h-t)/(1-r)))}return((e,t)=>b(Math.max(e,1)/x(t),t.latitudeDegrees))(o/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&&(this.target.bearing=((e,t,r)=>Math.abs(l(e,t))>r?t:e)(this.target.bearing,g(e,r),2.5)),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 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,t)))}targetAnchor(){return this.camera.anchor??w(this.camera.pitch)}ease(e){const{motion:t,target:r}=this,i=this.targetAnchor(),a=(t,r,i)=>f(t,r,e,i);this.motion={bearing:y(t.bearing,r.bearing,e,S,22),markerHeading:y(t.markerHeading,r.markerHeading,e,N),zoom:a(t.zoom,r.zoom,C),elevation:a(t.elevation,r.elevation,z),pitch:a(t.pitch,this.camera.pitch,F),anchorX:a(t.anchorX,i.x,U),anchorY:a(t.anchorY,i.y,U)}}settle(){const e=this.targetAnchor();this.motion={bearing:v(this.target.bearing),markerHeading:v(this.target.markerHeading),zoom:v(this.target.zoom),elevation:v(this.target.elevation),pitch:v(this.camera.pitch),anchorX:v(e.x),anchorY:v(e.y)}}placeMarkerOutright(){const e=this.positionAt(0);this.target.markerHeading=this.roadHeading()??this.target.markerHeading,this.motion.markerHeading=v(this.target.markerHeading),e&&this.marker.place(e,this.target.markerHeading)}placeCamera(e){const t=this.coordinateAt(this.traveledDistanceInMeters);if(!t)return;const r=void 0!==e&&this.flying;this.secondsSinceSurvey+=e??0;const i=!r||this.secondsSinceSurvey>=.2;i&&(this.secondsSinceSurvey=0,this.survey(t.position)),r?this.ease(e):this.settle(),this.flying=!0;const[a,n]=t.position,{bearing:o,zoom:s,pitch:h,elevation:m,anchorX:c,anchorY:d,markerHeading:u}=this.motion,g=this.map.mapLibreMap;this.pagePadding??=g.getPadding(),g.jumpTo({center:[a,n],elevation:m.value,bearing:o.value,pitch:h.value,zoom:s.value,padding:T({x:c.value,y:d.value},this.viewport)}),this.marker.place(t.position,u.value),i&&this.onProgress({traveledDistanceInMeters:t.distanceInMeters,traveledTimeInSeconds:t.travelTimeInSeconds})}};export{G as RouteFlyover,w as anchorForPitch};
@@ -59,7 +59,7 @@
59
59
  },
60
60
  {
61
61
  "name": "maplibre-gl",
62
- "version": "6.11.2",
62
+ "version": "6.12.0",
63
63
  "license": "BSD-3-Clause",
64
64
  "bundled": false,
65
65
  "description": "BSD licensed community fork of mapbox-gl, a WebGL interactive maps library",
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 in LICENSE.txt",
5
- "version": "0.0.3",
5
+ "version": "0.0.5",
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",
@@ -10,8 +10,8 @@
10
10
  "@turf/turf": "^7.4.0"
11
11
  },
12
12
  "peerDependencies": {
13
- "@tomtom-org/maps-sdk": ">=0.64.0 <1.0.0",
14
- "maplibre-gl": "^6.11.2"
13
+ "@tomtom-org/maps-sdk": ">=0.64.0 <1.0.0 || >=1.0.0-rc.0 <1.0.0",
14
+ "maplibre-gl": "^6.12.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@tsconfig/recommended": "^1.0.13",
@@ -23,14 +23,15 @@
23
23
  "typescript": "^6.0.3",
24
24
  "vite": "^8.3.2",
25
25
  "vitest": "^4.1.11",
26
- "@tomtom-org/maps-sdk": "0.64.0"
26
+ "@tomtom-org/maps-sdk": "1.0.0-rc.1"
27
27
  },
28
28
  "files": [
29
29
  "dist",
30
30
  "package.json",
31
31
  "README.md",
32
32
  "LICENSE.txt",
33
- "THIRD_PARTY.txt"
33
+ "THIRD_PARTY.txt",
34
+ "!**/*.map"
34
35
  ],
35
36
  "exports": {
36
37
  ".": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.es.js","names":["calculateProgressAtRoutePoint","generateId","getCoordinateAtRouteProgress","MercatorCoordinate","earthRadius","factors","centimeters","centimetres","cm","degrees","Math","PI","deg","feet","ft","inches","in","kilometers","kilometres","km","meters","metres","m","miles","mi","millimeters","millimetres","mm","nauticalmiles","nmi","radians","rad","yards","yd","point","coordinates","properties","options","Error","Array","isArray","length","isNumber","geom","feat","type","id","bbox","geometry","feature","radiansToDegrees","degreesToRadians","num","isNaN","getCoord","coord","bearing","start","end","final","bear","calculateFinalBearing","coordinates1","coordinates2","lon1","lon2","lat1","lat2","a","sin","cos","b","atan2","destination","origin","distance","longitude1","latitude1","bearingRad","units","factor","lengthToRadians","latitude2","asin","lng","lat","headingBetween","from","to","angle","bearingToAzimuth","shortestTurn","clamp","value","min","max","springAt","velocity","AT_REST","springTowards","spring","target","elapsedSeconds","settleSeconds","maxVelocity","Number","POSITIVE_INFINITY","rate","reach","offset","abs","drift","decay","exp","springTowardsBearing","next","clampFraction","anchorForPitch","pitchDegrees","x","y","anchorPadding","anchor","viewport","fromCentreX","fromCentreY","pad","fraction","extent","left","width","right","top","height","bottom","EQUATOR_METERS_PER_PIXEL","zoomForMetersPerPixel","latitudeDegrees","log2","cameraDistanceInPixels","viewportHeight","fieldOfViewDegrees","tan","cameraDistanceInMeters","zoom","lens","metersPerPixel","svgElement","name","attributes","element","document","createElementNS","attribute","Object","entries","setAttribute","svgDataUri","markup","encodeURIComponent","imageElement","image","side","source","fillColor","outlineColor","outlineOpacity","svg","viewBox","String","append","d","fill","stroke","XMLSerializer","serializeToString","arrowMarkup","Image","crossOrigin","src","includes","compile","gl","shader","createShader","shaderSource","compileShader","PositionMarker","map","renderingMode","config","placed","raster","drawing","removed","stopRestoring","constructor","this","addStyleChangeHandler","onStyleChanged","install","installWhenReady","apply","size","sizeInPixels","async","decode","canvas","createElement","scale","naturalWidth","naturalHeight","getContext","drawImage","rasterise","ceil","devicePixelRatio","then","releaseTexture","mapLibreMap","triggerRepaint","catch","error","console","warn","place","position","heading","remove","getLayer","removeLayer","onAdd","_map","program","createProgram","corners","createBuffer","attachShader","VERTEX_SHADER","FRAGMENT_SHADER","linkProgram","bindBuffer","ARRAY_BUFFER","bufferData","Float32Array","STATIC_DRAW","texture","uniforms","matrix","getUniformLocation","forward","corner","getAttribLocation","createDrawing","onRemove","deleteProgram","deleteBuffer","render","visible","createTexture","bindTexture","TEXTURE_2D","pixelStorei","UNPACK_PREMULTIPLY_ALPHA_WEBGL","texImage2D","RGBA","UNSIGNED_BYTE","generateMipmap","texParameteri","TEXTURE_MIN_FILTER","LINEAR_MIPMAP_LINEAR","TEXTURE_MAG_FILTER","LINEAR","TEXTURE_WRAP_S","CLAMP_TO_EDGE","TEXTURE_WRAP_T","uploadTexture","longitude","latitude","ground","queryTerrainElevation","centre","fromLngLat","half","getZoom","sine","cosine","useProgram","uniformMatrix4fv","projection","z","row","matrixAt","defaultProjectionData","mainMatrix","uniform2f","activeTexture","TEXTURE0","uniform1i","enableVertexAttribArray","vertexAttribPointer","FLOAT","disable","DEPTH_TEST","enable","BLEND","blendFunc","ONE","ONE_MINUS_SRC_ALPHA","drawArrays","TRIANGLE_STRIP","deleteTexture","mapReady","once","addLayer","DEFAULT_CAMERA","speedKMH","pitch","aim","mode","fallbackMetersAhead","instructionsAhead","SETTLE_SECONDS","HEADING_SPAN_METERS","clampZoom","RouteFlyover","route","camera","onProgress","marker","animationFrame","lastFrameTime","traveledDistanceInMeters","motion","framedZoom","secondsSinceSurvey","flying","pageCenterClampedToGround","pagePadding","targetAnchor","getBearing","getPitch","anchorX","anchorY","elevation","getCenterElevation","markerHeading","readViewport","on","placeMarkerOutright","running","getCenterClampedToGround","setCenterClampedToGround","performance","now","requestAnimationFrame","step","stop","cancelAnimationFrame","setRoute","placeCamera","setCamera","getCamera","setMarker","seek","routeLengthInMeters","off","setPadding","getCanvas","clientWidth","clientHeight","summary","lengthInMeters","frameTime","previousSpeedKMH","metersPerSecond","wrapped","coordinateAt","positionAt","offsetInMeters","distanceInMeters","routeLength","roadHeading","behind","ahead","instructions","guidance","instructionAhead","skip","findIndex","instruction","routeOffsetInMeters","pointIndexHere","low","high","middle","floor","progress","aimPosition","metersAhead","pointsAhead","modeZoom","doublings","foreshortened","pixels","zoomFitting","clearanceZoom","here","road","trailingInKilometers","sample","share","groundAt","zoomForCameraDistance","survey","getVerticalFieldOfView","steering","deadbandDegrees","steadyBearing","framed","deadband","getTerrain","ease","towards","settle","smooth","surveying","getPadding","jumpTo","center","padding","traveledTimeInSeconds","travelTimeInSeconds"],"sources":["../../../node_modules/.pnpm/@turf+helpers@7.4.0/node_modules/@turf/helpers/dist/esm/index.js","../../../node_modules/.pnpm/@turf+invariant@7.4.0/node_modules/@turf/invariant/dist/esm/index.js","../../../node_modules/.pnpm/@turf+bearing@7.4.0/node_modules/@turf/bearing/dist/esm/index.js","../../../node_modules/.pnpm/@turf+destination@7.4.0/node_modules/@turf/destination/dist/esm/index.js","../src/geodesy.ts","../src/framing.ts","../src/marker.ts","../src/RouteFlyover.ts"],"sourcesContent":["// index.ts\nvar earthRadius = 63710088e-1;\nvar factors = {\n centimeters: earthRadius * 100,\n centimetres: earthRadius * 100,\n cm: earthRadius * 100,\n degrees: 360 / (2 * Math.PI),\n deg: 360 / (2 * Math.PI),\n feet: earthRadius * 3.28084,\n ft: earthRadius * 3.28084,\n inches: earthRadius * 39.37,\n in: earthRadius * 39.37,\n kilometers: earthRadius / 1e3,\n kilometres: earthRadius / 1e3,\n km: earthRadius / 1e3,\n meters: earthRadius,\n metres: earthRadius,\n m: earthRadius,\n miles: earthRadius / 1609.344,\n mi: earthRadius / 1609.344,\n millimeters: earthRadius * 1e3,\n millimetres: earthRadius * 1e3,\n mm: earthRadius * 1e3,\n nauticalmiles: earthRadius / 1852,\n nmi: earthRadius / 1852,\n radians: 1,\n rad: 1,\n yards: earthRadius * 1.0936,\n yd: earthRadius * 1.0936\n};\nvar areaFactors = {\n acres: 247105e-9,\n ac: 247105e-9,\n centimeters: 1e4,\n centimetres: 1e4,\n cm: 1e4,\n feet: 10.763910417,\n ft: 10.763910417,\n hectares: 1e-4,\n ha: 1e-4,\n inches: 1550.003100006,\n in: 1550.003100006,\n kilometers: 1e-6,\n kilometres: 1e-6,\n km: 1e-6,\n meters: 1,\n metres: 1,\n m: 1,\n miles: 386e-9,\n mi: 386e-9,\n nauticalmiles: 29155334959812285e-23,\n nmi: 29155334959812285e-23,\n millimeters: 1e6,\n millimetres: 1e6,\n mm: 1e6,\n yards: 1.195990046,\n yd: 1.195990046\n};\nfunction feature(geom, properties, options = {}) {\n const feat = { type: \"Feature\" };\n if (options.id === 0 || options.id) {\n feat.id = options.id;\n }\n if (options.bbox) {\n feat.bbox = options.bbox;\n }\n feat.properties = properties || {};\n feat.geometry = geom;\n return feat;\n}\nfunction geometry(type, coordinates, _options = {}) {\n switch (type) {\n case \"Point\":\n return point(coordinates).geometry;\n case \"LineString\":\n return lineString(coordinates).geometry;\n case \"Polygon\":\n return polygon(coordinates).geometry;\n case \"MultiPoint\":\n return multiPoint(coordinates).geometry;\n case \"MultiLineString\":\n return multiLineString(coordinates).geometry;\n case \"MultiPolygon\":\n return multiPolygon(coordinates).geometry;\n default:\n throw new Error(type + \" is invalid\");\n }\n}\nfunction point(coordinates, properties, options = {}) {\n if (!coordinates) {\n throw new Error(\"coordinates is required\");\n }\n if (!Array.isArray(coordinates)) {\n throw new Error(\"coordinates must be an Array\");\n }\n if (coordinates.length < 2) {\n throw new Error(\"coordinates must be at least 2 numbers long\");\n }\n if (!isNumber(coordinates[0]) || !isNumber(coordinates[1])) {\n throw new Error(\"coordinates must contain numbers\");\n }\n const geom = {\n type: \"Point\",\n coordinates\n };\n return feature(geom, properties, options);\n}\nfunction points(coordinates, properties, options = {}) {\n return featureCollection(\n coordinates.map((coords) => {\n return point(coords, properties);\n }),\n options\n );\n}\nfunction polygon(coordinates, properties, options = {}) {\n for (const ring of coordinates) {\n if (ring.length < 4) {\n throw new Error(\n \"Each LinearRing of a Polygon must have 4 or more Positions.\"\n );\n }\n if (ring[ring.length - 1].length !== ring[0].length) {\n throw new Error(\"First and last Position are not equivalent.\");\n }\n for (let j = 0; j < ring[ring.length - 1].length; j++) {\n if (ring[ring.length - 1][j] !== ring[0][j]) {\n throw new Error(\"First and last Position are not equivalent.\");\n }\n }\n }\n const geom = {\n type: \"Polygon\",\n coordinates\n };\n return feature(geom, properties, options);\n}\nfunction polygons(coordinates, properties, options = {}) {\n return featureCollection(\n coordinates.map((coords) => {\n return polygon(coords, properties);\n }),\n options\n );\n}\nfunction lineString(coordinates, properties, options = {}) {\n if (coordinates.length < 2) {\n throw new Error(\"coordinates must be an array of two or more positions\");\n }\n const geom = {\n type: \"LineString\",\n coordinates\n };\n return feature(geom, properties, options);\n}\nfunction lineStrings(coordinates, properties, options = {}) {\n return featureCollection(\n coordinates.map((coords) => {\n return lineString(coords, properties);\n }),\n options\n );\n}\nfunction featureCollection(features, options = {}) {\n const fc = { type: \"FeatureCollection\" };\n if (options.id) {\n fc.id = options.id;\n }\n if (options.bbox) {\n fc.bbox = options.bbox;\n }\n fc.features = features;\n return fc;\n}\nfunction multiLineString(coordinates, properties, options = {}) {\n const geom = {\n type: \"MultiLineString\",\n coordinates\n };\n return feature(geom, properties, options);\n}\nfunction multiPoint(coordinates, properties, options = {}) {\n const geom = {\n type: \"MultiPoint\",\n coordinates\n };\n return feature(geom, properties, options);\n}\nfunction multiPolygon(coordinates, properties, options = {}) {\n const geom = {\n type: \"MultiPolygon\",\n coordinates\n };\n return feature(geom, properties, options);\n}\nfunction geometryCollection(geometries, properties, options = {}) {\n const geom = {\n type: \"GeometryCollection\",\n geometries\n };\n return feature(geom, properties, options);\n}\nfunction round(num, precision = 0) {\n if (precision && !(precision >= 0)) {\n throw new Error(\"precision must be a positive number\");\n }\n const multiplier = Math.pow(10, precision || 0);\n return Math.round(num * multiplier) / multiplier;\n}\nfunction radiansToLength(radians, units = \"kilometers\") {\n const factor = factors[units];\n if (!factor) {\n throw new Error(units + \" units is invalid\");\n }\n return radians * factor;\n}\nfunction lengthToRadians(distance, units = \"kilometers\") {\n const factor = factors[units];\n if (!factor) {\n throw new Error(units + \" units is invalid\");\n }\n return distance / factor;\n}\nfunction lengthToDegrees(distance, units) {\n return radiansToDegrees(lengthToRadians(distance, units));\n}\nfunction bearingToAzimuth(bearing) {\n let angle = bearing % 360;\n if (angle < 0) {\n angle += 360;\n }\n return angle;\n}\nfunction azimuthToBearing(angle) {\n angle = angle % 360;\n if (angle > 180) {\n return angle - 360;\n } else if (angle < -180) {\n return angle + 360;\n }\n return angle;\n}\nfunction radiansToDegrees(radians) {\n const normalisedRadians = radians % (2 * Math.PI);\n return normalisedRadians * 180 / Math.PI;\n}\nfunction degreesToRadians(degrees) {\n const normalisedDegrees = degrees % 360;\n return normalisedDegrees * Math.PI / 180;\n}\nfunction convertLength(length, originalUnit = \"kilometers\", finalUnit = \"kilometers\") {\n if (!(length >= 0)) {\n throw new Error(\"length must be a positive number\");\n }\n return radiansToLength(lengthToRadians(length, originalUnit), finalUnit);\n}\nfunction convertArea(area, originalUnit = \"meters\", finalUnit = \"kilometers\") {\n if (!(area >= 0)) {\n throw new Error(\"area must be a positive number\");\n }\n const startFactor = areaFactors[originalUnit];\n if (!startFactor) {\n throw new Error(\"invalid original units\");\n }\n const finalFactor = areaFactors[finalUnit];\n if (!finalFactor) {\n throw new Error(\"invalid final units\");\n }\n return area / startFactor * finalFactor;\n}\nfunction isNumber(num) {\n return !isNaN(num) && num !== null && !Array.isArray(num);\n}\nfunction isObject(input) {\n return input !== null && typeof input === \"object\" && !Array.isArray(input);\n}\nfunction removeBbox(geojson) {\n delete geojson.bbox;\n if (geojson.type === \"Feature\") {\n if (geojson.geometry) removeBbox(geojson.geometry);\n } else if (geojson.type === \"FeatureCollection\") {\n geojson.features.forEach(removeBbox);\n } else if (geojson.type === \"GeometryCollection\") {\n geojson.geometries.forEach(removeBbox);\n }\n}\nfunction validateBBox(bbox) {\n if (!bbox) {\n throw new Error(\"bbox is required\");\n }\n if (!Array.isArray(bbox)) {\n throw new Error(\"bbox must be an Array\");\n }\n if (bbox.length !== 4 && bbox.length !== 6) {\n throw new Error(\"bbox must be an Array of 4 or 6 numbers\");\n }\n bbox.forEach((num) => {\n if (!isNumber(num)) {\n throw new Error(\"bbox must only contain numbers\");\n }\n });\n}\nfunction validateId(id) {\n if (!id) {\n throw new Error(\"id is required\");\n }\n if ([\"string\", \"number\"].indexOf(typeof id) === -1) {\n throw new Error(\"id must be a number or a string\");\n }\n}\nexport {\n areaFactors,\n azimuthToBearing,\n bearingToAzimuth,\n convertArea,\n convertLength,\n degreesToRadians,\n earthRadius,\n factors,\n feature,\n featureCollection,\n geometry,\n geometryCollection,\n isNumber,\n isObject,\n lengthToDegrees,\n lengthToRadians,\n lineString,\n lineStrings,\n multiLineString,\n multiPoint,\n multiPolygon,\n point,\n points,\n polygon,\n polygons,\n radiansToDegrees,\n radiansToLength,\n removeBbox,\n round,\n validateBBox,\n validateId\n};\n//# sourceMappingURL=index.js.map","// index.ts\nimport { isNumber } from \"@turf/helpers\";\nfunction getCoord(coord) {\n if (!coord) {\n throw new Error(\"coord is required\");\n }\n if (!Array.isArray(coord)) {\n if (coord.type === \"Feature\" && coord.geometry !== null && coord.geometry.type === \"Point\") {\n return [...coord.geometry.coordinates];\n }\n if (coord.type === \"Point\") {\n return [...coord.coordinates];\n }\n }\n if (Array.isArray(coord) && coord.length >= 2 && !Array.isArray(coord[0]) && !Array.isArray(coord[1])) {\n return [...coord];\n }\n throw new Error(\"coord must be GeoJSON Point or an Array of numbers\");\n}\nfunction getCoords(coords) {\n if (Array.isArray(coords)) {\n return coords;\n }\n if (coords.type === \"Feature\") {\n if (coords.geometry !== null) {\n return coords.geometry.coordinates;\n }\n } else {\n if (coords.coordinates) {\n return coords.coordinates;\n }\n }\n throw new Error(\n \"coords must be GeoJSON Feature, Geometry Object or an Array\"\n );\n}\nfunction containsNumber(coordinates) {\n if (coordinates.length > 1 && isNumber(coordinates[0]) && isNumber(coordinates[1])) {\n return true;\n }\n if (Array.isArray(coordinates[0]) && coordinates[0].length) {\n return containsNumber(coordinates[0]);\n }\n throw new Error(\"coordinates must only contain numbers\");\n}\nfunction geojsonType(value, type, name) {\n if (!type || !name) {\n throw new Error(\"type and name required\");\n }\n if (!value || value.type !== type) {\n throw new Error(\n \"Invalid input to \" + name + \": must be a \" + type + \", given \" + value.type\n );\n }\n}\nfunction featureOf(feature, type, name) {\n if (!feature) {\n throw new Error(\"No feature passed\");\n }\n if (!name) {\n throw new Error(\".featureOf() requires a name\");\n }\n if (!feature || feature.type !== \"Feature\" || !feature.geometry) {\n throw new Error(\n \"Invalid input to \" + name + \", Feature with geometry required\"\n );\n }\n if (!feature.geometry || feature.geometry.type !== type) {\n throw new Error(\n \"Invalid input to \" + name + \": must be a \" + type + \", given \" + feature.geometry.type\n );\n }\n}\nfunction collectionOf(featureCollection, type, name) {\n if (!featureCollection) {\n throw new Error(\"No featureCollection passed\");\n }\n if (!name) {\n throw new Error(\".collectionOf() requires a name\");\n }\n if (!featureCollection || featureCollection.type !== \"FeatureCollection\") {\n throw new Error(\n \"Invalid input to \" + name + \", FeatureCollection required\"\n );\n }\n for (const feature of featureCollection.features) {\n if (!feature || feature.type !== \"Feature\" || !feature.geometry) {\n throw new Error(\n \"Invalid input to \" + name + \", Feature with geometry required\"\n );\n }\n if (!feature.geometry || feature.geometry.type !== type) {\n throw new Error(\n \"Invalid input to \" + name + \": must be a \" + type + \", given \" + feature.geometry.type\n );\n }\n }\n}\nfunction getGeom(geojson) {\n if (geojson.type === \"Feature\") {\n return geojson.geometry;\n }\n return geojson;\n}\nfunction getType(geojson, _name) {\n if (geojson.type === \"FeatureCollection\") {\n return \"FeatureCollection\";\n }\n if (geojson.type === \"GeometryCollection\") {\n return \"GeometryCollection\";\n }\n if (geojson.type === \"Feature\" && geojson.geometry !== null) {\n return geojson.geometry.type;\n }\n return geojson.type;\n}\nexport {\n collectionOf,\n containsNumber,\n featureOf,\n geojsonType,\n getCoord,\n getCoords,\n getGeom,\n getType\n};\n//# sourceMappingURL=index.js.map","// index.ts\nimport { degreesToRadians, radiansToDegrees } from \"@turf/helpers\";\nimport { getCoord } from \"@turf/invariant\";\nfunction bearing(start, end, options = {}) {\n if (options.final === true) {\n return calculateFinalBearing(start, end);\n }\n const coordinates1 = getCoord(start);\n const coordinates2 = getCoord(end);\n const lon1 = degreesToRadians(coordinates1[0]);\n const lon2 = degreesToRadians(coordinates2[0]);\n const lat1 = degreesToRadians(coordinates1[1]);\n const lat2 = degreesToRadians(coordinates2[1]);\n const a = Math.sin(lon2 - lon1) * Math.cos(lat2);\n const b = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);\n return radiansToDegrees(Math.atan2(a, b));\n}\nfunction calculateFinalBearing(start, end) {\n let bear = bearing(end, start);\n bear = (bear + 180) % 360;\n return bear > 180 ? bear - 360 : bear;\n}\nvar index_default = bearing;\nexport {\n bearing,\n index_default as default\n};\n//# sourceMappingURL=index.js.map","// index.ts\nimport {\n degreesToRadians,\n lengthToRadians,\n point,\n radiansToDegrees\n} from \"@turf/helpers\";\nimport { getCoord } from \"@turf/invariant\";\nfunction destination(origin, distance, bearing, options = {}) {\n const coordinates1 = getCoord(origin);\n const longitude1 = degreesToRadians(coordinates1[0]);\n const latitude1 = degreesToRadians(coordinates1[1]);\n const bearingRad = degreesToRadians(bearing);\n const radians = lengthToRadians(distance, options.units);\n const latitude2 = Math.asin(\n Math.sin(latitude1) * Math.cos(radians) + Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearingRad)\n );\n const longitude2 = longitude1 + Math.atan2(\n Math.sin(bearingRad) * Math.sin(radians) * Math.cos(latitude1),\n Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2)\n );\n const lng = radiansToDegrees(longitude2);\n const lat = radiansToDegrees(latitude2);\n if (coordinates1[2] !== void 0) {\n return point([lng, lat, coordinates1[2]], options.properties);\n }\n return point([lng, lat], options.properties);\n}\nvar index_default = destination;\nexport {\n index_default as default,\n destination\n};\n//# sourceMappingURL=index.js.map","/**\n * The arithmetic the camera runs on: headings, and the spring that carries every value it eases.\n *\n * @module\n * @ignore\n */\nimport { bearing, bearingToAzimuth } from '@turf/turf';\nimport type { Position } from 'geojson';\n\n/**\n * Initial bearing from one coordinate to the other, in degrees clockwise from north within 0..360 —\n * the direction of travel, which is where the camera and the marker face.\n * @ignore\n */\nexport const headingBetween = (from: Position, to: Position): number => bearingToAzimuth(bearing(from, to));\n\n/**\n * The shorter way round between two bearings, so a flight crossing north turns by a few degrees\n * rather than the 350 the raw difference reports.\n * @ignore\n */\nexport const shortestTurn = (from: number, to: number): number => ((to - from + 540) % 360) - 180;\n\n/**\n * The heading to steer for: the road's, once it has moved off the one already being steered for by\n * more than `deadbandDegrees` — and otherwise the one already being steered for, unchanged.\n *\n * A route's vertices are metres apart, so the direction to a point a few hundred metres ahead\n * wanders by a degree or two as the flight crosses them. That is the line's sampling rather than\n * the road's shape, and a camera that answers it is a camera that never quite settles.\n * @ignore\n */\nexport const steadyBearing = (steering: number, road: number, deadbandDegrees: number): number =>\n Math.abs(shortestTurn(steering, road)) > deadbandDegrees ? road : steering;\n\n/** `value` held within `min`..`max`. @ignore */\nexport const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max);\n\n/**\n * A value in motion: where it is, and how fast it is getting there, in its own unit per second.\n * @ignore\n */\nexport type Spring = { value: number; velocity: number };\n\n/** @ignore */\nexport const springAt = (value: number): Spring => ({ value, velocity: 0 });\n\n// The spring's rate against the time it is given to settle: at this one, `settleSeconds` closes\n// 91% of a step, and twice that 99.7%.\nconst SETTLE_RATE = 4;\n\n// How close to its target, and how slow, a spring has to be to count as there. It only ever\n// approaches its target, and a value that changes by amounts nothing can show still costs the map\n// a new camera every frame.\nconst AT_REST = 1e-6;\n\n/**\n * `spring` carried `elapsedSeconds` towards `target`, critically damped: no overshoot, and a new\n * target bends the motion instead of kicking its velocity. Solved exactly, so for a target that\n * holds still, two half steps land where one whole one does, at any frame rate.\n *\n * `maxVelocity` caps the chase: the target is held no further off than the spring closes at that speed.\n * @ignore\n */\nexport const springTowards = (\n spring: Spring,\n target: number,\n elapsedSeconds: number,\n settleSeconds: number,\n maxVelocity = Number.POSITIVE_INFINITY,\n): Spring => {\n const rate = SETTLE_RATE / settleSeconds;\n const reach = (2 * maxVelocity) / rate;\n const offset = clamp(spring.value - target, -reach, reach);\n if (Math.abs(offset) < AT_REST && Math.abs(spring.velocity) < AT_REST) return springAt(target);\n\n const drift = (spring.velocity + rate * offset) * elapsedSeconds;\n const decay = Math.exp(-rate * elapsedSeconds);\n return {\n value: spring.value - offset + (offset + drift) * decay,\n velocity: (spring.velocity - rate * drift) * decay,\n };\n};\n\n/**\n * {@link springTowards} for a bearing: it takes the short way round and stays within 0..360.\n * @ignore\n */\nexport const springTowardsBearing = (\n spring: Spring,\n target: number,\n elapsedSeconds: number,\n settleSeconds: number,\n maxVelocity?: number,\n): Spring => {\n const next = springTowards(\n spring,\n spring.value + shortestTurn(spring.value, target),\n elapsedSeconds,\n settleSeconds,\n maxVelocity,\n );\n return { value: (next.value + 360) % 360, velocity: next.velocity };\n};\n","/**\n * Where the followed position sits on screen, and the zooms that fit a stretch of road into the\n * frame or lift the camera to a given height.\n *\n * @module\n */\nimport { degreesToRadians } from '@turf/turf';\nimport type { PaddingOptions } from 'maplibre-gl';\nimport { clamp } from './geodesy';\n\n/**\n * Where the position being followed sits in the view, as fractions of the viewport: `{ x: 0.5, y:\n * 0.5 }` is the middle of the screen, `y: 0.67` two thirds of the way down.\n *\n * @group Flyover\n */\nexport type FlyoverAnchor = { x: number; y: number };\n\n// How far down the frame a fully tilted camera puts the position it follows. Two thirds: the road\n// under the camera belongs in the lower third of a picture that is otherwise all of what lies\n// ahead, which is where a driver's own view of it sits.\nconst TILTED_ANCHOR_Y = 2 / 3;\n\n// The pitch at which the anchor has slid all the way to `TILTED_ANCHOR_Y`; beyond it, no further.\nconst FULLY_TILTED_DEGREES = 60;\n\n// How near the edge of the frame an anchor may put the position. A padding as deep as the viewport\n// leaves the camera nothing to centre in, and the projection stops meaning anything before that.\nconst ANCHOR_LIMIT = 0.05;\n\nconst clampFraction = (value: number): number => clamp(value, ANCHOR_LIMIT, 1 - ANCHOR_LIMIT);\n\n/**\n * Where a camera at this pitch puts the position it follows, absent an anchor of your own.\n *\n * A level map is a plan, so the position belongs in the middle. Tilt the camera and the picture\n * becomes mostly what lies *ahead*, so the position slides down with the pitch, reaching the lower\n * third at 60° — and a top-down pass stays centred without switching modes.\n *\n * @group Flyover\n */\nexport const anchorForPitch = (pitchDegrees: number): FlyoverAnchor => {\n const tilt = clamp(pitchDegrees, 0, FULLY_TILTED_DEGREES) / FULLY_TILTED_DEGREES;\n return { x: 0.5, y: 0.5 + (TILTED_ANCHOR_Y - 0.5) * tilt };\n};\n\n/**\n * The viewport padding that lands the followed position on `anchor`.\n *\n * MapLibre centres in the rectangle the padding leaves, so an anchor is padding on the side it sits\n * away from: two thirds of the way down a 600px view is 200px off the top. One side only, since\n * padding both would shrink the rectangle and take the zoom with it.\n * @ignore\n */\nexport const anchorPadding = (\n anchor: FlyoverAnchor,\n viewport: { width: number; height: number },\n): Required<PaddingOptions> => {\n const fromCentreX = clampFraction(anchor.x) - 0.5;\n const fromCentreY = clampFraction(anchor.y) - 0.5;\n // Not rounded to whole pixels: an anchor eased from one place to another would move the whole\n // view in one-pixel steps.\n const pad = (fraction: number, extent: number) => Math.max(2 * fraction * extent, 0);\n return {\n left: pad(fromCentreX, viewport.width),\n right: pad(-fromCentreX, viewport.width),\n top: pad(fromCentreY, viewport.height),\n bottom: pad(-fromCentreY, viewport.height),\n };\n};\n\n// MapLibre's world at zoom 0, in pixels: one 512-pixel tile.\nconst WORLD_PIXELS_AT_ZOOM_0 = 512;\n\n// Metres per pixel at zoom 0 on the equator: the Earth's circumference over that world, which is\n// what turns a ground distance into a zoom and back.\nconst EQUATOR_METERS_PER_PIXEL = 40_075_016.686 / WORLD_PIXELS_AT_ZOOM_0;\n\n/**\n * How much of MapLibre's mercator square — 1 unit across the world — a CSS pixel spans at `zoom`.\n * @ignore\n */\nexport const mercatorUnitsPerPixel = (zoom: number): number => 1 / (WORLD_PIXELS_AT_ZOOM_0 * 2 ** zoom);\n\nconst metersPerPixel = (zoom: number, latitudeDegrees: number): number =>\n (EQUATOR_METERS_PER_PIXEL * Math.cos(degreesToRadians(latitudeDegrees))) / 2 ** zoom;\n\nconst zoomForMetersPerPixel = (meters: number, latitudeDegrees: number): number =>\n Math.log2((EQUATOR_METERS_PER_PIXEL * Math.cos(degreesToRadians(latitudeDegrees))) / meters);\n\n/**\n * The zoom at which `distanceInMeters` of ground spans `pixels` of screen at `latitudeDegrees`.\n * @ignore\n */\nexport const zoomFitting = (distanceInMeters: number, pixels: number, latitudeDegrees: number): number =>\n zoomForMetersPerPixel(Math.max(distanceInMeters, 1) / Math.max(pixels, 1), latitudeDegrees);\n\n/**\n * What decides how far the camera is from the point it looks at, besides the zoom: MapLibre sets\n * that distance from the viewport's height and vertical field of view.\n * @ignore\n */\nexport type CameraLens = { latitudeDegrees: number; viewportHeight: number; fieldOfViewDegrees: number };\n\n// The camera's distance from the centre, in pixels: MapLibre's `cameraToCenterDistance`.\nconst cameraDistanceInPixels = ({ viewportHeight, fieldOfViewDegrees }: CameraLens): number =>\n (0.5 * viewportHeight) / Math.tan(degreesToRadians(fieldOfViewDegrees) / 2);\n\n/**\n * How far the camera is from the point it looks at, in metres, at `zoom`.\n * @ignore\n */\nexport const cameraDistanceInMeters = (zoom: number, lens: CameraLens): number =>\n cameraDistanceInPixels(lens) * metersPerPixel(zoom, lens.latitudeDegrees);\n\n/**\n * The zoom that puts the camera `distanceInMeters` from the point it looks at.\n * @ignore\n */\nexport const zoomForCameraDistance = (distanceInMeters: number, lens: CameraLens): number =>\n zoomForMetersPerPixel(Math.max(distanceInMeters, 1) / cameraDistanceInPixels(lens), lens.latitudeDegrees);\n","/**\n * What rides the route at the followed position: a MapLibre custom layer drawing one textured quad.\n *\n * - **In the map's own frame**, so map effects — depth of field included — and captures take it along.\n * - **Placed as each frame is drawn**, on the terrain under its route point. A symbol layer's source\n * goes through the worker and lands a frame or more behind the camera, which reads as wobble.\n *\n * @module\n */\nimport { generateId } from '@tomtom-org/maps-sdk/core';\nimport type { CustomImage, SVGIconStyleOptions, TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport { degreesToRadians } from '@turf/turf';\nimport type { Position } from 'geojson';\nimport { type CustomLayerInterface, type CustomRenderMethodInput, MercatorCoordinate } from 'maplibre-gl';\nimport { mercatorUnitsPerPixel } from './framing';\n\n/**\n * The image that rides the route, drawn pointing up: the flight turns it to face the road.\n *\n * - `arrow` — the plugin's own arrow, recoloured with the SDK's SVG icon options. Its fill defaults\n * to `#105287`, the SDK's default route outline colour, not a theme's accent, and its outline to white.\n * - `custom` — yours: a URL, raw SVG markup, a data URI or a loaded `<img>`, as for any SDK custom\n * image. A URL on another origin needs CORS, since the image becomes a WebGL texture.\n *\n * @group Flyover\n */\nexport type FlyoverMarkerImage =\n | ({ type: 'arrow' } & SVGIconStyleOptions)\n | { type: 'custom'; image: NonNullable<CustomImage['image']> };\n\n/**\n * The marker that rides the route at the followed position, lying on the map and facing the road.\n *\n * @group Flyover\n */\nexport type FlyoverMarkerConfig = {\n /** @default true */\n visible?: boolean;\n /** @default `{ type: 'arrow' }` */\n image?: FlyoverMarkerImage;\n /** Width and height on screen, in pixels, before the pitch foreshortens it. @default 44 */\n size?: number;\n};\n\n// Lying on the map, the marker is foreshortened by the pitch: this is half as tall at 60°.\nconst DEFAULT_SIZE = 44;\n\n// The texture's side: a power of two, so it can be mipmapped — a quad seen at a steep pitch is\n// squeezed several times over, and without mipmaps it shimmers — and no more than a display needs.\nconst textureSize = (sizeInPixels: number): number =>\n Math.min(2 ** Math.ceil(Math.log2(Math.max(sizeInPixels * devicePixelRatio, 1))), 512);\n\nconst DEFAULT_ARROW_COLOR = '#105287';\n\nconst SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\nconst svgElement = <K extends 'svg' | 'path'>(name: K, attributes: Record<string, string>) => {\n const element = document.createElementNS(SVG_NAMESPACE, name);\n for (const [attribute, value] of Object.entries(attributes)) element.setAttribute(attribute, value);\n return element;\n};\n\nconst svgDataUri = (markup: string): string => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;\n\n// One path, pointing up in a 24-unit box: its stroke is the outline, painted under the fill, so\n// only the outer half of it shows.\nconst arrowMarkup = (\n { fillColor = DEFAULT_ARROW_COLOR, outlineColor = 'white', outlineOpacity = 1 }: SVGIconStyleOptions,\n side: number,\n): string => {\n const svg = svgElement('svg', { viewBox: '0 0 24 24', width: String(side), height: String(side) });\n svg.append(\n svgElement('path', {\n d: 'M12 3l8 18-8-4-8 4z',\n fill: fillColor,\n stroke: outlineColor,\n 'stroke-opacity': String(outlineOpacity),\n 'stroke-width': '3',\n 'stroke-linejoin': 'round',\n 'paint-order': 'stroke',\n }),\n );\n return new XMLSerializer().serializeToString(svg);\n};\n\n/**\n * The image to rasterise: the arrow drawn at `side` pixels, or yours. Raw SVG markup is told apart\n * from a URL the way the SDK's own custom images are.\n * @ignore\n */\nexport const imageElement = (image: FlyoverMarkerImage, side: number): HTMLImageElement => {\n const source = image.type === 'arrow' ? svgDataUri(arrowMarkup(image, side)) : image.image;\n if (typeof source !== 'string') return source;\n\n const element = new Image();\n element.crossOrigin = 'anonymous';\n element.src = source.includes('<svg') ? svgDataUri(source) : source;\n return element;\n};\n\n// The image drawn into a square of `side` pixels, centred and scaled to fit: the texture's source.\nconst rasterise = async (image: FlyoverMarkerImage, side: number): Promise<HTMLCanvasElement> => {\n const element = imageElement(image, side);\n await element.decode();\n const canvas = document.createElement('canvas');\n canvas.width = side;\n canvas.height = side;\n const scale = side / Math.max(element.naturalWidth, element.naturalHeight, 1);\n const [width, height] = [element.naturalWidth * scale, element.naturalHeight * scale];\n canvas.getContext('2d')?.drawImage(element, (side - width) / 2, (side - height) / 2, width, height);\n return canvas;\n};\n\n// A quad of corners at ±1, spanned by the two axes the flight turns to face the road.\nconst 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}`;\n\nconst FRAGMENT_SHADER = `\nprecision mediump float;\nuniform sampler2D u_image;\nvarying vec2 v_uv;\nvoid main() {\n gl_FragColor = texture2D(u_image, v_uv);\n}`;\n\ntype GL = WebGLRenderingContext | WebGL2RenderingContext;\n\ntype Drawing = {\n program: WebGLProgram;\n corners: WebGLBuffer;\n texture: WebGLTexture | undefined;\n uniforms: Record<'matrix' | 'right' | 'forward' | 'image', WebGLUniformLocation | null>;\n corner: number;\n};\n\nconst compile = (gl: GL, type: number, source: string): WebGLShader => {\n const shader = gl.createShader(type);\n if (!shader) throw new Error('The fly-over marker could not create a shader');\n\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n return shader;\n};\n\nconst createDrawing = (gl: GL): Drawing => {\n const program = gl.createProgram();\n const corners = gl.createBuffer();\n if (!program || !corners) throw new Error('The fly-over marker could not create its program');\n\n gl.attachShader(program, compile(gl, gl.VERTEX_SHADER, VERTEX_SHADER));\n gl.attachShader(program, compile(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER));\n gl.linkProgram(program);\n gl.bindBuffer(gl.ARRAY_BUFFER, corners);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n return {\n program,\n corners,\n texture: undefined,\n uniforms: {\n matrix: gl.getUniformLocation(program, 'u_matrix'),\n right: gl.getUniformLocation(program, 'u_right'),\n forward: gl.getUniformLocation(program, 'u_forward'),\n image: gl.getUniformLocation(program, 'u_image'),\n },\n corner: gl.getAttribLocation(program, 'a_corner'),\n };\n};\n\nconst uploadTexture = (gl: GL, raster: HTMLCanvasElement): WebGLTexture | undefined => {\n const texture = gl.createTexture() ?? undefined;\n gl.bindTexture(gl.TEXTURE_2D, texture ?? null);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, raster);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n gl.generateMipmap(gl.TEXTURE_2D);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n return texture;\n};\n\n// The map's matrix with the marker's centre folded in, in double precision: the quad's own corners\n// are then a few millionths of the world across, which single precision holds exactly where a\n// centre near 0.5 would not.\nconst matrixAt = (projection: ArrayLike<number>, { x, y, z }: MercatorCoordinate): Float32Array => {\n const matrix = Float32Array.from(projection);\n for (let row = 0; row < 4; row++) {\n matrix[12 + row] =\n projection[row] * x + projection[4 + row] * y + projection[8 + row] * z + projection[12 + row];\n }\n return matrix;\n};\n\n/**\n * The marker's layer, which re-adds itself after a style change and remembers where it was last\n * put so a new image lands in its place.\n * @ignore\n */\nexport class PositionMarker implements CustomLayerInterface {\n readonly id = `tomtom-flyover-marker-${generateId()}`;\n readonly type = 'custom';\n readonly renderingMode = '2d';\n\n private config: FlyoverMarkerConfig = {};\n private placed: { position: Position; heading: number } | undefined;\n private raster: HTMLCanvasElement | undefined;\n private gl: GL | undefined;\n private drawing: Drawing | undefined;\n private removed = false;\n private readonly stopRestoring: () => void;\n\n constructor(\n private readonly map: TomTomMap,\n config: FlyoverMarkerConfig,\n ) {\n this.stopRestoring = map.addStyleChangeHandler({ onStyleChanged: () => this.install() });\n this.installWhenReady();\n this.apply(config);\n }\n\n apply(config: FlyoverMarkerConfig): void {\n this.config = config;\n const size = config.size ?? DEFAULT_SIZE;\n rasterise(config.image ?? { type: 'arrow' }, textureSize(size))\n .then((raster) => {\n // A later image may have been asked for while this one loaded.\n if (this.config !== config) return;\n\n this.raster = raster;\n this.releaseTexture();\n this.map.mapLibreMap.triggerRepaint();\n })\n .catch((error) => console.warn('The fly-over marker image could not be loaded', error));\n this.map.mapLibreMap.triggerRepaint();\n }\n\n /** Puts the marker at a position on the route, facing `heading`. */\n place(position: Position, heading: number): void {\n this.placed = { position, heading };\n this.map.mapLibreMap.triggerRepaint();\n }\n\n remove(): void {\n this.removed = true;\n this.stopRestoring();\n if (this.map.mapLibreMap.getLayer(this.id)) this.map.mapLibreMap.removeLayer(this.id);\n }\n\n onAdd(_map: unknown, gl: GL): void {\n this.gl = gl;\n this.drawing = createDrawing(gl);\n }\n\n onRemove(_map: unknown, gl: GL): void {\n this.releaseTexture();\n if (this.drawing) {\n gl.deleteProgram(this.drawing.program);\n gl.deleteBuffer(this.drawing.corners);\n }\n this.drawing = undefined;\n this.gl = undefined;\n }\n\n render(gl: GL, options: CustomRenderMethodInput): void {\n const { drawing, placed, raster } = this;\n if (!drawing || !placed || !raster || this.config.visible === false) return;\n\n drawing.texture ??= uploadTexture(gl, raster);\n const mapLibreMap = this.map.mapLibreMap;\n const [longitude, latitude] = placed.position;\n // The ground under the route point, read as the frame is drawn: whatever the camera is\n // doing, the marker is on the road.\n const ground = mapLibreMap.queryTerrainElevation([longitude, latitude]) ?? 0;\n const centre = MercatorCoordinate.fromLngLat([longitude, latitude], ground);\n const half = ((this.config.size ?? DEFAULT_SIZE) / 2) * mercatorUnitsPerPixel(mapLibreMap.getZoom());\n // Mercator y runs south, so the road's heading points up the square at -cos.\n const heading = degreesToRadians(placed.heading);\n const [sine, cosine] = [Math.sin(heading) * half, Math.cos(heading) * half];\n\n gl.useProgram(drawing.program);\n gl.uniformMatrix4fv(drawing.uniforms.matrix, false, matrixAt(options.defaultProjectionData.mainMatrix, centre));\n gl.uniform2f(drawing.uniforms.right, cosine, sine);\n gl.uniform2f(drawing.uniforms.forward, sine, -cosine);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, drawing.texture ?? null);\n gl.uniform1i(drawing.uniforms.image, 0);\n gl.bindBuffer(gl.ARRAY_BUFFER, drawing.corners);\n gl.enableVertexAttribArray(drawing.corner);\n gl.vertexAttribPointer(drawing.corner, 2, gl.FLOAT, false, 0, 0);\n // Always on top of the ground it lies on, and blended as the premultiplied texture it is.\n gl.disable(gl.DEPTH_TEST);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n\n private releaseTexture(): void {\n if (this.drawing?.texture) this.gl?.deleteTexture(this.drawing.texture);\n if (this.drawing) this.drawing.texture = undefined;\n }\n\n private installWhenReady(): void {\n if (this.map.mapReady) {\n this.install();\n } else {\n this.map.mapLibreMap.once('styledata', () => this.installWhenReady());\n }\n }\n\n private install(): void {\n if (!this.removed && !this.map.mapLibreMap.getLayer(this.id)) this.map.mapLibreMap.addLayer(this);\n }\n}\n","import type { Route } from '@tomtom-org/maps-sdk/core';\nimport { calculateProgressAtRoutePoint, getCoordinateAtRouteProgress } from '@tomtom-org/maps-sdk/core';\nimport type { TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport { degreesToRadians, destination } from '@turf/turf';\nimport type { Position } from 'geojson';\nimport type { PaddingOptions } from 'maplibre-gl';\nimport {\n anchorForPitch,\n anchorPadding,\n type CameraLens,\n cameraDistanceInMeters,\n type FlyoverAnchor,\n zoomFitting,\n zoomForCameraDistance,\n} from './framing';\nimport {\n clamp,\n headingBetween,\n type Spring,\n springAt,\n springTowards,\n springTowardsBearing,\n steadyBearing,\n} from './geodesy';\nimport { type FlyoverMarkerConfig, PositionMarker } from './marker';\n\n/**\n * What the camera points at, which is what decides how early it turns into a bend.\n *\n * - `distance` — a fixed distance down the road. Predictable, and blind to what the road does.\n * - `points` — a number of the route's own vertices. The line is denser through bends, so the aim\n * shortens exactly where the road is turning.\n * - `instruction` — the next guidance instruction, so the camera looks at the junction it is\n * heading for. Needs a route asked for with `guidance`; without instructions it aims by distance.\n *\n * @group Flyover\n */\nexport type FlyoverAim =\n | { mode: 'distance'; metersAhead: number }\n | { mode: 'points'; pointsAhead: number }\n | { mode: 'instruction'; fallbackMetersAhead: number };\n\n/**\n * How high the camera rides. Whatever the mode, it pulls back further where terrain would otherwise\n * come between the camera and the road.\n *\n * - `fixed` — one MapLibre zoom, whatever the road does.\n * - `instructions` — framed on the junctions ahead: the zoom that fits the next N instructions (one\n * at least) in the view, so a run of hairpins pulls back and a straight closes in.\n * - `speed` — tied to the ground speed, the way a driver's eyes lift with it.\n *\n * @group Flyover\n */\nexport type FlyoverZoom =\n | { mode: 'fixed'; zoom: number }\n | { mode: 'instructions'; instructionsAhead: number }\n | { mode: 'speed' };\n\n/**\n * How the camera flies.\n *\n * @group Flyover\n */\nexport type FlyoverCamera = {\n /** Ground speed of the camera, in kilometres per hour. @default 200 */\n speedKMH: number;\n /** Camera tilt, in degrees; the map's `maxPitch` is the ceiling. @default 70 */\n pitch: number;\n /** What the camera aims at down the route. @default `{ mode: 'instruction', fallbackMetersAhead: 250 }` */\n aim: FlyoverAim;\n /** What sets the camera's height. @default `{ mode: 'instructions', instructionsAhead: 2 }` */\n zoom: FlyoverZoom;\n /**\n * Where the followed position sits on screen. Omitted, it follows the pitch — see\n * {@link anchorForPitch}.\n */\n anchor?: FlyoverAnchor;\n};\n\n/**\n * How far along the route the flight has got, reported as it advances.\n *\n * @group Flyover\n */\nexport type FlyoverProgress = {\n traveledDistanceInMeters: number;\n traveledTimeInSeconds: number;\n};\n\n/**\n * @group Flyover\n */\nexport type RouteFlyoverOptions = {\n /** The route to fly. Replace it later with {@link RouteFlyover.setRoute}. */\n route: Route;\n /** Overrides for the camera; anything left out takes the default on {@link FlyoverCamera}. */\n camera?: Partial<FlyoverCamera>;\n /** The marker riding the route at the followed position; an arrow by default. */\n marker?: FlyoverMarkerConfig;\n /** Called with the flight's progress, once per survey rather than once per frame. */\n onProgress?: (progress: FlyoverProgress) => void;\n};\n\nconst DEFAULT_CAMERA: FlyoverCamera = {\n speedKMH: 200,\n pitch: 70,\n aim: { mode: 'instruction', fallbackMetersAhead: 250 },\n zoom: { mode: 'instructions', instructionsAhead: 2 },\n};\n\n/**\n * How long each eased value takes to settle onto a new target, in seconds — see\n * {@link springTowards}. The altitude is the quickest, so what the camera looks at stays on the road.\n */\nconst SETTLE_SECONDS = {\n speed: 2,\n bearing: 2,\n zoom: 3,\n pitch: 1.5,\n anchor: 1.5,\n elevation: 0.6,\n markerHeading: 0.5,\n} as const;\n\n/**\n * How far the road's direction has to move before the camera adopts a new heading, in degrees (see\n * {@link steadyBearing}), and the fastest it may then turn, in degrees per second. The rate cap\n * covers a genuine change of target, such as the next junction becoming the current one.\n */\nconst BEARING_DEADBAND_DEGREES = 2.5;\nconst MAX_TURN_DEGREES_PER_SECOND = 22;\n\n// How much the framing has to want before the zoom follows it. A junction the camera is closing on\n// asks for a little more zoom every survey, and a map that re-tiles for every tenth of a level\n// spends the flight loading tiles it is about to leave.\nconst ZOOM_DEADBAND = 0.2;\n\n/**\n * How often the camera works out *where it is going*, in seconds — as opposed to where it is,\n * which it reads every frame.\n *\n * The route lookups behind the aim, the framing, the viewport and the terrain clearance all live on\n * this tick: their answers feed targets the camera eases toward anyway, and a target recomputed\n * five times a second cannot jitter at sixty.\n */\nconst SURVEY_INTERVAL_SECONDS = 0.2;\n\n/**\n * The longest frame the flight will act on: a backgrounded tab hands back one frame worth many\n * seconds, and without this the camera teleports down the route on return.\n *\n * Loose enough not to punish a slow renderer, which is the other thing a long frame means. Any\n * frame over the cap advances the flight by less than its own time, so a clamp at a tenth of a\n * second would fly a machine rendering at 5fps down the route at half speed.\n */\nconst MAX_FRAME_SECONDS = 0.25;\n\n// How high the camera keeps above the ground under it. Along its line of sight the margin narrows\n// to nothing at the road, which is where the camera is looking.\nconst CLEARANCE_METERS = 200;\n// Samples of the ground from under the camera towards the road, the road itself excluded.\nconst CLEARANCE_SAMPLES = 4;\n// The steepest pitch the camera's geometry is worked out for: towards 90° the camera needs an\n// unbounded distance to gain any height, and sees the road ahead foreshortened to nothing.\nconst MAX_WORKED_PITCH_DEGREES = 85;\n\n// Either side of the followed position, the stretch of road the marker takes its heading from:\n// long enough to span a vertex or two, short enough to still be the road under it.\nconst HEADING_SPAN_METERS = 15;\n\n// The zooms the automatic modes may choose between: closer than this the road fills the screen,\n// further out it stops reading as a flight.\nconst MIN_ZOOM = 11;\nconst MAX_ZOOM = 15;\n\n// What the framing modes fit their target into: a share of the viewport, since a junction pinned\n// to the very edge of the screen is one the camera is already passing.\nconst FRAMED_SHARE_OF_VIEWPORT = 0.6;\n\n// The speed the `speed` mode holds `MAX_ZOOM - 1` at, and how far it pulls back per doubling of it:\n// twice the speed needs about twice the road in shot, which is one zoom level.\nconst SPEED_REFERENCE_KMH = 90;\nconst ZOOM_PER_SPEED_DOUBLING = 1;\n\nconst clampZoom = (zoom: number): number => clamp(zoom, MIN_ZOOM, MAX_ZOOM);\n\n/** The values the camera eases, each towards the target the last survey or setting gave it. */\ntype Motion = Record<'bearing' | 'zoom' | 'pitch' | 'anchorX' | 'anchorY' | 'elevation' | 'markerHeading', Spring>;\n\n/**\n * A camera that flies a route on a loop: it advances a distance along the line every frame, reads\n * the coordinate the route has at that distance, and points itself at where it will be next.\n *\n * @remarks\n * The route is the only geometry it needs — no path of camera keyframes to author, and no\n * animation to re-time when the route changes. What it adds on top of following the line:\n *\n * - **it aims ahead** rather than at its own position, by distance, by route vertices or at the\n * next guidance instruction, so the view turns into a bend before the camera reaches it;\n * - **it frames** what is coming: the zoom that fits the next junctions, or one tied to the speed;\n * - **it clears the ground**, pulling back where terrain would come between the camera and the road;\n * - **it parks the position where you want it on screen** — see {@link FlyoverAnchor} — and marks\n * it with an arrow, or an image of your own, facing the road;\n * - **it moves like a camera**: every value it changes eases on a critically damped spring, so a new\n * heading, zoom or setting bends the motion rather than kicking it, and a start ramps the speed.\n *\n * While it runs, the flight owns the altitude of the point the camera looks at — the ground under\n * the followed position — so it takes MapLibre off clamping the centre to the ground, and gives\n * that back when it stops.\n *\n * @example\n * ```typescript\n * import { RouteFlyover } from '@tomtom-org/maps-sdk-plugin-flyover';\n *\n * const [route] = (await calculateRoute({ locations, guidance: { type: 'coded' } })).features;\n * const flyover = new RouteFlyover(map, { route, camera: { speedKMH: 300, pitch: 76 } });\n * flyover.start();\n * ```\n *\n * @group Flyover\n */\nexport class RouteFlyover {\n private route: Route;\n private camera: FlyoverCamera;\n private readonly onProgress: (progress: FlyoverProgress) => void;\n private readonly marker: PositionMarker;\n\n private animationFrame: number | undefined;\n private lastFrameTime = 0;\n private traveledDistanceInMeters = 0;\n private speedKMH = springAt(0);\n private motion: Motion;\n // What the last survey decided the camera should become; the flight is the motion closing on it.\n private readonly target: Record<'bearing' | 'zoom' | 'elevation' | 'markerHeading', number>;\n // The zoom the framing mode last asked for, before the terrain has its say.\n private framedZoom: number;\n private secondsSinceSurvey = 0;\n // Read when the map resizes rather than in flight: asking an element for its size mid-flight is\n // a layout the browser has to do before it can answer.\n private viewport = { width: 1, height: 1 };\n // Whether the camera is on the route at all: until it is, the page owns the view.\n private flying = false;\n // The page's own camera settings, handed back when the flight lets go of them.\n private pageCenterClampedToGround = true;\n private pagePadding: PaddingOptions | undefined;\n\n constructor(\n private readonly map: TomTomMap,\n options: RouteFlyoverOptions,\n ) {\n this.route = options.route;\n this.camera = { ...DEFAULT_CAMERA, ...options.camera };\n this.onProgress = options.onProgress ?? (() => {});\n const mapLibreMap = map.mapLibreMap;\n const anchor = this.targetAnchor();\n this.motion = {\n bearing: springAt(mapLibreMap.getBearing()),\n zoom: springAt(mapLibreMap.getZoom()),\n pitch: springAt(mapLibreMap.getPitch()),\n anchorX: springAt(anchor.x),\n anchorY: springAt(anchor.y),\n elevation: springAt(mapLibreMap.getCenterElevation()),\n markerHeading: springAt(0),\n };\n this.target = {\n bearing: this.motion.bearing.value,\n zoom: this.motion.zoom.value,\n elevation: this.motion.elevation.value,\n markerHeading: 0,\n };\n this.framedZoom = this.target.zoom;\n this.readViewport();\n mapLibreMap.on('resize', this.readViewport);\n this.marker = new PositionMarker(map, options.marker ?? {});\n this.placeMarkerOutright();\n }\n\n /** Whether the flight is advancing. */\n get running(): boolean {\n return this.animationFrame !== undefined;\n }\n\n /** Starts, or resumes, the flight, ramping up to its speed. */\n start(): void {\n if (this.running) return;\n\n const mapLibreMap = this.map.mapLibreMap;\n this.pageCenterClampedToGround = mapLibreMap.getCenterClampedToGround();\n mapLibreMap.setCenterClampedToGround(false);\n this.speedKMH = springAt(0);\n this.lastFrameTime = performance.now();\n this.animationFrame = requestAnimationFrame(this.step);\n }\n\n /**\n * Stops the flight where it is; {@link start} resumes from there. The centre clamp goes back to\n * what the map had before {@link start}.\n */\n stop(): void {\n if (this.animationFrame === undefined) return;\n\n cancelAnimationFrame(this.animationFrame);\n this.animationFrame = undefined;\n this.map.mapLibreMap.setCenterClampedToGround(this.pageCenterClampedToGround);\n }\n\n /** Puts a new route under the flight, from its departure point. */\n setRoute(route: Route): void {\n this.route = route;\n this.traveledDistanceInMeters = 0;\n this.flying = false;\n if (this.running) {\n this.placeCamera();\n } else {\n this.placeMarkerOutright();\n }\n }\n\n /** Applies camera settings — speed, pitch, aim, framing, anchor — paused or under way. */\n setCamera(camera: Partial<FlyoverCamera>): void {\n this.camera = { ...this.camera, ...camera };\n // Before the first frame the page still owns the view — that is how a whole route can be\n // shown before anyone flies it — so this only reframes a flight already on the line.\n if (this.flying && !this.running) this.placeCamera();\n }\n\n /** The camera the flight is using, including the defaults it was not given. */\n getCamera(): FlyoverCamera {\n return { ...this.camera };\n }\n\n /** Replaces the marker riding the route, or hides it with `visible: false`. */\n setMarker(marker: FlyoverMarkerConfig): void {\n this.marker.apply(marker);\n }\n\n /** Moves the flight to a point along the route, as a share of its length within 0..1. */\n seek(fraction: number): void {\n this.traveledDistanceInMeters = this.routeLengthInMeters() * clamp(fraction, 0, 1);\n this.placeCamera();\n }\n\n /**\n * Stops the flight and gives the map back: the marker leaves, and the padding returns to what\n * the map had before the flight first placed the camera.\n */\n remove(): void {\n this.stop();\n this.marker.remove();\n const mapLibreMap = this.map.mapLibreMap;\n mapLibreMap.off('resize', this.readViewport);\n if (this.pagePadding) mapLibreMap.setPadding(this.pagePadding);\n }\n\n private readonly readViewport = (): void => {\n const canvas = this.map.mapLibreMap.getCanvas();\n this.viewport = { width: Math.max(canvas.clientWidth, 1), height: Math.max(canvas.clientHeight, 1) };\n };\n\n private routeLengthInMeters(): number {\n return this.route.properties.summary.lengthInMeters;\n }\n\n private readonly step = (frameTime: number): void => {\n // A frame's time is its vsync, which can precede the `performance.now()` that `start` read.\n const elapsedSeconds = clamp((frameTime - this.lastFrameTime) / 1000, 0, MAX_FRAME_SECONDS);\n this.lastFrameTime = frameTime;\n\n const previousSpeedKMH = this.speedKMH.value;\n this.speedKMH = springTowards(this.speedKMH, this.camera.speedKMH, elapsedSeconds, SETTLE_SECONDS.speed);\n const metersPerSecond = (previousSpeedKMH + this.speedKMH.value) / 2 / 3.6;\n // Wrapping rather than stopping is what makes the flight endless: the arrival is also the\n // departure, and a loop over a round trip never lands.\n this.traveledDistanceInMeters = this.wrapped(this.traveledDistanceInMeters + metersPerSecond * elapsedSeconds);\n\n this.placeCamera(elapsedSeconds);\n this.animationFrame = requestAnimationFrame(this.step);\n };\n\n private coordinateAt(traveledDistanceInMeters: number) {\n return getCoordinateAtRouteProgress(this.route, { traveledDistanceInMeters });\n }\n\n // The position on the route a given distance from where the flight currently is, wrapped into\n // the route the way the flight itself wraps.\n private positionAt(offsetInMeters: number): Position | undefined {\n return this.coordinateAt(this.wrapped(this.traveledDistanceInMeters + offsetInMeters))?.position;\n }\n\n private wrapped(distanceInMeters: number): number {\n const routeLength = this.routeLengthInMeters();\n return routeLength > 0 ? ((distanceInMeters % routeLength) + routeLength) % routeLength : 0;\n }\n\n // The direction of the road under the followed position, which the marker faces. Not wrapped:\n // at the departure, the road behind is the departure itself rather than the far end of the loop.\n private roadHeading(): number | undefined {\n const [behind, ahead] = [-HEADING_SPAN_METERS, HEADING_SPAN_METERS].map(\n (offsetInMeters) => this.coordinateAt(this.traveledDistanceInMeters + offsetInMeters)?.position,\n );\n return behind && ahead ? headingBetween(behind, ahead) : undefined;\n }\n\n private instructions() {\n return this.route.properties.guidance?.instructions ?? [];\n }\n\n /**\n * The instruction the flight is heading for — the first one still ahead of it, and the first of\n * all once the loop has passed the last. `undefined` when the route carries no guidance.\n */\n private instructionAhead(skip = 0) {\n const instructions = this.instructions();\n if (!instructions.length) return undefined;\n\n const next = instructions.findIndex(\n (instruction) => instruction.routeOffsetInMeters > this.traveledDistanceInMeters,\n );\n const from = next === -1 ? 0 : next;\n return instructions[Math.min(from + skip, instructions.length - 1)];\n }\n\n // The route vertex the flight is standing on, found by bisecting the progress the route\n // carries per point rather than by measuring the line again.\n private pointIndexHere(): number {\n let low = 0;\n let high = this.route.geometry.coordinates.length - 1;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n const progress = calculateProgressAtRoutePoint(this.route, middle);\n if (progress !== undefined && progress.distanceInMeters < this.traveledDistanceInMeters) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n }\n\n /** Where the camera looks: the point the current {@link FlyoverAim} picks out. */\n private aimPosition(): Position | undefined {\n const aim = this.camera.aim;\n switch (aim.mode) {\n case 'distance':\n return this.positionAt(aim.metersAhead);\n case 'points': {\n const coordinates = this.route.geometry.coordinates;\n // Wrapped, so the aim crosses the loop's seam rather than pinning to the last vertex.\n return coordinates[(this.pointIndexHere() + aim.pointsAhead) % coordinates.length];\n }\n case 'instruction': {\n // Through the instruction's distance along the route rather than its own\n // `maneuverPoint`: the flight already speaks in distances, and the junction the\n // router names sits a few metres off the sampled line.\n const instruction = this.instructionAhead();\n const ahead =\n instruction && this.wrapped(instruction.routeOffsetInMeters - this.traveledDistanceInMeters);\n return ahead === undefined ? this.positionAt(aim.fallbackMetersAhead) : this.positionAt(ahead);\n }\n }\n }\n\n /** The zoom the current {@link FlyoverZoom} asks for at this point of the route. */\n private modeZoom(latitudeDegrees: number): number {\n const zoom = this.camera.zoom;\n if (zoom.mode === 'fixed') return zoom.zoom;\n\n if (zoom.mode === 'speed') {\n const doublings = Math.log2(Math.max(this.camera.speedKMH, 1) / SPEED_REFERENCE_KMH);\n return clampZoom(MAX_ZOOM - 1 - doublings * ZOOM_PER_SPEED_DOUBLING);\n }\n\n // Framed on the junctions ahead: the road still to come before the Nth of them, fitted into\n // the share of the viewport a flight wants it to occupy. A tilted camera sees that road\n // foreshortened, by the cosine of the pitch.\n const target = this.instructionAhead(Math.max(zoom.instructionsAhead, 1) - 1);\n if (!target) return clampZoom(MAX_ZOOM - 1);\n\n const ahead = this.wrapped(target.routeOffsetInMeters - this.traveledDistanceInMeters);\n const foreshortened = ahead * Math.cos(degreesToRadians(Math.min(this.camera.pitch, MAX_WORKED_PITCH_DEGREES)));\n return clampZoom(zoomFitting(foreshortened, this.viewport.height * FRAMED_SHARE_OF_VIEWPORT, latitudeDegrees));\n }\n\n /**\n * The closest zoom at which nothing between the camera and the road stands higher than the line\n * of sight.\n *\n * The camera trails the road along the bearing, so its ground track is sampled from under the\n * camera towards the followed position. A sample a share `t` of the way along needs the camera\n * `(ground − road) / (1 − t)` higher than the road, on top of a clearance that narrows the same\n * way — which puts the camera itself `CLEARANCE_METERS` over the ground beneath it.\n */\n private clearanceZoom(here: Position, road: number, lens: CameraLens): number {\n const pitch = degreesToRadians(Math.min(this.camera.pitch, MAX_WORKED_PITCH_DEGREES));\n const trailingInKilometers = (cameraDistanceInMeters(this.motion.zoom.value, lens) * Math.sin(pitch)) / 1000;\n const behind = this.motion.bearing.value + 180;\n let height = CLEARANCE_METERS;\n for (let sample = 0; sample < CLEARANCE_SAMPLES; sample++) {\n const share = sample / CLEARANCE_SAMPLES;\n const point = destination(here, trailingInKilometers * (1 - share), behind).geometry.coordinates;\n const ground = this.groundAt(point);\n if (ground !== undefined) height = Math.max(height, CLEARANCE_METERS + (ground - road) / (1 - share));\n }\n return zoomForCameraDistance(height / Math.cos(pitch), lens);\n }\n\n // Terrain elevations come back exaggerated, as the rendered mesh is, and `null` wherever the map\n // has no terrain or a tile has yet to load.\n private groundAt([longitude, latitude]: Position): number | undefined {\n return this.map.mapLibreMap.queryTerrainElevation([longitude, latitude]) ?? undefined;\n }\n\n /**\n * Where the camera is going: the heading the road asks for, the zoom the framing and the\n * terrain allow, and the ground the followed position is on.\n *\n * Everything in here costs a walk of the route or a question to the terrain, and none of it is\n * worth asking every frame — see {@link SURVEY_INTERVAL_SECONDS}.\n */\n private survey(here: Position): void {\n const lens: CameraLens = {\n latitudeDegrees: here[1],\n viewportHeight: this.viewport.height,\n fieldOfViewDegrees: this.map.mapLibreMap.getVerticalFieldOfView(),\n };\n\n const ahead = this.aimPosition();\n if (ahead) {\n this.target.bearing = steadyBearing(\n this.target.bearing,\n headingBetween(here, ahead),\n BEARING_DEADBAND_DEGREES,\n );\n }\n this.target.markerHeading = this.roadHeading() ?? this.target.markerHeading;\n\n // The deadband belongs to the framing mode alone, whose target creeps up every survey as\n // the junction it frames comes closer. The other two follow a caller's own figure, and a\n // value someone chose has to land whatever its size.\n const framed = this.modeZoom(lens.latitudeDegrees);\n const deadband = this.camera.zoom.mode === 'instructions' ? ZOOM_DEADBAND : 0;\n if (Math.abs(framed - this.framedZoom) > deadband) this.framedZoom = framed;\n\n const road = this.groundAt(here);\n if (road === undefined) {\n // A map without terrain is flat at sea level; a tile yet to load keeps the last height.\n if (!this.map.mapLibreMap.getTerrain()) this.target.elevation = 0;\n\n this.target.zoom = this.framedZoom;\n } else {\n this.target.elevation = road;\n this.target.zoom = Math.min(this.framedZoom, this.clearanceZoom(here, road, lens));\n }\n }\n\n private targetAnchor(): FlyoverAnchor {\n return this.camera.anchor ?? anchorForPitch(this.camera.pitch);\n }\n\n // A frame's step of every value towards its target, with the turn additionally capped so no\n // second can swing the view further than a passenger's head would.\n private ease(elapsedSeconds: number): void {\n const { motion, target } = this;\n const anchor = this.targetAnchor();\n const towards = (spring: Spring, value: number, settleSeconds: number) =>\n springTowards(spring, value, elapsedSeconds, settleSeconds);\n this.motion = {\n bearing: springTowardsBearing(\n motion.bearing,\n target.bearing,\n elapsedSeconds,\n SETTLE_SECONDS.bearing,\n MAX_TURN_DEGREES_PER_SECOND,\n ),\n markerHeading: springTowardsBearing(\n motion.markerHeading,\n target.markerHeading,\n elapsedSeconds,\n SETTLE_SECONDS.markerHeading,\n ),\n zoom: towards(motion.zoom, target.zoom, SETTLE_SECONDS.zoom),\n elevation: towards(motion.elevation, target.elevation, SETTLE_SECONDS.elevation),\n pitch: towards(motion.pitch, this.camera.pitch, SETTLE_SECONDS.pitch),\n anchorX: towards(motion.anchorX, anchor.x, SETTLE_SECONDS.anchor),\n anchorY: towards(motion.anchorY, anchor.y, SETTLE_SECONDS.anchor),\n };\n }\n\n // Every value put at its target outright, at rest: there is no motion to be smooth about.\n private settle(): void {\n const anchor = this.targetAnchor();\n this.motion = {\n bearing: springAt(this.target.bearing),\n markerHeading: springAt(this.target.markerHeading),\n zoom: springAt(this.target.zoom),\n elevation: springAt(this.target.elevation),\n pitch: springAt(this.camera.pitch),\n anchorX: springAt(anchor.x),\n anchorY: springAt(anchor.y),\n };\n }\n\n // Where the page can see the flight will start, before the camera is on the route.\n private placeMarkerOutright(): void {\n const here = this.positionAt(0);\n this.target.markerHeading = this.roadHeading() ?? this.target.markerHeading;\n this.motion.markerHeading = springAt(this.target.markerHeading);\n if (here) this.marker.place(here, this.target.markerHeading);\n }\n\n /**\n * One frame: where the flight has got to, and a step of the camera towards what the last survey\n * decided. Without `elapsedSeconds` — a seek, a new route, a changed setting — the camera is\n * surveyed and placed at its targets outright.\n */\n private placeCamera(elapsedSeconds?: number): void {\n const here = this.coordinateAt(this.traveledDistanceInMeters);\n if (!here) return;\n\n const smooth = elapsedSeconds !== undefined && this.flying;\n this.secondsSinceSurvey += elapsedSeconds ?? 0;\n const surveying = !smooth || this.secondsSinceSurvey >= SURVEY_INTERVAL_SECONDS;\n if (surveying) {\n this.secondsSinceSurvey = 0;\n this.survey(here.position);\n }\n if (smooth) {\n this.ease(elapsedSeconds);\n } else {\n this.settle();\n }\n this.flying = true;\n\n const [longitude, latitude] = here.position;\n const { bearing, zoom, pitch, elevation, anchorX, anchorY, markerHeading } = this.motion;\n const mapLibreMap = this.map.mapLibreMap;\n this.pagePadding ??= mapLibreMap.getPadding();\n // The centre is the followed position on the ground, so the anchor puts the road itself on\n // screen where it asked — not a point above it.\n mapLibreMap.jumpTo({\n center: [longitude, latitude],\n elevation: elevation.value,\n bearing: bearing.value,\n pitch: pitch.value,\n zoom: zoom.value,\n padding: anchorPadding({ x: anchorX.value, y: anchorY.value }, this.viewport),\n });\n this.marker.place(here.position, markerHeading.value);\n\n if (surveying) {\n this.onProgress({\n traveledDistanceInMeters: here.distanceInMeters,\n traveledTimeInSeconds: here.travelTimeInSeconds,\n });\n }\n }\n}\n"],"ignoreList":[0,1,2,3],"mappings":"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"}