├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── README.md ├── babel.config.json ├── docs ├── 257.796284dff4ae654c.js ├── 3rdpartylicenses.txt ├── 619.bff45a3b31716439.js ├── common.c9b086c64c41730d.js ├── favicon.ico ├── index.html ├── layers-2x.9859cd1231006a4a.png ├── layers-2x.png ├── layers.ef6db8722c2c3f9a.png ├── layers.png ├── main.732b92ddeb551656.js ├── marker-icon-2x.png ├── marker-icon.d577052aa271e13f.png ├── marker-icon.png ├── marker-shadow.png ├── polyfills.3f82d8a50a378092.js ├── runtime.493c48cd1133d22f.js └── styles.bcbc9063eb661df4.css ├── jest.config.js ├── jest.preset.js ├── migrations.json ├── nx.json ├── package-lock.json ├── package.json ├── packages ├── .gitkeep ├── annotation-plugin │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── annotation-plugin.spec.ts │ │ │ ├── annotation-plugin.ts │ │ │ ├── annotation.worker.ts │ │ │ ├── line-diff.ts │ │ │ ├── popup-component.ts │ │ │ └── utils.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── core │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── data-providers │ │ │ ├── errors │ │ │ │ ├── index.ts │ │ │ │ └── unauthorized.ts │ │ │ └── index.ts │ │ │ ├── libre-routing.model.ts │ │ │ ├── libre-routing.ts │ │ │ └── utils │ │ │ ├── dispatcher.ts │ │ │ ├── index.ts │ │ │ ├── random.ts │ │ │ └── requester.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── here-data-provider │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── here-data-provider.spec.ts │ │ │ ├── here-data-provider.ts │ │ │ ├── here.executor.ts │ │ │ ├── here.worker.ts │ │ │ └── utils │ │ │ └── select-route-strategy.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── libre-routing-playground │ ├── .eslintrc.json │ ├── jest.config.js │ ├── project.json │ ├── src │ │ ├── app │ │ │ ├── app.component.html │ │ │ ├── app.component.scss │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── cs.layer.ts │ │ │ └── leaflet │ │ │ │ ├── leaflet.component.html │ │ │ │ ├── leaflet.component.scss │ │ │ │ └── leaflet.component.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── env.shared.ts │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.scss │ │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.editor.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tsconfig.worker.json ├── maplibre-engine │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── maplibre-engine.spec.ts │ │ │ ├── maplibre-engine.ts │ │ │ ├── projector-plugin │ │ │ ├── index.ts │ │ │ ├── projector.plugin.defaults.ts │ │ │ ├── projector.plugin.ts │ │ │ └── projector.plugin.types.ts │ │ │ └── utils │ │ │ └── debounce.util.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json └── maplibre-layers-event-propagator │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ ├── index.ts │ └── lib │ │ ├── maplibre-layers-event-propagator.spec.ts │ │ └── maplibre-layers-event-propagator.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── readme └── images │ └── arch.jpg ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json └── tsconfig.base.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nrwl/nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nrwl/nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | .angular 42 | 43 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnyRouting 2 | 3 | A full featured(performance focused), modular, light directions plugin for all maps engines(MapLibre GL JS, Mapbox GL JS, Leaflet, OpenLayers etc.). 4 | 5 | ## Demo 6 | 7 | [Demo App](https://marucjmar.github.io/any-routing/) 8 | 9 | ## Installation 10 | 11 | ```js 12 | npm i --save @any-routing/core @any-routing/maplibre-engine @any-routing/annotation-plugin 13 | ``` 14 | 15 | ## Example 16 | 17 | ```js 18 | import { Map } from 'maplibre-gl'; 19 | import { AnyRouting } from '@any-routing/core'; 20 | import { HereProvider, HereRoutingData } from '@any-routing/here-data-provider'; 21 | import { defaultMapLibreProjectorOptions, MapLibreProjector } from '@any-routing/maplibre-engine'; 22 | import { AnnotationPlugin } from '@any-routing/annotation-plugin'; 23 | 24 | const map = new Map({...}); 25 | 26 | const dataProvider = new HereProvider({ apiKey: '1234' }); 27 | const projector = new MapLibreProjector({ 28 | ...defaultMapLibreProjectorOptions, 29 | map 30 | }); 31 | 32 | const routing = new AnyRouting({ 33 | dataProvider, 34 | waypointsSyncStrategy: 'none', 35 | plugins: [projector, new AnnotationPlugin({ map })], 36 | }); 37 | 38 | routing.on('routesFound', console.log); 39 | routing.on('routeSelected', console.log); 40 | 41 | map.on('load', () => { 42 | routing.initialize(); 43 | routing.setWaypoints([ 44 | { position: { lat: 49.9539315, lng: 18.8531001 }, properties: { label: 'A' } }, 45 | { position: { lng: 21.01178, lat: 52.22977 }, properties: { label: 'B' } }, 46 | ]); 47 | 48 | routing.recalculateRoute({ fitViewToData: true }); 49 | }); 50 | ``` 51 | 52 | ## Supported data providers 53 | 54 | - [x] [Here API](https://www.here.com/) 55 | - [ ] [MapBox API](https://docs.mapbox.com/help/glossary/directions-api/) 56 | - [ ] [Google API](https://developers.google.com/maps/documentation/directions/overview) 57 | - [ ] [OpenStreetMap API](https://wiki.openstreetmap.org/wiki/Routing) 58 | 59 | ## Supported Map engine 60 | 61 | - [x] [MapLibre GL JS](https://maplibre.org/maplibre-gl-js-docs/api/) 62 | - [x] [Mapbox GL JS](https://docs.mapbox.com/mapbox-gl-js/guides/) 63 | - [ ] [Leaflet](https://leafletjs.com/) 64 | - [ ] [OpenLayers](https://openlayers.org/) 65 | 66 | :exclamation: Help grow the library by sharing your providers 67 | 68 | ## Architecture 69 | 70 | ![Architecture](./readme/images/arch.jpg) 71 | 72 | ## AnyRouting Class 73 | 74 | ### Config 75 | 76 | | Property | Default | Description | 77 | | ----------------------- | ------- | ------------------------- | 78 | | `dataProvider`(Requred) | - | The request data provider | 79 | | `projector`(Requred) | - | The map projector | 80 | | `plugins` | `[]` | AnyRouting Plugins | 81 | 82 | ### Instance Properties 83 | 84 | | Property | Description | 85 | | --------- | -------------------------------------------- | 86 | | `map` | Return the MapLibre GL or Mapbox GL instance | 87 | | `options` | Return Config | 88 | 89 | ### Instance Methods 90 | 91 | | Method | Description | 92 | | ------------------------------ | ---------------------------------- | 93 | | `async recalculateRoute(opts)` | Calculate routes between waypoints | 94 | | `selectRoute(routeId)` | Select the alternative route | 95 | | `on(event, callback)` | Subscribe to specific event | 96 | | `off(event, callback)` | Unsubscribe from specific event | 97 | 98 | ### Events 99 | 100 | | Event | Description | Data | 101 | | ------------- | --------------------------- | ------------- | 102 | | `routesFound` | Fire when routes calculated | Provider data | 103 | 104 | ## Contribute 105 | 106 | [Nx](https://nx.dev/using-nx/nx-cli) CLI Required 107 | 108 | First install all depenencies by 109 | 110 | ```js 111 | npm i 112 | ``` 113 | 114 | ### Build library 115 | 116 | ```js 117 | npm run libre-routing:build 118 | ``` 119 | 120 | ### Run playground app 121 | 122 | ```js 123 | npm run start 124 | ``` 125 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "babelrcRoots": ["*"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/257.796284dff4ae654c.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var f,U={257:(f,m,g)=>{var v=6371008.8,d={centimeters:100*v,centimetres:100*v,degrees:v/111325,feet:3.28084*v,inches:39.37*v,kilometers:v/1e3,kilometres:v/1e3,meters:v,metres:v,miles:v/1609.344,millimeters:1e3*v,millimetres:1e3*v,nauticalmiles:v/1852,radians:1,yards:1.0936*v};function w(e,t,r){void 0===r&&(r={});var n={type:"Feature"};return(0===r.id||r.id)&&(n.id=r.id),r.bbox&&(n.bbox=r.bbox),n.properties=t||{},n.geometry=e,n}function C(e,t,r){if(void 0===r&&(r={}),!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(!$(e[0])||!$(e[1]))throw new Error("coordinates must contain numbers");return w({type:"Point",coordinates:e},t,r)}function _(e,t,r){if(void 0===r&&(r={}),e.length<2)throw new Error("coordinates must be an array of two or more positions");return w({type:"LineString",coordinates:e},t,r)}function N(e){return e%(2*Math.PI)*180/Math.PI}function M(e){return e%360*Math.PI/180}function $(e){return!isNaN(e)&&null!==e&&!Array.isArray(e)}function R(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 te(e){return"Feature"===e.type?e.geometry:e}function he(e,t){var r,n,a,i,o,l,u;for(n=1;n<=8;n*=2){for(r=[],i=!(q(a=e[e.length-1],t)&n),o=0;ot[2]&&(r|=2),e[1]t[3]&&(r|=8),r}function ge(e,t){var r=te(e),n=r.type,a="Feature"===e.type?e.properties:{},i=r.coordinates;switch(n){case"LineString":case"MultiLineString":var o=[];return"LineString"===n&&(i=[i]),i.forEach(function(l){!function ce(e,t,r){var o,l,u,s,h,n=e.length,a=q(e[0],t),i=[];for(r||(r=[]),o=1;o0&&((o[0][0]!==o[o.length-1][0]||o[0][1]!==o[o.length-1][1])&&o.push(o[0]),o.length>=4&&r.push(o))}return r}var ve=g(459);function B(e,t,r){if(null!==e)for(var n,a,i,o,l,u,s,P,h=0,y=0,L=e.type,E="FeatureCollection"===L,A="Feature"===L,Y=E?e.features.length:1,O=0;O=i&&o===a.length-1);o++){if(i>=t){var l=t-i;if(l){var u=T(a[o],a[o-1])-180;return Pe(a[o],l,u,r)}return C(a[o])}i+=oe(a[o],a[o+1],r)}return C(a[a.length-1])}function V(e,t){return void 0===t&&(t={}),function pe(e,t,r){var n=r,a=!1;return function de(e,t){H(e,function(r,n,a){var i=0;if(r.geometry){var o=r.geometry.type;if("Point"!==o&&"MultiPoint"!==o){var l,u=0,s=0,h=0;if(!1===B(r,function(y,P,L,E,A){if(void 0===l||n>u||E>s||A>h)return l=y,u=n,s=E,h=A,void(i=0);var Y=_([l,y],r.properties);if(!1===t(Y,n,a,A,i))return!1;i++,l=y}))return!1}}})}(e,function(i,o,l,u,s){n=!1===a&&void 0===r?i:t(n,i,o,l,u,s),a=!0}),n}(e,function(r,n){var a=n.geometry.coordinates;return r+oe(a[0],a[1],t)},0)}const ue=(e,t)=>Math.abs(e-t)<1e-5,W=e=>`${e[0].toFixed(6)},${e[1].toFixed(6)}`,le=(e=[])=>e[e.length-1];function _e(e){return _(ie(e).reduce((r,n)=>{const a=le(r);return(!a||!((e=[],t=[])=>ue(e[0],t[0])&&ue(e[1],t[1]))(a,n))&&r.push(n),r},[]),e.properties)}function Oe(e,t){return"vertical"==(Math.abs(e.localLineBearing)<45||Math.abs(e.localLineBearing)>135?"vertical":"horizontal")?t>0?"left":"right":Math.abs(t)<90?"bottom":"top"}function Re(e){return function Fe(e){return e.map((t,r)=>{const n=e.slice();n.splice(r,1);const a=function Ae(e,t){return t.map(r=>T(r.lngLat,e.lngLat)).reduce((r,n,a,{length:i})=>r+n/i,0)||0}(t,n);return{lngLat:t.lngLat,anchor:Oe(t,a),properties:t.properties}})}(e.map(t=>{let r=t;"MultiLineString"===t.geometry.type&&(r=_(t.geometry.coordinates[0]),t.geometry.coordinates.forEach((o,l)=>{if(0===l)return;const u=_(o);V(u)>V(r)&&(r=u)}));const n=V(r);return{lngLat:R(Q(r,.5*n)),localLineBearing:T(Q(r,.4*n),Q(r,.6*n)),properties:t.properties}}))}let X=[];(0,ve.p)({createChunks(e){X=function Ge(e=[]){const n=function qe(e){return e.reduce((t,r)=>(t[r.properties.routeId]?t[r.properties.routeId].geometry.coordinates=[...t[r.properties.routeId].geometry.coordinates,...r.geometry.coordinates]:t[r.properties.routeId]=r,t),[])}(Array.isArray(e)?e:e.features).map(_e);return function Ce(e){if(e.length<2)return e;const t=e.map(ie),r=new Map;return[].concat(...t).forEach(n=>{r.set(W(n),(r.get(W(n))||0)+1)}),t.map((n,a)=>function Le(e,t,r){const n=[[]];e.forEach(i=>{t.get(W(i))>1?n.push([]):le(n).push(i)});const a=n.filter(i=>i.length>0).reduce((i,o)=>o.length>i.length?o:i,[]);return _(0===a.length?e:a,r)}(n,r,e[a].properties))}(n)}(e.routesShapeGeojson.features)},recalculatePos({bbox:{ne:e,sw:t}}){const n=Re(X.map(i=>ge(i,[t.lng,t.lat,e.lng,e.lat])).filter(({geometry:{coordinates:i}})=>i.length>0));return{points:n,allInBbox:n.length===X.length}}})}},I={};function c(f){var m=I[f];if(void 0!==m)return m.exports;var g=I[f]={exports:{}};return U[f](g,g.exports,c),g.exports}c.m=U,c.x=()=>{var f=c.O(void 0,[76],()=>c(257));return c.O(f)},f=[],c.O=(m,g,v,d)=>{if(!g){var b=1/0;for(p=0;p=d)&&Object.keys(c.O).every(x=>c.O[x](g[S]))?g.splice(S--,1):(w=!1,d0&&f[p-1][2]>d;p--)f[p]=f[p-1];f[p]=[g,v,d]},c.d=(f,m)=>{for(var g in m)c.o(m,g)&&!c.o(f,g)&&Object.defineProperty(f,g,{enumerable:!0,get:m[g]})},c.f={},c.e=f=>Promise.all(Object.keys(c.f).reduce((m,g)=>(c.f[g](f,m),m),[])),c.u=f=>"common.c9b086c64c41730d.js",c.miniCssF=f=>{},c.o=(f,m)=>Object.prototype.hasOwnProperty.call(f,m),(()=>{var f;c.tt=()=>(void 0===f&&(f={createScriptURL:m=>m},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(f=trustedTypes.createPolicy("angular#bundler",f))),f)})(),c.tu=f=>c.tt().createScriptURL(f),c.p="",(()=>{var f={257:1};c.f.i=(d,p)=>{f[d]||importScripts(c.tu(c.p+c.u(d)))};var g=self.webpackChunklibre_routing_playground=self.webpackChunklibre_routing_playground||[],v=g.push.bind(g);g.push=d=>{var[p,b,w]=d;for(var S in b)c.o(b,S)&&(c.m[S]=b[S]);for(w&&w(c);p.length;)f[p.pop()]=1;v(d)}})(),(()=>{var f=c.x;c.x=()=>c.e(76).then(f)})(),c.x()})(); -------------------------------------------------------------------------------- /docs/619.bff45a3b31716439.js: -------------------------------------------------------------------------------- 1 | (()=>{var f,pe={952:f=>{const v="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",x=[62,-1,-1,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,63,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],l=typeof BigInt<"u"?BigInt:Number;function P(M){let c=M;return c&l(1)&&(c=~c),c>>=l(1),+c.toString()}function ue(M){let c="",m=l(M);for(;m>31;){const T=m&l(31)|l(32);c+=v[T],m>>=l(5)}return c+v[m]}function z(M){let c=l(M);const m=c<0;return c<<=l(1),m&&(c=~c),ue(c)}f.exports={encode:function I({precision:M=5,thirdDim:c=0,thirdDimPrecision:m=0,polyline:T}){const Z=10**M,U=10**m,Y=function J(M,c,m){if(M<0||M>15)throw new Error("precision out of range. Should be between 0 and 15");if(m<0||m>15)throw new Error("thirdDimPrecision out of range. Should be between 0 and 15");if(c<0||c>7||4===c||5===c)throw new Error("thirdDim should be between 0, 1, 2, 3, 6 or 7");const T=m<<7|c<<4|M;return ue(1)+ue(T)}(M,c,m),H=[];let re=l(0),j=l(0),D=l(0);return T.forEach(ne=>{const se=l(Math.round(ne[0]*Z));H.push(z(se-re)),re=se;const ee=l(Math.round(ne[1]*Z));if(H.push(z(ee-j)),j=ee,c){const ce=l(Math.round(ne[2]*U));H.push(z(ce-D)),D=ce}}),[...Y,...H].join("")},decode:function y(M){const c=function R(M){let c=l(0),m=l(0);const T=[];if(M.split("").forEach(Z=>{const U=l(function S(M){const c=M.charCodeAt(0);return x[c-45]}(Z));c|=(U&l(31))<0)throw new Error("Invalid encoding");return T}(M),m=function k(M,c){if(1!=+M.toString())throw new Error("Invalid format version");const m=+c.toString();return{precision:15&m,thirdDim:m>>4&7,thirdDimPrecision:m>>7&15}}(c[0],c[1]),T=10**m.precision,Z=10**m.thirdDimPrecision,{thirdDim:U}=m;let Y=0,H=0,re=0;const j=[];let D=2;for(;D{"use strict";var x=v(459);function _(n,t,e,r,o,i,a){try{var u=n[i](a),s=u.value}catch(p){return void e(p)}u.done?t(s):Promise.resolve(s).then(r,o)}function E(n){return function(){var t=this,e=arguments;return new Promise(function(r,o){var i=n.apply(t,e);function a(s){_(i,r,o,a,u,"next",s)}function u(s){_(i,r,o,a,u,"throw",s)}a(void 0)})}}var N=v(952),q=v(358);function J(n,t,e){if(void 0===e&&(e={}),n.length<2)throw new Error("coordinates must be an array of two or more positions");return function y(n,t,e){void 0===e&&(e={});var r={type:"Feature"};return(0===e.id||e.id)&&(r.id=e.id),e.bbox&&(r.bbox=e.bbox),r.properties=t||{},r.geometry=n,r}({type:"LineString",coordinates:n},t,e)}function z(n,t){void 0===t&&(t={});var e={type:"FeatureCollection"};return t.id&&(e.id=t.id),t.bbox&&(e.bbox=t.bbox),e.features=n,e}function ae(n,t,e){if(null!==n)for(var r,o,i,a,u,s,p,L,C=0,d=0,G=n.type,A="FeatureCollection"===G,K="Feature"===G,te=A?n.features.length:1,F=0;Fe[0]&&(t[0]=e[0]),t[1]>e[1]&&(t[1]=e[1]),t[2]r.maxBuffer&&(r.buffer[0].abort(),r.buffer.splice(0,1)),r.buffer.push(o);const a=yield fetch(t,{...e,signal:o.signal});if(r.cleanBuffer(o),200!==a.status)throw a;if(r.latestResponseId>i)throw new Error("Prev response");return r.pending=r.lastResponseId!==i,r.latestResponseId=i,yield a.json()})()}abortAllRequests(){this.buffer.forEach(t=>t.abort())}cleanBuffer(t){this.buffer=this.buffer.filter(e=>e!==t)}}const me=(n,t)=>n.some(e=>{const r=t[e.severity];return r&&("all"===r||r.includes(e.code))});function we(n,t){return q(n.map(([e,r])=>({x:e,y:r})),t,!0).map(e=>[+e.y.toFixed(6),+e.x.toFixed(6)])}const xe=new class Ce{constructor(){this.requester=new Me}request(t){var e=this;return E(function*(){try{const r=yield e.requester.request(t.url,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({}),...t.requestParams});if(!r.routes||0===r.routes.length||t.routeExcludeNotice&&((n,t)=>!!me(n.notices||[],t)||(n.routes||[]).some(e=>(e.sections||[]).some(r=>me(r.notices||[],t))))(r,t.routeExcludeNotice))throw new Error("No routes found");const o=r.routes.map((s,p)=>((n,t,e)=>{const{distance:r,cost:o,durationTime:i,waypoints:a,path:u,shape:s,shapePath:p,turnByTurnActions:C}=n.sections.reduce((d,L,G)=>{const A=(L.tolls||[]).reduce((V,O)=>(O.fares||[]).forEach(Q=>V+=(Q.convertedPrice||Q.price).value),0),K=L.summary?.duration||0,te=[];L.departure.place.originalLocation&&te.push(L.departure.place.location),L.arrival.place.originalLocation&&(n.sections.length>=2&&G===n.sections.length-1||1===n.sections.length)&&te.push(L.arrival.place.location);const F=function Fe(n){return(0,N.decode)(n).polyline}(L.polyline),ie=e.shapePolylinePrecision?we(F,e.shapePolylinePrecision):F,B=(L.turnByTurnActions||[]).map(V=>{const[O,Q]=F[V.offset];return{...V,offset:d.path.length+V.offset,position:{lat:O,lng:Q}}});let W=[];if("default"===e.mode&&e.geoJSONShapeSplitStrategies?.default&&e.geoJSONShapeSplitStrategies.default.length>0||"drag"===e.mode&&e.geoJSONShapeSplitStrategies?.drag&&e.geoJSONShapeSplitStrategies.drag.length>0){const V=L.spans?.length;W=L.spans?.reduce((O,Q,le)=>{const Se=F.slice(Q.offset,(le0)O[O.length-1].geometry.coordinates.push(...oe.slice(1,oe.length));else{const fe=J(oe,{waypoint:d.waypointIndex,routeId:t,isMarginalChunk:ge});O.push(fe)}return O},[])}else{const V=J(ie,{waypoint:d.waypointIndex,routeId:t});W.push(V)}return{distance:d.distance+L.summary?.length||0,durationTime:d.durationTime+K,cost:d.cost+A,waypoints:[...d.waypoints,...te],path:[...d.path,...F],turnByTurnActions:[...d.turnByTurnActions,...B],shape:z([...d.shape?.features||[],...W]),shapePath:[...d.shapePath,...ie],waypointIndex:"vehicle"===L.type&&n.sections[G+1]&&"vehicle"===n.sections[G+1].type?d.waypointIndex+1:d.waypointIndex}},{distance:0,cost:0,waypoints:[],path:[],durationTime:0,turnByTurnActions:[],shape:null,shapePath:[],waypointIndex:0});return{durationTime:i,distance:r,cost:o,path:u,arriveTime:new Date(n.sections[n.sections.length-1].arrival.time),departureTime:new Date(n.sections[0].departure.time),id:t,waypoints:a,label:n.routeLabels?n.routeLabels.map(d=>d.name.value).join(", "):void 0,shape:s,turnByTurnActions:C,shapePath:p,rawRoute:n}})(s,p,t)),i=function ke(n,t){return"fastest"===t?n.reduce(function(r,o){return r?.arriveTime.valueOf()null!=r.cost).reduce(function(r,o){return r?.cost[...s,...p.shape.features.map(C=>({...C,properties:{...C.properties,selected:i===C.properties.routeId}}))],[]));return{routesShapeBounds:Re(u),rawResponse:r,routes:o,selectedRouteId:i,routesShapeGeojson:u,version:performance.now(),latest:!e.requester.hasPendingRequests,mode:t.mode,requestOptions:t}}catch(r){if(r instanceof Response){const o=r,i=yield o.json();if(401===o.status)throw new Le(i)}throw r}})()}hasPendingRequests(){return this.requester.hasPendingRequests}abortAllRequests(){this.requester.abortAllRequests()}};(0,x.p)(xe)},358:(f,b,v)=>{var x;!function(){"use strict";function _(g,l){var y=g.x-l.x,S=g.y-l.y;return y*y+S*S}function E(g,l,y){var S=l.x,R=l.y,k=y.x-S,P=y.y-R;if(0!==k||0!==P){var I=((g.x-S)*k+(g.y-R)*P)/(k*k+P*P);I>1?(S=y.x,R=y.y):I>0&&(S+=k*I,R+=P*I)}return(k=g.x-S)*k+(P=g.y-R)*P}function q(g,l,y,S,R){for(var P,k=S,I=l+1;Ik&&(P=I,k=J)}k>S&&(P-l>1&&q(g,l,P,S,R),R.push(g[P]),y-P>1&&q(g,P,y,S,R))}function w(g,l){var y=g.length-1,S=[g[0]];return q(g,0,y,l,S),S.push(g[y]),S}function X(g,l,y){if(g.length<=2)return g;var S=void 0!==l?l*l:1;return g=y?g:function N(g,l){for(var R,y=g[0],S=[y],k=1,P=g.length;kl&&(S.push(R),y=R);return y!==R&&S.push(R),S}(g,S),w(g,S)}void 0!==(x=function(){return X}.call(b,v,b,f))&&(f.exports=x)}()}},ye={};function h(f){var b=ye[f];if(void 0!==b)return b.exports;var v=ye[f]={exports:{}};return pe[f](v,v.exports,h),v.exports}h.m=pe,h.x=()=>{var f=h.O(void 0,[76],()=>h(619));return h.O(f)},f=[],h.O=(b,v,x,_)=>{if(!v){var N=1/0;for(E=0;E=_)&&Object.keys(h.O).every(R=>h.O[R](v[w]))?v.splice(w--,1):(q=!1,_0&&f[E-1][2]>_;E--)f[E]=f[E-1];f[E]=[v,x,_]},h.d=(f,b)=>{for(var v in b)h.o(b,v)&&!h.o(f,v)&&Object.defineProperty(f,v,{enumerable:!0,get:b[v]})},h.f={},h.e=f=>Promise.all(Object.keys(h.f).reduce((b,v)=>(h.f[v](f,b),b),[])),h.u=f=>"common.c9b086c64c41730d.js",h.miniCssF=f=>{},h.o=(f,b)=>Object.prototype.hasOwnProperty.call(f,b),(()=>{var f;h.tt=()=>(void 0===f&&(f={createScriptURL:b=>b},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(f=trustedTypes.createPolicy("angular#bundler",f))),f)})(),h.tu=f=>h.tt().createScriptURL(f),h.p="",(()=>{var f={619:1};h.f.i=(_,E)=>{f[_]||importScripts(h.tu(h.p+h.u(_)))};var v=self.webpackChunklibre_routing_playground=self.webpackChunklibre_routing_playground||[],x=v.push.bind(v);v.push=_=>{var[E,N,q]=_;for(var w in N)h.o(N,w)&&(h.m[w]=N[w]);for(q&&q(h);E.length;)f[E.pop()]=1;x(_)}})(),(()=>{var f=h.x;h.x=()=>h.e(76).then(f)})(),h.x()})(); -------------------------------------------------------------------------------- /docs/common.c9b086c64c41730d.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunklibre_routing_playground=self.webpackChunklibre_routing_playground||[]).push([[76],{459:(D,M,A)=>{A.d(M,{p:()=>E});const w=Symbol("Comlink.proxy"),C=Symbol("Comlink.endpoint"),R=Symbol("Comlink.releaseProxy"),p=Symbol("Comlink.thrown"),k=e=>"object"==typeof e&&null!==e||"function"==typeof e,_=new Map([["proxy",{canHandle:e=>k(e)&&e[w],serialize(e){const{port1:t,port2:r}=new MessageChannel;return E(e,t),[r,[r]]},deserialize:e=>(e.start(),function N(e,t){return b(e,[],t)}(e))}],["throw",{canHandle:e=>k(e)&&p in e,serialize({value:e}){let t;return t=e instanceof Error?{isError:!0,value:{message:e.message,name:e.name,stack:e.stack}}:{isError:!1,value:e},[t,[]]},deserialize(e){throw e.isError?Object.assign(new Error(e.value.message),e.value):e.value}}]]);function E(e,t=self){t.addEventListener("message",function r(n){if(!n||!n.data)return;const{id:u,type:f,path:s}=Object.assign({path:[]},n.data),i=(n.data.argumentList||[]).map(d);let a;try{const o=s.slice(0,-1).reduce((c,m)=>c[m],e),l=s.reduce((c,m)=>c[m],e);switch(f){case"GET":a=l;break;case"SET":o[s.slice(-1)[0]]=d(n.data.value),a=!0;break;case"APPLY":a=l.apply(o,i);break;case"CONSTRUCT":a=function V(e){return Object.assign(e,{[w]:!0})}(new l(...i));break;case"ENDPOINT":{const{port1:c,port2:m}=new MessageChannel;E(e,m),a=function j(e,t){return S.set(e,t),e}(c,[c])}break;case"RELEASE":a=void 0;break;default:return}}catch(o){a={value:o,[p]:0}}Promise.resolve(a).catch(o=>({value:o,[p]:0})).then(o=>{const[l,c]=h(o);t.postMessage(Object.assign(Object.assign({},l),{id:u}),c),"RELEASE"===f&&(t.removeEventListener("message",r),L(t))})}),t.start&&t.start()}function L(e){(function O(e){return"MessagePort"===e.constructor.name})(e)&&e.close()}function y(e){if(e)throw new Error("Proxy has been released and is not useable")}function b(e,t=[],r=function(){}){let n=!1;const u=new Proxy(r,{get(f,s){if(y(n),s===R)return()=>g(e,{type:"RELEASE",path:t.map(i=>i.toString())}).then(()=>{L(e),n=!0});if("then"===s){if(0===t.length)return{then:()=>u};const i=g(e,{type:"GET",path:t.map(a=>a.toString())}).then(d);return i.then.bind(i)}return b(e,[...t,s])},set(f,s,i){y(n);const[a,o]=h(i);return g(e,{type:"SET",path:[...t,s].map(l=>l.toString()),value:a},o).then(d)},apply(f,s,i){y(n);const a=t[t.length-1];if(a===C)return g(e,{type:"ENDPOINT"}).then(d);if("bind"===a)return b(e,t.slice(0,-1));const[o,l]=P(i);return g(e,{type:"APPLY",path:t.map(c=>c.toString()),argumentList:o},l).then(d)},construct(f,s){y(n);const[i,a]=P(s);return g(e,{type:"CONSTRUCT",path:t.map(o=>o.toString()),argumentList:i},a).then(d)}});return u}function H(e){return Array.prototype.concat.apply([],e)}function P(e){const t=e.map(h);return[t.map(r=>r[0]),H(t.map(r=>r[1]))]}const S=new WeakMap;function h(e){for(const[t,r]of _)if(r.canHandle(e)){const[n,u]=r.serialize(e);return[{type:"HANDLER",name:t,value:n},u]}return[{type:"RAW",value:e},S.get(e)||[]]}function d(e){switch(e.type){case"HANDLER":return _.get(e.name).deserialize(e.value);case"RAW":return e.value}}function g(e,t,r){return new Promise(n=>{const u=function z(){return new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")}();e.addEventListener("message",function f(s){!s.data||!s.data.id||s.data.id!==u||(e.removeEventListener("message",f),n(s.data))}),e.start&&e.start(),e.postMessage(Object.assign({id:u},t),r)})}}}]); -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AnyRoutingPlayground 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/layers-2x.9859cd1231006a4a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/layers-2x.9859cd1231006a4a.png -------------------------------------------------------------------------------- /docs/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/layers-2x.png -------------------------------------------------------------------------------- /docs/layers.ef6db8722c2c3f9a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/layers.ef6db8722c2c3f9a.png -------------------------------------------------------------------------------- /docs/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/layers.png -------------------------------------------------------------------------------- /docs/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/marker-icon-2x.png -------------------------------------------------------------------------------- /docs/marker-icon.d577052aa271e13f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/marker-icon.d577052aa271e13f.png -------------------------------------------------------------------------------- /docs/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/marker-icon.png -------------------------------------------------------------------------------- /docs/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/docs/marker-shadow.png -------------------------------------------------------------------------------- /docs/runtime.493c48cd1133d22f.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e,i={},p={};function r(e){var t=p[e];if(void 0!==t)return t.exports;var a=p[e]={exports:{}};return i[e].call(a.exports,a,a.exports,r),a.exports}r.m=i,e=[],r.O=(t,a,f,u)=>{if(!a){var o=1/0;for(n=0;n=u)&&Object.keys(r.O).every(_=>r.O[_](a[l]))?a.splice(l--,1):(s=!1,u0&&e[n-1][2]>u;n--)e[n]=e[n-1];e[n]=[a,f,u]},r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var a in t)r.o(t,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},r.u=e=>e+"."+{257:"796284dff4ae654c",619:"bff45a3b31716439"}[e]+".js",r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:t=>t},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{r.b=document.baseURI||self.location.href;var e={121:0};r.O.j=f=>0===e[f];var t=(f,u)=>{var l,c,[n,o,s]=u,d=0;if(n.some(v=>0!==e[v])){for(l in o)r.o(o,l)&&(r.m[l]=o[l]);if(s)var b=s(r)}for(f&&f(u);d=14.1.0" 144 | }, 145 | "description": "Update the @angular/cli package version to ~14.1.0.", 146 | "factory": "./src/migrations/update-14-5-2/update-angular-cli", 147 | "package": "@nrwl/angular", 148 | "name": "update-angular-cli-version-14-1-0" 149 | }, 150 | { 151 | "cli": "nx", 152 | "version": "14.5.7-beta.0", 153 | "description": "Update the rxjs package version to ~7.5.0 if RxJS 7 is used in workspace.", 154 | "factory": "./src/migrations/update-14-5-7/update-rxjs", 155 | "package": "@nrwl/angular", 156 | "name": "update-rxjs-7-5-0" 157 | }, 158 | { 159 | "cli": "nx", 160 | "version": "14.6.0-beta.0", 161 | "requires": { 162 | "@angular/core": ">=14.2.0" 163 | }, 164 | "description": "Update the @angular/cli package version to ~14.2.0.", 165 | "factory": "./src/migrations/update-14-6-0/update-angular-cli", 166 | "package": "@nrwl/angular", 167 | "name": "update-angular-cli-version-14-2-0" 168 | }, 169 | { 170 | "cli": "nx", 171 | "version": "15.0.0-beta.0", 172 | "description": "Rename @nrwl/angular:webpack-server executor to @nrwl/angular:webpack-dev-server", 173 | "factory": "./src/migrations/update-14-8-0/rename-webpack-server", 174 | "package": "@nrwl/angular", 175 | "name": "rename-webpack-server-executor" 176 | }, 177 | { 178 | "cli": "nx", 179 | "version": "15.0.0-beta.0", 180 | "description": "Update the usages of @nrwl/angular/testing to import jasmine-marbles symbols from jasmine-marbles itself.", 181 | "factory": "./src/migrations/update-15-0-0/switch-to-jasmine-marbles", 182 | "package": "@nrwl/angular", 183 | "name": "switch-to-jasmine-marbles" 184 | }, 185 | { 186 | "cli": "nx", 187 | "version": "15.0.0-beta.1", 188 | "description": "Stop hashing karma spec files and config files for build targets and dependent tasks", 189 | "factory": "./src/migrations/update-15-0-0/add-karma-inputs", 190 | "package": "@nrwl/angular", 191 | "name": "add-karma-inputs" 192 | }, 193 | { 194 | "cli": "nx", 195 | "version": "15.2.0-beta.0", 196 | "requires": { 197 | "@angular/core": ">=15.0.0" 198 | }, 199 | "description": "Update the @angular/cli package version to ~15.0.0.", 200 | "factory": "./src/migrations/update-15-2-0/update-angular-cli", 201 | "package": "@nrwl/angular", 202 | "name": "update-angular-cli-version-15-0-0" 203 | }, 204 | { 205 | "cli": "nx", 206 | "version": "15.2.0-beta.0", 207 | "requires": { 208 | "@angular/core": ">=15.0.0" 209 | }, 210 | "description": "Remove browserlist config as it's handled by build-angular", 211 | "factory": "./src/migrations/update-15-2-0/remove-browserlist-config", 212 | "package": "@nrwl/angular", 213 | "name": "remove-browserlist-config" 214 | }, 215 | { 216 | "cli": "nx", 217 | "version": "15.2.0-beta.0", 218 | "requires": { 219 | "@angular/core": ">=15.0.0" 220 | }, 221 | "description": "Update typescript target to ES2022", 222 | "factory": "./src/migrations/update-15-2-0/update-typescript-target", 223 | "package": "@nrwl/angular", 224 | "name": "update-typescript-target" 225 | }, 226 | { 227 | "cli": "nx", 228 | "version": "15.2.0-beta.0", 229 | "requires": { 230 | "@angular/core": ">=15.0.0" 231 | }, 232 | "description": "Remove bundleDependencies from server targets", 233 | "factory": "./src/migrations/update-15-2-0/update-workspace-config", 234 | "package": "@nrwl/angular", 235 | "name": "update-workspace-config" 236 | }, 237 | { 238 | "cli": "ng", 239 | "version": "15.2.0-beta.0", 240 | "requires": { 241 | "@angular/core": ">=15.0.0" 242 | }, 243 | "description": "Remove exported `@angular/platform-server` `renderModule` method. The `renderModule` method is now exported by the Angular CLI.", 244 | "factory": "./src/migrations/update-15-2-0/remove-platform-server-exports", 245 | "package": "@nrwl/angular", 246 | "name": "update-platform-server-exports" 247 | }, 248 | { 249 | "cli": "ng", 250 | "version": "15.2.0-beta.0", 251 | "requires": { 252 | "@angular/core": ">=15.0.0" 253 | }, 254 | "description": "Remove no longer needed require calls in Karma builder main file.", 255 | "factory": "./src/migrations/update-15-2-0/update-karma-main-file", 256 | "package": "@nrwl/angular", 257 | "name": "update-karma-main-file" 258 | }, 259 | { 260 | "cli": "nx", 261 | "version": "15.5.0-beta.0", 262 | "requires": { 263 | "@angular/core": ">=15.1.0" 264 | }, 265 | "description": "Update the @angular/cli package version to ~15.1.0.", 266 | "factory": "./src/migrations/update-15-5-0/update-angular-cli", 267 | "package": "@nrwl/angular", 268 | "name": "update-angular-cli-version-15-1-0" 269 | }, 270 | { 271 | "cli": "nx", 272 | "version": "15.7.0-beta.1", 273 | "description": "Install the required angular-devkit packages as we do not directly depend on them anymore", 274 | "factory": "./src/migrations/update-15-7-0/install-required-packages", 275 | "package": "@nrwl/angular", 276 | "name": "install-required-packages" 277 | }, 278 | { 279 | "cli": "nx", 280 | "version": "15.8.0-beta.4", 281 | "description": "Update the @angular/cli package version to ~15.2.0.", 282 | "factory": "./src/migrations/update-15-8-0/update-angular-cli", 283 | "package": "@nrwl/angular", 284 | "name": "update-angular-cli-version-15-2-0" 285 | }, 286 | { 287 | "version": "14.0.0-beta.2", 288 | "cli": "nx", 289 | "description": "Update move jest config files to .ts files.", 290 | "factory": "./src/migrations/update-14-0-0/update-jest-config-ext", 291 | "package": "@nrwl/jest", 292 | "name": "update-jest-config-extensions" 293 | }, 294 | { 295 | "version": "14.1.5-beta.0", 296 | "cli": "nx", 297 | "description": "Update to export default in jest config and revert jest.preset.ts to jest.preset.js", 298 | "factory": "./src/migrations/update-14-1-5/update-exports-jest-config", 299 | "package": "@nrwl/jest", 300 | "name": "update-to-export-default" 301 | }, 302 | { 303 | "version": "14.5.5-beta.0", 304 | "cli": "nx", 305 | "description": "Exclude jest.config.ts from tsconfig where missing.", 306 | "factory": "./src/migrations/update-14-0-0/update-jest-config-ext", 307 | "package": "@nrwl/jest", 308 | "name": "exclude-jest-config-from-ts-config" 309 | }, 310 | { 311 | "version": "14.6.0-beta.0", 312 | "cli": "nx", 313 | "description": "Update jest configs to support jest 28 changes (https://jestjs.io/docs/upgrading-to-jest28#configuration-options)", 314 | "factory": "./src/migrations/update-14-6-0/update-configs-jest-28", 315 | "package": "@nrwl/jest", 316 | "name": "update-configs-jest-28" 317 | }, 318 | { 319 | "version": "14.6.0-beta.0", 320 | "cli": "nx", 321 | "description": "Update jest test files to support jest 28 changes (https://jestjs.io/docs/upgrading-to-jest28)", 322 | "factory": "./src/migrations/update-14-6-0/update-tests-jest-28", 323 | "package": "@nrwl/jest", 324 | "name": "update-tests-jest-28" 325 | }, 326 | { 327 | "version": "15.0.0-beta.0", 328 | "cli": "nx", 329 | "description": "Stop hashing jest spec files and config files for build targets and dependent tasks", 330 | "factory": "./src/migrations/update-15-0-0/add-jest-inputs", 331 | "package": "@nrwl/jest", 332 | "name": "add-jest-inputs" 333 | }, 334 | { 335 | "version": "15.8.0-beta.0", 336 | "cli": "nx", 337 | "description": "Update jest configs to support jest 29 changes (https://jestjs.io/docs/upgrading-to-jest29)", 338 | "factory": "./src/migrations/update-15-8-0/update-configs-jest-29", 339 | "package": "@nrwl/jest", 340 | "name": "update-configs-jest-29" 341 | }, 342 | { 343 | "version": "15.8.0-beta.0", 344 | "cli": "nx", 345 | "description": "Update jest test files to support jest 29 changes (https://jestjs.io/docs/upgrading-to-jest29)", 346 | "factory": "./src/migrations/update-15-8-0/update-tests-jest-29", 347 | "package": "@nrwl/jest", 348 | "name": "update-tests-jest-29" 349 | }, 350 | { 351 | "cli": "nx", 352 | "version": "14.7.6-beta.1", 353 | "description": "Update usages of webpack executors to @nrwl/webpack", 354 | "factory": "./src/migrations/update-14-7-6/update-webpack-executor", 355 | "package": "@nrwl/node", 356 | "name": "update-webpack-executor" 357 | }, 358 | { 359 | "cli": "nx", 360 | "version": "14.1.9-beta.0", 361 | "description": "Adds @swc/core and @swc-node as a dev dep if you are using them", 362 | "factory": "./src/migrations/update-14-1-9/add-swc-deps-if-needed", 363 | "package": "@nrwl/linter", 364 | "name": "add-swc-deps" 365 | }, 366 | { 367 | "cli": "nx", 368 | "version": "14.2.3-beta.0", 369 | "description": "Adds @swc/core and @swc-node as a dev dep if you are using them (repeated due to prior mistake)", 370 | "factory": "./src/migrations/update-14-1-9/add-swc-deps-if-needed", 371 | "package": "@nrwl/linter", 372 | "name": "add-swc-deps-again" 373 | }, 374 | { 375 | "cli": "nx", 376 | "version": "14.4.4", 377 | "description": "Adds @typescript-eslint/utils as a dev dep", 378 | "factory": "./src/migrations/update-14-4-4/experimental-to-utils-deps", 379 | "package": "@nrwl/linter", 380 | "name": "experimental-to-utils-deps" 381 | }, 382 | { 383 | "cli": "nx", 384 | "version": "14.4.4", 385 | "description": "Switch from @typescript-eslint/experimental-utils to @typescript-eslint/utils in all rules and rules.spec files", 386 | "factory": "./src/migrations/update-14-4-4/experimental-to-utils-rules", 387 | "package": "@nrwl/linter", 388 | "name": "experimental-to-utils-rules" 389 | }, 390 | { 391 | "cli": "nx", 392 | "version": "15.0.0-beta.0", 393 | "description": "Stop hashing eslint config files for build targets and dependent tasks", 394 | "factory": "./src/migrations/update-15-0-0/add-eslint-inputs", 395 | "package": "@nrwl/linter", 396 | "name": "add-eslint-inputs" 397 | }, 398 | { 399 | "cli": "nx", 400 | "version": "15.7.1-beta.0", 401 | "description": "Add node_modules to root eslint ignore", 402 | "factory": "./src/migrations/update-15-7-1/add-eslint-ignore", 403 | "package": "@nrwl/linter", 404 | "name": "add-eslint-ignore" 405 | }, 406 | { 407 | "cli": "nx", 408 | "version": "14.6.1-beta.0", 409 | "description": "Change Cypress e2e and component testing presets to use __filename instead of __dirname and include a devServerTarget for component testing.", 410 | "factory": "./src/migrations/update-14-6-1/update-cypress-configs-presets", 411 | "package": "@nrwl/cypress", 412 | "name": "update-cypress-configs-preset" 413 | }, 414 | { 415 | "cli": "nx", 416 | "version": "14.7.0-beta.0", 417 | "description": "Update Cypress if using v10 to support latest component testing features", 418 | "factory": "./src/migrations/update-14-7-0/update-cypress-version-if-10", 419 | "package": "@nrwl/cypress", 420 | "name": "update-cypress-if-v10" 421 | }, 422 | { 423 | "cli": "nx", 424 | "version": "15.0.0-beta.0", 425 | "description": "Stop hashing cypress spec files and config files for build targets and dependent tasks", 426 | "factory": "./src/migrations/update-15-0-0/add-cypress-inputs", 427 | "package": "@nrwl/cypress", 428 | "name": "add-cypress-inputs" 429 | }, 430 | { 431 | "cli": "nx", 432 | "version": "15.0.0-beta.4", 433 | "description": "Update to using cy.mount in the commands.ts file instead of importing mount for each component test file", 434 | "factory": "./src/migrations/update-15-0-0/update-cy-mount-usage", 435 | "package": "@nrwl/cypress", 436 | "name": "update-cy-mount-usage" 437 | }, 438 | { 439 | "cli": "nx", 440 | "version": "15.1.0-beta.0", 441 | "description": "Update to Cypress v11. This migration will only update if the workspace is already on v10. https://www.cypress.io/blog/2022/11/04/upcoming-changes-to-component-testing/", 442 | "factory": "./src/migrations/update-15-1-0/cypress-11", 443 | "package": "@nrwl/cypress", 444 | "name": "update-to-cypress-11" 445 | }, 446 | { 447 | "cli": "nx", 448 | "version": "15.5.0-beta.0", 449 | "description": "Update to Cypress v12. Cypress 12 contains a handful of breaking changes that might causes tests to start failing that nx cannot directly fix. Read more Cypress 12 changes: https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-12-0.This migration will only run if you are already using Cypress v11.", 450 | "factory": "./src/migrations/update-15-5-0/update-to-cypress-12", 451 | "package": "@nrwl/cypress", 452 | "name": "update-to-cypress-12" 453 | }, 454 | { 455 | "version": "13.6.0-beta.0", 456 | "description": "Remove old options that are no longer used", 457 | "cli": "nx", 458 | "implementation": "./src/migrations/update-13-6-0/remove-old-task-runner-options", 459 | "package": "@nrwl/workspace", 460 | "name": "13-6-0-remove-old-task-runner-options" 461 | }, 462 | { 463 | "version": "13.9.0-beta.0", 464 | "description": "Replace @nrwl/tao with nx", 465 | "cli": "nx", 466 | "implementation": "./src/migrations/update-13-9-0/replace-tao-with-nx", 467 | "package": "@nrwl/workspace", 468 | "name": "13-9-0-replace-tao-with-nx" 469 | }, 470 | { 471 | "version": "13.10.0-beta.0", 472 | "description": "Update the decorate-angular-cli script to require nx instead of @nrwl/cli", 473 | "cli": "nx", 474 | "implementation": "./src/migrations/update-13-10-0/update-decorate-cli", 475 | "package": "@nrwl/workspace", 476 | "name": "13-10-0-update-decorate-cli" 477 | }, 478 | { 479 | "version": "13.10.0-beta.0", 480 | "description": "Update the tasks runner property to import it from the nx package instead of @nrwl/workspace", 481 | "cli": "nx", 482 | "implementation": "./src/migrations/update-13-10-0/update-tasks-runner", 483 | "package": "@nrwl/workspace", 484 | "name": "13-10-0-update-tasks-runner" 485 | }, 486 | { 487 | "version": "14.0.0-beta.0", 488 | "description": "Changes the presets in nx.json to come from the nx package", 489 | "cli": "nx", 490 | "implementation": "./src/migrations/update-14-0-0/change-nx-json-presets", 491 | "package": "@nrwl/workspace", 492 | "name": "14-0-0-change-nx-json-presets" 493 | }, 494 | { 495 | "version": "14.0.0-beta.0", 496 | "description": "Migrates from @nrwl/workspace:run-script to nx:run-script", 497 | "cli": "nx", 498 | "implementation": "./src/migrations/update-14-0-0/change-npm-script-executor", 499 | "package": "@nrwl/workspace", 500 | "name": "14-0-0-change-npm-script-executor" 501 | }, 502 | { 503 | "version": "14.2.0", 504 | "description": "Explicitly enable sourceAnalysis for all workspaces extending from npm.json or core.json (this was default behavior prior to 14.2)", 505 | "cli": "nx", 506 | "implementation": "./src/migrations/update-14-2-0/enable-source-analysis", 507 | "package": "@nrwl/workspace", 508 | "name": "14-2-0-enable-source-analysis" 509 | }, 510 | { 511 | "version": "14.8.0-beta.0", 512 | "description": "Migrates from @nrwl/workspace:run-commands to nx:run-commands", 513 | "cli": "nx", 514 | "implementation": "./src/migrations/update-14-8-0/change-run-commands-executor", 515 | "package": "@nrwl/workspace", 516 | "name": "14-8-0-change-run-commands-executor" 517 | }, 518 | { 519 | "version": "15.7.0-beta.0", 520 | "description": "Split global configuration files (e.g., workspace.json) into individual project.json files.", 521 | "cli": "nx", 522 | "implementation": "./src/migrations/update-15-7-0/split-configuration-into-project-json-files", 523 | "package": "@nrwl/workspace", 524 | "name": "15-7-0-split-configuration-into-project-json-files" 525 | }, 526 | { 527 | "version": "15.0.0", 528 | "description": "Since Angular v15, the `RouterLink` contains the logic of the `RouterLinkWithHref` directive. This migration replaces all `RouterLinkWithHref` references with `RouterLink`.", 529 | "factory": "./migrations/router-link-with-href/bundle", 530 | "package": "@angular/core", 531 | "name": "migration-v15-router-link-with-href" 532 | }, 533 | { 534 | "version": "15.0.0", 535 | "description": "In Angular version 15, the deprecated `relativeLinkResolution` config parameter of the Router is removed. This migration removes all `relativeLinkResolution` fields from the Router config objects.", 536 | "factory": "./migrations/relative-link-resolution/bundle", 537 | "package": "@angular/core", 538 | "name": "migration-v15-relative-link-resolution" 539 | } 540 | ] 541 | } 542 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "targetDefaults": { 3 | "build": { 4 | "dependsOn": ["^build"], 5 | "inputs": ["production", "^production"] 6 | } 7 | }, 8 | "extends": "@nrwl/workspace/presets/core.json", 9 | "npmScope": "libre-routing", 10 | "affected": { 11 | "defaultBase": "main" 12 | }, 13 | "tasksRunnerOptions": { 14 | "default": { 15 | "runner": "@nrwl/workspace/tasks-runners/default", 16 | "options": { 17 | "cacheableOperations": ["build", "lint", "test", "e2e"] 18 | } 19 | } 20 | }, 21 | "generators": { 22 | "@nrwl/web:application": { 23 | "style": "scss", 24 | "linter": "eslint", 25 | "unitTestRunner": "jest", 26 | "e2eTestRunner": "cypress" 27 | }, 28 | "@nrwl/web:library": { 29 | "style": "scss", 30 | "linter": "eslint", 31 | "unitTestRunner": "jest" 32 | }, 33 | "@nrwl/angular:application": { 34 | "style": "scss", 35 | "linter": "eslint", 36 | "unitTestRunner": "jest", 37 | "e2eTestRunner": "cypress" 38 | }, 39 | "@nrwl/angular:library": { 40 | "linter": "eslint", 41 | "unitTestRunner": "jest" 42 | }, 43 | "@nrwl/angular:component": { 44 | "style": "scss" 45 | } 46 | }, 47 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 48 | "namedInputs": { 49 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 50 | "sharedGlobals": [], 51 | "production": ["default"] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libre-routing", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "nx run libre-routing-playground:serve", 7 | "libre-routing:build": "nx run libre-routing:build", 8 | "libre-routing:publish": "npm run libre-routing:build && npm publish ./dist/packages/libre-routing", 9 | "libre-routing:deploy": "npm run libre-routing:build && npm run libre-routing:publish", 10 | "libre-routing-playground:build": "nx run libre-routing-playground:build --output-path=docs --baseHref=./ --skip-nx-cache", 11 | "postinstall": "ngcc --properties es2020 browser module main" 12 | }, 13 | "private": true, 14 | "devDependencies": { 15 | "@angular-devkit/build-angular": "15.2.1", 16 | "@angular-eslint/eslint-plugin": "15.0.0", 17 | "@angular-eslint/eslint-plugin-template": "15.0.0", 18 | "@angular-eslint/template-parser": "15.0.0", 19 | "@angular/cli": "~15.0.0", 20 | "@angular/compiler-cli": "15.2.1", 21 | "@angular/language-service": "15.2.1", 22 | "@liberty-rider/flexpolyline": "^0.1.0", 23 | "@nrwl/angular": "15.8.0-beta.9", 24 | "@nrwl/cli": "13.9.4", 25 | "@nrwl/cypress": "15.8.0-beta.9", 26 | "@nrwl/eslint-plugin-nx": "15.8.0-beta.9", 27 | "@nrwl/jest": "15.8.0-beta.9", 28 | "@nrwl/js": "15.8.0-beta.9", 29 | "@nrwl/linter": "15.8.0-beta.9", 30 | "@nrwl/node": "15.8.0-beta.9", 31 | "@nrwl/tao": "15.8.0-beta.9", 32 | "@nrwl/workspace": "15.8.0-beta.9", 33 | "@types/jest": "29.4.0", 34 | "@types/node": "18.7.1", 35 | "@typescript-eslint/eslint-plugin": "5.54.0", 36 | "@typescript-eslint/parser": "5.54.0", 37 | "babel-jest": "29.4.3", 38 | "comlink": "^4.3.1", 39 | "cypress": "^9.1.0", 40 | "eslint": "8.15.0", 41 | "eslint-config-prettier": "8.1.0", 42 | "eslint-plugin-cypress": "^2.10.3", 43 | "jest": "29.4.3", 44 | "jest-environment-jsdom": "^29.4.1", 45 | "jest-preset-angular": "13.0.0", 46 | "mapbox-gl": "^3.4.0", 47 | "maplibre-gl": "4.3.2", 48 | "prettier": "2.8.4", 49 | "simplify-js": "1.2.4", 50 | "ts-jest": "29.0.5", 51 | "ts-node": "10.9.1", 52 | "typescript": "4.9.5" 53 | }, 54 | "dependencies": { 55 | "@angular/animations": "15.2.1", 56 | "@angular/common": "15.2.1", 57 | "@angular/compiler": "15.2.1", 58 | "@angular/core": "15.2.1", 59 | "@angular/forms": "15.2.1", 60 | "@angular/platform-browser": "15.2.1", 61 | "@angular/platform-browser-dynamic": "15.2.1", 62 | "@angular/router": "15.2.1", 63 | "@schematics/angular": "^15.2.1", 64 | "@turf/along": "^6.5.0", 65 | "@turf/bbox": "^6.5.0", 66 | "@turf/bbox-clip": "^6.5.0", 67 | "@turf/bearing": "^6.5.0", 68 | "@turf/helpers": "^6.5.0", 69 | "@turf/length": "^6.5.0", 70 | "@types/leaflet": "^1.9.0", 71 | "@types/mapbox-gl": "^3.1.0", 72 | "core-js": "^3.6.5", 73 | "deep-equal": "^2.2.0", 74 | "idb": "^7.1.1", 75 | "leaflet": "^1.9.3", 76 | "lodash-es": "^4.17.21", 77 | "parse-ms": "^3.0.0", 78 | "regenerator-runtime": "0.13.7", 79 | "rxjs": "~7.5.0", 80 | "tslib": "^2.3.0", 81 | "zone.js": "0.12.0" 82 | }, 83 | "resolutions": { 84 | "webpack": "5.41.1" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/packages/.gitkeep -------------------------------------------------------------------------------- /packages/annotation-plugin/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /packages/annotation-plugin/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/annotation-plugin/README.md: -------------------------------------------------------------------------------- 1 | # annotation-plugin 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test annotation-plugin` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /packages/annotation-plugin/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'annotation-plugin', 3 | preset: '../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../coverage/packages/annotation-plugin', 15 | }; 16 | -------------------------------------------------------------------------------- /packages/annotation-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any-routing/annotation-plugin", 3 | "version": "0.0.1", 4 | "private": false, 5 | "peerDependencies": { 6 | "maplibre-gl": "^2.*", 7 | "@any-routing/core": "^0.0.1" 8 | }, 9 | "dependencies": { 10 | "comlink": "^4.3.1", 11 | "@turf/along": "^6.5.0", 12 | "@turf/length": "^6.5.0", 13 | "@turf/bearing": "^6.5.0", 14 | "@turf/bbox-clip": "^6.5.0", 15 | "@turf/bbox": "^6.5.0", 16 | "@turf/helpers": "^6.5.0" 17 | } 18 | } -------------------------------------------------------------------------------- /packages/annotation-plugin/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "annotation-plugin", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/annotation-plugin/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nrwl/linter:eslint", 9 | "outputs": [ 10 | "{options.outputFile}" 11 | ], 12 | "options": { 13 | "lintFilePatterns": [ 14 | "packages/annotation-plugin/**/*.ts" 15 | ] 16 | } 17 | }, 18 | "test": { 19 | "executor": "@nrwl/jest:jest", 20 | "outputs": [ 21 | "{workspaceRoot}/coverage/packages/annotation-plugin" 22 | ], 23 | "options": { 24 | "jestConfig": "packages/annotation-plugin/jest.config.js", 25 | "passWithNoTests": true 26 | } 27 | }, 28 | "build": { 29 | "executor": "@nrwl/js:tsc", 30 | "outputs": [ 31 | "{options.outputPath}" 32 | ], 33 | "options": { 34 | "outputPath": "dist/packages/annotation-plugin", 35 | "tsConfig": "packages/annotation-plugin/tsconfig.lib.json", 36 | "packageJson": "packages/annotation-plugin/package.json", 37 | "main": "packages/annotation-plugin/src/index.ts", 38 | "assets": [ 39 | "packages/annotation-plugin/*.md" 40 | ] 41 | } 42 | } 43 | }, 44 | "tags": [] 45 | } -------------------------------------------------------------------------------- /packages/annotation-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/annotation-plugin'; 2 | -------------------------------------------------------------------------------- /packages/annotation-plugin/src/lib/annotation-plugin.spec.ts: -------------------------------------------------------------------------------- 1 | // import { annotationPlugin } from './annotation-plugin'; 2 | 3 | // describe('annotationPlugin', () => { 4 | // it('should work', () => { 5 | // expect(annotationPlugin()).toEqual('annotation-plugin'); 6 | // }); 7 | // }); 8 | -------------------------------------------------------------------------------- /packages/annotation-plugin/src/lib/annotation-plugin.ts: -------------------------------------------------------------------------------- 1 | import { LngLatBounds, Map, Popup } from 'maplibre-gl'; 2 | import type { AnyRouting, AnyRoutingPlugin, AnyRoutingDataResponse } from '@any-routing/core'; 3 | import { BBox, featureCollection, point } from '@turf/helpers'; 4 | import { wrap } from 'comlink'; 5 | import bbox from '@turf/bbox'; 6 | import type { AnnotationWorkerApi } from './annotation.worker'; 7 | import { AnnotationPopupComponent, AnnotationPopupComponentI, OnAttach } from './popup-component'; 8 | 9 | export interface AnnotationPluginOptions { 10 | calculatePopupOnFly: boolean; 11 | map: Map; 12 | componentFactory(id, routeData: any, ctx: AnyRouting): AnnotationPopupComponentI; 13 | } 14 | 15 | const defaultConfig: Omit = { 16 | calculatePopupOnFly: true, 17 | componentFactory(routeId, data, ctx) { 18 | return new AnnotationPopupComponent(routeId, data, ctx); 19 | }, 20 | }; 21 | 22 | type Require = T & { [P in K]-?: T[P] }; 23 | 24 | export class AnnotationPlugin implements AnyRoutingPlugin { 25 | private map!: Map; 26 | private mapBounds!: LngLatBounds; 27 | private bounds?: BBox; 28 | private worker: Worker; 29 | private popups: Popup[] = []; 30 | private allInBbox = false; 31 | private ctx!: AnyRouting; 32 | private options: AnnotationPluginOptions; 33 | private workerApi: AnnotationWorkerApi; 34 | private data; 35 | private components: AnnotationPopupComponentI[] = []; 36 | private clearMapHandler = () => { 37 | this.data = undefined; 38 | this.destroyView(); 39 | }; 40 | private routeCalculatedHandler = this.routeCalculated.bind(this); 41 | private mapMoveEndHandler = this.recalculate.bind(this); 42 | 43 | constructor(options: Require, 'map'>) { 44 | this.options = { ...defaultConfig, ...options }; 45 | this.worker = new Worker(new URL('./annotation.worker', import.meta.url), { 46 | type: 'module', 47 | }); 48 | 49 | this.map = this.options.map; 50 | 51 | // @ts-ignore 52 | this.workerApi = wrap(this.worker); 53 | } 54 | 55 | onAdd(ctx: AnyRouting) { 56 | this.ctx = ctx; 57 | 58 | this.ctx.on('routesFound', this.routeCalculatedHandler); 59 | this.ctx.on('calculationStarted', this.clearMapHandler); 60 | 61 | this.map.on(this.options.calculatePopupOnFly ? 'render' : 'idle', this.mapMoveEndHandler); 62 | } 63 | 64 | onRemove() { 65 | this.ctx.off('routesFound', this.routeCalculatedHandler); 66 | this.ctx.off('calculationStarted', this.clearMapHandler); 67 | this.worker.terminate(); 68 | this.destroyView(); 69 | } 70 | 71 | private async routeCalculated() { 72 | const data: AnyRoutingDataResponse | undefined = this.ctx.data; 73 | 74 | if (!data) { 75 | return; 76 | } 77 | 78 | if (this.data?.version === data.version || !data.latest) { 79 | return; 80 | } 81 | 82 | this.data = data; 83 | 84 | this.destroyView(); 85 | 86 | if (this.ctx.state.data?.latest) { 87 | await this.workerApi.createChunks(this.data); 88 | await this.recalculate(true); 89 | } 90 | } 91 | 92 | public async recalculate(force = false) { 93 | this.mapBounds = this.map.getBounds(); 94 | 95 | if (!this.data) { 96 | return; 97 | } 98 | 99 | if ( 100 | this.bounds && 101 | this.bounds.length === 4 && 102 | this.mapBounds.contains([this.bounds[0], this.bounds[1]]) && 103 | this.mapBounds.contains([this.bounds[2], this.bounds[3]]) && 104 | this.allInBbox && 105 | !force 106 | ) { 107 | return; 108 | } 109 | 110 | const sw = this.mapBounds.getSouthWest(); 111 | const ne = this.mapBounds.getNorthEast(); 112 | 113 | const { points, allInBbox } = await this.workerApi.recalculatePos({ 114 | bbox: { sw, ne }, 115 | }); 116 | 117 | if (!points.length) return; 118 | 119 | if ( 120 | this.bounds && 121 | this.bounds.length === 4 && 122 | this.mapBounds.contains([this.bounds[0], this.bounds[1]]) && 123 | this.mapBounds.contains([this.bounds[2], this.bounds[3]]) && 124 | !force 125 | ) { 126 | return; 127 | } 128 | 129 | this.destroyView(); 130 | 131 | this.allInBbox = allInBbox; 132 | 133 | this.bounds = bbox(featureCollection(points.map((p) => point(p.lngLat)))); 134 | 135 | this.components = []; 136 | 137 | points.forEach((point) => { 138 | const component = this.options.componentFactory(point.properties.routeId, this.data, this.ctx!); 139 | 140 | const popup = new Popup({ 141 | closeButton: false, 142 | closeOnClick: false, 143 | className: 'no-mouse-events', 144 | }) 145 | .setLngLat(point.lngLat) 146 | .setDOMContent(component.container) 147 | .addTo(this.map); 148 | 149 | if ('onAttach' in component) { 150 | (component as AnnotationPopupComponentI & OnAttach).onAttach(popup); 151 | } 152 | 153 | const popupElem = popup.getElement(); 154 | (popupElem.querySelector('.maplibregl-popup-content') as any).style.padding = '0'; 155 | 156 | this.components.push(component); 157 | this.popups.push(popup); 158 | }); 159 | } 160 | 161 | private destroyView() { 162 | this.allInBbox = false; 163 | this.components.forEach((c) => c.destroy()); 164 | this.popups.forEach((p) => p.remove()); 165 | 166 | this.popups = []; 167 | this.components = []; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /packages/annotation-plugin/src/lib/annotation.worker.ts: -------------------------------------------------------------------------------- 1 | import bboxClip from '@turf/bbox-clip'; 2 | import { FeatureCollection } from '@turf/helpers'; 3 | import { expose } from 'comlink'; 4 | 5 | import type { AnyRoutingDataResponse } from '@any-routing/core'; 6 | import { calculatePos, getDistinctSegments } from './line-diff'; 7 | 8 | let chunks = []; 9 | 10 | const api = { 11 | createChunks(data: AnyRoutingDataResponse) { 12 | const fc: FeatureCollection = data.routesShapeGeojson; 13 | 14 | // @ts-ignore 15 | chunks = getDistinctSegments(fc.features); 16 | }, 17 | 18 | recalculatePos({ bbox: { ne, sw } }) { 19 | const bboxChunks = chunks 20 | .map((chunk) => { 21 | return bboxClip(chunk, [sw.lng, sw.lat, ne.lng, ne.lat]); 22 | }) 23 | .filter(({ geometry: { coordinates } }) => coordinates.length > 0); 24 | 25 | const points = calculatePos(bboxChunks); 26 | const allInBbox = points.length === chunks.length; 27 | 28 | return { points, allInBbox }; 29 | }, 30 | }; 31 | 32 | export type AnnotationWorkerApi = typeof api; 33 | 34 | expose(api); 35 | -------------------------------------------------------------------------------- /packages/annotation-plugin/src/lib/line-diff.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { coordAll } from '@turf/meta'; 3 | import { Feature, lineString } from '@turf/helpers'; 4 | import { getCoord } from '@turf/invariant'; 5 | import along from '@turf/along'; 6 | import lineLength from '@turf/length'; 7 | import bearing from '@turf/bearing'; 8 | 9 | const TOLERANCE = 0.00001; 10 | const floatEquals = (f1, f2) => Math.abs(f1 - f2) < TOLERANCE; 11 | const coordEquals = (c1 = [], c2 = []) => floatEquals(c1[0], c2[0]) && floatEquals(c1[1], c2[1]); 12 | const asKey = (coord) => `${coord[0].toFixed(6)},${coord[1].toFixed(6)}`; 13 | const last = (array = []) => array[array.length - 1]; 14 | 15 | // find the point at the given distance ratio on the linestring 16 | const project = (ratio) => (ls) => { 17 | let line = ls; 18 | 19 | if (ls.geometry.type === 'MultiLineString') { 20 | line = lineString(ls.geometry.coordinates[0]); 21 | 22 | ls.geometry.coordinates.forEach((cords, index) => { 23 | if (index === 0) return; 24 | 25 | const cordsLine = lineString(cords); 26 | 27 | if (lineLength(cordsLine) > lineLength(line)) { 28 | line = cordsLine; 29 | } 30 | }); 31 | } 32 | 33 | const length = lineLength(line); 34 | const lngLat = getCoord(along(line, length * ratio)); 35 | // keep the local bearing of the line to later choose an anchor minimizing the portion of line covered. 36 | const localLineBearing = bearing(along(line, length * (ratio - 0.1)), along(line, length * (ratio + 0.1))); 37 | 38 | return { lngLat, localLineBearing, properties: ls.properties }; 39 | }; 40 | 41 | function distinctSegment(coordinates, coordCounts, properties) { 42 | const adjacentCoordsUsedOnce = [[]]; 43 | coordinates.forEach((coord) => { 44 | if (coordCounts.get(asKey(coord)) > 1) { 45 | adjacentCoordsUsedOnce.push([]); 46 | } else { 47 | last(adjacentCoordsUsedOnce).push(coord); 48 | } 49 | }); 50 | const longestDistinctSegment = adjacentCoordsUsedOnce 51 | .filter((a) => a.length > 0) 52 | .reduce((longest, current) => (current.length > longest.length ? current : longest), []); 53 | 54 | return lineString(longestDistinctSegment.length === 0 ? coordinates : longestDistinctSegment, properties); 55 | } 56 | 57 | // extract the longest segment of each linestring 58 | // whose coordinates don't overlap with another feature 59 | export function findDistinctSegments(linestrings: Feature[]) { 60 | if (linestrings.length < 2) { 61 | return linestrings; 62 | } 63 | // extract raw coordinates 64 | const featuresCoords = linestrings.map(coordAll); 65 | // count occurences of each coordinate accross all features 66 | const coordCounts = new Map(); 67 | [].concat(...featuresCoords).forEach((coord) => { 68 | coordCounts.set(asKey(coord), (coordCounts.get(asKey(coord)) || 0) + 1); 69 | }); 70 | return featuresCoords.map((coordinates, index) => 71 | distinctSegment(coordinates, coordCounts, linestrings[index].properties) 72 | ); 73 | } 74 | 75 | function toSimpleLinestring(feature) { 76 | const allCoordsWithNoDups = coordAll(feature).reduce((noDups, coord) => { 77 | const prevCoord = last(noDups); 78 | if (!prevCoord || !coordEquals(prevCoord, coord)) { 79 | noDups.push(coord); 80 | } 81 | return noDups; 82 | }, []); 83 | return lineString(allCoordsWithNoDups, feature.properties); 84 | } 85 | 86 | // Reduce possibilities of collision by chosing anchors so that labels repulse each other 87 | function optimizeAnchors(positions) { 88 | return positions.map((position, index) => { 89 | const others = positions.slice(); 90 | others.splice(index, 1); 91 | const othersBearing = getBearingFromOtherPoints(position, others); 92 | return { 93 | lngLat: position.lngLat, 94 | anchor: getAnchor(position, othersBearing), 95 | properties: position.properties, 96 | }; 97 | }); 98 | } 99 | 100 | function getBearingFromOtherPoints(position, others) { 101 | return ( 102 | others 103 | .map((other) => bearing(other.lngLat, position.lngLat)) 104 | .reduce((avg, value, _index, { length }) => avg + value / length, 0) || // mean 105 | 0 106 | ); 107 | } 108 | 109 | function getAnchor(position, otherBearing) { 110 | const axis = 111 | Math.abs(position.localLineBearing) < 45 || Math.abs(position.localLineBearing) > 135 ? 'vertical' : 'horizontal'; 112 | 113 | if (axis === 'vertical') { 114 | return otherBearing > 0 ? 'left' : 'right'; 115 | } 116 | return Math.abs(otherBearing) < 90 ? 'bottom' : 'top'; 117 | } 118 | 119 | // routes can be a FeatureCollection or an array of Feature or Geometry 120 | 121 | export function getDistinctSegments(routes = []) { 122 | const featuresOrGeoms = Array.isArray(routes) ? routes : routes.features; 123 | const mergedRoutes = mergeRoutes(featuresOrGeoms); 124 | const lineStrings = mergedRoutes.map(toSimpleLinestring); 125 | return findDistinctSegments(lineStrings); 126 | } 127 | 128 | export function calculatePos(segments) { 129 | const positions = segments.map(project(0.5)); 130 | return optimizeAnchors(positions); 131 | } 132 | 133 | function mergeRoutes(featureCollections) { 134 | return featureCollections.reduce((acc, feature) => { 135 | if (acc[feature.properties.routeId]) { 136 | acc[feature.properties.routeId].geometry.coordinates = [ 137 | ...acc[feature.properties.routeId].geometry.coordinates, 138 | ...feature.geometry.coordinates, 139 | ]; 140 | } else { 141 | acc[feature.properties.routeId] = feature; 142 | } 143 | 144 | return acc; 145 | }, []); 146 | } 147 | -------------------------------------------------------------------------------- /packages/annotation-plugin/src/lib/popup-component.ts: -------------------------------------------------------------------------------- 1 | import { formatMS } from './utils'; 2 | import type { Popup } from 'maplibre-gl'; 3 | import type { AnyRoutingDataResponse, AnyRouting } from '@any-routing/core'; 4 | 5 | const icon = 6 | ''; 7 | 8 | export interface AnnotationPopupComponentI { 9 | container: HTMLElement; 10 | destroy(): void; 11 | } 12 | 13 | export interface OnAttach { 14 | onAttach(popup: Popup): void; 15 | } 16 | 17 | export class AnnotationPopupComponent implements AnnotationPopupComponentI { 18 | public readonly container; 19 | 20 | private routeSelectedHandler = this.routeSelected.bind(this); 21 | 22 | constructor(private routeId, data, private ctx: AnyRouting) { 23 | this.container = document.createElement('div'); 24 | 25 | this.container.onclick = () => { 26 | ctx.selectRoute(routeId); 27 | }; 28 | 29 | ctx.on('routeSelected', this.routeSelectedHandler); 30 | 31 | this.render(routeId, data); 32 | } 33 | 34 | private render(routeId, data: AnyRoutingDataResponse) { 35 | this.container.style.padding = '4px 5px'; 36 | this.container.style.display = 'flex'; 37 | this.container.style.cursor = 'pointer'; 38 | 39 | this.container.innerHTML = ` 40 | ${icon} 41 |
42 |
${formatMS(data.routes[routeId]?.durationTime * 1000)}
43 | ${(data.routes[routeId]?.distance / 1000).toFixed( 44 | 1 45 | )} km 46 |
47 | `; 48 | 49 | this.applyColor(); 50 | } 51 | 52 | destroy() { 53 | this.ctx.off('routeSelected', this.routeSelectedHandler); 54 | } 55 | 56 | private routeSelected() { 57 | this.applyColor(); 58 | } 59 | 60 | private applyColor() { 61 | const color = this.ctx.selectedRouteId === this.routeId ? '#e207ff' : '#33C9EB'; 62 | 63 | this.container.querySelector('svg #car')?.setAttribute('fill', color); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/annotation-plugin/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import parseMilliseconds from 'parse-ms'; 2 | 3 | export function formatMS(ms: number) { 4 | const { days, seconds, minutes, hours } = parseMilliseconds(ms); 5 | 6 | if (days === 0 && hours === 0 && minutes === 0) return `${seconds}s`; 7 | else if (days === 0 && hours === 0) return `${minutes}m ${seconds} s`; 8 | else if (days === 0) return `${hours}h ${minutes}m`; 9 | else return `${days * 24 + hours}h`; 10 | } 11 | -------------------------------------------------------------------------------- /packages/annotation-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/annotation-plugin/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ES2020", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"] 8 | }, 9 | "exclude": ["**/*.spec.ts", "**/*.test.ts"], 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/annotation-plugin/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.test.ts", 10 | "**/*.spec.ts", 11 | "**/*.test.tsx", 12 | "**/*.spec.tsx", 13 | "**/*.test.js", 14 | "**/*.spec.js", 15 | "**/*.test.jsx", 16 | "**/*.spec.jsx", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # core 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test core` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'core', 3 | preset: '../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../coverage/packages/core', 15 | }; 16 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any-routing/core", 3 | "version": "0.1.0", 4 | "private": false, 5 | "license": "MIT", 6 | "author": "Marcin Lazar", 7 | "keywords": [ 8 | "MapLibre GL", 9 | "Routing", 10 | "Directions", 11 | "Route", 12 | "OSMR", 13 | "Here", 14 | "Plugin", 15 | "Map", 16 | "js", 17 | "leaflet" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/marucjmar/any-routing" 22 | }, 23 | "dependencies": { 24 | "@turf/helpers": "^6.5.0" 25 | } 26 | } -------------------------------------------------------------------------------- /packages/core/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "core", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/core/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nrwl/linter:eslint", 9 | "outputs": [ 10 | "{options.outputFile}" 11 | ], 12 | "options": { 13 | "lintFilePatterns": [ 14 | "packages/core/**/*.ts" 15 | ] 16 | } 17 | }, 18 | "test": { 19 | "executor": "@nrwl/jest:jest", 20 | "outputs": [ 21 | "{workspaceRoot}/coverage/packages/core" 22 | ], 23 | "options": { 24 | "jestConfig": "packages/core/jest.config.js", 25 | "passWithNoTests": true 26 | } 27 | }, 28 | "build": { 29 | "executor": "@nrwl/js:tsc", 30 | "outputs": [ 31 | "{options.outputPath}" 32 | ], 33 | "options": { 34 | "outputPath": "dist/packages/core", 35 | "tsConfig": "packages/core/tsconfig.lib.json", 36 | "packageJson": "packages/core/package.json", 37 | "main": "packages/core/src/index.ts", 38 | "assets": [ 39 | "packages/core/*.md" 40 | ] 41 | } 42 | } 43 | }, 44 | "tags": [ 45 | "scope:public", 46 | "type:util", 47 | "target:all" 48 | ] 49 | } -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/libre-routing'; 2 | export * from './lib/data-providers'; 3 | export * from './lib/libre-routing.model'; 4 | export * from './lib/utils'; 5 | -------------------------------------------------------------------------------- /packages/core/src/lib/data-providers/errors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './unauthorized'; 2 | -------------------------------------------------------------------------------- /packages/core/src/lib/data-providers/errors/unauthorized.ts: -------------------------------------------------------------------------------- 1 | export class UnauthorizedError { 2 | public readonly code = 401; 3 | public readonly message = 'Unauthorized'; 4 | 5 | constructor(public readonly response: any) {} 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/lib/data-providers/index.ts: -------------------------------------------------------------------------------- 1 | import type { BBox, FeatureCollection, Geometry, LineString } from '@turf/helpers'; 2 | import { Waypoint, LngLatPosition, Mode } from '../libre-routing.model'; 3 | 4 | export interface AnyRoutingDataProvider { 5 | request: (waypoints: Waypoint[], opts: RequestOptions) => Promise; 6 | 7 | destroy(): void; 8 | hasPendingRequests(): Promise; 9 | abortAllRequests(): void; 10 | } 11 | 12 | export interface RequestOptions { 13 | mode: Mode; 14 | } 15 | 16 | export interface AnyRoutingDataResponse { 17 | rawResponse: any; 18 | routesShapeGeojson: FeatureCollection; 19 | routes: RouteSummary[]; 20 | selectedRouteId?: number | null; 21 | routesShapeBounds?: BBox; 22 | version: number; 23 | latest: boolean; 24 | mode: Mode; 25 | } 26 | 27 | export type RoutePath = LngLatPosition[]; 28 | 29 | export interface RouteSummary { 30 | id: number; 31 | label?: string; 32 | path: RoutePath; 33 | durationTime: number; 34 | arriveTime: Date; 35 | departureTime: Date; 36 | distance: number; 37 | cost?: number; 38 | waypoints: { lat: number; lng: number }[]; 39 | shape: FeatureCollection; 40 | } 41 | 42 | export * from './errors'; 43 | -------------------------------------------------------------------------------- /packages/core/src/lib/libre-routing.model.ts: -------------------------------------------------------------------------------- 1 | import { AnyRoutingDataProvider, AnyRoutingDataResponse, RouteSummary } from './data-providers'; 2 | import type { AnyRouting } from './libre-routing'; 3 | 4 | export type LngLatPosition = [number, number]; 5 | 6 | export type WaypointPosition = { lat: number; lng: number }; 7 | 8 | export type Mode = 'default' | 'drag'; 9 | 10 | export type BaseEvent = { 11 | state: AnyRoutingState; 12 | eventName: keyof MessagePortEventMap; 13 | }; 14 | 15 | export type CalculationStartedEvent = BaseEvent & { 16 | waypoints: InternalWaypoint[]; 17 | recalculateOptions: RecalculateOptions; 18 | mode: Mode; 19 | }; 20 | 21 | export type CalculationErrorEvent = BaseEvent & { 22 | error: Error; 23 | recalculateOptions: RecalculateOptions; 24 | }; 25 | 26 | export type RoutesFoundEvent = BaseEvent & { 27 | waypoints: InternalWaypoint[]; 28 | data: R; 29 | mode: Mode; 30 | recalculateOptions: RecalculateOptions; 31 | }; 32 | 33 | export type RouteSelectedEvent = BaseEvent & { 34 | routeId: number; 35 | route: RouteSummary; 36 | }; 37 | 38 | export type WaypointsChangedEvent = BaseEvent & { 39 | waypoints: InternalWaypoint[]; 40 | }; 41 | 42 | export type WaypointGeocoded = BaseEvent & { 43 | waypoint: InternalWaypoint; 44 | }; 45 | 46 | export type LoadingChangedEvent = BaseEvent & { 47 | loading: boolean; 48 | }; 49 | 50 | export type StateUpdateSource = 'internal' | 'external'; 51 | 52 | export type StateUpdatedEvent = BaseEvent & { 53 | state: AnyRoutingState; 54 | source: StateUpdateSource; 55 | updatedProperties: Array>; 56 | }; 57 | 58 | export interface AnyRoutingEventsMap { 59 | calculationStarted: CalculationStartedEvent; 60 | calculationError: CalculationErrorEvent; 61 | routesFound: RoutesFoundEvent; 62 | routeSelected: RouteSelectedEvent; 63 | waypointsChanged: WaypointsChangedEvent; 64 | waypointGeocoded: WaypointGeocoded; 65 | loadingChanged: LoadingChangedEvent; 66 | stateUpdated: StateUpdatedEvent; 67 | } 68 | 69 | export type AnyRoutingEvents = keyof AnyRoutingEventsMap; 70 | 71 | export type InternalWaypointProperties = { 72 | index: number; 73 | isFirst: boolean; 74 | isLast: boolean; 75 | }; 76 | 77 | export type AnyRoutingState = { 78 | data: DataType | undefined; 79 | waypoints: InternalWaypoint[]; 80 | loading: boolean; 81 | selectedRouteId: number | undefined | null; 82 | mode: Mode; 83 | routesShapeGeojson?: AnyRoutingDataResponse['routesShapeGeojson']; 84 | }; 85 | 86 | export interface InternalWaypoint { 87 | index: number; 88 | position: WaypointPosition; 89 | originalPosition: WaypointPosition; 90 | properties: InternalWaypointProperties & Record; 91 | geocoded?: boolean; 92 | } 93 | 94 | export function isWaypointPositionEqual(posA: WaypointPosition, posB: WaypointPosition): boolean { 95 | return posA.lat === posB.lat && posA.lng === posB.lng; 96 | } 97 | 98 | export class InternalWaypointC { 99 | public static fromWaypoint(waypoint: Waypoint, props: InternalWaypointProperties): InternalWaypoint { 100 | return { 101 | index: props.index, 102 | originalPosition: waypoint.position, 103 | position: waypoint.position, 104 | geocoded: waypoint.geocoded, 105 | properties: { ...props, ...(waypoint.properties || {}) }, 106 | }; 107 | } 108 | } 109 | 110 | export interface Waypoint { 111 | position: WaypointPosition; 112 | originalPosition?: WaypointPosition; 113 | properties?: Record; 114 | geocoded?: boolean; 115 | } 116 | 117 | export type PluginFactory = AnyRoutingPlugin | (new (...args: any[]) => AnyRoutingPlugin); 118 | 119 | export interface AnyRoutingGeocoder { 120 | geocode(waypoint: InternalWaypoint): Promise; 121 | } 122 | 123 | export type AnyRoutingOptions = { 124 | dataProvider: AnyRoutingDataProvider; 125 | geocoder?: AnyRoutingGeocoder | ((waypoint: InternalWaypoint) => Promise); 126 | plugins?: PluginFactory[]; 127 | uniqueKey?: string; 128 | waypointsSyncStrategy: 'none' | 'toPath' | 'geocodeFirst'; 129 | }; 130 | 131 | export interface AnyRoutingPlugin { 132 | onAdd(AnyRouting: AnyRouting): void; 133 | onRemove(AnyRouting: AnyRouting): void; 134 | } 135 | 136 | export interface RecalculateOptions { 137 | fitViewToData?: boolean; 138 | clearMap?: boolean; 139 | dropPendingRequests?: boolean; 140 | syncWaypoints?: boolean; 141 | } 142 | -------------------------------------------------------------------------------- /packages/core/src/lib/libre-routing.ts: -------------------------------------------------------------------------------- 1 | import { AnyRoutingDataResponse, RouteSummary } from './data-providers'; 2 | import { Dispatcher } from './utils/dispatcher'; 3 | import { randomId } from './utils/random'; 4 | import { 5 | AnyRoutingEventsMap, 6 | AnyRoutingOptions, 7 | AnyRoutingState, 8 | PluginFactory, 9 | StateUpdateSource, 10 | Waypoint as InputWaypoint, 11 | AnyRoutingPlugin, 12 | InternalWaypoint, 13 | InternalWaypointC, 14 | RecalculateOptions, 15 | Mode, 16 | AnyRoutingGeocoder, 17 | } from './libre-routing.model'; 18 | import { featureCollection } from '@turf/helpers'; 19 | 20 | export class AnyRouting { 21 | private readonly dispatcher = new Dispatcher(); 22 | private readonly geocoder?: AnyRoutingGeocoder; 23 | 24 | private _options: AnyRoutingOptions; 25 | private _plugins: PluginFactory[] = []; 26 | 27 | private _initialState: AnyRoutingState = { 28 | waypoints: [], 29 | data: undefined, 30 | loading: false, 31 | selectedRouteId: undefined, 32 | mode: 'default', 33 | routesShapeGeojson: undefined, 34 | }; 35 | 36 | private _state: AnyRoutingState = { ...this._initialState }; 37 | 38 | public get options(): AnyRoutingOptions { 39 | return this._options; 40 | } 41 | 42 | public get data() { 43 | return this._state.data; 44 | } 45 | 46 | public get state(): AnyRoutingState { 47 | return this._state; 48 | } 49 | 50 | public get dataProvider() { 51 | return this.options.dataProvider; 52 | } 53 | 54 | public get selectedRouteId(): number | undefined | null { 55 | return this._state.selectedRouteId; 56 | } 57 | 58 | private get waypoints(): InternalWaypoint[] { 59 | return this._state.waypoints; 60 | } 61 | 62 | constructor(options: AnyRoutingOptions) { 63 | this._options = { 64 | ...{ uniqueKey: randomId() }, 65 | ...options, 66 | }; 67 | 68 | this._plugins = options.plugins ?? []; 69 | 70 | if (this.options.waypointsSyncStrategy === 'geocodeFirst' && !this.options.geocoder) { 71 | throw new Error('Geocoder is required when waypointsSyncStrategy is `geocodeFirst`'); 72 | } 73 | 74 | this.geocoder = 75 | this.options.geocoder && typeof this.options.geocoder === 'function' 76 | ? { geocode: this.options.geocoder } 77 | : (this.options.geocoder as AnyRoutingGeocoder); 78 | } 79 | 80 | public initialize() { 81 | this.initPlugins(); 82 | } 83 | 84 | public onRemove() { 85 | this.detachPlugins(); 86 | this.options.dataProvider?.destroy(); 87 | } 88 | 89 | public setMode(mode: Mode) { 90 | this._patchState({ mode }); 91 | } 92 | 93 | public setWaypoints(waypoints: InputWaypoint[]) { 94 | const internalWaypoints: InternalWaypoint[] = this.transformToInternalWaypoints(waypoints); 95 | this._patchState({ waypoints: internalWaypoints }); 96 | } 97 | 98 | public getWaypoint(waypointId: number) { 99 | return this._state.waypoints[waypointId]; 100 | } 101 | 102 | public async recalculateRoute(opts: RecalculateOptions & Record = {}): Promise { 103 | if (this.waypoints.length < 2) return; 104 | 105 | if (!this.options.dataProvider) { 106 | throw new Error('No data provider'); 107 | } 108 | 109 | if (opts.dropPendingRequests) { 110 | await this.options.dataProvider.abortAllRequests(); 111 | } 112 | 113 | let patchState: Partial> = {}; 114 | 115 | if (!this._state.loading) { 116 | patchState = { ...patchState, loading: true }; 117 | } 118 | 119 | this.fire('calculationStarted', { 120 | mode: this.state.mode, 121 | waypoints: this.waypoints, 122 | recalculateOptions: opts, 123 | }); 124 | 125 | this._patchState(patchState); 126 | 127 | if ('loading' in patchState) { 128 | this.fire('loadingChanged', { loading: true }); 129 | } 130 | 131 | try { 132 | let patchState: Partial> = {}; 133 | 134 | if (opts.syncWaypoints !== false && this.options.waypointsSyncStrategy === 'geocodeFirst') { 135 | const waypointsToSyncIds: number[] = this._state.waypoints.reduce((acc: number[], w, index) => { 136 | if (!w.geocoded) { 137 | acc.push(index) 138 | } 139 | 140 | return acc; 141 | }, []); 142 | 143 | if (waypointsToSyncIds.length > 0) { 144 | const waypoints = await this.geocodeWaypoints(this._state.waypoints); 145 | this._patchState({ waypoints }); 146 | 147 | waypointsToSyncIds.forEach((waypointId) => { 148 | this.fire('waypointGeocoded', { waypoint: waypoints[waypointId] }); 149 | }); 150 | } 151 | } 152 | 153 | const data: R = (await this.options.dataProvider.request(this._state.waypoints, { 154 | mode: this.state.mode, 155 | })) as R; 156 | 157 | patchState = { 158 | ...patchState, 159 | data, 160 | selectedRouteId: data.selectedRouteId, 161 | routesShapeGeojson: data.routesShapeGeojson, 162 | }; 163 | 164 | if (opts.syncWaypoints !== false && this.options.waypointsSyncStrategy === 'toPath') { 165 | const syncedWaypoints: InternalWaypoint[] = this.syncWaypointsPositions(data.routes[0].waypoints); 166 | patchState = { ...patchState, waypoints: syncedWaypoints }; 167 | } 168 | 169 | this._patchState(patchState); 170 | 171 | this.fire('routesFound', { 172 | mode: this.state.mode, 173 | waypoints: this._state.waypoints, 174 | data, 175 | recalculateOptions: opts, 176 | }); 177 | 178 | return this._state.data; 179 | } catch (e: unknown) { 180 | this.fire('calculationError', { error: e as Error, recalculateOptions: opts }); 181 | 182 | throw e; 183 | } finally { 184 | if (this._state.loading && (this.data?.latest || !(await this.dataProvider.hasPendingRequests()))) { 185 | this._patchState({ loading: false }); 186 | this.fire('loadingChanged', { loading: false }); 187 | } 188 | } 189 | } 190 | 191 | public on(event: E, callback: (event: AnyRoutingEventsMap[E]) => void) { 192 | this.dispatcher.on(event, callback); 193 | } 194 | 195 | public off(event: E, callback: (event: AnyRoutingEventsMap[E]) => void) { 196 | this.dispatcher.off(event, callback); 197 | } 198 | 199 | public selectRoute(routeId: number) { 200 | if (!this.data) { 201 | return; 202 | } 203 | 204 | const features = this.data.routesShapeGeojson.features.map((feature) => { 205 | return { 206 | ...feature, 207 | properties: { 208 | ...feature.properties, 209 | selected: feature.properties.routeId === routeId, 210 | }, 211 | }; 212 | }); 213 | 214 | this._patchState({ 215 | selectedRouteId: routeId, 216 | data: { 217 | ...this._state.data!, 218 | routesShapeGeojson: featureCollection(features), 219 | selectedRouteId: routeId, 220 | }, 221 | routesShapeGeojson: featureCollection(features), 222 | }); 223 | 224 | this.fire('routeSelected', { 225 | route: this.data!.routes[routeId], 226 | routeId: routeId, 227 | }); 228 | } 229 | 230 | public reset(): void { 231 | this._setState({ ...this._initialState }, 'external'); 232 | } 233 | 234 | public getUniqueName(name: string) { 235 | return `${name}-${this.options.uniqueKey}`; 236 | } 237 | 238 | public syncWaypointsPositions(waypoints: RouteSummary['waypoints']): InternalWaypoint[] { 239 | const newWaypoints: InternalWaypoint[] = waypoints.map( 240 | (waypoint, index): InternalWaypoint => ({ 241 | ...this._state.waypoints[index], 242 | position: waypoint, 243 | originalPosition: this._state.waypoints[index].originalPosition || this._state.waypoints[index].position, 244 | properties: this._state.waypoints[index].properties, 245 | }) 246 | ); 247 | 248 | return newWaypoints; 249 | } 250 | 251 | public setState({ 252 | waypoints, 253 | data, 254 | routesShapeGeojson, 255 | selectedRouteId, 256 | }: { 257 | waypoints?: InputWaypoint[]; 258 | data?: R; 259 | routesShapeGeojson?: R['routesShapeGeojson']; 260 | selectedRouteId?: number | null; 261 | }): void { 262 | let newState: AnyRoutingState = { ...this._state }; 263 | if (waypoints) { 264 | const internalWaypoints = this.transformToInternalWaypoints(waypoints); 265 | newState = { ...newState, waypoints: internalWaypoints }; 266 | } 267 | 268 | if (routesShapeGeojson) { 269 | newState = { ...newState, routesShapeGeojson }; 270 | } 271 | 272 | if (data) { 273 | newState = { ...newState, data, routesShapeGeojson: data.routesShapeGeojson }; 274 | } 275 | 276 | if (selectedRouteId != null) { 277 | newState = { ...newState, selectedRouteId }; 278 | } 279 | 280 | if (data || waypoints || routesShapeGeojson || selectedRouteId != null) { 281 | this._setState(newState, 'external'); 282 | } 283 | } 284 | 285 | private initPlugins(): void { 286 | this._plugins.forEach((plugin) => this.resolvePlugin(plugin).onAdd(this)); 287 | } 288 | 289 | private detachPlugins(): void { 290 | this._plugins.forEach((plugin) => this.resolvePlugin(plugin).onRemove(this)); 291 | } 292 | 293 | private resolvePlugin(plugin: AnyRoutingPlugin | (new (...args: any[]) => AnyRoutingPlugin)): AnyRoutingPlugin { 294 | if (typeof plugin === 'function') { 295 | return new plugin({}); 296 | } 297 | 298 | return plugin; 299 | } 300 | 301 | private _setState( 302 | newState: AnyRoutingState, 303 | source: StateUpdateSource = 'internal', 304 | updatedKeys?: Array> 305 | ): void { 306 | this._state = newState; 307 | this.fire('stateUpdated', { 308 | source, 309 | updatedProperties: updatedKeys ?? (Object.keys(this.state) as Array>), 310 | }); 311 | } 312 | 313 | private _patchState(patchState: Partial>, source: StateUpdateSource = 'internal'): void { 314 | if (Object.keys(patchState).length === 0) { 315 | return; 316 | } 317 | 318 | this._setState( 319 | { ...this._state, ...patchState }, 320 | source, 321 | Object.keys(patchState) as Array> 322 | ); 323 | 324 | if ('waypoints' in patchState) { 325 | this.fire('waypointsChanged', { waypoints: this.state.waypoints }); 326 | } 327 | } 328 | 329 | private transformToInternalWaypoints(waypoints: InputWaypoint[]): InternalWaypoint[] { 330 | return waypoints.map((waypoint, index) => 331 | InternalWaypointC.fromWaypoint(waypoint, { index, isFirst: index === 0, isLast: index === waypoints.length - 1 }) 332 | ); 333 | } 334 | 335 | private fire( 336 | event: E, 337 | data: Omit 338 | ) { 339 | //@ts-ignore 340 | this.dispatcher.fire(event, { ...data, state: this.state, eventName: event }); 341 | } 342 | 343 | private geocodeWaypoints(waypoints: InternalWaypoint[]): Promise { 344 | const waypointGeocodes = waypoints.map(async (waypoint) => { 345 | if (waypoint.geocoded) { 346 | return waypoint; 347 | } 348 | 349 | const geocodedWaypoint = await this.geocoder!.geocode(waypoint); 350 | geocodedWaypoint.geocoded = true; 351 | 352 | return geocodedWaypoint; 353 | }); 354 | 355 | return Promise.all(waypointGeocodes); 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /packages/core/src/lib/utils/dispatcher.ts: -------------------------------------------------------------------------------- 1 | export class Dispatcher { 2 | private callbacks: { [key in keyof EventMap]?: Array<(...args) => void> } = {}; 3 | 4 | public fire(event: E, data: EventMap[E]) { 5 | this.getEventCallbacks(event).forEach((callback) => { 6 | try { 7 | callback(data); 8 | } catch (e) { 9 | console.error(e); 10 | } 11 | }); 12 | } 13 | 14 | public on(event: E, callback: (event: EventMap[E]) => void): void { 15 | this.callbacks[event] = [...this.getEventCallbacks(event), callback]; 16 | } 17 | 18 | public off(event: E, callback: (event: EventMap[E]) => void) { 19 | this.callbacks[event] = this.getEventCallbacks(event).filter((c) => c !== callback); 20 | } 21 | 22 | private getEventCallbacks(event: E): Array<(...args) => void> { 23 | return this.callbacks[event] || []; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dispatcher'; 2 | export * from './requester'; 3 | export * from './random'; 4 | -------------------------------------------------------------------------------- /packages/core/src/lib/utils/random.ts: -------------------------------------------------------------------------------- 1 | export const randomId = () => (Math.random() + 1).toString(36).substring(7); 2 | -------------------------------------------------------------------------------- /packages/core/src/lib/utils/requester.ts: -------------------------------------------------------------------------------- 1 | export class Requester { 2 | private latestResponseId = 0; 3 | private lastResponseId = 0; 4 | private buffer: AbortController[] = []; 5 | private maxBuffer = 4; 6 | private pending = false; 7 | 8 | public get hasPendingRequests(): boolean { 9 | return this.pending; 10 | } 11 | 12 | public async request(url, params?: RequestInit) { 13 | const controller = new AbortController(); 14 | const executionId = Date.now(); 15 | this.lastResponseId = executionId; 16 | this.pending = true; 17 | 18 | if (this.buffer.length > this.maxBuffer) { 19 | this.buffer[0].abort(); 20 | this.buffer.splice(0, 1); 21 | } 22 | 23 | this.buffer.push(controller); 24 | 25 | const response = await fetch(url, { 26 | ...params, 27 | signal: controller.signal, 28 | }); 29 | 30 | this.cleanBuffer(controller); 31 | 32 | if (response.status !== 200) { 33 | throw response; 34 | } 35 | 36 | if (this.latestResponseId > executionId) { 37 | throw new Error('Prev response'); 38 | } 39 | 40 | this.pending = this.lastResponseId !== executionId; 41 | 42 | this.latestResponseId = executionId; 43 | 44 | return await response.json(); 45 | } 46 | 47 | public abortAllRequests(): void { 48 | this.buffer.forEach((buff) => buff.abort()); 49 | } 50 | 51 | private cleanBuffer(controller) { 52 | this.buffer = this.buffer.filter((ctrl) => ctrl !== controller); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ES2020", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "ES6" 9 | }, 10 | "exclude": ["**/*.spec.ts", "**/*.test.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.test.ts", 10 | "**/*.spec.ts", 11 | "**/*.test.tsx", 12 | "**/*.spec.tsx", 13 | "**/*.test.js", 14 | "**/*.spec.js", 15 | "**/*.test.jsx", 16 | "**/*.spec.jsx", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/here-data-provider/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /packages/here-data-provider/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/here-data-provider/README.md: -------------------------------------------------------------------------------- 1 | # here-data-provider 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test here-data-provider` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /packages/here-data-provider/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'here-data-provider', 3 | preset: '../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../coverage/packages/here-data-provider', 15 | }; 16 | -------------------------------------------------------------------------------- /packages/here-data-provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any-routing/here-data-provider", 3 | "version": "0.1.0", 4 | "private": false, 5 | "dependencies": { 6 | "comlink": "^4.3.1", 7 | "simplify-js": "1.2.4", 8 | "@turf/helpers": "^6.5.0", 9 | "@liberty-rider/flexpolyline": "^0.1.0", 10 | "@turf/bbox": "^6.5.0" 11 | }, 12 | "peerDependencies": { 13 | "@any-routing/core": "^0.0.0" 14 | } 15 | } -------------------------------------------------------------------------------- /packages/here-data-provider/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "here-data-provider", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/here-data-provider/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nrwl/linter:eslint", 9 | "outputs": [ 10 | "{options.outputFile}" 11 | ], 12 | "options": { 13 | "lintFilePatterns": [ 14 | "packages/here-data-provider/**/*.ts" 15 | ] 16 | } 17 | }, 18 | "test": { 19 | "executor": "@nrwl/jest:jest", 20 | "outputs": [ 21 | "{workspaceRoot}/coverage/packages/here-data-provider" 22 | ], 23 | "options": { 24 | "jestConfig": "packages/here-data-provider/jest.config.js", 25 | "passWithNoTests": true 26 | } 27 | }, 28 | "build": { 29 | "executor": "@nrwl/js:tsc", 30 | "outputs": [ 31 | "{options.outputPath}" 32 | ], 33 | "options": { 34 | "outputPath": "dist/packages/here-data-provider", 35 | "tsConfig": "packages/here-data-provider/tsconfig.lib.json", 36 | "packageJson": "packages/here-data-provider/package.json", 37 | "main": "packages/here-data-provider/src/index.ts", 38 | "assets": [ 39 | "packages/here-data-provider/*.md" 40 | ] 41 | } 42 | } 43 | }, 44 | "tags": [] 45 | } -------------------------------------------------------------------------------- /packages/here-data-provider/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/here-data-provider'; 2 | -------------------------------------------------------------------------------- /packages/here-data-provider/src/lib/here-data-provider.spec.ts: -------------------------------------------------------------------------------- 1 | import { hereDataProvider } from './here-data-provider'; 2 | 3 | describe('hereDataProvider', () => { 4 | it('should work', () => { 5 | expect(hereDataProvider()).toEqual('here-data-provider'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/here-data-provider/src/lib/here-data-provider.ts: -------------------------------------------------------------------------------- 1 | import { wrap } from 'comlink'; 2 | 3 | import { ExecutorRequestOptions, HereExecutor } from './here.executor'; 4 | import type { 5 | AnyRoutingDataProvider, 6 | AnyRoutingDataResponse, 7 | RequestOptions, 8 | RouteSummary, 9 | LngLatPosition, 10 | Waypoint, 11 | InternalWaypoint, 12 | WaypointPosition, 13 | } from '@any-routing/core'; 14 | 15 | export type SelectRouteStrategy = 'fastest' | 'shortest' | 'cheapest' | 'none'; 16 | 17 | export type RouteExcludeNoticeDefinitions = { 18 | [key in 'critical' | 'info']?: string[] | 'all'; 19 | }; 20 | 21 | export type TurnByTurnAction = { 22 | action: 'arrive' | 'turn' | 'depart'; 23 | duration: number; 24 | length: number; 25 | position: { lat: number; lng: number }; 26 | offset: number; 27 | }; 28 | 29 | export type RoutePath = LngLatPosition[]; 30 | 31 | export interface HereRouteSummary extends RouteSummary { 32 | turnByTurnActions: TurnByTurnAction[]; 33 | shapePath: RoutePath; 34 | rawRoute: any; 35 | } 36 | 37 | export { ExecutorRequestOptions } from './here.executor'; 38 | 39 | export interface HereRoutingData extends AnyRoutingDataResponse { 40 | routes: HereRouteSummary[]; 41 | requestOptions: ExecutorRequestOptions; 42 | } 43 | 44 | export type GeoJsonSplitStrategy = 'jamFactor'; 45 | 46 | export type Options = { 47 | alternatives?: number; 48 | worker?: boolean; 49 | apiKey: string; 50 | baseUrl?: string; 51 | selectRouteStrategy?: SelectRouteStrategy; 52 | spans?: string[]; 53 | return?: string[]; 54 | currency?: string; 55 | transportMode?: string; 56 | queryParams?: Record; 57 | requestParams?: RequestInit; 58 | routeExcludeNotice?: RouteExcludeNoticeDefinitions; 59 | shapePolylinePrecision?: number; 60 | geoJSONShapeSplitStrategies?: { drag?: Array; default?: Array }, 61 | buildUrl?: (ctx: { waypoints: Waypoint[]; options: Options & RequestOptions }, url: string) => string; 62 | }; 63 | 64 | const defaultOptions: Partial = { 65 | baseUrl: 'https://router.hereapi.com/v8/routes', 66 | transportMode: 'car', 67 | worker: true, 68 | alternatives: 2, 69 | shapePolylinePrecision: 0.0000065, 70 | geoJSONShapeSplitStrategies: {}, 71 | routeExcludeNotice: { 72 | critical: 'all', 73 | }, 74 | }; 75 | 76 | export class HereProvider implements AnyRoutingDataProvider { 77 | private worker?: Worker; 78 | private executorAPI: HereExecutor; 79 | private _options: Options; 80 | 81 | public get options(): Options { 82 | return this._options; 83 | } 84 | 85 | constructor(options: Options) { 86 | this._options = { ...defaultOptions, ...options }; 87 | 88 | if (this.options.worker === true) { 89 | this.worker = new Worker(new URL('./here.worker', import.meta.url), { 90 | type: 'module', 91 | }); 92 | 93 | // @ts-ignore 94 | this.executorAPI = wrap(this.worker); 95 | } else { 96 | this.executorAPI = new HereExecutor(); 97 | } 98 | } 99 | 100 | public destroy(): void { 101 | if (this.worker) { 102 | this.worker.terminate(); 103 | } 104 | } 105 | 106 | public request(waypoints, opts: RequestOptions): Promise { 107 | const url = this.buildUrl(waypoints, { ...this.options, ...opts }); 108 | 109 | const requestParams = { url, ...this.options, ...opts }; 110 | delete requestParams['buildUrl']; 111 | 112 | return this.executorAPI.request(requestParams); 113 | } 114 | 115 | public abortAllRequests(): void { 116 | return this.executorAPI.abortAllRequests(); 117 | } 118 | 119 | public setOption(optionKey: T, value: Options[T]): void { 120 | this.options[optionKey] = value; 121 | } 122 | 123 | public async hasPendingRequests() { 124 | return this.executorAPI.hasPendingRequests(); 125 | } 126 | 127 | private buildUrl(waypoints: InternalWaypoint[], opts: Options & RequestOptions): string { 128 | const start = waypoints[0].position; 129 | const end = waypoints[waypoints.length - 1].position; 130 | const spans = this.options.spans ? this.options.spans.join(',') : null; 131 | const Return = 132 | opts.mode === 'drag' 133 | ? 'polyline,summary' 134 | : [...(this.options.return || []), 'polyline', 'summary', 'routeLabels'].join(','); 135 | 136 | const qpObj = { 137 | apiKey: this.options.apiKey, 138 | origin: this.formatWp(start), 139 | destination: this.formatWp(end), 140 | transportMode: this.options.transportMode || '', 141 | spans, 142 | return: Return, 143 | alternatives: opts.mode === 'default' ? opts?.alternatives ?? 0 : 0, 144 | currency: this.options.currency || undefined, 145 | ...this.options.queryParams, 146 | }; 147 | 148 | if (this.options.selectRouteStrategy === 'cheapest' && !qpObj.return.includes('tolls')) { 149 | qpObj.return += ',tolls'; 150 | } 151 | 152 | const queryParams = Object.keys(qpObj).reduce((acc, key) => { 153 | if (qpObj[key]) { 154 | acc[key] = qpObj[key]; 155 | } 156 | 157 | return acc; 158 | }, {}); 159 | 160 | let qp = new URLSearchParams(queryParams).toString(); 161 | 162 | if (waypoints.length > 2) { 163 | qp += `&${this.serializeWaypoints(waypoints)}`; 164 | } 165 | 166 | const url = `${this.options.baseUrl}?${qp}`; 167 | 168 | return this.options.buildUrl ? this.options.buildUrl({ waypoints, options: opts }, url) : url; 169 | } 170 | 171 | private serializeWaypoints(waypoints: InternalWaypoint[]) { 172 | return waypoints 173 | .slice(1, waypoints.length - 1) 174 | .map((w) => `via=${this.formatWp(w.position)}`) 175 | .join('&'); 176 | } 177 | 178 | private formatWp({ lat, lng }: WaypointPosition): string { 179 | return `${lat},${lng}`; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /packages/here-data-provider/src/lib/here.executor.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '@liberty-rider/flexpolyline'; 2 | import * as simplify from 'simplify-js'; 3 | import bbox from '@turf/bbox'; 4 | import { featureCollection, lineString, LineString, Feature, BBox } from '@turf/helpers'; 5 | 6 | import { HereRouteSummary, HereRoutingData, Options, RouteExcludeNoticeDefinitions, TurnByTurnAction } from '..'; 7 | import { Mode, Requester, UnauthorizedError } from '@any-routing/core'; 8 | import { selectRouteByStrategy } from './utils/select-route-strategy'; 9 | 10 | export type ExecutorRequestOptions = { url: string; mode: Mode } & Options; 11 | 12 | export class HereExecutor { 13 | private readonly requester = new Requester(); 14 | 15 | async request(opts: ExecutorRequestOptions): Promise { 16 | try { 17 | const data = await this.requester.request(opts.url, { 18 | method: 'POST', 19 | headers: { 20 | Accept: 'application/json', 21 | 'Content-Type': 'application/json', 22 | }, 23 | body: JSON.stringify({}), 24 | ...opts.requestParams, 25 | }); 26 | 27 | if ( 28 | !data.routes || 29 | data.routes.length === 0 || 30 | (opts.routeExcludeNotice && violatedResponseNotices(data, opts.routeExcludeNotice)) 31 | ) { 32 | throw new Error('No routes found'); 33 | } 34 | 35 | const routesSummary: HereRouteSummary[] = data.routes.map((route, index) => summaryRoutes(route, index, opts)); 36 | 37 | const selectedRouteId = selectRouteByStrategy(routesSummary, opts.selectRouteStrategy); 38 | 39 | const features: Feature[] = routesSummary.reduce((acc: Feature[], c) => { 40 | return [ 41 | ...acc, 42 | ...c.shape.features.map((f) => ({ 43 | ...f, 44 | properties: { 45 | ...f.properties, 46 | selected: selectedRouteId === f.properties.routeId, 47 | }, 48 | })), 49 | ]; 50 | }, []); 51 | 52 | const FC: any = featureCollection(features); 53 | 54 | return { 55 | routesShapeBounds: bbox(FC) as BBox, 56 | rawResponse: data, 57 | routes: routesSummary, 58 | selectedRouteId, 59 | routesShapeGeojson: FC, 60 | version: performance.now(), 61 | latest: !this.requester.hasPendingRequests, 62 | mode: opts.mode, 63 | requestOptions: opts, 64 | }; 65 | } catch (error: any) { 66 | if (error instanceof Response) { 67 | const response: Response = error; 68 | const body = await response.json(); 69 | 70 | if (response.status === 401) { 71 | throw new UnauthorizedError(body); 72 | } 73 | } 74 | 75 | throw error; 76 | } 77 | } 78 | 79 | hasPendingRequests() { 80 | return this.requester.hasPendingRequests; 81 | } 82 | 83 | abortAllRequests() { 84 | this.requester.abortAllRequests(); 85 | } 86 | } 87 | 88 | const summaryRoutes = (route, routeId, options: ExecutorRequestOptions): HereRouteSummary => { 89 | const { distance, cost, durationTime, waypoints, path, shape, shapePath, turnByTurnActions } = route.sections.reduce( 90 | (acc, section, index) => { 91 | const cost = (section.tolls || []).reduce( 92 | (costAcc, toll) => (toll.fares || []).forEach((fare) => (costAcc += (fare.convertedPrice || fare.price).value)), 93 | 0 94 | ); 95 | 96 | const durationTime = section.summary?.duration || 0; 97 | 98 | const waypoints = []; 99 | 100 | if (section.departure.place.originalLocation) { 101 | //@ts-ignore 102 | waypoints.push(section.departure.place.location); 103 | } 104 | 105 | // Push departure location 106 | if ( 107 | section.arrival.place.originalLocation && 108 | ((route.sections.length >= 2 && index === route.sections.length - 1) || route.sections.length === 1) 109 | ) { 110 | //@ts-ignore 111 | waypoints.push(section.arrival.place.location); 112 | } 113 | 114 | const path = decodePolyline(section.polyline); 115 | const shapePath = options.shapePolylinePrecision ? simplifyPath(path, options.shapePolylinePrecision) : path; 116 | 117 | const turnByTurnActions = (section.turnByTurnActions || []).map((action): TurnByTurnAction => { 118 | const [lat, lng] = path[action.offset]; 119 | 120 | return { 121 | ...action, 122 | offset: acc.path.length + action.offset, 123 | position: { lat, lng }, 124 | }; 125 | }); 126 | 127 | let shapes: Feature[] = []; 128 | 129 | const shouldSplit: boolean | undefined = 130 | (options.mode === 'default' && options.geoJSONShapeSplitStrategies?.default && options.geoJSONShapeSplitStrategies.default.length > 0) || 131 | (options.mode === 'drag' && 132 | options.geoJSONShapeSplitStrategies?.drag && 133 | options.geoJSONShapeSplitStrategies.drag.length > 0); 134 | 135 | if (shouldSplit) { 136 | const spansCount: number = section.spans?.length; 137 | 138 | shapes = section.spans?.reduce((shapesAcc: Feature[], span, index) => { 139 | const closeOffset: number = index < spansCount - 1 ? section.spans[index + 1].offset : path.length - 1; 140 | const spanPath = path.slice(span.offset, closeOffset + 1); 141 | const shapePath = options.shapePolylinePrecision 142 | ? simplifyPath(spanPath, options.shapePolylinePrecision) 143 | : spanPath; 144 | const isMarginalChunk: boolean = index === 0 || index === spansCount - 1; 145 | 146 | if ( 147 | ((options.mode === 'default' && options.geoJSONShapeSplitStrategies?.default && 148 | options.geoJSONShapeSplitStrategies.default.includes('jamFactor')) || 149 | (options.mode === 'drag' && options.geoJSONShapeSplitStrategies?.drag?.includes('jamFactor'))) && 150 | span.dynamicSpeedInfo 151 | ) { 152 | const jamFactor: number = span.dynamicSpeedInfo.trafficSpeed / span.dynamicSpeedInfo.baseSpeed; 153 | const prevJamFactor: number = shapesAcc[shapesAcc.length - 1]?.properties?.['jamFactor']; 154 | 155 | if (jamFactor === prevJamFactor && shapesAcc[shapesAcc.length - 1]) { 156 | shapesAcc[shapesAcc.length - 1].geometry.coordinates.push(...shapePath.slice(1, shapePath.length)); 157 | shapesAcc[shapesAcc.length - 1].properties!['isMarginalChunk'] = isMarginalChunk; 158 | } else { 159 | const shape = lineString(shapePath, { 160 | waypoint: acc.waypointIndex, 161 | routeId, 162 | jamFactor, 163 | isMarginalChunk, 164 | }); 165 | 166 | shapesAcc.push(shape); 167 | } 168 | } else if (shapesAcc.length > 0) { 169 | shapesAcc[shapesAcc.length - 1].geometry.coordinates.push(...shapePath.slice(1, shapePath.length)); 170 | } else { 171 | const shape = lineString(shapePath, { 172 | waypoint: acc.waypointIndex, 173 | routeId, 174 | isMarginalChunk, 175 | }); 176 | 177 | shapesAcc.push(shape); 178 | } 179 | 180 | return shapesAcc; 181 | }, []); 182 | } else { 183 | const line = lineString(shapePath, { 184 | waypoint: acc.waypointIndex, 185 | routeId, 186 | }); 187 | 188 | shapes.push(line); 189 | } 190 | 191 | return { 192 | distance: acc.distance + section.summary?.length || 0, 193 | durationTime: acc.durationTime + durationTime, 194 | cost: acc.cost + cost, 195 | waypoints: [...acc.waypoints, ...waypoints], 196 | path: [...acc.path, ...path], 197 | turnByTurnActions: [...acc.turnByTurnActions, ...turnByTurnActions], 198 | shape: featureCollection([...(acc.shape?.features || []), ...shapes]), 199 | shapePath: [...acc.shapePath, ...shapePath], 200 | waypointIndex: 201 | section.type === 'vehicle' && route.sections[index + 1] && route.sections[index + 1].type === 'vehicle' 202 | ? acc.waypointIndex + 1 203 | : acc.waypointIndex, 204 | }; 205 | }, 206 | { 207 | distance: 0, 208 | cost: 0, 209 | waypoints: [], 210 | path: [], 211 | durationTime: 0, 212 | turnByTurnActions: [], 213 | shape: null, 214 | shapePath: [], 215 | waypointIndex: 0, 216 | } 217 | ); 218 | 219 | return { 220 | durationTime, 221 | distance, 222 | cost, 223 | path, 224 | arriveTime: new Date(route.sections[route.sections.length - 1].arrival.time), 225 | departureTime: new Date(route.sections[0].departure.time), 226 | id: routeId, 227 | waypoints, 228 | label: route.routeLabels ? route.routeLabels.map((l) => l.name.value).join(', ') : undefined, 229 | shape, 230 | turnByTurnActions, 231 | shapePath, 232 | rawRoute: route, 233 | }; 234 | }; 235 | 236 | const violatedResponseNotices = (data, routeExcludeNotice: RouteExcludeNoticeDefinitions): boolean => { 237 | if (violatedNotices(data.notices || [], routeExcludeNotice)) { 238 | return true; 239 | } 240 | 241 | return (data.routes || []).some((route) => 242 | (route.sections || []).some((section) => violatedNotices(section.notices || [], routeExcludeNotice)) 243 | ); 244 | }; 245 | 246 | const violatedNotices = ( 247 | notices: { code: string; severity: 'critical' | 'info' }[], 248 | routeExcludeNotice: RouteExcludeNoticeDefinitions 249 | ): boolean => { 250 | return notices.some((notice) => { 251 | const definition = routeExcludeNotice[notice.severity]; 252 | 253 | return definition && (definition === 'all' || definition.includes(notice.code)); 254 | }); 255 | }; 256 | 257 | export function posIsDiff(posA, posB): boolean { 258 | const { lat: latA, lng: lngA } = posA; 259 | const { lat: latB, lng: lngB } = posB; 260 | 261 | return latA.toFixed(6) !== latB.toFixed(6) || lngA.toFixed(6) !== lngB.toFixed(6); 262 | } 263 | 264 | function decodePolyline(polyline: string): [number, number][] { 265 | return decode(polyline).polyline; 266 | } 267 | 268 | function simplifyPath(path: [number, number][], precision: number): [number, number][] { 269 | return simplify( 270 | path.map(([x, y]) => ({ x, y })), 271 | precision, 272 | true 273 | ).map((p) => [+p.y.toFixed(6), +p.x.toFixed(6)]); 274 | } 275 | -------------------------------------------------------------------------------- /packages/here-data-provider/src/lib/here.worker.ts: -------------------------------------------------------------------------------- 1 | import { expose } from 'comlink'; 2 | import { HereExecutor } from './here.executor'; 3 | 4 | const executor = new HereExecutor(); 5 | 6 | expose(executor); 7 | -------------------------------------------------------------------------------- /packages/here-data-provider/src/lib/utils/select-route-strategy.ts: -------------------------------------------------------------------------------- 1 | import type { RouteSummary } from '@any-routing/core'; 2 | import type { SelectRouteStrategy } from '../here-data-provider'; 3 | 4 | export function selectRouteByStrategy(summaryRoutes: RouteSummary[], strategy?: SelectRouteStrategy) { 5 | if (strategy === 'fastest') { 6 | const fastest = summaryRoutes.reduce(function (prev, current) { 7 | return prev?.arriveTime.valueOf() < current?.arriveTime.valueOf() ? prev : current; 8 | }); 9 | 10 | return fastest?.id; 11 | } else if (strategy === 'shortest') { 12 | const shortest = summaryRoutes.reduce(function (prev, current) { 13 | return prev?.distance < current?.distance ? prev : current; 14 | }); 15 | 16 | return shortest?.id; 17 | } else if (strategy === 'cheapest') { 18 | const cheapest = summaryRoutes 19 | .filter((s) => s.cost != null) 20 | .reduce(function (prev, current) { 21 | //@ts-ignore 22 | return prev?.cost < current?.cost ? prev : current; 23 | }); 24 | 25 | return cheapest?.id; 26 | } else if (strategy === 'none') { 27 | return null; 28 | } 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /packages/here-data-provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/here-data-provider/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ES2020", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"] 8 | }, 9 | "exclude": ["**/*.spec.ts", "**/*.test.ts"], 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/here-data-provider/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.test.ts", 10 | "**/*.spec.ts", 11 | "**/*.test.tsx", 12 | "**/*.spec.tsx", 13 | "**/*.test.js", 14 | "**/*.spec.js", 15 | "**/*.test.jsx", 16 | "**/*.spec.jsx", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": ["plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], 8 | "rules": { 9 | "@angular-eslint/directive-selector": [ 10 | "error", 11 | { 12 | "type": "attribute", 13 | "prefix": "AnyRouting", 14 | "style": "camelCase" 15 | } 16 | ], 17 | "@angular-eslint/component-selector": [ 18 | "error", 19 | { 20 | "type": "element", 21 | "prefix": "libre-routing", 22 | "style": "kebab-case" 23 | } 24 | ] 25 | } 26 | }, 27 | { 28 | "files": ["*.html"], 29 | "extends": ["plugin:@nrwl/nx/angular-template"], 30 | "rules": {} 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'libre-routing-playground', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/packages/libre-routing-playground', 12 | transform: { 13 | '^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular', 14 | }, 15 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/serializers/no-ng-attributes', 18 | 'jest-preset-angular/build/serializers/ng-snapshot', 19 | 'jest-preset-angular/build/serializers/html-comment', 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libre-routing-playground", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "packages/libre-routing-playground/src", 6 | "prefix": "libre-routing", 7 | "cli": { 8 | "cache": { 9 | "enabled": false 10 | } 11 | }, 12 | "targets": { 13 | "build": { 14 | "executor": "@angular-devkit/build-angular:browser", 15 | "outputs": ["{options.outputPath}"], 16 | "options": { 17 | "outputPath": "dist/packages/libre-routing-playground", 18 | "index": "packages/libre-routing-playground/src/index.html", 19 | "main": "packages/libre-routing-playground/src/main.ts", 20 | "polyfills": "packages/libre-routing-playground/src/polyfills.ts", 21 | "tsConfig": "packages/libre-routing-playground/tsconfig.app.json", 22 | "inlineStyleLanguage": "scss", 23 | "assets": [ 24 | "packages/libre-routing-playground/src/favicon.ico", 25 | "packages/libre-routing-playground/src/assets", 26 | { 27 | "glob": "**/*", 28 | "input": "node_modules/leaflet/dist/images", 29 | "output": "." 30 | } 31 | ], 32 | "styles": ["packages/libre-routing-playground/src/styles.scss"], 33 | "scripts": [], 34 | "webWorkerTsConfig": "packages/libre-routing-playground/tsconfig.worker.json" 35 | }, 36 | "configurations": { 37 | "production": { 38 | "budgets": [ 39 | { 40 | "type": "initial", 41 | "maximumWarning": "500kb", 42 | "maximumError": "1mb" 43 | }, 44 | { 45 | "type": "anyComponentStyle", 46 | "maximumWarning": "2kb", 47 | "maximumError": "4kb" 48 | } 49 | ], 50 | "fileReplacements": [], 51 | "outputHashing": "all" 52 | }, 53 | "development": { 54 | "buildOptimizer": false, 55 | "optimization": false, 56 | "vendorChunk": false, 57 | "extractLicenses": false, 58 | "sourceMap": true, 59 | "namedChunks": true, 60 | "fileReplacements": [] 61 | } 62 | }, 63 | "defaultConfiguration": "production" 64 | }, 65 | "serve": { 66 | "executor": "@angular-devkit/build-angular:dev-server", 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "libre-routing-playground:build:production" 70 | }, 71 | "development": { 72 | "browserTarget": "libre-routing-playground:build:development" 73 | } 74 | }, 75 | "defaultConfiguration": "development" 76 | }, 77 | "extract-i18n": { 78 | "executor": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "libre-routing-playground:build" 81 | } 82 | }, 83 | "lint": { 84 | "executor": "@nrwl/linter:eslint", 85 | "options": { 86 | "lintFilePatterns": [ 87 | "packages/libre-routing-playground/src/**/*.ts", 88 | "packages/libre-routing-playground/src/**/*.html" 89 | ] 90 | } 91 | }, 92 | "test": { 93 | "executor": "@nrwl/jest:jest", 94 | "outputs": ["{workspaceRoot}/coverage/packages/libre-routing-playground"], 95 | "options": { 96 | "jestConfig": "packages/libre-routing-playground/jest.config.js", 97 | "passWithNoTests": true 98 | } 99 | } 100 | }, 101 | "tags": [] 102 | } 103 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | #map { 2 | display: block; 3 | width: 100vw; 4 | height: 100vh; 5 | } 6 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [RouterTestingModule], 9 | declarations: [AppComponent], 10 | }).compileComponents(); 11 | }); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'libre-routing-playground'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.componentInstance; 22 | expect(app.title).toEqual('libre-routing-playground'); 23 | }); 24 | 25 | it('should render title', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.nativeElement as HTMLElement; 29 | expect(compiled.querySelector('h1')?.textContent).toContain('Welcome libre-routing-playground'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, AfterViewInit, ElementRef, ViewChild, NgZone } from '@angular/core'; 2 | import { LayerSpecification, Map, MapLayerMouseEvent, Marker, SymbolLayerSpecification } from 'maplibre-gl'; 3 | import { AnyRouting, StateUpdatedEvent } from '@any-routing/core'; 4 | import { HereProvider, HereRoutingData } from '@any-routing/here-data-provider'; 5 | import { defaultMapLibreProjectorOptions, MapLibreProjector } from '@any-routing/maplibre-engine'; 6 | import { environment } from '../environments/environment'; 7 | import { PropagationEvent, setupLayerEventsPropagator } from '@any-routing/maplibre-layers-event-propagator'; 8 | 9 | @Component({ 10 | selector: 'libre-routing-root', 11 | templateUrl: './app.component.html', 12 | styleUrls: ['./app.component.scss'], 13 | }) 14 | export class AppComponent implements AfterViewInit { 15 | @ViewChild('mapContainer') mapContainer?: ElementRef; 16 | 17 | constructor(private zone: NgZone) {} 18 | 19 | ngAfterViewInit() { 20 | this.zone.runOutsideAngular(() => { 21 | const map = new Map({ 22 | container: this.mapContainer!.nativeElement, 23 | center: [13, 51], 24 | zoom: 4, 25 | style: `https://assets.vector.hereapi.com/styles/berlin/base/mapbox/tilezen?apikey=${environment.hereApiKey}`, 26 | }); 27 | 28 | const dataProvider = new HereProvider({ 29 | apiKey: environment.hereApiKey, 30 | transportMode: 'truck', 31 | return: ['routeLabels', 'turnByTurnActions'], 32 | }); 33 | 34 | const projector = new MapLibreProjector({ 35 | ...defaultMapLibreProjectorOptions, 36 | map, 37 | }); 38 | 39 | const routing = new AnyRouting({ 40 | dataProvider, 41 | plugins: [projector], 42 | waypointsSyncStrategy: 'geocodeFirst', 43 | geocoder: async (waypoint) => { 44 | const geocode = await fetch(`https://revgeocode.search.hereapi.com/v1/revgeocode?apiKey=${environment.hereApiKey}&in=circle:${waypoint.position.lat},${waypoint.position.lng};r=50&limit=1&lang=pl&types=houseNumber,city,postalCode,area,district,street&includeUnnamedStreet=true`); 45 | const data = await geocode.json(); 46 | 47 | console.log(waypoint.position, data.items[0]) 48 | return { ...waypoint, mappedPosition: data.items[0].position, position: data.items[0].position }; 49 | } 50 | }); 51 | 52 | map.on('load', async () => { 53 | routing.initialize(); 54 | // setupLayerEventsPropagator(map); 55 | 56 | // new Marker({ draggable: true }).setLngLat([0, 0]).addTo(map); 57 | 58 | // map.on('click', console.log) 59 | // map.on('mousemove', 'lake-label', (e: PropagationEvent) => { 60 | // //@ts-ignore 61 | // console.log(e.features[0].layer.id, e, '1'); 62 | // //@ts-ignore 63 | // e.stopPropagation ? e.stopPropagation() : null; 64 | // }) 65 | // map.on('mousemove', 'lake-labelx', (e: PropagationEvent) => { 66 | // //@ts-ignore 67 | // console.log(e.features[0].layer.id, e, '1'); 68 | // //@ts-ignore 69 | // e.stopPropagation ? e.stopPropagation() : null; 70 | // e.stopImmediatePropagation ? e.stopImmediatePropagation() : null; 71 | // }) 72 | // //@ts-ignore 73 | // map.on('click', 'water', (e) => console.log(e.features[0].layer.id)); 74 | // map.on('click', 'lake-label', (e: PropagationEvent) => { 75 | // if (e.isCancelled && e.isCancelled()) { 76 | // return; 77 | // } 78 | 79 | // console.log(e, 'click on water label printed'); 80 | // e.stopPropagation ? e.stopPropagation() : null; 81 | // e.stopImmediatePropagation ? e.stopImmediatePropagation() : null; 82 | // }); 83 | 84 | // map.on('click', 'lake-label', (e: PropagationEvent) => { 85 | // if (e.isCancelled && e.isCancelled()) { 86 | // return; 87 | // } 88 | 89 | // console.log(e, 'click on water label no printed'); 90 | // e.stopPropagation ? e.stopPropagation() : null; 91 | // }); 92 | 93 | // map.on('click', 'water', (e: PropagationEvent) => { 94 | // if (e.isCancelled && e.isCancelled()) { 95 | // return; 96 | // } 97 | 98 | // console.log(e, 'click on water'); 99 | // e.stopPropagation ? e.stopPropagation() : null; 100 | // }); 101 | 102 | // map.on('click', (e: PropagationEvent) => { 103 | // //For non layer handlers 104 | // if (e.originalEvent.isCancelled && e.originalEvent.isCancelled()) { 105 | // return; 106 | // } 107 | 108 | // console.log(e, 'click anywhere else'); 109 | // }); 110 | 111 | // const l: SymbolLayerSpecification = { 112 | // id: 'lake-labelx', 113 | // type: 'symbol', 114 | // source: 'omv', 115 | // 'source-layer': 'water', 116 | // filter: ['all', ['==', '$type', 'Point']], 117 | // layout: { 118 | // 'text-field': ['coalesce', ['get', 'name:en'], ['get', 'name']], 119 | // visibility: 'visible', 120 | // 'symbol-placement': 'point', 121 | // 'icon-allow-overlap': true, 122 | // 'text-ignore-placement': true, 123 | // // "text-allow-overlap": true, 124 | // 'text-font': ['Fira GO Regular'], 125 | // 'text-max-width': 6, 126 | // 'text-line-height': 1, 127 | // }, 128 | // paint: { 129 | // 'text-color': 'rgba(1, 35, 55, 1)', 130 | // 'text-opacity': 1, 131 | // 'text-halo-color': 'rgba(255, 255, 255, 0.3)', 132 | // 'text-halo-width': 1, 133 | // }, 134 | // }; 135 | 136 | // setTimeout(() => { 137 | // map.addLayer(l); 138 | // }, 7000); 139 | 140 | //@ts-ignore 141 | // map.on('click', 'lake-label', h) 142 | // map.off('click', 'lake-label', h) 143 | // routing.initialize(); 144 | 145 | routing.setWaypoints([ 146 | { position: { lat: 49.9539315, lng: 18.8531001 }, properties: { label: 'A' } }, 147 | { position: { lng: 21.01178, lat: 52.22977 }, properties: { label: 'B' } }, 148 | ]); 149 | 150 | routing.recalculateRoute({ fitViewToData: true }); 151 | }); 152 | }); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { RouterModule } from '@angular/router'; 6 | // import { LeafletComponent } from './leaflet/leaflet.component'; 7 | 8 | @NgModule({ 9 | declarations: [AppComponent], 10 | imports: [BrowserModule, RouterModule.forRoot([], { initialNavigation: 'enabledBlocking' })], 11 | providers: [], 12 | bootstrap: [AppComponent], 13 | }) 14 | export class AppModule {} 15 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/cs.layer.ts: -------------------------------------------------------------------------------- 1 | import { CustomLayerInterface, CustomRenderMethod, Map, MercatorCoordinate } from 'maplibre-gl'; 2 | 3 | export class CustomLayer implements CustomLayerInterface { 4 | public type: 'custom' = 'custom'; 5 | public id = 'xd'; 6 | 7 | private program: any; 8 | private buffer: any; 9 | private aPos: any; 10 | 11 | onAdd(map: Map, gl: WebGLRenderingContext): void { 12 | const vertexSource = 13 | '' + 14 | 'uniform mat4 u_matrix;' + 15 | 'attribute vec2 a_pos;' + 16 | 'void main() {' + 17 | ' gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);' + 18 | '}'; 19 | 20 | // create GLSL source for fragment shader 21 | const fragmentSource = '' + 'void main() {' + ' gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);' + '}'; 22 | 23 | // create a vertex shader 24 | const vertexShader: any = gl.createShader(gl.VERTEX_SHADER); 25 | gl.shaderSource(vertexShader, vertexSource); 26 | gl.compileShader(vertexShader); 27 | 28 | // create a fragment shader 29 | const fragmentShader: any = gl.createShader(gl.FRAGMENT_SHADER); 30 | gl.shaderSource(fragmentShader, fragmentSource); 31 | gl.compileShader(fragmentShader); 32 | 33 | // link the two shaders into a WebGL program 34 | this.program = gl.createProgram(); 35 | gl.attachShader(this.program, vertexShader); 36 | gl.attachShader(this.program, fragmentShader); 37 | gl.linkProgram(this.program); 38 | 39 | this.aPos = gl.getAttribLocation(this.program, 'a_pos'); 40 | 41 | // define vertices of the triangle to be rendered in the custom style layer 42 | const helsinki = MercatorCoordinate.fromLngLat({ 43 | lng: 25.004, 44 | lat: 60.239, 45 | }); 46 | const berlin = MercatorCoordinate.fromLngLat({ 47 | lng: 13.403, 48 | lat: 52.562, 49 | }); 50 | const kyiv = MercatorCoordinate.fromLngLat({ 51 | lng: 30.498, 52 | lat: 50.541, 53 | }); 54 | 55 | // create and initialize a WebGLBuffer to store vertex and color data 56 | this.buffer = gl.createBuffer(); 57 | gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); 58 | gl.bufferData( 59 | gl.ARRAY_BUFFER, 60 | new Float32Array([helsinki.x, helsinki.y, berlin.x, berlin.y, kyiv.x, kyiv.y]), 61 | gl.STATIC_DRAW 62 | ); 63 | } 64 | 65 | render(gl, matrix) { 66 | gl.useProgram(this.program); 67 | gl.uniformMatrix4fv(gl.getUniformLocation(this.program, 'u_matrix'), false, matrix); 68 | gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); 69 | gl.enableVertexAttribArray(this.aPos); 70 | gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0); 71 | gl.enable(gl.BLEND); 72 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 73 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/leaflet/leaflet.component.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/leaflet/leaflet.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | width: 100vw; 4 | height: 100vh; 5 | 6 | #map { 7 | display: block; 8 | width: 100vw; 9 | height: 100vh; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/app/leaflet/leaflet.component.ts: -------------------------------------------------------------------------------- 1 | // import { Component, NgZone, OnInit } from '@angular/core'; 2 | // import { Map, TileLayer, Marker } from 'leaflet'; 3 | // import { AnnotationPlugin, HereProvider, HereRoutingData, LeafletProjector, AnyRouting, MapLibreProjector, MousePlugin } from 'libre-routing'; 4 | // import { environment } from '../../environments/environment'; 5 | 6 | // @Component({ 7 | // selector: 'libre-routing-leaflet', 8 | // templateUrl: 'leaflet.component.html', 9 | // styleUrls: ['leaflet.component.scss'] 10 | // }) 11 | 12 | // export class LeafletComponent implements OnInit { 13 | // constructor(private zone: NgZone) {} 14 | 15 | // ngOnInit() { } 16 | 17 | // ngAfterViewInit() { 18 | // this.zone.runOutsideAngular(() => { 19 | // const map = new Map('map', { preferCanvas: true }).setView([51.505, -0.09], 13); 20 | 21 | // new TileLayer ('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { 22 | // attribution: '© OpenStreetMap contributors' 23 | // }).addTo(map); 24 | 25 | // const dataProvider = new HereProvider({ 26 | // apiKey: environment.hereApiKey, 27 | // transportMode: 'truck', 28 | // return: ['routeLabels', 'turnByTurnActions'], 29 | // }); 30 | 31 | // const projector = new LeafletProjector({}); 32 | 33 | // const routing = new AnyRouting({ 34 | // alternatives: 2, 35 | // dataProvider, 36 | // projector, 37 | // plugins: [new MousePlugin()], 38 | // waypointsSyncStrategy: 'toPath', 39 | // }); 40 | 41 | // //@ts-ignore 42 | // routing.onAdd(map); 43 | 44 | // routing.setWaypoints([ 45 | // { position: [18.8531001, 49.9539315], properties: { label: 'A' } }, 46 | // { position: [21.01178, 52.22977], properties: { label: 'B' } } 47 | // ]); 48 | 49 | // routing.recalculateRoute(); 50 | 51 | // }); 52 | 53 | // } 54 | // } 55 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/packages/libre-routing-playground/src/assets/.gitkeep -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/environments/env.shared.ts: -------------------------------------------------------------------------------- 1 | export const sharedEnv = { 2 | hereApiKey: 'g-RioYnqadudSGBaiPMnvkrpVHvGBz6Wkrqrab9tpaw', 3 | }; 4 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | import { sharedEnv } from './env.shared'; 2 | 3 | export const environment = { 4 | production: true, 5 | hereApiKey: sharedEnv.hereApiKey, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | import { sharedEnv } from './env.shared'; 5 | 6 | export const environment = { 7 | production: false, 8 | hereApiKey: sharedEnv.hereApiKey, 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/packages/libre-routing-playground/src/favicon.ico -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AnyRoutingPlayground 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | /*************************************************************************************************** 51 | * APPLICATION IMPORTS 52 | */ 53 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~maplibre-gl/dist/maplibre-gl.css'; 2 | @import '~leaflet/dist/leaflet.css'; 3 | 4 | html, 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | } 9 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [], 6 | "lib": ["es2018", "dom"], 7 | "target": "ES2022", 8 | "useDefineForClassFields": false 9 | }, 10 | "files": ["src/main.ts", "src/polyfills.ts"], 11 | "include": ["src/**/*.d.ts"], 12 | "exclude": ["**/*.test.ts", "**/*.spec.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "types": ["jest", "node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | }, 12 | { 13 | "path": "./tsconfig.editor.json" 14 | } 15 | ], 16 | "compilerOptions": { 17 | "forceConsistentCasingInFileNames": true, 18 | "strict": true, 19 | "noImplicitOverride": true, 20 | "noPropertyAccessFromIndexSignature": true, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "skipLibCheck": true, 24 | "noImplicitAny": false, 25 | "allowSyntheticDefaultImports": true, 26 | "declaration": false, 27 | "resolveJsonModule": true, 28 | "target": "es2020" 29 | }, 30 | "angularCompilerOptions": { 31 | "strictInjectionParameters": true, 32 | "strictInputAccessModifiers": true, 33 | "strictTemplates": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/libre-routing-playground/tsconfig.worker.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/worker", 5 | "lib": ["es2018", "webworker", "dom"], 6 | "types": [] 7 | }, 8 | "include": ["../**/*.worker.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/maplibre-engine/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /packages/maplibre-engine/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/maplibre-engine/README.md: -------------------------------------------------------------------------------- 1 | # maplibre-engine 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test maplibre-engine` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /packages/maplibre-engine/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'maplibre-engine', 3 | preset: '../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../coverage/packages/maplibre-engine', 15 | }; 16 | -------------------------------------------------------------------------------- /packages/maplibre-engine/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any-routing/maplibre-engine", 3 | "version": "0.2.0", 4 | "private": false, 5 | "peerDependencies": { 6 | "maplibre-gl": "^2.*", 7 | "@any-routing/core": "^0.0.1" 8 | } 9 | } -------------------------------------------------------------------------------- /packages/maplibre-engine/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maplibre-engine", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/maplibre-engine/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nrwl/linter:eslint", 9 | "outputs": [ 10 | "{options.outputFile}" 11 | ], 12 | "options": { 13 | "lintFilePatterns": [ 14 | "packages/maplibre-engine/**/*.ts" 15 | ] 16 | } 17 | }, 18 | "test": { 19 | "executor": "@nrwl/jest:jest", 20 | "outputs": [ 21 | "{workspaceRoot}/coverage/packages/maplibre-engine" 22 | ], 23 | "options": { 24 | "jestConfig": "packages/maplibre-engine/jest.config.js", 25 | "passWithNoTests": true 26 | } 27 | }, 28 | "build": { 29 | "executor": "@nrwl/js:tsc", 30 | "outputs": [ 31 | "{options.outputPath}" 32 | ], 33 | "options": { 34 | "outputPath": "dist/packages/maplibre-engine", 35 | "tsConfig": "packages/maplibre-engine/tsconfig.lib.json", 36 | "packageJson": "packages/maplibre-engine/package.json", 37 | "main": "packages/maplibre-engine/src/index.ts", 38 | "assets": [ 39 | "packages/maplibre-engine/*.md" 40 | ] 41 | } 42 | } 43 | }, 44 | "tags": [] 45 | } -------------------------------------------------------------------------------- /packages/maplibre-engine/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/maplibre-engine'; 2 | -------------------------------------------------------------------------------- /packages/maplibre-engine/src/lib/maplibre-engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { maplibreEngine } from './maplibre-engine'; 2 | 3 | describe('maplibreEngine', () => { 4 | it('should work', () => { 5 | expect(maplibreEngine()).toEqual('maplibre-engine'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/maplibre-engine/src/lib/maplibre-engine.ts: -------------------------------------------------------------------------------- 1 | export * from './projector-plugin'; 2 | -------------------------------------------------------------------------------- /packages/maplibre-engine/src/lib/projector-plugin/index.ts: -------------------------------------------------------------------------------- 1 | export * from './projector.plugin'; 2 | export * from './projector.plugin.defaults'; 3 | export * from './projector.plugin.types'; 4 | -------------------------------------------------------------------------------- /packages/maplibre-engine/src/lib/projector-plugin/projector.plugin.defaults.ts: -------------------------------------------------------------------------------- 1 | import { LayerSpecification, Marker } from 'maplibre-gl'; 2 | import type { 3 | LayerFactoryContext, 4 | LayerRef, 5 | MapLibreProjectorOptions, 6 | MarkerFactoryContext, 7 | } from './projector.plugin.types'; 8 | 9 | export const defaultMarkerFactory = ({ waypoint, routeHover }: MarkerFactoryContext): Marker => { 10 | if ((waypoint && !waypoint.properties.isFirst && !waypoint.properties.isLast) || routeHover) { 11 | const el = document.createElement('div'); 12 | const width = waypoint ? 16 : 12; 13 | const height = waypoint ? 16 : 12; 14 | el.className = 'marker'; 15 | el.style.backgroundImage = `url(https://upload.wikimedia.org/wikipedia/commons/6/64/Blue_dot_with_stroke.svg)`; 16 | el.style.width = `${width}px`; 17 | el.style.height = `${height}px`; 18 | el.style.backgroundSize = '100%'; 19 | 20 | return new Marker(el); 21 | } 22 | 23 | return new Marker(); 24 | }; 25 | 26 | export const mapLibreProjectorDefaultRoutetLayer = ({ sourceId, routing }: LayerFactoryContext): LayerRef => { 27 | const specification: LayerSpecification = { 28 | id: routing.getUniqueName(`base-route`), 29 | type: 'line', 30 | source: sourceId, 31 | minzoom: 1, 32 | maxzoom: 20, 33 | layout: { 34 | 'line-join': 'round', 35 | 'line-cap': 'round', 36 | 'line-sort-key': ['case', ['==', ['get', 'selected'], true], 1, 0], 37 | }, 38 | paint: { 39 | 'line-color': ['case', ['==', ['get', 'selected'], true], '#e207ff', '#33C9EB'], 40 | 'line-width': 4, 41 | }, 42 | }; 43 | 44 | return { specification }; 45 | }; 46 | 47 | export const defaultMapLibreProjectorOptions: Omit = { 48 | routeLayersFactory: [mapLibreProjectorDefaultRoutetLayer], 49 | markerFactory: defaultMarkerFactory, 50 | routesSourceId: 'routes', 51 | }; 52 | -------------------------------------------------------------------------------- /packages/maplibre-engine/src/lib/projector-plugin/projector.plugin.ts: -------------------------------------------------------------------------------- 1 | import { featureCollection } from '@turf/helpers'; 2 | import { FitBoundsOptions, LngLatBounds, Map, MapLayerMouseEvent, Marker } from 'maplibre-gl'; 3 | import { 4 | AnyRouting, 5 | AnyRoutingDataResponse, 6 | AnyRoutingPlugin, 7 | Dispatcher, 8 | InternalWaypoint, 9 | InternalWaypointC, 10 | Mode, 11 | } from '@any-routing/core'; 12 | import bbox from '@turf/bbox'; 13 | import { debounce } from '../utils/debounce.util'; 14 | import { LayerFactory, LayerRef, MapLibreProjectorEventMap, MapLibreProjectorOptions } from './projector.plugin.types'; 15 | 16 | export class MapLibreProjector implements AnyRoutingPlugin { 17 | private routing!: AnyRouting; 18 | private readonly map: Map; 19 | private readonly options: MapLibreProjectorOptions; 20 | private readonly dispatcher: Dispatcher = new Dispatcher(); 21 | 22 | private addWaypointMarker: Marker = new Marker(); 23 | private _waypointsMarkers: Marker[] = []; 24 | private _canAddWaypoints = true; 25 | private _canDragWaypoints = true; 26 | private _canSelectRoute = true; 27 | private _maxWaypoints = Infinity; 28 | private _sourceId: string; 29 | 30 | private readonly routeClickHandler = this.onRouteClick.bind(this); 31 | private readonly routeHoverHandler = this.onRouteHover.bind(this); 32 | private readonly routeHoverOutHandler = this.onRouteHoverOut.bind(this); 33 | private readonly routeMouseDownHandler = this.onRouteMouseDown.bind(this); 34 | private readonly routeMoveHandler = this.onRouteMove.bind(this); 35 | private readonly dragCommitHandler; 36 | 37 | public get waypointsMarkers(): Marker[] { 38 | return this._waypointsMarkers; 39 | } 40 | 41 | public get routesLayerIds(): string[] { 42 | return this._routesLayerIds; 43 | } 44 | 45 | public get isEditable(): boolean { 46 | return this._canAddWaypoints || this._canDragWaypoints; 47 | } 48 | 49 | private get waypoints(): InternalWaypoint[] { 50 | return this.routing.state.waypoints; 51 | } 52 | 53 | private _routesLayerIds: string[] = []; 54 | 55 | private layers: LayerRef[] = []; 56 | 57 | constructor(options: MapLibreProjectorOptions) { 58 | this.options = { 59 | maxWaypoints: Infinity, 60 | canAddWaypoints: true, 61 | canDragWaypoints: true, 62 | canSelectRoute: true, 63 | routesWhileDragging: true, 64 | waypointDragCommitDebounceTime: 150, 65 | sourceTolerance: 0.01, 66 | ...options, 67 | }; 68 | 69 | this.map = options.map; 70 | this._maxWaypoints = this.options.maxWaypoints ?? Infinity; 71 | this._sourceId = options.routesSourceId || ''; 72 | 73 | this.dragCommitHandler = debounce( 74 | (newWaypoints: InternalWaypoint[], index: number) => { 75 | this.dispatcher.fire('waypointDragCommit', { 76 | waypoint: newWaypoints[index], 77 | }); 78 | 79 | this.setAndRecalculate(newWaypoints, 'drag'); 80 | }, 81 | 50, 82 | { maxWait: this.options.waypointDragCommitDebounceTime } 83 | ); 84 | } 85 | 86 | public projectRoute(routesShapeGeojson: AnyRoutingDataResponse['routesShapeGeojson']): void { 87 | this.map 88 | ?.getSource(this._sourceId) 89 | //@ts-ignore 90 | ?.setData(routesShapeGeojson); 91 | 92 | this.dispatcher.fire('routesProjected', { routesShapeGeojson }); 93 | } 94 | 95 | public onAdd(anyRouting: AnyRouting): void { 96 | this.routing = anyRouting; 97 | 98 | this._sourceId = this.options.routesSourceId ?? this.routing.getUniqueName('any-routing-maplibre-projector'); 99 | 100 | this.map.addSource(this._sourceId, { 101 | data: featureCollection([]), 102 | type: 'geojson', 103 | lineMetrics: this.options.sourceLineMetrics === true, 104 | tolerance: this.options.sourceTolerance, 105 | }); 106 | 107 | this.setLayers(this.options.routeLayersFactory || []); 108 | 109 | this.setCanAddWaypoints(this.options.editable ?? !!this.options.canAddWaypoints); 110 | this.setCanSelectRoute(this.options.editable ?? !!this.options.canSelectRoute); 111 | this.setCanDragWaypoint(this.options.editable ?? !!this.options.canDragWaypoints); 112 | this.bindLayerEvents(); 113 | 114 | this.routing.on('waypointsChanged', (event) => { 115 | if (event.state.mode === 'drag') { 116 | return; 117 | } 118 | 119 | this.projectWaypoints(event.waypoints); 120 | }); 121 | 122 | this.routing.on('routesFound', (event) => { 123 | if (event.state.routesShapeGeojson) { 124 | this.projectRoute(event.state.routesShapeGeojson); 125 | 126 | if (event.state.mode === 'default') { 127 | this.projectWaypoints(event.state.waypoints); 128 | } 129 | 130 | if (event.recalculateOptions.fitViewToData) { 131 | this.fitViewToData(); 132 | } 133 | } 134 | }); 135 | 136 | this.routing.on('routeSelected', (event) => { 137 | if (event.state.routesShapeGeojson) { 138 | this.projectRoute(event.state.routesShapeGeojson); 139 | } 140 | }); 141 | 142 | this.routing.on('calculationStarted', (event) => { 143 | if (event.recalculateOptions.clearMap) { 144 | this.clearRoutes(); 145 | } 146 | }); 147 | 148 | this.routing.on('stateUpdated', (event) => { 149 | if ( 150 | event.source === 'external' && 151 | (event.updatedProperties.includes('routesShapeGeojson') || event.updatedProperties.includes('waypoints')) 152 | ) { 153 | if (event.state.routesShapeGeojson) { 154 | this.projectRoute(event.state.routesShapeGeojson); 155 | } 156 | 157 | this.projectWaypoints(event.state.waypoints); 158 | } 159 | }); 160 | } 161 | 162 | public onRemove(): void { 163 | this.destroy(); 164 | } 165 | 166 | public projectWaypoints(waypoints: InternalWaypoint[]): void { 167 | this._waypointsMarkers.forEach((waypointMarker) => waypointMarker.remove()); 168 | 169 | this._waypointsMarkers = waypoints.map((waypoint, index): Marker => { 170 | const marker: Marker = this.options.markerFactory({ waypoint }); 171 | marker 172 | .setDraggable(this._canDragWaypoints) 173 | .setLngLat([waypoint.position.lng, waypoint.position.lat]) 174 | .addTo(this.map!); 175 | 176 | const dragHandler = (event) => { 177 | const newWaypoints = [...this.waypoints]; 178 | newWaypoints[index] = { 179 | ...newWaypoints[index], 180 | position: { 181 | lat: event.target._lngLat.lat, 182 | lng: event.target._lngLat.lng, 183 | }, 184 | geocoded: false, 185 | }; 186 | 187 | this.dispatcher.fire('waypointDrag', { 188 | waypoint: newWaypoints[index], 189 | }); 190 | 191 | this.dragCommitHandler(newWaypoints, index); 192 | }; 193 | 194 | if (this.options.routesWhileDragging) { 195 | marker.on('drag', dragHandler); 196 | } 197 | 198 | marker.on('dragend', (e) => { 199 | this.dragCommitHandler.cancel(); 200 | marker.off('drag', dragHandler); 201 | 202 | const newWaypoints = [...this.routing.state.waypoints]; 203 | newWaypoints[index] = { 204 | ...newWaypoints[index], 205 | position: { 206 | lat: e.target._lngLat.lat, 207 | lng: e.target._lngLat.lng, 208 | }, 209 | geocoded: false, 210 | }; 211 | 212 | this.dispatcher.fire('waypointDragEnd', { 213 | waypoint: newWaypoints[index], 214 | }); 215 | 216 | this.setAndRecalculate(newWaypoints, 'default'); 217 | }); 218 | 219 | return marker; 220 | }); 221 | } 222 | 223 | public destroy(): void { 224 | this.layers.forEach((layer) => { 225 | if (this.map?.getLayer(layer.specification.id)) { 226 | this.map?.removeLayer(layer.specification.id); 227 | } 228 | }); 229 | 230 | if (this.map?.getSource(this._sourceId)) { 231 | this.map?.removeSource(this._sourceId); 232 | } 233 | 234 | this._waypointsMarkers?.forEach((waypointMarker) => waypointMarker.remove()); 235 | this.unbindLayerEvents(); 236 | } 237 | 238 | public setLayers(layersFactories: LayerFactory[]): void { 239 | this.destroyLayers(); 240 | this.layers = layersFactories.map((layerFactory) => 241 | layerFactory({ routing: this.routing, sourceId: this._sourceId }) 242 | ); 243 | this._routesLayerIds = this.layers.map(({ specification: { id } }) => id); 244 | this.layers.forEach((layer) => this.map!.addLayer(layer.specification, layer.addBefore)); 245 | } 246 | 247 | private destroyLayers(): void { 248 | this.layers.forEach((layer) => this.map!.removeLayer(layer.specification.id)); 249 | this.layers = []; 250 | this._routesLayerIds = []; 251 | } 252 | 253 | public clearRoutes(): void { 254 | this.map 255 | ?.getSource(this._sourceId) 256 | //@ts-ignore 257 | ?.setData(featureCollection([])); 258 | } 259 | 260 | public fitViewToData(options: FitBoundsOptions = {}): void { 261 | const state = this.routing.state; 262 | let bounds: LngLatBounds; 263 | 264 | if (state.routesShapeGeojson || state.data?.routesShapeBounds) { 265 | bounds = state.data?.routesShapeBounds 266 | ? new LngLatBounds(state.data.routesShapeBounds as [number, number, number, number]) 267 | : new LngLatBounds(bbox(state.routesShapeGeojson) as [number, number, number, number]); 268 | } else if (state.waypoints.length > 0) { 269 | bounds = new LngLatBounds(); 270 | 271 | state.waypoints.forEach((w) => bounds.extend([w.position.lng, w.position.lat])); 272 | } else { 273 | return; 274 | } 275 | 276 | this.map!.fitBounds(bounds, { 277 | padding: 40, 278 | ...options, 279 | }); 280 | } 281 | 282 | public on( 283 | event: E, 284 | call: (event: MapLibreProjectorEventMap[E]) => void 285 | ): void { 286 | this.dispatcher.on(event, call); 287 | } 288 | 289 | public off( 290 | event: E, 291 | call: (event: MapLibreProjectorEventMap[E]) => void 292 | ): void { 293 | this.dispatcher.off(event, call); 294 | } 295 | 296 | private onRouteClick(event: MapLayerMouseEvent): void { 297 | if ( 298 | //@ts-ignore 299 | !event.features[0].properties?.selected && 300 | //@ts-ignore 301 | event.features[0].properties?.routeId != null && 302 | this._canSelectRoute && 303 | !this.eventIsCancelled(event) 304 | ) { 305 | this.stopEventPropagation(event); 306 | //@ts-ignore 307 | const routeId: number = event.features[0].properties.routeId; 308 | 309 | this.routing.selectRoute(routeId); 310 | this.dispatcher.fire('routeClick', { routeId }); 311 | } 312 | } 313 | 314 | private onRouteHover(event: MapLayerMouseEvent): void { 315 | this.map!.getCanvas().style.cursor = 'pointer'; 316 | 317 | if ( 318 | event.originalEvent.target === this.map.getCanvas() && 319 | //@ts-ignore 320 | event.features[0].properties?.selected && 321 | //@ts-ignore 322 | event.features[0].properties?.routeId != null && 323 | this._canAddWaypoints && 324 | this.waypoints.length < this._maxWaypoints && 325 | !this.eventIsCancelled(event) 326 | ) { 327 | this.stopEventPropagation(event); 328 | 329 | this.addWaypointMarker?.remove(); 330 | this.addWaypointMarker = this.options.markerFactory({ 331 | //@ts-ignore 332 | routeHover: event.features[0], 333 | }); 334 | this.addWaypointMarker.setLngLat(event.lngLat).addTo(this.map); 335 | this.addWaypointMarker.getElement().style.pointerEvents = 'none'; 336 | } 337 | } 338 | 339 | private onRouteHoverOut(): void { 340 | this.map!.getCanvas().style.cursor = ''; 341 | 342 | this.addWaypointMarker.remove(); 343 | } 344 | 345 | private onRouteMouseDown(event: MapLayerMouseEvent): void { 346 | //@ts-ignore 347 | if ( 348 | (event.originalEvent.target as HTMLElement) !== this.map.getCanvas() || 349 | !event.features || 350 | event.features[0] == null || 351 | //@ts-ignore 352 | event.features[0].properties?.waypoint == null || 353 | //@ts-ignore 354 | !event.features[0].properties?.selected || 355 | !this._canAddWaypoints || 356 | this.waypoints.length >= this._maxWaypoints || 357 | this.eventIsCancelled(event) 358 | ) { 359 | return; 360 | } 361 | 362 | if ((event.originalEvent.target as HTMLElement).closest('.marker')) { 363 | return; 364 | } 365 | 366 | event.preventDefault(); 367 | this.stopEventPropagation(event); 368 | 369 | //@ts-ignore 370 | const newWaypointIndex: number = event.features[0].properties.waypoint + 1; 371 | 372 | const waypoint: InternalWaypoint = InternalWaypointC.fromWaypoint( 373 | { 374 | position: { lat: event.lngLat.lat, lng: event.lngLat.lng }, 375 | }, 376 | { isFirst: false, isLast: false, index: newWaypointIndex } 377 | ); 378 | 379 | const newWaypointMarker = this.options 380 | .markerFactory({ 381 | waypoint, 382 | }) 383 | .setLngLat(event.lngLat) 384 | .addTo(this.map!); 385 | 386 | const newWaypoints = [...this.waypoints]; 387 | newWaypoints.splice(newWaypointIndex, 0, waypoint); 388 | 389 | this.dispatcher.fire('waypointAdded', { waypoint }); 390 | this.setAndRecalculate(newWaypoints, 'drag'); 391 | 392 | const mouseMoveHandler = (event) => { 393 | newWaypointMarker.setLngLat(event.lngLat); 394 | const newWaypoints = [...this.waypoints]; 395 | const waypoint = InternalWaypointC.fromWaypoint( 396 | { 397 | position: { lat: event.lngLat.lat, lng: event.lngLat.lng }, 398 | }, 399 | { isFirst: false, isLast: false, index: newWaypointIndex } 400 | ); 401 | 402 | newWaypoints[newWaypointIndex] = waypoint; 403 | this.dispatcher.fire('waypointDrag', { 404 | waypoint: newWaypoints[newWaypointIndex], 405 | }); 406 | 407 | if (this.options.routesWhileDragging) { 408 | this.dragCommitHandler(newWaypoints, newWaypointIndex); 409 | } 410 | }; 411 | 412 | this.map!.on('mousemove', mouseMoveHandler); 413 | 414 | this.map!.once('mouseup', (event) => { 415 | this.map!.off('mousemove', mouseMoveHandler); 416 | this.dragCommitHandler.cancel(); 417 | const newWaypoints = [...this.waypoints]; 418 | const waypoint = InternalWaypointC.fromWaypoint( 419 | { 420 | position: { lat: event.lngLat.lat, lng: event.lngLat.lng }, 421 | }, 422 | { isFirst: false, isLast: false, index: newWaypointIndex } 423 | ); 424 | 425 | newWaypoints[newWaypointIndex] = waypoint; 426 | this.projectWaypoints(newWaypoints); 427 | newWaypointMarker.remove(); 428 | this.dispatcher.fire('waypointDragEnd', { 429 | waypoint: newWaypoints[newWaypointIndex], 430 | }); 431 | this.setAndRecalculate(newWaypoints, 'default'); 432 | }); 433 | } 434 | 435 | private setAndRecalculate(waypoints: InternalWaypoint[], mode: Mode): void { 436 | this.routing.setMode(mode); 437 | this.routing.setWaypoints(waypoints.map((w) => ({ ...w }))); 438 | this.routing 439 | .recalculateRoute({ 440 | clearMap: false, 441 | dropPendingRequests: mode === 'default', 442 | fitViewToData: false, 443 | syncWaypoints: mode === 'default', 444 | }) 445 | .catch(() => this.clearRoutes()); 446 | } 447 | 448 | private onRouteMove(event: MapLayerMouseEvent): void { 449 | if (!this._canAddWaypoints || this.waypoints.length >= this._maxWaypoints || this.eventIsCancelled(event)) { 450 | return; 451 | } 452 | 453 | if ( 454 | event.originalEvent.target === this.map.getCanvas() && 455 | //@ts-ignore 456 | event.features[0].properties?.selected && 457 | //@ts-ignore 458 | event.features[0].properties?.routeId != null 459 | ) { 460 | this.stopEventPropagation(event); 461 | 462 | this.addWaypointMarker.setLngLat(event.lngLat).addTo(this.map); 463 | this.addWaypointMarker.getElement().style.pointerEvents = 'none'; 464 | } 465 | } 466 | 467 | public setEditable(isEditable: boolean): void { 468 | this.setCanAddWaypoints(isEditable); 469 | this.setCanSelectRoute(isEditable); 470 | this.setCanDragWaypoint(isEditable); 471 | } 472 | 473 | public setCanDragWaypoint(canDragWaypoints: boolean): void { 474 | this._canDragWaypoints = canDragWaypoints; 475 | this.addWaypointMarker?.remove(); 476 | this.waypointsMarkers.forEach((marker) => marker.setDraggable(canDragWaypoints)); 477 | } 478 | 479 | public setCanSelectRoute(canSelectRoute: boolean): void { 480 | this._canSelectRoute = canSelectRoute; 481 | } 482 | 483 | public setCanAddWaypoints(canAddWaypoints: boolean): void { 484 | this._canAddWaypoints = canAddWaypoints; 485 | 486 | if (!canAddWaypoints) { 487 | this.addWaypointMarker?.remove(); 488 | } 489 | } 490 | 491 | public setMaxWaypoints(maxWaypoints: number): void { 492 | this._maxWaypoints = maxWaypoints; 493 | } 494 | 495 | private bindLayerEvents(): void { 496 | this.routesLayerIds.forEach((layerId) => { 497 | this.map.on('click', layerId, this.routeClickHandler); 498 | this.map.on('mouseenter', layerId, this.routeHoverHandler); 499 | this.map.on('mouseleave', layerId, this.routeHoverOutHandler); 500 | this.map.on('mousedown', layerId, this.routeMouseDownHandler); 501 | this.map.on('mousemove', layerId, this.routeMoveHandler); 502 | }); 503 | } 504 | 505 | private unbindLayerEvents(): void { 506 | this.routesLayerIds.forEach((layerId) => { 507 | this.map.off('click', layerId, this.routeClickHandler); 508 | this.map.off('mouseenter', layerId, this.routeHoverHandler); 509 | this.map.off('mouseleave', layerId, this.routeHoverOutHandler); 510 | this.map.off('mousedown', layerId, this.routeMoveHandler); 511 | this.map.off('mousemove', layerId, this.routeMoveHandler); 512 | }); 513 | } 514 | 515 | private eventIsCancelled(event: MapLayerMouseEvent): boolean { 516 | //@ts-ignore 517 | return event.originalEvent.handledFor ? !!event.originalEvent.handledFor.includes(event.type) : false; 518 | } 519 | 520 | private stopEventPropagation(event: MapLayerMouseEvent): void { 521 | //@ts-ignore 522 | event.originalEvent.handledFor = [...(event.originalEvent.handledFor || []), event.type]; 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /packages/maplibre-engine/src/lib/projector-plugin/projector.plugin.types.ts: -------------------------------------------------------------------------------- 1 | import { AnyRouting, AnyRoutingDataResponse, InternalWaypoint } from '@any-routing/core'; 2 | import { Feature, Geometry } from '@turf/helpers'; 3 | import { LayerSpecification, Map, Marker } from 'maplibre-gl'; 4 | 5 | export type WaypointDragEvent = { 6 | waypoint: InternalWaypoint; 7 | }; 8 | 9 | export type RouteClickEvent = { 10 | routeId: number; 11 | }; 12 | 13 | export type RoutesProjectedEvent = { 14 | routesShapeGeojson: AnyRoutingDataResponse['routesShapeGeojson']; 15 | }; 16 | 17 | export type WaypointsProjectedEvent = { 18 | waypoints: InternalWaypoint[]; 19 | }; 20 | 21 | export type WaypointDragEndEvent = { 22 | waypoint: InternalWaypoint; 23 | }; 24 | 25 | export type LoadingChangedEvent = { 26 | loading: boolean; 27 | }; 28 | 29 | export type WaypointAddedEvent = { 30 | waypoint: InternalWaypoint; 31 | }; 32 | 33 | export interface MapLibreProjectorEventMap { 34 | waypointDrag: WaypointDragEvent; 35 | waypointDragCommit: WaypointDragEvent; 36 | waypointDragEnd: WaypointDragEndEvent; 37 | waypointAdded: WaypointAddedEvent; 38 | routesProjected: RoutesProjectedEvent; 39 | waypointsProjected: WaypointsProjectedEvent; 40 | routeClick: RouteClickEvent; 41 | } 42 | 43 | export type LayerFactoryContext = { routing: AnyRouting; sourceId: string }; 44 | 45 | export type LayerRef = { specification: LayerSpecification; addBefore?: string }; 46 | export type LayerFactory = (context: LayerFactoryContext) => LayerRef; 47 | 48 | export type MarkerFactoryContext = { 49 | waypoint?: InternalWaypoint; 50 | routeHover?: Feature; 51 | }; 52 | 53 | export type MapLibreProjectorOptions = { 54 | map: Map; 55 | routesSourceId?: string; 56 | routeLayersFactory: LayerFactory[]; 57 | editable?: boolean; 58 | maxWaypoints?: number; 59 | canAddWaypoints?: boolean; 60 | canDragWaypoints?: boolean; 61 | canSelectRoute?: boolean; 62 | routesWhileDragging?: boolean; 63 | waypointDragCommitDebounceTime?: number; 64 | sourceLineMetrics?: boolean; 65 | sourceTolerance?: number; 66 | markerFactory: (ctx: MarkerFactoryContext) => Marker; 67 | }; 68 | -------------------------------------------------------------------------------- /packages/maplibre-engine/src/lib/utils/debounce.util.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | const nativeMax = Math.max; 3 | const nativeMin = Math.min; 4 | export function debounce(func, wait, options) { 5 | let lastArgs, 6 | lastThis, 7 | maxWait, 8 | result, 9 | timerId, 10 | lastCallTime, 11 | lastInvokeTime = 0, 12 | leading = false, 13 | maxing = false, 14 | trailing = true; 15 | if (typeof func !== 'function') { 16 | throw new TypeError(FUNC_ERROR_TEXT); 17 | } 18 | wait = Number(wait) || 0; 19 | if (typeof options === 'object') { 20 | leading = !!options.leading; 21 | maxing = 'maxWait' in options; 22 | maxWait = maxing ? nativeMax(Number(options.maxWait) || 0, wait) : maxWait; 23 | trailing = 'trailing' in options ? !!options.trailing : trailing; 24 | } 25 | 26 | function invokeFunc(time) { 27 | const args = lastArgs, 28 | thisArg = lastThis; 29 | 30 | lastArgs = lastThis = undefined; 31 | lastInvokeTime = time; 32 | result = func.apply(thisArg, args); 33 | return result; 34 | } 35 | 36 | function leadingEdge(time) { 37 | // Reset any `maxWait` timer. 38 | lastInvokeTime = time; 39 | // Start the timer for the trailing edge. 40 | timerId = setTimeout(timerExpired, wait); 41 | // Invoke the leading edge. 42 | return leading ? invokeFunc(time) : result; 43 | } 44 | 45 | function remainingWait(time) { 46 | const timeSinceLastCall = time - lastCallTime, 47 | timeSinceLastInvoke = time - lastInvokeTime, 48 | result = wait - timeSinceLastCall; 49 | return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; 50 | } 51 | 52 | function shouldInvoke(time) { 53 | const timeSinceLastCall = time - lastCallTime, 54 | timeSinceLastInvoke = time - lastInvokeTime; 55 | // Either this is the first call, activity has stopped and we're at the trailing 56 | // edge, the system time has gone backwards and we're treating it as the 57 | // trailing edge, or we've hit the `maxWait` limit. 58 | return ( 59 | lastCallTime === undefined || 60 | timeSinceLastCall >= wait || 61 | timeSinceLastCall < 0 || 62 | (maxing && timeSinceLastInvoke >= maxWait) 63 | ); 64 | } 65 | 66 | function timerExpired() { 67 | const time = Date.now(); 68 | if (shouldInvoke(time)) { 69 | return trailingEdge(time); 70 | } 71 | // Restart the timer. 72 | timerId = setTimeout(timerExpired, remainingWait(time)); 73 | } 74 | 75 | function trailingEdge(time) { 76 | timerId = undefined; 77 | 78 | // Only invoke if we have `lastArgs` which means `func` has been debounced at 79 | // least once. 80 | if (trailing && lastArgs) { 81 | return invokeFunc(time); 82 | } 83 | lastArgs = lastThis = undefined; 84 | return result; 85 | } 86 | 87 | function cancel() { 88 | if (timerId !== undefined) { 89 | clearTimeout(timerId); 90 | } 91 | lastInvokeTime = 0; 92 | lastArgs = lastCallTime = lastThis = timerId = undefined; 93 | } 94 | 95 | function flush() { 96 | return timerId === undefined ? result : trailingEdge(Date.now()); 97 | } 98 | 99 | function debounced() { 100 | const time = Date.now(), 101 | isInvoking = shouldInvoke(time); 102 | lastArgs = arguments; 103 | lastThis = this; 104 | lastCallTime = time; 105 | 106 | if (isInvoking) { 107 | if (timerId === undefined) { 108 | return leadingEdge(lastCallTime); 109 | } 110 | if (maxing) { 111 | // Handle invocations in a tight loop. 112 | timerId = setTimeout(timerExpired, wait); 113 | return invokeFunc(lastCallTime); 114 | } 115 | } 116 | if (timerId === undefined) { 117 | timerId = setTimeout(timerExpired, wait); 118 | } 119 | return result; 120 | } 121 | debounced.cancel = cancel; 122 | debounced.flush = flush; 123 | return debounced; 124 | } 125 | -------------------------------------------------------------------------------- /packages/maplibre-engine/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/maplibre-engine/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"] 8 | }, 9 | "exclude": ["**/*.spec.ts", "**/*.test.ts"], 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/maplibre-engine/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.test.ts", 10 | "**/*.spec.ts", 11 | "**/*.test.tsx", 12 | "**/*.spec.tsx", 13 | "**/*.test.js", 14 | "**/*.spec.js", 15 | "**/*.test.jsx", 16 | "**/*.spec.jsx", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/js/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/README.md: -------------------------------------------------------------------------------- 1 | # maplibre-layers-event-propagator 2 | 3 | Maplibre GL and MapBox GL JS map engine add-on to stop event propagation between layers. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm i --save @any-routing/maplibre-layers-event-propagator 9 | ``` 10 | 11 | ## Example of usage 12 | 13 | ```ts 14 | import { Map } from 'maplibre'; 15 | import { PropagationEvent, setupLayerEventsPropagator } from '@any-routing/layers-event-propagator'; 16 | 17 | const map = new Map({}); 18 | 19 | map.on('load', () => { 20 | setupLayerEventsPropagator(map); // Must be handled after map loaded 21 | 22 | map.on('click', 'lake-label', (e: PropagationEvent) => { 23 | if (e.isCancelled && e.isCancelled()) { 24 | return; 25 | } 26 | 27 | console.log(e, 'click on water label printed'); 28 | e.stopPropagation ? e.stopPropagation() : null; 29 | e.stopImmediatePropagation ? e.stopImmediatePropagation() : null; 30 | }); 31 | 32 | map.on('click', 'lake-label', (e: PropagationEvent) => { 33 | if (e.isCancelled && e.isCancelled()) { 34 | return; 35 | } 36 | 37 | console.log(e, 'click on water label no printed'); 38 | e.stopPropagation ? e.stopPropagation() : null; 39 | }); 40 | 41 | map.on('click', 'water', (e: PropagationEvent) => { 42 | if (e.isCancelled && e.isCancelled()) { 43 | return; 44 | } 45 | 46 | console.log(e, 'click on water'); 47 | e.stopPropagation ? e.stopPropagation() : null; 48 | }); 49 | 50 | map.on('click', (e: PropagationEvent) => { 51 | //For non layer handlers 52 | if (e.originalEvent.isCancelled && e.originalEvent.isCancelled()) { 53 | return; 54 | } 55 | 56 | console.log(e, 'click anywhere else'); 57 | }); 58 | }); 59 | ``` 60 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'maplibre-layers-event-propagator', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../coverage/packages/maplibre-layers-event-propagator', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any-routing/maplibre-layers-event-propagator", 3 | "version": "0.1.1", 4 | "peerDependencies": { 5 | "maplibre-gl": "^3", 6 | "mapbox-gl": "^3" 7 | } 8 | } -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maplibre-layers-event-propagator", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/maplibre-layers-event-propagator/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nrwl/linter:eslint", 9 | "outputs": ["{options.outputFile}"], 10 | "options": { 11 | "lintFilePatterns": ["packages/maplibre-layers-event-propagator/**/*.ts"] 12 | } 13 | }, 14 | "test": { 15 | "executor": "@nrwl/jest:jest", 16 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 17 | "options": { 18 | "jestConfig": "packages/maplibre-layers-event-propagator/jest.config.ts", 19 | "passWithNoTests": true 20 | }, 21 | "configurations": { 22 | "ci": { 23 | "ci": true, 24 | "codeCoverage": true 25 | } 26 | } 27 | }, 28 | "build": { 29 | "executor": "@nrwl/js:tsc", 30 | "outputs": ["{options.outputPath}"], 31 | "options": { 32 | "outputPath": "dist/packages/maplibre-layers-event-propagator", 33 | "tsConfig": "packages/maplibre-layers-event-propagator/tsconfig.lib.json", 34 | "packageJson": "packages/maplibre-layers-event-propagator/package.json", 35 | "main": "packages/maplibre-layers-event-propagator/src/index.ts", 36 | "assets": ["packages/maplibre-layers-event-propagator/*.md"] 37 | } 38 | } 39 | }, 40 | "tags": [] 41 | } 42 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/maplibre-layers-event-propagator'; 2 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/src/lib/maplibre-layers-event-propagator.spec.ts: -------------------------------------------------------------------------------- 1 | import { setupLayerEventsPropagator } from './maplibre-layers-event-propagator'; 2 | 3 | describe('setupLayerEventsPropagator', () => { 4 | it('should work', () => { 5 | // expect(setupLayerEventsPropagator()).toEqual('maplibre-layers-event-propagator'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/src/lib/maplibre-layers-event-propagator.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { 3 | Map as MapLibreMap, 4 | MapLayerMouseEvent as MapLayerMouseEventMapLibre, 5 | MapMouseEvent as MapMouseEventMapLibre, 6 | } from 'maplibre-gl'; 7 | import type { 8 | Map as MapBoxMap, 9 | MapLayerMouseEvent as MapLayerMouseEventMapBox, 10 | MapMouseEvent as MapMouseEventMapBox, 11 | } from 'mapbox-gl'; 12 | 13 | type Map = MapBoxMap | MapLibreMap; 14 | type MapLayerMouseEvent = MapLayerMouseEventMapLibre | MapLayerMouseEventMapBox; 15 | type MapMouseEvent = MapMouseEventMapBox | MapMouseEventMapLibre; 16 | 17 | export type PropagationEvent = E & { 18 | stopPropagationCancelledLayerId?: string; 19 | immediatePropagationCancelledLayerId?: string; 20 | stopPropagation?(): void; 21 | isCancelled?: () => boolean; 22 | stopImmediatePropagation?(): void; 23 | originalEvent: E['originalEvent'] & { isCancelled?: () => boolean }; 24 | }; 25 | 26 | function reorderHandlers(map, eventName: string, layers: string[]) { 27 | if (!map._delegatedListeners || !map._listeners) { 28 | return; 29 | } 30 | 31 | map._delegatedListeners[eventName]?.sort(function (a, b) { 32 | return layers.indexOf(b.layer) - layers.indexOf(a.layer); 33 | }); 34 | 35 | map._listeners[eventName]?.sort((a, b) => { 36 | const delegatedIndexA: number = 37 | map._delegatedListeners[eventName]?.findIndex((x) => x.delegates[eventName] === a) ?? -1; 38 | const delegatedIndexB: number = 39 | map._delegatedListeners[eventName]?.findIndex((x) => x.delegates[eventName] === b) ?? -1; 40 | 41 | return (delegatedIndexA != -1 ? delegatedIndexA : Infinity) - (delegatedIndexB != -1 ? delegatedIndexB : Infinity); 42 | }); 43 | } 44 | 45 | function wrapCallback(originalCallback) { 46 | return (...args) => { 47 | const event: PropagationEvent = args[0]; 48 | const _layerId = (): boolean => { 49 | return event && 'features' in event && event.features ? event.features[0]?.layer?.id : '__no-layer__'; 50 | }; 51 | const layerId = _layerId(); 52 | 53 | const isCancelled = () => { 54 | const layerId = _layerId(); 55 | 56 | return ( 57 | !event?.type || 58 | (event?.originalEvent.stopPropagationCancelledLayerId && 59 | event?.originalEvent.stopPropagationCancelledLayerId !== layerId) || 60 | event?.immediatePropagationCancelledLayerId === layerId 61 | ); 62 | }; 63 | 64 | if (!event.isCancelled) { 65 | event.originalEvent.isCancelled = isCancelled; 66 | 67 | event.originalEvent.layerStopPropagation = () => { 68 | event.originalEvent.stopPropagationCancelledLayerId = layerId; 69 | }; 70 | 71 | event.originalEvent.layerStopImmediatePropagation = () => { 72 | event.immediatePropagationCancelledLayerId = layerId; 73 | }; 74 | 75 | event.isCancelled = event.originalEvent.isCancelled; 76 | event.stopPropagation = event.originalEvent.layerStopPropagation; 77 | event.stopImmediatePropagation = event.originalEvent.layerStopImmediatePropagation; 78 | } 79 | 80 | originalCallback(...args); 81 | }; 82 | } 83 | 84 | export function setupLayerEventsPropagator(map: Map): void { 85 | if ('_anyRoutingLayersEventPropagatorAdded' in map) { 86 | return; 87 | } 88 | 89 | //@ts-ignore 90 | map._anyRoutingLayersEventPropagatorAdded = true; 91 | 92 | const originalOn = map.on; 93 | let layers = map.getStyle().layers.map((l) => l.id); 94 | 95 | const reorderAll = (): void => { 96 | if (!map._delegatedListeners) { 97 | return; 98 | } 99 | 100 | Object.keys(map._delegatedListeners).forEach((eventName) => { 101 | reorderHandlers(map, eventName, layers); 102 | }); 103 | }; 104 | 105 | map.on('styledata', () => { 106 | layers = map.getStyle().layers.map((l) => l.id); 107 | reorderAll(); 108 | }); 109 | 110 | reorderAll(); 111 | 112 | const ownOn = (...args): Map => { 113 | if (args[2] === undefined) { 114 | map.on = originalOn; 115 | //@ts-ignore 116 | map.on(...args); 117 | map.on = ownOn; 118 | reorderHandlers(map, args[0], layers); 119 | 120 | return map; 121 | } 122 | 123 | args[2] = wrapCallback(args[2]); 124 | map.on = originalOn; 125 | map.on(...args); 126 | map.on = ownOn; 127 | reorderHandlers(map, args[0], layers); 128 | 129 | return map; 130 | }; 131 | 132 | map.on = ownOn; 133 | } 134 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"] 8 | }, 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], 10 | "include": ["src/**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/maplibre-layers-event-propagator/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.test.tsx", 13 | "src/**/*.spec.tsx", 14 | "src/**/*.test.js", 15 | "src/**/*.spec.js", 16 | "src/**/*.test.jsx", 17 | "src/**/*.spec.jsx", 18 | "src/**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /readme/images/arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/readme/images/arch.jpg -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpepr/any-routing/6afd9d6b391af6db59dad3b104139e00f3c88f7f/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@any-routing/annotation-plugin": ["packages/annotation-plugin/src/index.ts"], 19 | "@any-routing/core": ["packages/core/src/index.ts"], 20 | "@any-routing/here-data-provider": ["packages/here-data-provider/src/index.ts"], 21 | "@any-routing/maplibre-engine": ["packages/maplibre-engine/src/index.ts"], 22 | "@any-routing/maplibre-layers-event-propagator": ["packages/maplibre-layers-event-propagator/src/index.ts"] 23 | } 24 | }, 25 | "exclude": ["node_modules", "tmp"] 26 | } 27 | --------------------------------------------------------------------------------