@tomtom-org/maps-sdk-plugin-flyover 0.0.7 → 0.1.1

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/README.md CHANGED
@@ -4,6 +4,8 @@ This plugin adds a camera that flies a route on a [TomTom Maps SDK for JavaScrip
4
4
  map. It follows the line, aims at the junctions ahead and frames them. It keeps clear of the terrain
5
5
  it crosses, and it parks the followed position, marked with an arrow, where you want it on screen.
6
6
 
7
+ > **Public preview.** This plugin is in [Public Preview](https://docs.tomtom.com/legal/public-preview) until its 1.0.0 release.
8
+
7
9
  ## Docs & examples
8
10
 
9
11
  - Developer guide: https://docs.tomtom.com/maps-sdk-js/guides/plugins/flyover
@@ -52,3 +54,7 @@ never lands. With `loop: false`, it stops at the destination instead.
52
54
  ## License
53
55
 
54
56
  See [LICENSE.txt](./LICENSE.txt).
57
+
58
+ ## Security
59
+
60
+ To report a security vulnerability, follow our [security policy](https://github.com/tomtom-international/maps-sdk-js/security/policy), which keeps it private.
package/THIRD_PARTY.txt CHANGED
@@ -49,7 +49,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
49
49
  ---
50
50
 
51
51
  Name: maplibre-gl
52
- Version: 6.13.0
52
+ Version: 6.15.0
53
53
  License: BSD-3-Clause
54
54
  Bundled: false (peer dependency)
55
55
  Description: BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
@@ -39,7 +39,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39
39
  ---
40
40
 
41
41
  Name: maplibre-gl
42
- Version: 6.13.0
42
+ Version: 6.15.0
43
43
  License: BSD-3-Clause
44
44
  Bundled: false (peer dependency)
45
45
  Description: BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
package/dist/index.d.ts CHANGED
@@ -152,6 +152,8 @@ type RouteFlyoverOptions = {
152
152
  * where it will be next.
153
153
  *
154
154
  * @remarks
155
+ * **[Public Preview](https://docs.tomtom.com/legal/public-preview)** until the plugin's 1.0.0 release.
156
+ *
155
157
  * The route is the only geometry it needs. There's no path of camera keyframes to author and no
156
158
  * animation to re-time when the route changes. What it adds on top of following the line:
157
159
  *
package/dist/index.es.js CHANGED
@@ -1 +1 @@
1
- import{bearing,bearingToAzimuth,degreesToRadians,destination}from"@turf/turf";import{calculateProgressAtRoutePoint,generateId,getCoordinateAtRouteProgress}from"@tomtom-org/maps-sdk/core";import{MercatorCoordinate}from"maplibre-gl";var headingBetween=(e,t)=>bearingToAzimuth(bearing(e,t)),shortestTurn=(e,t)=>(t-e+540)%360-180,clamp=(e,t,r)=>Math.min(Math.max(e,t),r),springAt=e=>({value:e,velocity:0}),springTowards=(e,t,r,i,a=Number.POSITIVE_INFINITY)=>{const o=4/i,n=2*a/o,s=clamp(e.value-t,-n,n);if(Math.abs(s)<1e-6&&Math.abs(e.velocity)<1e-6)return springAt(t);const h=(e.velocity+o*s)*r,m=Math.exp(-o*r);return{value:e.value-s+(s+h)*m,velocity:(e.velocity-o*h)*m}},springTowardsBearing=(e,t,r,i,a)=>{const o=springTowards(e,e.value+shortestTurn(e.value,t),r,i,a);return{value:(o.value+360)%360,velocity:o.velocity}},clampFraction=e=>clamp(e,.05,.95),anchorForPitch=e=>({x:.5,y:.5+clamp(e,0,60)/60*.16666666666666663}),anchorPadding=(e,t)=>{const r=clampFraction(e.x)-.5,i=clampFraction(e.y)-.5,a=(e,t)=>Math.max(2*e*t,0);return{left:a(r,t.width),right:a(-r,t.width),top:a(i,t.height),bottom:a(-i,t.height)}},zoomForMetersPerPixel=(e,t)=>Math.log2(78271.51696484374*Math.cos(degreesToRadians(t))/e),cameraDistanceInPixels=({viewportHeight:e,fieldOfViewDegrees:t})=>.5*e/Math.tan(degreesToRadians(t)/2),svgElement=(e,t)=>{const r=document.createElementNS("http://www.w3.org/2000/svg",e);for(const[i,a]of Object.entries(t))r.setAttribute(i,a);return r},svgDataUri=e=>`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,compile=(e,t,r)=>{const i=e.createShader(t);if(!i)throw new Error("The fly-over marker could not create a shader");return e.shaderSource(i,r),e.compileShader(i),i},PositionMarker=class{map;id=`tomtom-flyover-marker-${generateId()}`;type="custom";renderingMode="2d";config={};placed;raster;gl;drawing;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=((e,t)=>{const r="arrow"===e.type?svgDataUri((({fillColor:e="#105287",outlineColor:t="white",outlineOpacity:r=1},i)=>{const a=svgElement("svg",{viewBox:"0 0 24 24",width:String(i),height:String(i)});return a.append(svgElement("path",{d:"M12 3l8 18-8-4-8 4z",fill:e,stroke:t,"stroke-opacity":String(r),"stroke-width":"3","stroke-linejoin":"round","paint-order":"stroke"})),(new XMLSerializer).serializeToString(a)})(e,t)):e.image;if("string"!=typeof r)return r;const i=new Image;return i.crossOrigin="anonymous",i.src=r.includes("<svg")?svgDataUri(r):r,i})(e,t);await r.decode();const i=document.createElement("canvas");i.width=t,i.height=t;const a=t/Math.max(r.naturalWidth,r.naturalHeight,1),[o,n]=[r.naturalWidth*a,r.naturalHeight*a];return i.getContext("2d")?.drawImage(r,(t-o)/2,(t-n)/2,o,n),i})(e.image??{type:"arrow"},(r=t,Math.min(2**Math.ceil(Math.log2(Math.max(r*devicePixelRatio,1))),512))).then(t=>{this.config===e&&(this.raster=t,this.releaseTexture(),this.map.mapLibreMap.triggerRepaint())}).catch(e=>console.warn("The fly-over marker image could not be loaded",e)),this.map.mapLibreMap.triggerRepaint()}place(e,t){this.placed={position:e,heading:t},this.map.mapLibreMap.triggerRepaint()}remove(){this.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,compile(e,e.VERTEX_SHADER,"\nuniform mat4 u_matrix;\nuniform vec2 u_right;\nuniform vec2 u_forward;\nattribute vec2 a_corner;\nvarying vec2 v_uv;\nvoid main() {\n v_uv = vec2(a_corner.x, -a_corner.y) * 0.5 + 0.5;\n gl_Position = u_matrix * vec4(u_right * a_corner.x + u_forward * a_corner.y, 0.0, 1.0);\n}")),e.attachShader(t,compile(e,e.FRAGMENT_SHADER,"\nprecision mediump float;\nuniform sampler2D u_image;\nvarying vec2 v_uv;\nvoid main() {\n gl_FragColor = texture2D(u_image, v_uv);\n}")),e.linkProgram(t),e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),e.STATIC_DRAW),{program:t,corners:r,texture:void 0,uniforms:{matrix:e.getUniformLocation(t,"u_matrix"),right:e.getUniformLocation(t,"u_right"),forward:e.getUniformLocation(t,"u_forward"),image:e.getUniformLocation(t,"u_image")},corner:e.getAttribLocation(t,"a_corner")}})(t)}onRemove(e,t){this.releaseTexture(),this.drawing&&(t.deleteProgram(this.drawing.program),t.deleteBuffer(this.drawing.corners)),this.drawing=void 0,this.gl=void 0}render(e,t){const{drawing:r,placed:i,raster:a}=this;if(!r||!i||!a||!1===this.config.visible)return;r.texture??=((e,t)=>{const r=e.createTexture()??void 0;return e.bindTexture(e.TEXTURE_2D,r??null),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),e.generateMipmap(e.TEXTURE_2D),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR_MIPMAP_LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),r})(e,a);const o=this.map.mapLibreMap,[n,s]=i.position,h=o.queryTerrainElevation([n,s])??0,m=MercatorCoordinate.fromLngLat([n,s],h),c=(this.config.size??44)/2*(1/(512*2**o.getZoom())),g=degreesToRadians(i.heading),[d,l]=[Math.sin(g)*c,Math.cos(g)*c];e.useProgram(r.program),e.uniformMatrix4fv(r.uniforms.matrix,!1,((e,{x:t,y:r,z:i})=>{const a=Float32Array.from(e);for(let o=0;o<4;o++)a[12+o]=e[o]*t+e[4+o]*r+e[8+o]*i+e[12+o];return a})(t.defaultProjectionData.mainMatrix,m)),e.uniform2f(r.uniforms.right,l,d),e.uniform2f(r.uniforms.forward,d,-l),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,r.texture??null),e.uniform1i(r.uniforms.image,0),e.bindBuffer(e.ARRAY_BUFFER,r.corners),e.enableVertexAttribArray(r.corner),e.vertexAttribPointer(r.corner,2,e.FLOAT,!1,0,0),e.disable(e.DEPTH_TEST),e.enable(e.BLEND),e.blendFunc(e.ONE,e.ONE_MINUS_SRC_ALPHA),e.drawArrays(e.TRIANGLE_STRIP,0,4)}releaseTexture(){this.drawing?.texture&&this.gl?.deleteTexture(this.drawing.texture),this.drawing&&(this.drawing.texture=void 0)}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)}},DEFAULT_CAMERA={speedKMH:200,pitch:70,aim:{mode:"instruction",fallbackMetersAhead:250},zoom:{mode:"instructions",instructionsAhead:2}},HEADING_SPAN_METERS=15,clampZoom=e=>clamp(e,11,15),RouteFlyover=class{map;route;camera;onProgress;loop;onArrive;marker;animationFrame;lastFrameTime=0;traveledDistanceInMeters=0;speedKMH=springAt(0);motion;target;framedZoom;secondsSinceSurvey=0;viewport={width:1,height:1};flying=!1;pageCenterClampedToGround=!0;pagePadding;constructor(e,t){this.map=e,this.route=t.route,this.camera={...DEFAULT_CAMERA,...t.camera},this.onProgress=t.onProgress??(()=>{}),this.loop=t.loop??!0,this.onArrive=t.onArrive??(()=>{});const r=e.mapLibreMap,i=this.targetAnchor();this.motion={bearing:springAt(r.getBearing()),zoom:springAt(r.getZoom()),pitch:springAt(r.getPitch()),anchorX:springAt(i.x),anchorY:springAt(i.y),elevation:springAt(r.getCenterElevation()),markerHeading:springAt(0)},this.target={bearing:this.motion.bearing.value,zoom:this.motion.zoom.value,elevation:this.motion.elevation.value,markerHeading:0},this.framedZoom=this.target.zoom,this.readViewport(),r.on("resize",this.readViewport),this.marker=new PositionMarker(e,t.marker??{}),this.placeMarkerOutright()}get running(){return void 0!==this.animationFrame}start(){if(this.running)return;this.hasArrived()&&(this.traveledDistanceInMeters=0,this.flying=!1);const e=this.map.mapLibreMap;this.pageCenterClampedToGround=e.getCenterClampedToGround(),e.setCenterClampedToGround(!1),this.speedKMH=springAt(0),this.lastFrameTime=performance.now(),this.animationFrame=requestAnimationFrame(this.step)}stop(){void 0!==this.animationFrame&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=void 0,this.map.mapLibreMap.setCenterClampedToGround(this.pageCenterClampedToGround))}setRoute(e){this.route=e,this.traveledDistanceInMeters=0,this.flying=!1,this.running?this.placeCamera():this.placeMarkerOutright()}setCamera(e){this.camera={...this.camera,...e},this.flying&&!this.running&&this.placeCamera()}getCamera(){return{...this.camera}}setMarker(e){this.marker.apply(e)}seek(e){const t=this.routeLengthInMeters();this.traveledDistanceInMeters="number"==typeof e?t*clamp(e,0,1):clamp(this.distanceAt(e),0,t),this.placeCamera()}remove(){this.stop(),this.marker.remove();const e=this.map.mapLibreMap;e.off("resize",this.readViewport),this.pagePadding&&e.setPadding(this.pagePadding)}readViewport=()=>{const e=this.map.mapLibreMap.getCanvas();this.viewport={width:Math.max(e.clientWidth,1),height:Math.max(e.clientHeight,1)}};routeLengthInMeters(){return this.route.properties.summary.lengthInMeters}distanceAt(e){return"traveledDistanceInMeters"in e?e.traveledDistanceInMeters:getCoordinateAtRouteProgress(this.route,e)?.distanceInMeters??0}hasArrived(){return!this.loop&&this.traveledDistanceInMeters>=this.routeLengthInMeters()}step=e=>{const t=clamp((e-this.lastFrameTime)/1e3,0,.25);this.lastFrameTime=e;const r=this.speedKMH.value;this.speedKMH=springTowards(this.speedKMH,this.camera.speedKMH,t,2);const i=(r+this.speedKMH.value)/2/3.6;this.traveledDistanceInMeters=this.alongRoute(this.traveledDistanceInMeters+i*t);const a=this.hasArrived();if(this.placeCamera(t,a),a)return this.stop(),void this.onArrive();this.animationFrame=requestAnimationFrame(this.step)};coordinateAt(e){return getCoordinateAtRouteProgress(this.route,{traveledDistanceInMeters:e})}positionAt(e){return this.coordinateAt(this.alongRoute(this.traveledDistanceInMeters+e))?.position}metersAheadTo(e){const t=e-this.traveledDistanceInMeters;return this.loop?this.alongRoute(t):Math.max(t,0)}alongRoute(e){const t=this.routeLengthInMeters();return this.loop?t>0?(e%t+t)%t:0:clamp(e,0,t)}roadHeading(){const[e,t]=[-15,HEADING_SPAN_METERS].map(e=>this.coordinateAt(this.traveledDistanceInMeters+e)?.position);return e&&t?headingBetween(e,t):void 0}instructions(){return this.route.properties.guidance?.instructions??[]}instructionAhead(e=0){const t=this.instructions();if(!t.length)return;const r=t.findIndex(e=>e.routeOffsetInMeters>this.traveledDistanceInMeters),i=this.loop?0:t.length-1;return t[Math.min((-1===r?i:r)+e,t.length-1)]}pointIndexHere(){let e=0,t=this.route.geometry.coordinates.length-1;for(;e<t;){const r=Math.floor((e+t)/2),i=calculateProgressAtRoutePoint(this.route,r);void 0!==i&&i.distanceInMeters<this.traveledDistanceInMeters?e=r+1:t=r}return e}aimPosition(){const e=this.camera.aim;switch(e.mode){case"distance":return this.positionAt(e.metersAhead);case"points":{const t=this.route.geometry.coordinates,r=this.pointIndexHere()+e.pointsAhead;return t[this.loop?r%t.length:Math.min(r,t.length-1)]}case"instruction":{const t=this.instructionAhead(),r=t&&this.metersAheadTo(t.routeOffsetInMeters);return void 0===r?this.positionAt(e.fallbackMetersAhead):this.positionAt(r)}}}modeZoom(e){const t=this.camera.zoom;if("fixed"===t.mode)return t.zoom;if("speed"===t.mode){const e=Math.log2(Math.max(this.camera.speedKMH,1)/90);return clampZoom(14-1*e)}const r=this.instructionAhead(Math.max(t.instructionsAhead,1)-1);if(!r)return clampZoom(14);const i=this.metersAheadTo(r.routeOffsetInMeters)*Math.cos(degreesToRadians(Math.min(this.camera.pitch,85)));return clampZoom(((e,t,r)=>zoomForMetersPerPixel(Math.max(e,1)/Math.max(t,1),r))(i,.6*this.viewport.height,e))}clearanceZoom(e,t,r){const i=degreesToRadians(Math.min(this.camera.pitch,85)),a=((e,t)=>cameraDistanceInPixels(t)*((e,t)=>78271.51696484374*Math.cos(degreesToRadians(t))/2**e)(e,t.latitudeDegrees))(this.motion.zoom.value,r)*Math.sin(i)/1e3,o=this.motion.bearing.value+180;let n=200;for(let s=0;s<4;s++){const r=s/4,i=destination(e,a*(1-r),o).geometry.coordinates,h=this.groundAt(i);void 0!==h&&(n=Math.max(n,200+(h-t)/(1-r)))}return((e,t)=>zoomForMetersPerPixel(Math.max(e,1)/cameraDistanceInPixels(t),t.latitudeDegrees))(n/Math.cos(i),r)}groundAt([e,t]){return this.map.mapLibreMap.queryTerrainElevation([e,t])??void 0}survey(e){const t={latitudeDegrees:e[1],viewportHeight:this.viewport.height,fieldOfViewDegrees:this.map.mapLibreMap.getVerticalFieldOfView()},r=this.aimPosition();!r||r[0]===e[0]&&r[1]===e[1]||(this.target.bearing=((e,t)=>Math.abs(shortestTurn(e,t))>2.5?t:e)(this.target.bearing,headingBetween(e,r))),this.target.markerHeading=this.roadHeading()??this.target.markerHeading;const i=this.modeZoom(t.latitudeDegrees),a="instructions"===this.camera.zoom.mode?.2:0;Math.abs(i-this.framedZoom)>a&&(this.framedZoom=i);const o=this.groundAt(e);void 0===o?(this.map.mapLibreMap.getTerrain()||(this.target.elevation=0),this.target.zoom=this.framedZoom):(this.target.elevation=o,this.target.zoom=Math.min(this.framedZoom,this.clearanceZoom(e,o,t)))}targetAnchor(){return this.camera.anchor??anchorForPitch(this.camera.pitch)}ease(e){const{motion:t,target:r}=this,i=this.targetAnchor(),a=(t,r,i)=>springTowards(t,r,e,i);this.motion={bearing:springTowardsBearing(t.bearing,r.bearing,e,2,22),markerHeading:springTowardsBearing(t.markerHeading,r.markerHeading,e,.5),zoom:a(t.zoom,r.zoom,3),elevation:a(t.elevation,r.elevation,.6),pitch:a(t.pitch,this.camera.pitch,1.5),anchorX:a(t.anchorX,i.x,1.5),anchorY:a(t.anchorY,i.y,1.5)}}settle(){const e=this.targetAnchor();this.motion={bearing:springAt(this.target.bearing),markerHeading:springAt(this.target.markerHeading),zoom:springAt(this.target.zoom),elevation:springAt(this.target.elevation),pitch:springAt(this.camera.pitch),anchorX:springAt(e.x),anchorY:springAt(e.y)}}placeMarkerOutright(){const e=this.positionAt(0);this.target.markerHeading=this.roadHeading()??this.target.markerHeading,this.motion.markerHeading=springAt(this.target.markerHeading),e&&this.marker.place(e,this.target.markerHeading)}placeCamera(e,t=!1){const r=this.coordinateAt(this.traveledDistanceInMeters);if(!r)return;const i=void 0!==e&&this.flying;this.secondsSinceSurvey+=e??0;const a=!i||t||this.secondsSinceSurvey>=.2;a&&(this.secondsSinceSurvey=0,this.survey(r.position)),i?this.ease(e):this.settle(),this.flying=!0;const[o,n]=r.position,{bearing:s,zoom:h,pitch:m,elevation:c,anchorX:g,anchorY:d,markerHeading:l}=this.motion,p=this.map.mapLibreMap;this.pagePadding??=p.getPadding(),p.jumpTo({center:[o,n],elevation:c.value,bearing:s.value,pitch:m.value,zoom:h.value,padding:anchorPadding({x:g.value,y:d.value},this.viewport)}),this.marker.place(r.position,l.value),a&&this.onProgress({traveledDistanceInMeters:r.distanceInMeters,traveledTimeInSeconds:r.travelTimeInSeconds})}};export{RouteFlyover,anchorForPitch};
1
+ import{MERCATOR_UNITS_PER_EPSG3857_METER,localFrameMatrix}from"@tomtom-org/maps-sdk/map";import{bearing,bearingToAzimuth,degreesToRadians,destination}from"@turf/turf";import{calculateProgressAtRoutePoint,generateId,getCoordinateAtRouteProgress}from"@tomtom-org/maps-sdk/core";import{MercatorCoordinate}from"maplibre-gl";var headingBetween=(e,t)=>bearingToAzimuth(bearing(e,t)),shortestTurn=(e,t)=>(t-e+540)%360-180,clamp=(e,t,r)=>Math.min(Math.max(e,t),r),springAt=e=>({value:e,velocity:0}),springTowards=(e,t,r,i,a=Number.POSITIVE_INFINITY)=>{const o=4/i,n=2*a/o,s=clamp(e.value-t,-n,n);if(Math.abs(s)<1e-6&&Math.abs(e.velocity)<1e-6)return springAt(t);const h=(e.velocity+o*s)*r,m=Math.exp(-o*r);return{value:e.value-s+(s+h)*m,velocity:(e.velocity-o*h)*m}},springTowardsBearing=(e,t,r,i,a)=>{const o=springTowards(e,e.value+shortestTurn(e.value,t),r,i,a);return{value:(o.value+360)%360,velocity:o.velocity}},clampFraction=e=>clamp(e,.05,.95),anchorForPitch=e=>({x:.5,y:.5+clamp(e,0,60)/60*.16666666666666663}),anchorPadding=(e,t)=>{const r=clampFraction(e.x)-.5,i=clampFraction(e.y)-.5,a=(e,t)=>Math.max(2*e*t,0);return{left:a(r,t.width),right:a(-r,t.width),top:a(i,t.height),bottom:a(-i,t.height)}},EQUATOR_METERS_PER_PIXEL=1/(512*MERCATOR_UNITS_PER_EPSG3857_METER),epsg3857MetersPerPixel=e=>EQUATOR_METERS_PER_PIXEL/2**e,metersPerPixel=(e,t)=>epsg3857MetersPerPixel(e)*Math.cos(degreesToRadians(t)),zoomForMetersPerPixel=(e,t)=>Math.log2(metersPerPixel(0,t)/e),cameraDistanceInPixels=({viewportHeight:e,fieldOfViewDegrees:t})=>.5*e/Math.tan(degreesToRadians(t)/2),svgElement=(e,t)=>{const r=document.createElementNS("http://www.w3.org/2000/svg",e);for(const[i,a]of Object.entries(t))r.setAttribute(i,a);return r},svgDataUri=e=>`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,compile=(e,t,r)=>{const i=e.createShader(t);if(!i)throw new Error("The fly-over marker could not create a shader");return e.shaderSource(i,r),e.compileShader(i),i},PositionMarker=class{map;id=`tomtom-flyover-marker-${generateId()}`;type="custom";renderingMode="2d";config={};placed;raster;gl;drawing;customLayer;constructor(e,t){this.map=e,this.customLayer=e.addCustomLayer(this),this.apply(t)}apply(e){this.config=e;const t=e.size??44;var r;(async(e,t)=>{const r=((e,t)=>{const r="arrow"===e.type?svgDataUri((({fillColor:e="#105287",outlineColor:t="white",outlineOpacity:r=1},i)=>{const a=svgElement("svg",{viewBox:"0 0 24 24",width:String(i),height:String(i)});return a.append(svgElement("path",{d:"M12 3l8 18-8-4-8 4z",fill:e,stroke:t,"stroke-opacity":String(r),"stroke-width":"3","stroke-linejoin":"round","paint-order":"stroke"})),(new XMLSerializer).serializeToString(a)})(e,t)):e.image;if("string"!=typeof r)return r;const i=new Image;return i.crossOrigin="anonymous",i.src=r.includes("<svg")?svgDataUri(r):r,i})(e,t);await r.decode();const i=document.createElement("canvas");i.width=t,i.height=t;const a=t/Math.max(r.naturalWidth,r.naturalHeight,1),[o,n]=[r.naturalWidth*a,r.naturalHeight*a];return i.getContext("2d")?.drawImage(r,(t-o)/2,(t-n)/2,o,n),i})(e.image??{type:"arrow"},(r=t,Math.min(2**Math.ceil(Math.log2(Math.max(r*devicePixelRatio,1))),512))).then(t=>{this.config===e&&(this.raster=t,this.releaseTexture(),this.map.mapLibreMap.triggerRepaint())}).catch(e=>console.warn("The fly-over marker image could not be loaded",e)),this.map.mapLibreMap.triggerRepaint()}place(e,t){this.placed={position:e,heading:t},this.map.mapLibreMap.triggerRepaint()}remove(){this.customLayer.remove()}onAdd(e,t){this.gl=t,this.drawing=(e=>{const t=e.createProgram(),r=e.createBuffer();if(!t||!r)throw new Error("The fly-over marker could not create its program");return e.attachShader(t,compile(e,e.VERTEX_SHADER,"\nuniform mat4 u_matrix;\nuniform vec2 u_right;\nuniform vec2 u_forward;\nattribute vec2 a_corner;\nvarying vec2 v_uv;\nvoid main() {\n v_uv = vec2(a_corner.x, -a_corner.y) * 0.5 + 0.5;\n gl_Position = u_matrix * vec4(u_right * a_corner.x + u_forward * a_corner.y, 0.0, 1.0);\n}")),e.attachShader(t,compile(e,e.FRAGMENT_SHADER,"\nprecision mediump float;\nuniform sampler2D u_image;\nvarying vec2 v_uv;\nvoid main() {\n gl_FragColor = texture2D(u_image, v_uv);\n}")),e.linkProgram(t),e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),e.STATIC_DRAW),{program:t,corners:r,texture:void 0,uniforms:{matrix:e.getUniformLocation(t,"u_matrix"),right:e.getUniformLocation(t,"u_right"),forward:e.getUniformLocation(t,"u_forward"),image:e.getUniformLocation(t,"u_image")},corner:e.getAttribLocation(t,"a_corner")}})(t)}onRemove(e,t){this.releaseTexture(),this.drawing&&(t.deleteProgram(this.drawing.program),t.deleteBuffer(this.drawing.corners)),this.drawing=void 0,this.gl=void 0}render(e,t){const{drawing:r,placed:i,raster:a}=this;if(!r||!i||!a||!1===this.config.visible)return;r.texture??=((e,t)=>{const r=e.createTexture()??void 0;return e.bindTexture(e.TEXTURE_2D,r??null),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),e.generateMipmap(e.TEXTURE_2D),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR_MIPMAP_LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),r})(e,a);const o=this.map.mapLibreMap,[n,s]=i.position,h=o.queryTerrainElevation([n,s])??0,m=localFrameMatrix(t.defaultProjectionData.mainMatrix,[n,s],{altitude:MercatorCoordinate.fromLngLat([0,0],h).z/MERCATOR_UNITS_PER_EPSG3857_METER,units:"mercatorMeters",centerLatitude:o.getCenter().lat}),c=(this.config.size??44)/2*epsg3857MetersPerPixel(o.getZoom()),g=degreesToRadians(i.heading),[d,l]=[Math.sin(g)*c,Math.cos(g)*c];e.useProgram(r.program),e.uniformMatrix4fv(r.uniforms.matrix,!1,Float32Array.from(m)),e.uniform2f(r.uniforms.right,l,-d),e.uniform2f(r.uniforms.forward,d,l),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,r.texture??null),e.uniform1i(r.uniforms.image,0),e.bindBuffer(e.ARRAY_BUFFER,r.corners),e.enableVertexAttribArray(r.corner),e.vertexAttribPointer(r.corner,2,e.FLOAT,!1,0,0),e.disable(e.DEPTH_TEST),e.enable(e.BLEND),e.blendFunc(e.ONE,e.ONE_MINUS_SRC_ALPHA),e.drawArrays(e.TRIANGLE_STRIP,0,4)}releaseTexture(){this.drawing?.texture&&this.gl?.deleteTexture(this.drawing.texture),this.drawing&&(this.drawing.texture=void 0)}},DEFAULT_CAMERA={speedKMH:200,pitch:70,aim:{mode:"instruction",fallbackMetersAhead:250},zoom:{mode:"instructions",instructionsAhead:2}},HEADING_SPAN_METERS=15,clampZoom=e=>clamp(e,11,15),RouteFlyover=class{map;route;camera;onProgress;loop;onArrive;marker;animationFrame;lastFrameTime=0;traveledDistanceInMeters=0;speedKMH=springAt(0);motion;target;framedZoom;secondsSinceSurvey=0;viewport={width:1,height:1};flying=!1;pageCenterClampedToGround=!0;pagePadding;constructor(e,t){this.map=e,this.route=t.route,this.camera={...DEFAULT_CAMERA,...t.camera},this.onProgress=t.onProgress??(()=>{}),this.loop=t.loop??!0,this.onArrive=t.onArrive??(()=>{});const r=e.mapLibreMap,i=this.targetAnchor();this.motion={bearing:springAt(r.getBearing()),zoom:springAt(r.getZoom()),pitch:springAt(r.getPitch()),anchorX:springAt(i.x),anchorY:springAt(i.y),elevation:springAt(r.getCenterElevation()),markerHeading:springAt(0)},this.target={bearing:this.motion.bearing.value,zoom:this.motion.zoom.value,elevation:this.motion.elevation.value,markerHeading:0},this.framedZoom=this.target.zoom,this.readViewport(),r.on("resize",this.readViewport),this.marker=new PositionMarker(e,t.marker??{}),this.placeMarkerOutright()}get running(){return void 0!==this.animationFrame}start(){if(this.running)return;this.hasArrived()&&(this.traveledDistanceInMeters=0,this.flying=!1);const e=this.map.mapLibreMap;this.pageCenterClampedToGround=e.getCenterClampedToGround(),e.setCenterClampedToGround(!1),this.speedKMH=springAt(0),this.lastFrameTime=performance.now(),this.animationFrame=requestAnimationFrame(this.step)}stop(){void 0!==this.animationFrame&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=void 0,this.map.mapLibreMap.setCenterClampedToGround(this.pageCenterClampedToGround))}setRoute(e){this.route=e,this.traveledDistanceInMeters=0,this.flying=!1,this.running?this.placeCamera():this.placeMarkerOutright()}setCamera(e){this.camera={...this.camera,...e},this.flying&&!this.running&&this.placeCamera()}getCamera(){return{...this.camera}}setMarker(e){this.marker.apply(e)}seek(e){const t=this.routeLengthInMeters();this.traveledDistanceInMeters="number"==typeof e?t*clamp(e,0,1):clamp(this.distanceAt(e),0,t),this.placeCamera()}remove(){this.stop(),this.marker.remove();const e=this.map.mapLibreMap;e.off("resize",this.readViewport),this.pagePadding&&e.setPadding(this.pagePadding)}readViewport=()=>{const e=this.map.mapLibreMap.getCanvas();this.viewport={width:Math.max(e.clientWidth,1),height:Math.max(e.clientHeight,1)}};routeLengthInMeters(){return this.route.properties.summary.lengthInMeters}distanceAt(e){return"traveledDistanceInMeters"in e?e.traveledDistanceInMeters:getCoordinateAtRouteProgress(this.route,e)?.distanceInMeters??0}hasArrived(){return!this.loop&&this.traveledDistanceInMeters>=this.routeLengthInMeters()}step=e=>{const t=clamp((e-this.lastFrameTime)/1e3,0,.25);this.lastFrameTime=e;const r=this.speedKMH.value;this.speedKMH=springTowards(this.speedKMH,this.camera.speedKMH,t,2);const i=(r+this.speedKMH.value)/2/3.6;this.traveledDistanceInMeters=this.alongRoute(this.traveledDistanceInMeters+i*t);const a=this.hasArrived();if(this.placeCamera(t,a),a)return this.stop(),void this.onArrive();this.animationFrame=requestAnimationFrame(this.step)};coordinateAt(e){return getCoordinateAtRouteProgress(this.route,{traveledDistanceInMeters:e})}positionAt(e){return this.coordinateAt(this.alongRoute(this.traveledDistanceInMeters+e))?.position}metersAheadTo(e){const t=e-this.traveledDistanceInMeters;return this.loop?this.alongRoute(t):Math.max(t,0)}alongRoute(e){const t=this.routeLengthInMeters();return this.loop?t>0?(e%t+t)%t:0:clamp(e,0,t)}roadHeading(){const[e,t]=[-15,HEADING_SPAN_METERS].map(e=>this.coordinateAt(this.traveledDistanceInMeters+e)?.position);return e&&t?headingBetween(e,t):void 0}instructions(){return this.route.properties.guidance?.instructions??[]}instructionAhead(e=0){const t=this.instructions();if(!t.length)return;const r=t.findIndex(e=>e.routeOffsetInMeters>this.traveledDistanceInMeters),i=this.loop?0:t.length-1;return t[Math.min((-1===r?i:r)+e,t.length-1)]}pointIndexHere(){let e=0,t=this.route.geometry.coordinates.length-1;for(;e<t;){const r=Math.floor((e+t)/2),i=calculateProgressAtRoutePoint(this.route,r);void 0!==i&&i.distanceInMeters<this.traveledDistanceInMeters?e=r+1:t=r}return e}aimPosition(){const e=this.camera.aim;switch(e.mode){case"distance":return this.positionAt(e.metersAhead);case"points":{const t=this.route.geometry.coordinates,r=this.pointIndexHere()+e.pointsAhead;return t[this.loop?r%t.length:Math.min(r,t.length-1)]}case"instruction":{const t=this.instructionAhead(),r=t&&this.metersAheadTo(t.routeOffsetInMeters);return void 0===r?this.positionAt(e.fallbackMetersAhead):this.positionAt(r)}}}modeZoom(e){const t=this.camera.zoom;if("fixed"===t.mode)return t.zoom;if("speed"===t.mode){const e=Math.log2(Math.max(this.camera.speedKMH,1)/90);return clampZoom(14-1*e)}const r=this.instructionAhead(Math.max(t.instructionsAhead,1)-1);if(!r)return clampZoom(14);const i=this.metersAheadTo(r.routeOffsetInMeters)*Math.cos(degreesToRadians(Math.min(this.camera.pitch,85)));return clampZoom(((e,t,r)=>zoomForMetersPerPixel(Math.max(e,1)/Math.max(t,1),r))(i,.6*this.viewport.height,e))}clearanceZoom(e,t,r){const i=degreesToRadians(Math.min(this.camera.pitch,85)),a=((e,t)=>cameraDistanceInPixels(t)*metersPerPixel(e,t.latitudeDegrees))(this.motion.zoom.value,r)*Math.sin(i)/1e3,o=this.motion.bearing.value+180;let n=200;for(let s=0;s<4;s++){const r=s/4,i=destination(e,a*(1-r),o).geometry.coordinates,h=this.groundAt(i);void 0!==h&&(n=Math.max(n,200+(h-t)/(1-r)))}return((e,t)=>zoomForMetersPerPixel(Math.max(e,1)/cameraDistanceInPixels(t),t.latitudeDegrees))(n/Math.cos(i),r)}groundAt([e,t]){return this.map.mapLibreMap.queryTerrainElevation([e,t])??void 0}survey(e){const t={latitudeDegrees:e[1],viewportHeight:this.viewport.height,fieldOfViewDegrees:this.map.mapLibreMap.getVerticalFieldOfView()},r=this.aimPosition();!r||r[0]===e[0]&&r[1]===e[1]||(this.target.bearing=((e,t)=>Math.abs(shortestTurn(e,t))>2.5?t:e)(this.target.bearing,headingBetween(e,r))),this.target.markerHeading=this.roadHeading()??this.target.markerHeading;const i=this.modeZoom(t.latitudeDegrees),a="instructions"===this.camera.zoom.mode?.2:0;Math.abs(i-this.framedZoom)>a&&(this.framedZoom=i);const o=this.groundAt(e);void 0===o?(this.map.mapLibreMap.getTerrain()||(this.target.elevation=0),this.target.zoom=this.framedZoom):(this.target.elevation=o,this.target.zoom=Math.min(this.framedZoom,this.clearanceZoom(e,o,t)))}targetAnchor(){return this.camera.anchor??anchorForPitch(this.camera.pitch)}ease(e){const{motion:t,target:r}=this,i=this.targetAnchor(),a=(t,r,i)=>springTowards(t,r,e,i);this.motion={bearing:springTowardsBearing(t.bearing,r.bearing,e,2,22),markerHeading:springTowardsBearing(t.markerHeading,r.markerHeading,e,.5),zoom:a(t.zoom,r.zoom,3),elevation:a(t.elevation,r.elevation,.6),pitch:a(t.pitch,this.camera.pitch,1.5),anchorX:a(t.anchorX,i.x,1.5),anchorY:a(t.anchorY,i.y,1.5)}}settle(){const e=this.targetAnchor();this.motion={bearing:springAt(this.target.bearing),markerHeading:springAt(this.target.markerHeading),zoom:springAt(this.target.zoom),elevation:springAt(this.target.elevation),pitch:springAt(this.camera.pitch),anchorX:springAt(e.x),anchorY:springAt(e.y)}}placeMarkerOutright(){const e=this.positionAt(0);this.target.markerHeading=this.roadHeading()??this.target.markerHeading,this.motion.markerHeading=springAt(this.target.markerHeading),e&&this.marker.place(e,this.target.markerHeading)}placeCamera(e,t=!1){const r=this.coordinateAt(this.traveledDistanceInMeters);if(!r)return;const i=void 0!==e&&this.flying;this.secondsSinceSurvey+=e??0;const a=!i||t||this.secondsSinceSurvey>=.2;a&&(this.secondsSinceSurvey=0,this.survey(r.position)),i?this.ease(e):this.settle(),this.flying=!0;const[o,n]=r.position,{bearing:s,zoom:h,pitch:m,elevation:c,anchorX:g,anchorY:d,markerHeading:l}=this.motion,p=this.map.mapLibreMap;this.pagePadding??=p.getPadding(),p.jumpTo({center:[o,n],elevation:c.value,bearing:s.value,pitch:m.value,zoom:h.value,padding:anchorPadding({x:g.value,y:d.value},this.viewport)}),this.marker.place(r.position,l.value),a&&this.onProgress({traveledDistanceInMeters:r.distanceInMeters,traveledTimeInSeconds:r.travelTimeInSeconds})}};export{RouteFlyover,anchorForPitch};
@@ -12,7 +12,7 @@
12
12
  },
13
13
  {
14
14
  "name": "maplibre-gl",
15
- "version": "6.13.0",
15
+ "version": "6.15.0",
16
16
  "license": "BSD-3-Clause",
17
17
  "bundled": false,
18
18
  "description": "BSD licensed community fork of mapbox-gl, a WebGL interactive maps library",
package/package.json CHANGED
@@ -1,28 +1,28 @@
1
1
  {
2
2
  "name": "@tomtom-org/maps-sdk-plugin-flyover",
3
3
  "author": "TomTom International B.V.",
4
- "license": "See in LICENSE.txt",
5
- "version": "0.0.7",
4
+ "license": "SEE LICENSE IN LICENSE.txt",
5
+ "version": "0.1.1",
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",
9
9
  "sideEffects": false,
10
10
  "peerDependencies": {
11
- "@tomtom-org/maps-sdk": ">=0.64.0 <1.0.0 || >=1.0.0-rc.0 <1.0.0",
11
+ "@tomtom-org/maps-sdk": "^1.0.0-rc.4",
12
12
  "@turf/turf": "^7.4.0",
13
- "maplibre-gl": "^6.13.0"
13
+ "maplibre-gl": "^6.15.0"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@tsconfig/recommended": "^1.0.13",
17
17
  "@types/geojson": "^7946.0.16",
18
- "@types/node": "^26.6.4",
19
- "@vitest/coverage-v8": "^5.0.3",
20
- "jsdom": "^30.1.2",
21
- "rolldown": "^1.2.13",
18
+ "@types/node": "^26.6.5",
19
+ "@vitest/coverage-v8": "^5.0.5",
20
+ "jsdom": "^30.1.3",
21
+ "rolldown": "^1.2.14",
22
22
  "typescript": "^6.0.3",
23
- "vite": "8.3.3",
24
- "vitest": "^5.0.3",
25
- "@tomtom-org/maps-sdk": "1.0.0-rc.3"
23
+ "vite": "^8.3.4",
24
+ "vitest": "^5.0.5",
25
+ "@tomtom-org/maps-sdk": "1.0.0-rc.5"
26
26
  },
27
27
  "files": [
28
28
  "dist",