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

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/dist/index.d.ts CHANGED
@@ -27,7 +27,7 @@ export declare const anchorForPitch: (pitchDegrees: number) => FlyoverAnchor;
27
27
  * The image that rides the route, drawn pointing up: the flight turns it to face the road.
28
28
  *
29
29
  * - `arrow` — the plugin's own arrow, recoloured with the SDK's SVG icon options. Its fill defaults
30
- * to the SDK's default route outline colour, not a theme's accent, and its outline to white.
30
+ * to `#105287`, the SDK's default route outline colour, not a theme's accent, and its outline to white.
31
31
  * - `custom` — yours: a URL, raw SVG markup, a data URI or a loaded `<img>`, as for any SDK custom
32
32
  * image. A URL on another origin needs CORS, since the image becomes a WebGL texture.
33
33
  *
package/dist/index.es.js CHANGED
@@ -1,2 +1,2 @@
1
- import{calculateProgressAtRoutePoint as e,generateId as t,getCoordinateAtRouteProgress as r}from"@tomtom-org/maps-sdk/core";import{ROUTE_LINE_OUTLINE_COLOR as i}from"@tomtom-org/maps-sdk/map";import{MercatorCoordinate as a}from"maplibre-gl";var n=6371008.8,o={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*n,in:39.37*n,kilometers:6371.0088,kilometres:6371.0088,km:6371.0088,meters:n,metres:n,m:n,miles:3958.761333810546,mi:3958.761333810546,millimeters:6371008800,millimetres:6371008800,mm:6371008800,nauticalmiles:n/1852,nmi:n/1852,radians:1,rad:1,yards:6967335.223679999,yd:6967335.223679999};function s(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(!c(e[0])||!c(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 h(e){return e%(2*Math.PI)*180/Math.PI}function m(e){return e%360*Math.PI/180}function c(e){return!isNaN(e)&&null!==e&&!Array.isArray(e)}function d(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 u(e,t,r={}){if(!0===r.final)return function(e,t){let r=u(t,e);return r=(r+180)%360,r>180?r-360:r}(e,t);const i=d(e),a=d(t),n=m(i[0]),o=m(a[0]),s=m(i[1]),c=m(a[1]),g=Math.sin(o-n)*Math.cos(c),l=Math.cos(s)*Math.sin(c)-Math.sin(s)*Math.cos(c)*Math.cos(o-n);return h(Math.atan2(g,l))}function g(e,t,r,i={}){const a=d(e),n=m(a[0]),c=m(a[1]),u=m(r),g=function(e,t="kilometers"){const r=o[t];if(!r)throw new Error(t+" units is invalid");return e/r}(t,i.units),l=Math.asin(Math.sin(c)*Math.cos(g)+Math.cos(c)*Math.sin(g)*Math.cos(u)),p=h(n+Math.atan2(Math.sin(u)*Math.sin(g)*Math.cos(c),Math.cos(g)-Math.sin(c)*Math.sin(l))),v=h(l);return void 0!==a[2]?s([p,v,a[2]],i.properties):s([p,v],i.properties)}var l=(e,t)=>function(e){let t=e%360;return t<0&&(t+=360),t}(u(e,t)),p=(e,t)=>(t-e+540)%360-180,v=(e,t,r)=>Math.min(Math.max(e,t),r),M=e=>({value:e,velocity:0}),f=1e-6,y=(e,t,r,i,a=Number.POSITIVE_INFINITY)=>{const n=4/i,o=2*a/n,s=v(e.value-t,-o,o);if(Math.abs(s)<f&&Math.abs(e.velocity)<f)return M(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}},A=(e,t,r,i,a)=>{const n=y(e,e.value+p(e.value,t),r,i,a);return{value:(n.value+360)%360,velocity:n.velocity}},w=e=>v(e,.05,.95),T=e=>({x:.5,y:.5+v(e,0,60)/60*.16666666666666663}),E=(e,t)=>{const r=w(e.x)-.5,i=w(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)}},b=40075016.686/512,x=(e,t)=>Math.log2(b*Math.cos(m(t))/e),_=({viewportHeight:e,fieldOfViewDegrees:t})=>.5*e/Math.tan(m(t)/2),I=(e,t)=>_(t)*((e,t)=>b*Math.cos(m(t))/2**e)(e,t.latitudeDegrees),R=(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},L=e=>`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,P=(e,t)=>{const r="arrow"===e.type?L((({fillColor:e=i,outlineColor:t="white",outlineOpacity:r=1},a)=>{const n=R("svg",{viewBox:"0 0 24 24",width:String(a),height:String(a)});return n.append(R("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(n)})(e,t)):e.image;if("string"!=typeof r)return r;const a=new Image;return a.crossOrigin="anonymous",a.src=r.includes("<svg")?L(r):r,a},k=(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},D=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=P(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,k(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,k(e,e.FRAGMENT_SHADER,"\nprecision mediump float;\nuniform sampler2D u_image;\nvarying vec2 v_uv;\nvoid main() {\n gl_FragColor = texture2D(u_image, v_uv);\n}")),e.linkProgram(t),e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),e.STATIC_DRAW),{program:t,corners:r,texture:void 0,uniforms:{matrix:e.getUniformLocation(t,"u_matrix"),right:e.getUniformLocation(t,"u_right"),forward:e.getUniformLocation(t,"u_forward"),image:e.getUniformLocation(t,"u_image")},corner:e.getAttribLocation(t,"a_corner")}})(t)}onRemove(e,t){this.releaseTexture(),this.drawing&&(t.deleteProgram(this.drawing.program),t.deleteBuffer(this.drawing.corners)),this.drawing=void 0,this.gl=void 0}render(e,t){const{drawing:r,placed:i,raster:n}=this;if(!r||!i||!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,h]=i.position,c=o.queryTerrainElevation([s,h])??0,d=a.fromLngLat([s,h],c),u=(this.config.size??44)/2*(1/(512*2**o.getZoom()));const g=m(i.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)}},H={speedKMH:200,pitch:70,aim:{mode:"instruction",fallbackMetersAhead:250},zoom:{mode:"instructions",instructionsAhead:2}},S=2,C=2,F=3,U=1.5,z=1.5,N=.6,X=.5,O=15,G=e=>v(e,11,15),B=class{map;route;camera;onProgress;marker;animationFrame;lastFrameTime=0;traveledDistanceInMeters=0;speedKMH=M(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={...H,...t.camera},this.onProgress=t.onProgress??(()=>{});const r=e.mapLibreMap,i=this.targetAnchor();this.motion={bearing:M(r.getBearing()),zoom:M(r.getZoom()),pitch:M(r.getPitch()),anchorX:M(i.x),anchorY:M(i.y),elevation:M(r.getCenterElevation()),markerHeading:M(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 D(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=M(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()*v(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=v((e-this.lastFrameTime)/1e3,0,.25);this.lastFrameTime=e;const r=this.speedKMH.value;this.speedKMH=y(this.speedKMH,this.camera.speedKMH,t,S);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,O].map(e=>this.coordinateAt(this.traveledDistanceInMeters+e)?.position);return e&&t?l(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 G(14-1*e)}const r=this.instructionAhead(Math.max(t.instructionsAhead,1)-1);if(!r)return G(14);const i=this.wrapped(r.routeOffsetInMeters-this.traveledDistanceInMeters)*Math.cos(m(Math.min(this.camera.pitch,85)));return G(((e,t,r)=>x(Math.max(e,1)/Math.max(t,1),r))(i,.6*this.viewport.height,e))}clearanceZoom(e,t,r){const i=m(Math.min(this.camera.pitch,85)),a=I(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=g(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)=>x(Math.max(e,1)/_(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(p(e,t))>r?t:e)(this.target.bearing,l(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??T(this.camera.pitch)}ease(e){const{motion:t,target:r}=this,i=this.targetAnchor(),a=(t,r,i)=>y(t,r,e,i);this.motion={bearing:A(t.bearing,r.bearing,e,C,22),markerHeading:A(t.markerHeading,r.markerHeading,e,X),zoom:a(t.zoom,r.zoom,F),elevation:a(t.elevation,r.elevation,N),pitch:a(t.pitch,this.camera.pitch,U),anchorX:a(t.anchorX,i.x,z),anchorY:a(t.anchorY,i.y,z)}}settle(){const e=this.targetAnchor();this.motion={bearing:M(this.target.bearing),markerHeading:M(this.target.markerHeading),zoom:M(this.target.zoom),elevation:M(this.target.elevation),pitch:M(this.camera.pitch),anchorX:M(e.x),anchorY:M(e.y)}}placeMarkerOutright(){const e=this.positionAt(0);this.target.markerHeading=this.roadHeading()??this.target.markerHeading,this.motion.markerHeading=M(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:E({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{B as RouteFlyover,T as anchorForPitch};
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
2
  //# sourceMappingURL=index.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":["calculateProgressAtRoutePoint","generateId","getCoordinateAtRouteProgress","ROUTE_LINE_OUTLINE_COLOR","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 { ROUTE_LINE_OUTLINE_COLOR } 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 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 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 = ROUTE_LINE_OUTLINE_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"],"x_google_ignoreList":[0,1,2,3],"mappings":"iPACA,IAAIK,EAAc,UACdC,EAAU,CACZC,YAAaF,UACbG,YAAaH,UACbI,GAAIJ,UACJK,QAAS,KAAO,EAAIC,KAAKC,IACzBC,IAAK,KAAO,EAAIF,KAAKC,IACrBE,KAAMT,gBACNU,GAAIV,gBACJW,OAAsB,MAAdX,EACRY,GAAkB,MAAdZ,EACJa,WAAYb,UACZc,WAAYd,UACZe,GAAIf,UACJgB,OAAQhB,EACRiB,OAAQjB,EACRkB,EAAGlB,EACHmB,MAAOnB,kBACPoB,GAAIpB,kBACJqB,YAAarB,WACbsB,YAAatB,WACbuB,GAAIvB,WACJwB,cAAexB,EAAc,KAC7ByB,IAAKzB,EAAc,KACnB0B,QAAS,EACTC,IAAK,EACLC,MAAO5B,kBACP6B,GAAI7B,mBA4DN,SAAS8B,EAAMC,EAAaC,EAAYC,EAAU,CAAC,GACjD,IAAKF,EACH,MAAM,IAAIG,MAAM,2BAElB,IAAKC,MAAMC,QAAQL,GACjB,MAAM,IAAIG,MAAM,gCAElB,GAAIH,EAAYM,OAAS,EACvB,MAAM,IAAIH,MAAM,+CAElB,IAAKI,EAASP,EAAY,MAAQO,EAASP,EAAY,IACrD,MAAM,IAAIG,MAAM,oCAMlB,OA/CF,SAAiBK,EAAMP,EAAYC,EAAU,CAAC,GAC5C,MAAMO,EAAO,CAAEC,KAAM,WASrB,OARmB,IAAfR,EAAQS,IAAYT,EAAQS,MAC9BF,EAAKE,GAAKT,EAAQS,IAEhBT,EAAQU,OACVH,EAAKG,KAAOV,EAAQU,MAEtBH,EAAKR,WAAaA,GAAc,CAAC,EACjCQ,EAAKI,SAAWL,EACTC,CACT,CAoCSK,CAAQ,CAHbJ,KAAM,QACNV,eAEmBC,EAAYC,EACnC,CAwIA,SAASa,EAAiBpB,GAExB,OAD0BA,GAAW,EAAIpB,KAAKC,IACnB,IAAMD,KAAKC,EACxC,CACA,SAASwC,EAAiB1C,GAExB,OAD0BA,EAAU,IACTC,KAAKC,GAAK,GACvC,CAqBA,SAAS+B,EAASU,GAChB,OAAQC,MAAMD,IAAgB,OAARA,IAAiBb,MAAMC,QAAQY,EACvD,CC9QA,SAASE,EAASC,GAChB,IAAKA,EACH,MAAM,IAAIjB,MAAM,qBAElB,IAAKC,MAAMC,QAAQe,GAAQ,CACzB,GAAmB,YAAfA,EAAMV,MAAyC,OAAnBU,EAAMP,UAA6C,UAAxBO,EAAMP,SAASH,KACxE,MAAO,IAAIU,EAAMP,SAASb,aAE5B,GAAmB,UAAfoB,EAAMV,KACR,MAAO,IAAIU,EAAMpB,YAErB,CACA,GAAII,MAAMC,QAAQe,IAAUA,EAAMd,QAAU,IAAMF,MAAMC,QAAQe,EAAM,MAAQhB,MAAMC,QAAQe,EAAM,IAChG,MAAO,IAAIA,GAEb,MAAM,IAAIjB,MAAM,qDAClB,CCfA,SAASkB,EAAQC,EAAOC,EAAKrB,EAAU,CAAC,GACtC,IAAsB,IAAlBA,EAAQsB,MACV,OAYJ,SAA+BF,EAAOC,GACpC,IAAIE,EAAOJ,EAAQE,EAAKD,GAExB,OADAG,GAAQA,EAAO,KAAO,IACfA,EAAO,IAAMA,EAAO,IAAMA,CACnC,CAhBWC,CAAsBJ,EAAOC,GAEtC,MAAMI,EAAeR,EAASG,GACxBM,EAAeT,EAASI,GACxBM,EAAOb,EAAiBW,EAAa,IACrCG,EAAOd,EAAiBY,EAAa,IACrCG,EAAOf,EAAiBW,EAAa,IACrCK,EAAOhB,EAAiBY,EAAa,IACrCK,EAAI1D,KAAK2D,IAAIJ,EAAOD,GAAQtD,KAAK4D,IAAIH,GACrCI,EAAI7D,KAAK4D,IAAIJ,GAAQxD,KAAK2D,IAAIF,GAAQzD,KAAK2D,IAAIH,GAAQxD,KAAK4D,IAAIH,GAAQzD,KAAK4D,IAAIL,EAAOD,GAC9F,OAAOd,EAAiBxC,KAAK8D,MAAMJ,EAAGG,GACxC,CCRA,SAASE,EAAYC,EAAQC,EAAUnB,EAASnB,EAAU,CAAC,GACzD,MAAMyB,EAAeR,EAASoB,GACxBE,EAAazB,EAAiBW,EAAa,IAC3Ce,EAAY1B,EAAiBW,EAAa,IAC1CgB,EAAa3B,EAAiBK,GAC9B1B,EH2MR,SAAyB6C,EAAUI,EAAQ,cACzC,MAAMC,EAAS3E,EAAQ0E,GACvB,IAAKC,EACH,MAAM,IAAI1C,MAAMyC,EAAQ,qBAE1B,OAAOJ,EAAWK,CACpB,CGjNkBC,CAAgBN,EAAUtC,EAAQ0C,OAC5CG,EAAYxE,KAAKyE,KACrBzE,KAAK2D,IAAIQ,GAAanE,KAAK4D,IAAIxC,GAAWpB,KAAK4D,IAAIO,GAAanE,KAAK2D,IAAIvC,GAAWpB,KAAK4D,IAAIQ,IAMzFM,EAAMlC,EAJO0B,EAAalE,KAAK8D,MACnC9D,KAAK2D,IAAIS,GAAcpE,KAAK2D,IAAIvC,GAAWpB,KAAK4D,IAAIO,GACpDnE,KAAK4D,IAAIxC,GAAWpB,KAAK2D,IAAIQ,GAAanE,KAAK2D,IAAIa,KAG/CG,EAAMnC,EAAiBgC,GAC7B,YAAwB,IAApBpB,EAAa,GACR5B,EAAM,CAACkD,EAAKC,EAAKvB,EAAa,IAAKzB,EAAQD,YAE7CF,EAAM,CAACkD,EAAKC,GAAMhD,EAAQD,WACnC,CCbA,IAAakD,EAAA,CAAkBC,EAAgBC,IJoN/C,SAA0BhC,GACxB,IAAIiC,EAAQjC,EAAU,IAItB,OAHIiC,EAAQ,IACVA,GAAS,KAEJA,CACT,CI1NwEC,CAAiBlC,EAAQ+B,EAAMC,IAO1FG,EAAA,CAAgBJ,EAAcC,KAAyBA,EAAKD,EAAO,KAAO,IAAO,IAejFK,EAAA,CAASC,EAAeC,EAAaC,IAAwBrF,KAAKoF,IAAIpF,KAAKqF,IAAIF,EAAOC,GAAMC,GAS5FC,EAAYH,IAAA,CAA6BA,QAAOI,SAAU,IASjEC,EAAU,KAUHC,EAAA,CACTC,EACAC,EACAC,EACAC,EACAC,EAAcC,OAAOC,qBAErB,MAAMC,EAtBU,EAsBWJ,EACrBK,EAAS,EAAIJ,EAAeG,EAC5BE,EAASjB,EAAMQ,EAAOP,MAAQQ,GAASO,EAAOA,GACpD,GAAIlG,KAAKoG,IAAID,GAAUX,GAAWxF,KAAKoG,IAAIV,EAAOH,UAAYC,EAAS,OAAOF,EAASK,GAEvF,MAAMU,GAASX,EAAOH,SAAWU,EAAOE,GAAUP,EAC5CU,EAAQtG,KAAKuG,KAAKN,EAAOL,GAC/B,MAAO,CACHT,MAAOO,EAAOP,MAAQgB,GAAUA,EAASE,GAASC,EAClDf,UAAWG,EAAOH,SAAWU,EAAOI,GAASC,IAQxCE,EAAA,CACTd,EACAC,EACAC,EACAC,EACAC,KAEA,MAAMW,EAAOhB,EACTC,EACAA,EAAOP,MAAQF,EAAaS,EAAOP,MAAOQ,GAC1CC,EACAC,EACAC,GAEJ,MAAO,CAAEX,OAAQsB,EAAKtB,MAAQ,KAAO,IAAKI,SAAUkB,EAAKlB,WCxEvDmB,EAAiBvB,GAA0BD,EAAMC,EAFlC,IAEuD,KAW/DwB,EAAkBC,IAEpB,CAAEC,EAAG,GAAKC,EAAG,GADP5B,EAAM0B,EAAc,EAlBR,OAmBE,qBAWlBG,EAAA,CACTC,EACAC,KAEA,MAAMC,EAAcR,EAAcM,EAAOH,GAAK,GACxCM,EAAcT,EAAcM,EAAOF,GAAK,GAGxCM,EAAA,CAAOC,EAAkBC,IAAmBtH,KAAKqF,IAAI,EAAIgC,EAAWC,EAAQ,GAClF,MAAO,CACHC,KAAMH,EAAIF,EAAaD,EAASO,OAChCC,MAAOL,GAAKF,EAAaD,EAASO,OAClCE,IAAKN,EAAID,EAAaF,EAASU,QAC/BC,OAAQR,GAAKD,EAAaF,EAASU,UASrCE,EAA2B,aAJF,IAezBC,EAAA,CAAyBpH,EAAgBqH,IAC3C/H,KAAKgI,KAAMH,EAA2B7H,KAAK4D,IAAInB,EAAiBsF,IAAqBrH,GAiBnFuH,EAAA,EAA4BC,iBAAgBC,wBAC7C,GAAMD,EAAkBlI,KAAKoI,IAAI3F,EAAiB0F,GAAsB,GAMhEE,EAAA,CAA0BC,EAAcC,IACjDN,EAAuBM,GA7BrB,EAAkBD,EAAcP,IACjCF,EAA2B7H,KAAK4D,IAAInB,EAAiBsF,IAAqB,GAAKO,EA4BjDE,CAAeF,EAAMC,EAAKR,iBC1DvDU,EAAA,CAAwCC,EAASC,KACnD,MAAMC,EAAUC,SAASC,gBAHP,6BAGsCJ,GACxD,IAAK,MAAOK,EAAW5D,KAAU6D,OAAOC,QAAQN,GAAaC,EAAQM,aAAaH,EAAW5D,GAC7F,OAAOyD,GAGLO,EAAcC,GAA2B,oCAAoCC,mBAAmBD,KA4BzFE,EAAA,CAAgBC,EAA2BC,KACpD,MAAMC,EAAwB,UAAfF,EAAMpH,KAAmBgH,EAzBtC,GACAO,YAAYlK,EAA0BmK,eAAe,QAASC,iBAAiB,GACjFJ,KAEA,MAAMK,EAAMpB,EAAW,MAAO,CAAEqB,QAAS,YAAatC,MAAOuC,OAAOP,GAAO7B,OAAQoC,OAAOP,KAY1F,OAXAK,EAAIG,OACAvB,EAAW,OAAQ,CACfwB,EAAG,sBACHC,KAAMR,EACNS,OAAQR,EACR,iBAAkBI,OAAOH,GACzB,eAAgB,IAChB,kBAAmB,QACnB,cAAe,aAGhB,IAAIQ,eAAgBC,kBAAkBR,IASMS,CAAYf,EAAOC,IAASD,EAAMA,MACrF,GAAsB,iBAAXE,EAAqB,OAAOA,EAEvC,MAAMb,EAAU,IAAI2B,MAGpB,OAFA3B,EAAQ4B,YAAc,YACtB5B,EAAQ6B,IAAMhB,EAAOiB,SAAS,QAAUvB,EAAWM,GAAUA,EACtDb,GA8CL+B,EAAA,CAAWC,EAAQzI,EAAcsH,KACnC,MAAMoB,EAASD,EAAGE,aAAa3I,GAC/B,IAAK0I,EAAQ,MAAM,IAAIjJ,MAAM,iDAI7B,OAFAgJ,EAAGG,aAAaF,EAAQpB,GACxBmB,EAAGI,cAAcH,GACVA,GA0DEI,EAAb,MAcyBC,IAbrB9I,GAAc,yBAAyB9C,MACvC6C,KAAgB,SAChBgJ,cAAyB,KAEzBC,OAAsC,CAAC,EACvCC,OACAC,OACAV,GACAW,QACAC,SAAkB,EAClBC,cAEA,WAAAC,CACIR,EACAE,GADiBO,KAAAT,IAAAA,EAGjBS,KAAKF,cAAgBP,EAAIU,sBAAsB,CAAEC,eAAA,IAAsBF,KAAKG,YAC5EH,KAAKI,mBACLJ,KAAKK,MAAMZ,EACf,CAEA,KAAAY,CAAMZ,GACFO,KAAKP,OAASA,EACd,MAAMa,EAAOb,EAAOa,MAxLP,GAIf,IAAeC,EAkDHC,OAAO5C,EAA2BC,KAChD,MAAMZ,EAAUU,EAAaC,EAAOC,SAC9BZ,EAAQwD,SACd,MAAMC,EAASxD,SAASyD,cAAc,UACtCD,EAAO7E,MAAQgC,EACf6C,EAAO1E,OAAS6B,EAChB,MAAM+C,EAAQ/C,EAAOxJ,KAAKqF,IAAIuD,EAAQ4D,aAAc5D,EAAQ6D,cAAe,IACpEjF,EAAOG,GAAU,CAACiB,EAAQ4D,aAAeD,EAAO3D,EAAQ6D,cAAgBF,GAE/E,OADAF,EAAOK,WAAW,OAAOC,UAAU/D,GAAUY,EAAOhC,GAAS,GAAIgC,EAAO7B,GAAU,EAAGH,EAAOG,GACrF0E,GA0HHO,CAAUxB,EAAO7B,OAAS,CAAEpH,KAAM,UArLrB+J,EAqL4CD,EApL7DjM,KAAKoF,IAAI,GAAKpF,KAAK6M,KAAK7M,KAAKgI,KAAKhI,KAAKqF,IAAI6G,EAAeY,iBAAkB,KAAM,OAqLzEC,KAAMzB,IAECK,KAAKP,SAAWA,IAEpBO,KAAKL,OAASA,EACdK,KAAKqB,iBACLrB,KAAKT,IAAI+B,YAAYC,oBAExBC,MAAOC,GAAUC,QAAQC,KAAK,gDAAiDF,IACpFzB,KAAKT,IAAI+B,YAAYC,gBACzB,CAGA,KAAAK,CAAMC,EAAoBC,GACtB9B,KAAKN,OAAS,CAAEmC,WAAUC,WAC1B9B,KAAKT,IAAI+B,YAAYC,gBACzB,CAEA,MAAAQ,GACI/B,KAAKH,SAAU,EACfG,KAAKF,gBACDE,KAAKT,IAAI+B,YAAYU,SAAShC,KAAKvJ,KAAKuJ,KAAKT,IAAI+B,YAAYW,YAAYjC,KAAKvJ,GACtF,CAEA,KAAAyL,CAAMC,EAAelD,GACjBe,KAAKf,GAAKA,EACVe,KAAKJ,QA3GP,CAAiBX,IACnB,MAAMmD,EAAUnD,EAAGoD,gBACbC,EAAUrD,EAAGsD,eACnB,IAAKH,IAAYE,EAAS,MAAM,IAAIrM,MAAM,oDAO1C,OALAgJ,EAAGuD,aAAaJ,EAASpD,EAAQC,EAAIA,EAAGwD,cA3CtB,iSA4ClBxD,EAAGuD,aAAaJ,EAASpD,EAAQC,EAAIA,EAAGyD,gBAjCpB,+IAkCpBzD,EAAG0D,YAAYP,GACfnD,EAAG2D,WAAW3D,EAAG4D,aAAcP,GAC/BrD,EAAG6D,WAAW7D,EAAG4D,aAAc,IAAIE,aAAa,EAAC,GAAI,EAAI,GAAG,GAAI,EAAI,EAAG,EAAG,IAAK9D,EAAG+D,aAC3E,CACHZ,UACAE,UACAW,aAAS,EACTC,SAAU,CACNC,OAAQlE,EAAGmE,mBAAmBhB,EAAS,YACvCtG,MAAOmD,EAAGmE,mBAAmBhB,EAAS,WACtCiB,QAASpE,EAAGmE,mBAAmBhB,EAAS,aACxCxE,MAAOqB,EAAGmE,mBAAmBhB,EAAS,YAE1CkB,OAAQrE,EAAGsE,kBAAkBnB,EAAS,cAuFvBoB,CAAcvE,EACjC,CAEA,QAAAwE,CAAStB,EAAelD,GACpBe,KAAKqB,iBACDrB,KAAKJ,UACLX,EAAGyE,cAAc1D,KAAKJ,QAAQwC,SAC9BnD,EAAG0E,aAAa3D,KAAKJ,QAAQ0C,UAEjCtC,KAAKJ,aAAU,EACfI,KAAKf,QAAK,CACd,CAEA,MAAA2E,CAAO3E,EAAQjJ,GACX,MAAM4J,QAAEA,EAAAF,OAASA,EAAAC,OAAQA,GAAWK,KACpC,IAAKJ,IAAYF,IAAWC,IAAkC,IAAxBK,KAAKP,OAAOoE,QAAmB,OAErEjE,EAAQqD,UApGV,EAAiBhE,EAAQU,KAC3B,MAAMsD,EAAUhE,EAAG6E,sBAAmB,EAUtC,OATA7E,EAAG8E,YAAY9E,EAAG+E,WAAYf,GAAW,MACzChE,EAAGgF,YAAYhF,EAAGiF,gCAAgC,GAClDjF,EAAGkF,WAAWlF,EAAG+E,WAAY,EAAG/E,EAAGmF,KAAMnF,EAAGmF,KAAMnF,EAAGoF,cAAe1E,GACpEV,EAAGgF,YAAYhF,EAAGiF,gCAAgC,GAClDjF,EAAGqF,eAAerF,EAAG+E,YACrB/E,EAAGsF,cAActF,EAAG+E,WAAY/E,EAAGuF,mBAAoBvF,EAAGwF,sBAC1DxF,EAAGsF,cAActF,EAAG+E,WAAY/E,EAAGyF,mBAAoBzF,EAAG0F,QAC1D1F,EAAGsF,cAActF,EAAG+E,WAAY/E,EAAG2F,eAAgB3F,EAAG4F,eACtD5F,EAAGsF,cAActF,EAAG+E,WAAY/E,EAAG6F,eAAgB7F,EAAG4F,eAC/C5B,GAyFiB8B,CAAc9F,EAAIU,GACtC,MAAM2B,EAActB,KAAKT,IAAI+B,aACtB0D,EAAWC,GAAYvF,EAAOmC,SAG/BqD,EAAS5D,EAAY6D,sBAAsB,CAACH,EAAWC,KAAc,EACrEG,EAAStR,EAAmBuR,WAAW,CAACL,EAAWC,GAAWC,GAC9DI,GAAStF,KAAKP,OAAOa,MA5Od,IA4OsC,GDxMI,GAVhC,IAU8D,GCwMPgB,EAAYiE,YAE1F,MAAMzD,EAAUhL,EAAiB4I,EAAOoC,UACjC0D,EAAMC,GAAU,CAACpR,KAAK2D,IAAI8J,GAAWwD,EAAMjR,KAAK4D,IAAI6J,GAAWwD,GAEtErG,EAAGyG,WAAW9F,EAAQwC,SACtBnD,EAAG0G,iBAAiB/F,EAAQsD,SAASC,QAAQ,EAhG/C,EAAYyC,GAAiC1K,IAAGC,IAAG0K,QACrD,MAAM1C,EAASJ,aAAa7J,KAAK0M,GACjC,IAAK,IAAIE,EAAM,EAAGA,EAAM,EAAGA,IACvB3C,EAAO,GAAK2C,GACRF,EAAWE,GAAO5K,EAAI0K,EAAW,EAAIE,GAAO3K,EAAIyK,EAAW,EAAIE,GAAOD,EAAID,EAAW,GAAKE,GAElG,OAAO3C,GA0FiD4C,CAAS/P,EAAQgQ,sBAAsBC,WAAYb,IACvGnG,EAAGiH,UAAUtG,EAAQsD,SAASpH,MAAO2J,EAAQD,GAC7CvG,EAAGiH,UAAUtG,EAAQsD,SAASG,QAASmC,GAAOC,GAC9CxG,EAAGkH,cAAclH,EAAGmH,UACpBnH,EAAG8E,YAAY9E,EAAG+E,WAAYpE,EAAQqD,SAAW,MACjDhE,EAAGoH,UAAUzG,EAAQsD,SAAStF,MAAO,GACrCqB,EAAG2D,WAAW3D,EAAG4D,aAAcjD,EAAQ0C,SACvCrD,EAAGqH,wBAAwB1G,EAAQ0D,QACnCrE,EAAGsH,oBAAoB3G,EAAQ0D,OAAQ,EAAGrE,EAAGuH,OAAO,EAAO,EAAG,GAE9DvH,EAAGwH,QAAQxH,EAAGyH,YACdzH,EAAG0H,OAAO1H,EAAG2H,OACb3H,EAAG4H,UAAU5H,EAAG6H,IAAK7H,EAAG8H,qBACxB9H,EAAG+H,WAAW/H,EAAGgI,eAAgB,EAAG,EACxC,CAEA,cAAA5F,GACQrB,KAAKJ,SAASqD,SAASjD,KAAKf,IAAIiI,cAAclH,KAAKJ,QAAQqD,SAC3DjD,KAAKJ,UAASI,KAAKJ,QAAQqD,aAAU,EAC7C,CAEA,gBAAA7C,GACQJ,KAAKT,IAAI4H,SACTnH,KAAKG,UAELH,KAAKT,IAAI+B,YAAY8F,KAAK,YAAA,IAAmBpH,KAAKI,mBAE1D,CAEA,OAAAD,GACSH,KAAKH,SAAYG,KAAKT,IAAI+B,YAAYU,SAAShC,KAAKvJ,KAAKuJ,KAAKT,IAAI+B,YAAY+F,SAASrH,KAChG,GCxNEsH,EAAgC,CAClCC,SAAU,IACVC,MAAO,GACPC,IAAK,CAAEC,KAAM,cAAeC,oBAAqB,KACjDhL,KAAM,CAAE+K,KAAM,eAAgBE,kBAAmB,IAO/CC,EACK,EADLA,EAEO,EAFPA,EAGI,EAHJA,EAIK,IAJLA,EAKM,IALNA,EAMS,GANTA,EAOa,GA+CbC,EAAsB,GAgBtBC,EAAapL,GAAyBpD,EAAMoD,EAZjC,GACA,IAgDJqL,EAAb,MA0ByBzI,IAzBrB0I,MACAC,OACAC,WACAC,OAEAC,eACAC,cAAwB,EACxBC,yBAAmC,EACnChB,SAAmB5N,EAAS,GAC5B6O,OAEAxO,OAEAyO,WACAC,mBAA6B,EAG7BpN,SAAmB,CAAEO,MAAO,EAAGG,OAAQ,GAEvC2M,QAAiB,EAEjBC,2BAAoC,EACpCC,YAEA,WAAA9I,CACIR,EACAvJ,GADiBgK,KAAAT,IAAAA,EAGjBS,KAAKiI,MAAQjS,EAAQiS,MACrBjI,KAAKkI,OAAS,IAAKZ,KAAmBtR,EAAQkS,QAC9ClI,KAAKmI,WAAanS,EAAQmS,YAAA,MAAsB,GAChD,MAAM7G,EAAc/B,EAAI+B,YAClBjG,EAAS2E,KAAK8I,eACpB9I,KAAKwI,OAAS,CACVrR,QAASwC,EAAS2H,EAAYyH,cAC9BpM,KAAMhD,EAAS2H,EAAYiE,WAC3BiC,MAAO7N,EAAS2H,EAAY0H,YAC5BC,QAAStP,EAAS0B,EAAOH,GACzBgO,QAASvP,EAAS0B,EAAOF,GACzBgO,UAAWxP,EAAS2H,EAAY8H,sBAChCC,cAAe1P,EAAS,IAE5BqG,KAAKhG,OAAS,CACV7C,QAAS6I,KAAKwI,OAAOrR,QAAQqC,MAC7BmD,KAAMqD,KAAKwI,OAAO7L,KAAKnD,MACvB2P,UAAWnJ,KAAKwI,OAAOW,UAAU3P,MACjC6P,cAAe,GAEnBrJ,KAAKyI,WAAazI,KAAKhG,OAAO2C,KAC9BqD,KAAKsJ,eACLhI,EAAYiI,GAAG,SAAUvJ,KAAKsJ,cAC9BtJ,KAAKoI,OAAS,IAAI9I,EAAeC,EAAKvJ,EAAQoS,QAAU,CAAC,GACzDpI,KAAKwJ,qBACT,CAGA,WAAIC,GACA,YAA+B,IAAxBzJ,KAAKqI,cAChB,CAGA,KAAAjR,GACI,GAAI4I,KAAKyJ,QAAS,OAElB,MAAMnI,EAActB,KAAKT,IAAI+B,YAC7BtB,KAAK4I,0BAA4BtH,EAAYoI,2BAC7CpI,EAAYqI,0BAAyB,GACrC3J,KAAKuH,SAAW5N,EAAS,GACzBqG,KAAKsI,cAAgBsB,YAAYC,MACjC7J,KAAKqI,eAAiByB,sBAAsB9J,KAAK+J,KACrD,CAMA,IAAAC,QACgC,IAAxBhK,KAAKqI,iBAET4B,qBAAqBjK,KAAKqI,gBAC1BrI,KAAKqI,oBAAiB,EACtBrI,KAAKT,IAAI+B,YAAYqI,yBAAyB3J,KAAK4I,2BACvD,CAGA,QAAAsB,CAASjC,GACLjI,KAAKiI,MAAQA,EACbjI,KAAKuI,yBAA2B,EAChCvI,KAAK2I,QAAS,EACV3I,KAAKyJ,QACLzJ,KAAKmK,cAELnK,KAAKwJ,qBAEb,CAGA,SAAAY,CAAUlC,GACNlI,KAAKkI,OAAS,IAAKlI,KAAKkI,UAAWA,GAG/BlI,KAAK2I,SAAW3I,KAAKyJ,SAASzJ,KAAKmK,aAC3C,CAGA,SAAAE,GACI,MAAO,IAAKrK,KAAKkI,OACrB,CAGA,SAAAoC,CAAUlC,GACNpI,KAAKoI,OAAO/H,MAAM+H,EACtB,CAGA,IAAAmC,CAAK7O,GACDsE,KAAKuI,yBAA2BvI,KAAKwK,sBAAwBjR,EAAMmC,EAAU,EAAG,GAChFsE,KAAKmK,aACT,CAMA,MAAApI,GACI/B,KAAKgK,OACLhK,KAAKoI,OAAOrG,SACZ,MAAMT,EAActB,KAAKT,IAAI+B,YAC7BA,EAAYmJ,IAAI,SAAUzK,KAAKsJ,cAC3BtJ,KAAK6I,aAAavH,EAAYoJ,WAAW1K,KAAK6I,YACtD,CAEAS,aAAA,KACI,MAAM5I,EAASV,KAAKT,IAAI+B,YAAYqJ,YACpC3K,KAAK1E,SAAW,CAAEO,MAAOxH,KAAKqF,IAAIgH,EAAOkK,YAAa,GAAI5O,OAAQ3H,KAAKqF,IAAIgH,EAAOmK,aAAc,KAGpG,mBAAAL,GACI,OAAOxK,KAAKiI,MAAMlS,WAAW+U,QAAQC,cACzC,CAEAhB,KAAyBiB,IAErB,MAAM/Q,EAAiBV,GAAOyR,EAAYhL,KAAKsI,eAAiB,IAAM,EAlNpD,KAmNlBtI,KAAKsI,cAAgB0C,EAErB,MAAMC,EAAmBjL,KAAKuH,SAAS/N,MACvCwG,KAAKuH,SAAWzN,EAAckG,KAAKuH,SAAUvH,KAAKkI,OAAOX,SAAUtN,EAAgB4N,GACnF,MAAMqD,GAAmBD,EAAmBjL,KAAKuH,SAAS/N,OAAS,EAAI,IAGvEwG,KAAKuI,yBAA2BvI,KAAKmL,QAAQnL,KAAKuI,yBAA2B2C,EAAkBjR,GAE/F+F,KAAKmK,YAAYlQ,GACjB+F,KAAKqI,eAAiByB,sBAAsB9J,KAAK+J,OAGrD,YAAAqB,CAAqB7C,GACjB,OAAO3U,EAA6BoM,KAAKiI,MAAO,CAAEM,4BACtD,CAIA,UAAA8C,CAAmBC,GACf,OAAOtL,KAAKoL,aAAapL,KAAKmL,QAAQnL,KAAKuI,yBAA2B+C,KAAkBzJ,QAC5F,CAEA,OAAAsJ,CAAgBI,GACZ,MAAMC,EAAcxL,KAAKwK,sBACzB,OAAOgB,EAAc,GAAMD,EAAmBC,EAAeA,GAAeA,EAAc,CAC9F,CAIA,WAAAC,GACI,MAAOC,EAAQC,GAAS,EAAC,GAAsB7D,GAAqBvI,IAC/D+L,GAAmBtL,KAAKoL,aAAapL,KAAKuI,yBAA2B+C,IAAiBzJ,UAE3F,OAAO6J,GAAUC,EAAQ1S,EAAeyS,EAAQC,QAAS,CAC7D,CAEA,YAAAC,GACI,OAAO5L,KAAKiI,MAAMlS,WAAW8V,UAAUD,cAAgB,EAC3D,CAMA,gBAAAE,CAAyBC,EAAO,GAC5B,MAAMH,EAAe5L,KAAK4L,eAC1B,IAAKA,EAAaxV,OAAQ,OAE1B,MAAM0E,EAAO8Q,EAAaI,UACrBC,GAAgBA,EAAYC,oBAAsBlM,KAAKuI,0BAG5D,OAAOqD,EAAavX,KAAKoF,MADH,IAATqB,EAAc,EAAIA,GACKiR,EAAMH,EAAaxV,OAAS,GACpE,CAIA,cAAA+V,GACI,IAAIC,EAAM,EACNC,EAAOrM,KAAKiI,MAAMtR,SAASb,YAAYM,OAAS,EACpD,KAAOgW,EAAMC,GAAM,CACf,MAAMC,EAASjY,KAAKkY,OAAOH,EAAMC,GAAQ,GACnCG,EAAW9Y,EAA8BsM,KAAKiI,MAAOqE,QAC1C,IAAbE,GAA0BA,EAASjB,iBAAmBvL,KAAKuI,yBAC3D6D,EAAME,EAAS,EAEfD,EAAOC,CAEf,CACA,OAAOF,CACX,CAGA,WAAAK,GACI,MAAMhF,EAAMzH,KAAKkI,OAAOT,IACxB,OAAQA,EAAIC,MACR,IAAK,WACD,OAAO1H,KAAKqL,WAAW5D,EAAIiF,aAC/B,IAAK,SAAU,CACX,MAAM5W,EAAckK,KAAKiI,MAAMtR,SAASb,YAExC,OAAOA,GAAakK,KAAKmM,iBAAmB1E,EAAIkF,aAAe7W,EAAYM,OAC/E,CACA,IAAK,cAAe,CAIhB,MAAM6V,EAAcjM,KAAK8L,mBACnBH,EACFM,GAAejM,KAAKmL,QAAQc,EAAYC,oBAAsBlM,KAAKuI,0BACvE,YAAiB,IAAVoD,EAAsB3L,KAAKqL,WAAW5D,EAAIE,qBAAuB3H,KAAKqL,WAAWM,EAC5F,EAER,CAGA,QAAAiB,CAAiBxQ,GACb,MAAMO,EAAOqD,KAAKkI,OAAOvL,KACzB,GAAkB,UAAdA,EAAK+K,KAAkB,OAAO/K,EAAKA,KAEvC,GAAkB,UAAdA,EAAK+K,KAAkB,CACvB,MAAMmF,EAAYxY,KAAKgI,KAAKhI,KAAKqF,IAAIsG,KAAKkI,OAAOX,SAAU,GA/R3C,IAgShB,OAAOQ,EAAU,GA/RG,EA+RY8E,EACpC,CAKA,MAAM7S,EAASgG,KAAK8L,iBAAiBzX,KAAKqF,IAAIiD,EAAKiL,kBAAmB,GAAK,GAC3E,IAAK5N,EAAQ,OAAO+N,EAAU,IAG9B,MAAM+E,EADQ9M,KAAKmL,QAAQnR,EAAOkS,oBAAsBlM,KAAKuI,0BAC/BlU,KAAK4D,IAAInB,EAAiBzC,KAAKoF,IAAIuG,KAAKkI,OAAOV,MA3TpD,MA4TzB,OAAOO,EFlYF,EAAewD,EAA0BwB,EAAgB3Q,IAClED,EAAsB9H,KAAKqF,IAAI6R,EAAkB,GAAKlX,KAAKqF,IAAIqT,EAAQ,GAAI3Q,GEiYtD4Q,CAAYF,EA/SJ,GA+SmB9M,KAAK1E,SAASU,OAAmCI,GACjG,CAWA,aAAA6Q,CAAsBC,EAAgBC,EAAcvQ,GAChD,MAAM4K,EAAQ1Q,EAAiBzC,KAAKoF,IAAIuG,KAAKkI,OAAOV,MAzU3B,KA0UnB4F,EAAwB1Q,EAAuBsD,KAAKwI,OAAO7L,KAAKnD,MAAOoD,GAAQvI,KAAK2D,IAAIwP,GAAU,IAClGkE,EAAS1L,KAAKwI,OAAOrR,QAAQqC,MAAQ,IAC3C,IAAIwC,EAjVa,IAkVjB,IAAK,IAAIqR,EAAS,EAAGA,EAhVH,EAgV+BA,IAAU,CACvD,MAAMC,EAAQD,EAjVA,EAkVRxX,EAAQuC,EAAY8U,EAAME,GAAwB,EAAIE,GAAQ5B,GAAQ/U,SAASb,YAC/EoP,EAASlF,KAAKuN,SAAS1X,QACd,IAAXqP,IAAsBlJ,EAAS3H,KAAKqF,IAAIsC,EAtV/B,KAsV2DkJ,EAASiI,IAAS,EAAIG,IAClG,CACA,MFhYK,EAAyB/B,EAA0B3O,IAC5DT,EAAsB9H,KAAKqF,IAAI6R,EAAkB,GAAKjP,EAAuBM,GAAOA,EAAKR,iBE+X9EoR,CAAsBxR,EAAS3H,KAAK4D,IAAIuP,GAAQ5K,EAC3D,CAIA,QAAA2Q,EAAkBvI,EAAWC,IACzB,OAAOjF,KAAKT,IAAI+B,YAAY6D,sBAAsB,CAACH,EAAWC,UAAc,CAChF,CASA,MAAAwI,CAAeP,GACX,MAAMtQ,EAAmB,CACrBR,gBAAiB8Q,EAAK,GACtB3Q,eAAgByD,KAAK1E,SAASU,OAC9BQ,mBAAoBwD,KAAKT,IAAI+B,YAAYoM,0BAGvC/B,EAAQ3L,KAAKyM,cACfd,IACA3L,KAAKhG,OAAO7C,QHhfX,EAAiBwW,EAAkBR,EAAcS,IAC1DvZ,KAAKoG,IAAInB,EAAaqU,EAAUR,IAASS,EAAkBT,EAAOQ,EG+epCE,CAClB7N,KAAKhG,OAAO7C,QACZ8B,EAAeiU,EAAMvB,GAjZJ,MAqZzB3L,KAAKhG,OAAOqP,cAAgBrJ,KAAKyL,eAAiBzL,KAAKhG,OAAOqP,cAK9D,MAAMyE,EAAS9N,KAAK4M,SAAShQ,EAAKR,iBAC5B2R,EAAqC,iBAA1B/N,KAAKkI,OAAOvL,KAAK+K,KArZpB,GAqZ8D,EACxErT,KAAKoG,IAAIqT,EAAS9N,KAAKyI,YAAcsF,IAAU/N,KAAKyI,WAAaqF,GAErE,MAAMX,EAAOnN,KAAKuN,SAASL,QACd,IAATC,GAEKnN,KAAKT,IAAI+B,YAAY0M,eAAchO,KAAKhG,OAAOmP,UAAY,GAEhEnJ,KAAKhG,OAAO2C,KAAOqD,KAAKyI,aAExBzI,KAAKhG,OAAOmP,UAAYgE,EACxBnN,KAAKhG,OAAO2C,KAAOtI,KAAKoF,IAAIuG,KAAKyI,WAAYzI,KAAKiN,cAAcC,EAAMC,EAAMvQ,IAEpF,CAEA,YAAAkM,GACI,OAAO9I,KAAKkI,OAAO7M,QAAUL,EAAegF,KAAKkI,OAAOV,MAC5D,CAIA,IAAAyG,CAAahU,GACT,MAAMuO,OAAEA,EAAAxO,OAAQA,GAAWgG,KACrB3E,EAAS2E,KAAK8I,eACdoF,EAAA,CAAWnU,EAAgBP,EAAeU,IAC5CJ,EAAcC,EAAQP,EAAOS,EAAgBC,GACjD8F,KAAKwI,OAAS,CACVrR,QAAS0D,EACL2N,EAAOrR,QACP6C,EAAO7C,QACP8C,EACA4N,EAzboB,IA4bxBwB,cAAexO,EACX2N,EAAOa,cACPrP,EAAOqP,cACPpP,EACA4N,GAEJlL,KAAMuR,EAAQ1F,EAAO7L,KAAM3C,EAAO2C,KAAMkL,GACxCsB,UAAW+E,EAAQ1F,EAAOW,UAAWnP,EAAOmP,UAAWtB,GACvDL,MAAO0G,EAAQ1F,EAAOhB,MAAOxH,KAAKkI,OAAOV,MAAOK,GAChDoB,QAASiF,EAAQ1F,EAAOS,QAAS5N,EAAOH,EAAG2M,GAC3CqB,QAASgF,EAAQ1F,EAAOU,QAAS7N,EAAOF,EAAG0M,GAEnD,CAGA,MAAAsG,GACI,MAAM9S,EAAS2E,KAAK8I,eACpB9I,KAAKwI,OAAS,CACVrR,QAASwC,EAASqG,KAAKhG,OAAO7C,SAC9BkS,cAAe1P,EAASqG,KAAKhG,OAAOqP,eACpC1M,KAAMhD,EAASqG,KAAKhG,OAAO2C,MAC3BwM,UAAWxP,EAASqG,KAAKhG,OAAOmP,WAChC3B,MAAO7N,EAASqG,KAAKkI,OAAOV,OAC5ByB,QAAStP,EAAS0B,EAAOH,GACzBgO,QAASvP,EAAS0B,EAAOF,GAEjC,CAGA,mBAAAqO,GACI,MAAM0D,EAAOlN,KAAKqL,WAAW,GAC7BrL,KAAKhG,OAAOqP,cAAgBrJ,KAAKyL,eAAiBzL,KAAKhG,OAAOqP,cAC9DrJ,KAAKwI,OAAOa,cAAgB1P,EAASqG,KAAKhG,OAAOqP,eAC7C6D,GAAMlN,KAAKoI,OAAOxG,MAAMsL,EAAMlN,KAAKhG,OAAOqP,cAClD,CAOA,WAAAc,CAAoBlQ,GAChB,MAAMiT,EAAOlN,KAAKoL,aAAapL,KAAKuI,0BACpC,IAAK2E,EAAM,OAEX,MAAMkB,OAA4B,IAAnBnU,GAAgC+F,KAAK2I,OACpD3I,KAAK0I,oBAAsBzO,GAAkB,EAC7C,MAAMoU,GAAaD,GAAUpO,KAAK0I,oBA5dV,GA6dpB2F,IACArO,KAAK0I,mBAAqB,EAC1B1I,KAAKyN,OAAOP,EAAKrL,WAEjBuM,EACApO,KAAKiO,KAAKhU,GAEV+F,KAAKmO,SAETnO,KAAK2I,QAAS,EAEd,MAAO3D,EAAWC,GAAYiI,EAAKrL,UAC7B1K,QAAEA,EAAAwF,KAASA,EAAA6K,MAAMA,EAAA2B,UAAOA,EAAAF,QAAWA,EAAAC,QAASA,EAAAG,cAASA,GAAkBrJ,KAAKwI,OAC5ElH,EAActB,KAAKT,IAAI+B,YAC7BtB,KAAK6I,cAAgBvH,EAAYgN,aAGjChN,EAAYiN,OAAO,CACfC,OAAQ,CAACxJ,EAAWC,GACpBkE,UAAWA,EAAU3P,MACrBrC,QAASA,EAAQqC,MACjBgO,MAAOA,EAAMhO,MACbmD,KAAMA,EAAKnD,MACXiV,QAASrT,EAAc,CAAEF,EAAG+N,EAAQzP,MAAO2B,EAAG+N,EAAQ1P,OAASwG,KAAK1E,YAExE0E,KAAKoI,OAAOxG,MAAMsL,EAAKrL,SAAUwH,EAAc7P,OAE3C6U,GACArO,KAAKmI,WAAW,CACZI,yBAA0B2E,EAAK3B,iBAC/BmD,sBAAuBxB,EAAKyB,qBAGxC"}
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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"}
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.2",
5
+ "version": "0.0.3",
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,21 +10,20 @@
10
10
  "@turf/turf": "^7.4.0"
11
11
  },
12
12
  "peerDependencies": {
13
- "@tomtom-org/maps-sdk": ">=0.61.0 <1.0.0",
13
+ "@tomtom-org/maps-sdk": ">=0.64.0 <1.0.0",
14
14
  "maplibre-gl": "^6.11.2"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@tsconfig/recommended": "^1.0.13",
18
18
  "@types/geojson": "^7946.0.16",
19
- "@types/node": "^26.6.2",
19
+ "@types/node": "^26.6.4",
20
20
  "@vitest/coverage-v8": "^4.1.11",
21
21
  "jsdom": "^29.1.1",
22
- "rolldown": "^1.2.11",
23
- "ts-node": "^10.9.2",
22
+ "rolldown": "^1.2.12",
24
23
  "typescript": "^6.0.3",
25
- "vite": "^8.3.1",
24
+ "vite": "^8.3.2",
26
25
  "vitest": "^4.1.11",
27
- "@tomtom-org/maps-sdk": "0.61.0"
26
+ "@tomtom-org/maps-sdk": "0.64.0"
28
27
  },
29
28
  "files": [
30
29
  "dist",