terra-route 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -17
- package/dist/terra-route.cjs +1 -1
- package/dist/terra-route.cjs.map +1 -1
- package/dist/terra-route.d.ts +1 -2
- package/dist/terra-route.modern.js +1 -1
- package/dist/terra-route.modern.js.map +1 -1
- package/dist/terra-route.module.js +1 -1
- package/dist/terra-route.module.js.map +1 -1
- package/dist/terra-route.umd.js +1 -1
- package/dist/terra-route.umd.js.map +1 -1
- package/instructions.md +13 -0
- package/package.json +2 -1
- package/src/terra-route.compare.spec.ts +81 -0
- package/src/terra-route.spec.ts +576 -0
- package/src/terra-route.ts +370 -154
- package/src/graph/graph.spec.ts +0 -238
- package/src/graph/graph.ts +0 -212
- package/src/graph/methods/bounding-box.spec.ts +0 -199
- package/src/graph/methods/bounding-box.ts +0 -85
- package/src/graph/methods/connected.spec.ts +0 -219
- package/src/graph/methods/connected.ts +0 -168
- package/src/graph/methods/duplicates.spec.ts +0 -161
- package/src/graph/methods/duplicates.ts +0 -117
- package/src/graph/methods/leaf.spec.ts +0 -224
- package/src/graph/methods/leaf.ts +0 -88
- package/src/graph/methods/nodes.spec.ts +0 -317
- package/src/graph/methods/nodes.ts +0 -77
- package/src/graph/methods/spatial-index/geokdbush.spec.ts +0 -86
- package/src/graph/methods/spatial-index/geokdbush.ts +0 -189
- package/src/graph/methods/spatial-index/kdbush.spec.ts +0 -67
- package/src/graph/methods/spatial-index/kdbush.ts +0 -189
- package/src/graph/methods/spatial-index/tinyqueue.spec.ts +0 -51
- package/src/graph/methods/spatial-index/tinyqueue.ts +0 -108
- package/src/graph/methods/unify.spec.ts +0 -475
- package/src/graph/methods/unify.ts +0 -132
- package/src/graph/methods/unique.spec.ts +0 -65
- package/src/graph/methods/unique.ts +0 -69
package/README.md
CHANGED
|
@@ -45,19 +45,9 @@ const route = router.getRoute(startPoint, endPoint);
|
|
|
45
45
|
console.log("Shortest route:", JSON.stringify(route, null, 2));
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
##
|
|
48
|
+
## Terra Route Graph
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
const graph = new LineStringGraph(network);
|
|
54
|
-
|
|
55
|
-
// Return all nodes in the graph as FeatureCollection<Point>, where each unique node is a Feature<Point>
|
|
56
|
-
const graphPoints = graph.getNodes();
|
|
57
|
-
|
|
58
|
-
// Return all the unique edges as FeatureCollection<LineString>, where each unique edge is a Feature<LineString>
|
|
59
|
-
const graphEdges = graph.getEdges();
|
|
60
|
-
```
|
|
50
|
+
Understanding and debugging routing graphs can be complicated. There is an additional helper library called [Terra Route Graph](https://www.github.com/JamesLMilner/terra-route-graph) (`terra-route-graph`) which can be used to help understand the graphs you are working with.
|
|
61
51
|
|
|
62
52
|
## Benchmarks
|
|
63
53
|
|
|
@@ -70,16 +60,16 @@ npm run benchmark
|
|
|
70
60
|
Here is an example output of a benchmark run for routing:
|
|
71
61
|
|
|
72
62
|
<pre>
|
|
73
|
-
Terra Route
|
|
74
|
-
GeoJSON Path Finder
|
|
75
|
-
ngraph.graph
|
|
63
|
+
Terra Route | █████ 151ms
|
|
64
|
+
GeoJSON Path Finder | ██████████████████ 571ms
|
|
65
|
+
ngraph.graph | ██████████████████████████████████████████████████ 1564ms
|
|
76
66
|
</pre>
|
|
77
67
|
|
|
78
|
-
Using default Haversine distance, Terra Route is approximately
|
|
68
|
+
Using default Haversine distance, Terra Route is approximately 3.75x faster than GeoJSON Path Finder with Haversine distance for A -> B path finding. If you pass in the CheapRuler distance metric (you can use the exposed `createCheapRuler` function), it is approximately x8 faster than GeoJSON Path Finder with Haversine distance.
|
|
79
69
|
|
|
80
70
|
For initialisation of the network, Terra Route is approximately 10x faster with Haversine than GeoJSON Path Finder. Terra Draw splits out instantiating the Class of the library from the actual graph building, which is done via `buildRouteGraph`. This allows you to defer graph creation to an appropriate time.
|
|
81
71
|
|
|
82
|
-
Terra Route uses an [A*
|
|
72
|
+
Terra Route uses an [bi-directional A* algorithm for pathfinding](https://en.wikipedia.org/wiki/A*_search_algorithm) and by default uses a [four-ary heap](https://en.wikipedia.org/wiki/D-ary_heap) for the underlying priority queue, although this is configurable.
|
|
83
73
|
|
|
84
74
|
## Limitations
|
|
85
75
|
|
package/dist/terra-route.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var t=function(t,e){var r=function(t){return t*Math.PI/180},n=r(t[1]),i=r(t[0]),a=r(e[1]),s=a-n,o=r(e[0])-i,h=Math.sin(s/2)*Math.sin(s/2)+Math.cos(n)*Math.cos(a)*Math.sin(o/2)*Math.sin(o/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};function e(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r<e;r++)n[r]=t[r];return n}function r(t,r){var n="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(n)return(n=n.call(t)).next.bind(n);if(Array.isArray(t)||(n=function(t,r){if(t){if("string"==typeof t)return e(t,r);var n={}.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?e(t,r):void 0}}(t))||r&&t&&"number"==typeof t.length){n&&(t=n);var i=0;return function(){return i>=t.length?{done:!0}:{done:!1,value:t[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function n(){return n=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var r=arguments[e];for(var n in r)({}).hasOwnProperty.call(r,n)&&(t[n]=r[n])}return t},n.apply(null,arguments)}function i(t,e,n){n[t]=!0;for(var a,s=r(e[t]);!(a=s()).done;){var o=a.value;n[o]||i(o,e,n)}}function a(t,e){var r=function(t,e){var r=t[0],n=e[0];return r<n||r===n&&t[1]<=e[1]?[t,e]:[e,t]}(t,e);return JSON.stringify([r[0],r[1]])}function s(t){for(var e,n=new Map,i=r(t.features);!(e=i()).done;)for(var s=e.value.geometry.coordinates,o=0;o<s.length-1;o++){var h=s[o],u=s[o+1],f=a(h,u);n.has(f)||n.set(f,{type:"Feature",geometry:{type:"LineString",coordinates:[h,u]},properties:{}})}return{type:"FeatureCollection",features:Array.from(n.values())}}function o(e){for(var r=e.geometry.coordinates,n=0,i=0;i<r.length-1;i++)n+=t(r[i],r[i+1]);return n}function h(t,e){var r=t.length,n=e.length;if(r>n)return!1;for(var i=0;i<=n-r;i++){for(var a=!0,s=0;s<r;s++)if(e[i+s][0]!==t[s][0]||e[i+s][1]!==t[s][1]){a=!1;break}if(a)return!0}for(var o=[].concat(t).reverse(),h=0;h<=n-r;h++){for(var u=!0,f=0;f<r;f++)if(e[h+f][0]!==o[f][0]||e[h+f][1]!==o[f][1]){u=!1;break}if(u)return!0}return!1}function u(t){var e=s(t),r=new Map;function n(t){return t.join(",")}for(var i=0;i<e.features.length;i++){var a=e.features[i].geometry.coordinates;if(!(a.length<2)){var o=n(a[0]),h=n(a[a.length-1]);r.set(o,(r.get(o)||0)+1),r.set(h,(r.get(h)||0)+1)}}for(var u=new Map,f=new Map,c=0;c<e.features.length;c++){var d=e.features[c],v=d.geometry.coordinates;if(!(v.length<2)){var l=n(v[0]),g=n(v[v.length-1]),p=r.get(l)||0,y=r.get(g)||0,m=v.map(n).join(";");1===p||1===y?u.has(m)||u.set(m,d):f.has(m)||f.set(m,d)}}return{leafEdges:{type:"FeatureCollection",features:Array.from(u.values())},nonLeafEdges:{type:"FeatureCollection",features:Array.from(f.values())}}}function f(t,e,n,i,a){for(var s,o=r(t.geometry.coordinates);!(s=o()).done;)if(!c(s.value,e,n,i,a))return!1;return!0}function c(t,e,r,n,i){var a=t[0],s=t[1];return a>=e&&a<=n&&s>=r&&s<=i}var d=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array],v=/*#__PURE__*/function(){function t(t,e,r,n){if(void 0===e&&(e=64),void 0===r&&(r=Float64Array),this.data=void 0,this.ids=void 0,this.coords=void 0,this._pos=void 0,this._finished=void 0,this.numItems=void 0,this.nodeSize=void 0,this.ArrayType=void 0,this.IndexArrayType=void 0,isNaN(t)||t<0)throw new Error("Unexpected numItems value: "+t+".");this.numItems=t,this.nodeSize=Math.min(Math.max(e,2),65535),this.ArrayType=r,this.IndexArrayType=t<65536?Uint16Array:Uint32Array;var i=d.indexOf(this.ArrayType),a=2*t*this.ArrayType.BYTES_PER_ELEMENT,s=t*this.IndexArrayType.BYTES_PER_ELEMENT,o=(8-s%8)%8;if(i<0)throw new Error("Unexpected typed array class: "+r+".");n?(this.data=n,this.ids=new this.IndexArrayType(this.data,8,t),this.coords=new this.ArrayType(this.data,8+s+o,2*t),this._pos=2*t,this._finished=!0):(this.data=new ArrayBuffer(8+a+s+o),this.ids=new this.IndexArrayType(this.data,8,t),this.coords=new this.ArrayType(this.data,8+s+o,2*t),this._pos=0,this._finished=!1,new Uint8Array(this.data,0,2).set([219,16+i]),new Uint16Array(this.data,2,1)[0]=this.nodeSize,new Uint32Array(this.data,4,1)[0]=this.numItems)}var e=t.prototype;return e.add=function(t,e){var r=this._pos>>1;return this.ids[r]=r,this.coords[this._pos++]=t,this.coords[this._pos++]=e,r},e.finish=function(){var t=this._pos>>1;if(t!==this.numItems)throw new Error("Added "+t+" items when expected "+this.numItems+".");return l(this.ids,this.coords,this.nodeSize,0,this.numItems-1,0),this._finished=!0,this},t}();function l(t,e,r,n,i,a){if(!(i-n<=r)){var s=n+i>>1;g(t,e,s,n,i,a),l(t,e,r,n,s-1,1-a),l(t,e,r,s+1,i,1-a)}}function g(t,e,r,n,i,a){for(;i>n;){if(i-n>600){var s=i-n+1,o=r-n+1,h=Math.log(s),u=.5*Math.exp(2*h/3),f=.5*Math.sqrt(h*u*(s-u)/s)*(o-s/2<0?-1:1);g(t,e,r,Math.max(n,Math.floor(r-o*u/s+f)),Math.min(i,Math.floor(r+(s-o)*u/s+f)),a)}var c=e[2*r+a],d=n,v=i;for(p(t,e,n,r),e[2*i+a]>c&&p(t,e,n,i);d<v;){for(p(t,e,d,v),d++,v--;e[2*d+a]<c;)d++;for(;e[2*v+a]>c;)v--}e[2*n+a]===c?p(t,e,n,v):p(t,e,++v,i),v<=r&&(n=v+1),r<=v&&(i=v-1)}}function p(t,e,r,n){y(t,r,n),y(e,2*r,2*n),y(e,2*r+1,2*n+1)}function y(t,e,r){var n=t[e];t[e]=t[r],t[r]=n}var m=/*#__PURE__*/function(){function t(t,e){if(void 0===t&&(t=[]),void 0===e&&(e=function(t,e){return t<e?-1:t>e?1:0}),this.data=void 0,this.length=void 0,this.compare=void 0,this.data=t,this.length=this.data.length,this.compare=e,this.length>0)for(var r=(this.length>>1)-1;r>=0;r--)this._down(r)}var e=t.prototype;return e.push=function(t){this.data.push(t),this._up(this.length++)},e.pop=function(){if(0!==this.length){var t=this.data[0],e=this.data.pop();return this.length--,this.length>0&&(this.data[0]=e,this._down(0)),t}},e.peek=function(){return this.data[0]},e._up=function(t){for(var e=this.data,r=this.compare,n=e[t];t>0;){var i=t-1>>1,a=e[i];if(r(n,a)>=0)break;e[t]=a,t=i}e[t]=n},e._down=function(t){for(var e=this.data,r=this.compare,n=this.length>>1,i=e[t];t<n;){var a=1+(t<<1),s=a+1;if(s<this.length&&r(e[s],e[a])<0&&(a=s),r(e[a],i)>=0)break;e[t]=e[a],t=a}e[t]=i},t}(),w=Math.PI/180;function M(t,e,r,n){var i=n.minLng,a=n.maxLng,s=n.minLat,o=n.maxLat;if(t>=i&&t<=a)return e<s?S((e-s)*w):e>o?S((e-o)*w):0;var h=Math.min(S((t-i)*w),S((t-a)*w)),u=function(t,e){var r=1-2*e;return r<=0?t>0?90:-90:Math.atan(Math.tan(t*w)/r)/w}(e,h);return u>s&&u<o?A(h,r,e,u):Math.min(A(h,r,e,s),A(h,r,e,o))}function x(t,e){return t.dist-e.dist}function S(t){var e=Math.sin(t/2);return e*e}function A(t,e,r,n){return e*Math.cos(n*w)*t+S((r-n)*w)}function I(t,e,r,n,i){return A(S((t-r)*w),i,e,n)}var k=/*#__PURE__*/function(){function e(t){this.network=void 0,this.network=t}var a=e.prototype;return a.setNetwork=function(t){this.network=t},a.getNetwork=function(){return this.network},a.getNetworkInBoundingBox=function(t){return function(t,e){var n=e[0],i=e[1],a=e[2],s=e[3];if(n>=a||i>=s)throw new Error("Invalid bounding box: min values must be less than max values");for(var o,h=[],u=r(t.features);!(o=u()).done;){var c=o.value;f(c,n,i,a,s)&&h.push(c)}return{type:"FeatureCollection",features:h}}(this.network,t)},a.getNetworkWithoutDuplicatesOrSubsections=function(){return function(t){for(var e=t.features,r=new Set,n=0;n<e.length;n++)for(var i=e[n].geometry.coordinates,a=0;a<e.length;a++)if(n!==a){var s=e[a].geometry.coordinates;if(h(i,s)&&(i.length<s.length||i.length===s.length&&n>a)){r.add(n);break}}return{type:"FeatureCollection",features:e.filter(function(t,e){return!r.has(e)})}}(this.network)},a.getConnectedComponents=function(){return function(t){var e=t.features,n=new Map,i=new Map;function a(t){return t[0]+","+t[1]}for(var s=0;s<e.length;s++)for(var o,h=r(e[s].geometry.coordinates);!(o=h()).done;){var u=a(o.value);i.has(u)||i.set(u,new Set),i.get(u).add(s)}for(var f=0;f<e.length;f++){n.set(f,new Set);for(var c,d=r(e[f].geometry.coordinates);!(c=d()).done;){var v=a(c.value),l=i.get(v);if(l)for(var g,p=r(l);!(g=p()).done;){var y=g.value;y!==f&&n.get(f).add(y)}}}var m=new Set,w=[];function M(t,i){for(var a=[t];a.length>0;){var s=a.pop();if(!m.has(s)){m.add(s),i.push(e[s]);var o=n.get(s);if(o)for(var h,u=r(o);!(h=u()).done;){var f=h.value;m.has(f)||a.push(f)}}}}for(var x=0;x<e.length;x++)if(!m.has(x)){var S=[];M(x,S),w.push({type:"FeatureCollection",features:S})}return w.sort(function(t,e){return t.features.length-e.features.length}),w}(this.network)},a.getConnectedComponentCount=function(){return function(t){for(var e,n=t.features,a=n.length,s=new Map,o=0;o<a;o++)for(var h,u=r(n[o].geometry.coordinates);!(h=u()).done;){var f=(e=h.value)[0]+","+e[1];s.has(f)||s.set(f,[]),s.get(f).push(o)}for(var c,d=Array.from({length:a},function(){return[]}),v=r(s.values());!(c=v()).done;)for(var l=c.value,g=0;g<l.length;g++)for(var p=g+1;p<l.length;p++){var y=l[g],m=l[p];d[y].push(m),d[m].push(y)}for(var w=new Array(a).fill(!1),M=0,x=0;x<a;x++)w[x]||(i(x,d,w),M++);return M}(this.network)},a.getNodeAndEdgeCount=function(){return function(t){for(var e,n=new Set,i=new Set,a=r(t.features);!(e=a()).done;){for(var s,o=e.value.geometry.coordinates,h=r(o);!(s=h()).done;)n.add(JSON.stringify(s.value));for(var u=0;u<o.length-1;u++){var f=(c=o[u+1],(d=JSON.stringify(o[u]))<(v=JSON.stringify(c))?d+"|"+v:v+"|"+d);i.add(f)}}var c,d,v;return{nodeCount:n.size,edgeCount:i.size}}(this.network)},a.getNodes=function(){return{type:"FeatureCollection",features:function(t){for(var e,n=new Set,i=[],a=r(t.features);!(e=a()).done;)for(var s,o=r(e.value.geometry.coordinates);!(s=o()).done;){var h=s.value,u=h.join(",");n.has(u)||(n.add(u),i.push({type:"Feature",geometry:{type:"Point",coordinates:h},properties:{}}))}return i}(this.network)}},a.getNodeCount=function(){return this.getNodeAndEdgeCount().nodeCount},a.getEdges=function(){return s(this.network)},a.getLongestEdgeLength=function(){var t=this.getLongestEdge();return t?o(t):-1},a.getShortestEdgeLength=function(){var t=this.getShortestEdge();return t?o(t):-1},a.getLongestEdge=function(){var t=this.getEdges().features;if(0===t.length)return null;var e=t.sort(function(t,e){return o(t)-o(e)});return e[e.length-1]},a.getShortestEdge=function(){var t=this.getEdges().features;return 0===t.length?null:t.sort(function(t,e){return o(t)-o(e)})[0]},a.getEdgeCount=function(){return this.getNodeAndEdgeCount().edgeCount},a.getLeafEdges=function(){return u(this.network).leafEdges},a.getPrunedEdges=function(t){if(t&&t>0){for(var e=this.network,r=0;r<t;r++)e=u(e).nonLeafEdges;return e}return u(this.network).nonLeafEdges},a.getUnifiedNetwork=function(e){return function(e,i){if(e.features.length<2)return e;for(var a,s=new Set,o=new Map,h=[],u=new Map,f=r(e.features);!(a=f()).done;)for(var c,d=r(a.value.geometry.coordinates);!(c=d()).done;){var l=c.value,g=l[0]+","+l[1];u.has(g)||(u.set(g,h.length),h.push(l))}for(var p=new v(h.length),y=0,A=h;y<A.length;y++){var k=A[y];p.add(k[0],k[1])}function L(e,n,a){for(var o,u=null,f=Infinity,c=r(function(t,e,r,n,i){void 0===n&&(n=Infinity),void 0===i&&(i=Infinity);var a=1,s=[];void 0===n&&(n=Infinity),void 0!==i&&(a=S(i/6371));for(var o=new m([],x),h={left:0,right:t.ids.length-1,axis:0,dist:0,minLng:-180,minLat:-90,maxLng:180,maxLat:90},u=Math.cos(r*w);h;){var f=h.right,c=h.left;if(f-c<=t.nodeSize)for(var d=c;d<=f;d++){var v=t.ids[d],l=I(e,r,t.coords[2*d],t.coords[2*d+1],u);o.push({id:v,dist:l})}else{var g=c+f>>1,p=t.coords[2*g],y=t.coords[2*g+1],A=t.ids[g],k=I(e,r,p,y,u);o.push({id:A,dist:k});var L=(h.axis+1)%2,C={left:c,right:g-1,axis:L,minLng:h.minLng,minLat:h.minLat,maxLng:0===h.axis?p:h.maxLng,maxLat:1===h.axis?y:h.maxLat,dist:0},E={left:g+1,right:f,axis:L,minLng:0===h.axis?p:h.minLng,minLat:1===h.axis?y:h.minLat,maxLng:h.maxLng,maxLat:h.maxLat,dist:0};C.dist=M(e,r,u,C),E.dist=M(e,r,u,E),o.push(C),o.push(E)}for(;o.length&&null!=o.peek().id;){var b=o.pop();if(b.dist>a)return s;if(s.push(b.id),s.length===n)return s}h=o.pop()}return s}(p,e[0],e[1],Infinity,i/1e3));!(o=c()).done;){var d=h[o.value],v=d[0]+","+d[1];if(s.has(v)&&!n.includes(d)&&!a.has(v)){var l=1e3*t(d,e);l<=i&&l<f&&(u=d,f=l)}}return null!==u?u:(s.add(e[0]+","+e[1]),e)}p.finish();var C=e.features.map(function(t){for(var e,i=[],a=[],s=new Set,h=r(t.geometry.coordinates);!(e=h()).done;){var u=e.value,f=u[0]+","+u[1];if(!o.has(f)){var c=L(u,i,s);s.has(c[0]+","+c[1])?o.set(f,u):o.set(f,c)}var d=o.get(f),v=d[0]+","+d[1];s.has(v)||(a.push(d),s.add(v)),i.push(u)}return n({},t,{geometry:n({},t.geometry,{coordinates:a})})});return n({},e,{features:C})}(this.network,e)},e}(),L=/*#__PURE__*/function(){function t(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var r=this.length;this.length=r+1;for(var n=t,i=e,a=this.insertCounter++;r>0;){var s=r-1>>>2,o=this.keys[s],h=this.idxs[s];if(n>o||n===o&&a>h)break;this.keys[r]=o,this.values[r]=this.values[s],this.idxs[r]=h,r=s}this.keys[r]=n,this.values[r]=i,this.idxs[r]=a},e.extractMin=function(){var t=this.length;if(0===t)return null;var e=this.values[0],r=t-1;return this.length=r,r>0&&(this.keys[0]=this.keys[r],this.values[0]=this.values[r],this.idxs[0]=this.idxs[r],this.bubbleDown(0)),e},e.size=function(){return this.length},e.bubbleDown=function(t){for(var e=this.length,r=this.keys,n=this.values,i=this.idxs,a=r[t],s=n[t],o=i[t];;){var h=1+(t<<2);if(h>=e)break;var u=h,f=r[h],c=i[h],d=n[h],v=h+1;if(v<e){var l=r[v],g=i[v];(l<f||l===f&&g<c)&&(u=v,f=l,c=g,d=n[v])}var p=h+2;if(p<e){var y=r[p],m=i[p];(y<f||y===f&&m<c)&&(u=p,f=y,c=m,d=n[p])}var w=h+3;if(w<e){var M=r[w],x=i[w];(M<f||M===f&&x<c)&&(u=w,f=M,c=x,d=n[w])}if(!(f<a||f===a&&c<o))break;r[t]=f,n[t]=d,i[t]=c,t=u}r[t]=a,n[t]=s,i[t]=o},t}(),C=/*#__PURE__*/function(){function e(e){var r,n;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(r=null==e?void 0:e.distanceMeasurement)?r:t,this.heapConstructor=null!=(n=null==e?void 0:e.heap)?n:L}var r=e.prototype;return r.buildRouteGraph=function(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;for(var e=this.coordinateIndexMap,r=this.coordinates,n=this.distanceMeasurement,i=t.features,a=[],s=0,o=i.length;s<o;s++)for(var h=i[s].geometry.coordinates,u=0,f=h.length-1;u<f;u++){var c,d,v=h[u],l=h[u+1],g=v[0],p=v[1],y=l[0],m=l[1],w=e.get(g);void 0===w&&(w=new Map,e.set(g,w));var M=w.get(p);void 0===M&&(M=r.length,r.push(v),w.set(p,M));var x=e.get(y);void 0===x&&(x=new Map,e.set(y,x));var S=x.get(m);void 0===S&&(S=r.length,r.push(l),x.set(m,S)),a[M]=(null!=(c=a[M])?c:0)+1,a[S]=(null!=(d=a[S])?d:0)+1}var A=this.coordinates.length;this.csrNodeCount=A;for(var I=new Int32Array(A+1),k=0;k<A;k++){var L,C=null!=(L=a[k])?L:0;I[k+1]=I[k]+C}for(var E=I[A],b=new Int32Array(E),N=new Float64Array(E),O=I.slice(),F=0,_=i.length;F<_;F++)for(var T=i[F].geometry.coordinates,U=0,j=T.length-1;U<j;U++){var P=T[U],z=T[U+1],D=P[1],R=z[0],q=z[1],B=this.coordinateIndexMap.get(P[0]).get(D),J=this.coordinateIndexMap.get(R).get(q),G=n(P,z),Y=O[B]++;b[Y]=J,N[Y]=G,b[Y=O[J]++]=B,N[Y]=G}this.csrOffsets=I,this.csrIndices=b,this.csrDistances=N,this.adjacencyList=new Array(A)},r.getRoute=function(t,e){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var r=this.getOrCreateIndex(t.geometry.coordinates),n=this.getOrCreateIndex(e.geometry.coordinates);if(r===n)return null;var i=this.coordinates,a=this.adjacencyList,s=this.distanceMeasurement,o=i.length;this.ensureScratch(o);var h=this.gScoreScratch,u=this.cameFromScratch,f=this.visitedScratch,c=this.hScratch;h.fill(Number.POSITIVE_INFINITY,0,o),u.fill(-1,0,o),f.fill(0,0,o),c.fill(-1,0,o);var d=new this.heapConstructor,v=i[n],l=c[r];for(l<0&&(l=s(i[r],v),c[r]=l),d.insert(l,r),h[r]=0;d.size()>0;){var g=d.extractMin();if(0===f[g]){if(g===n)break;if(f[g]=1,this.csrOffsets&&g<this.csrNodeCount)for(var p=this.csrOffsets,y=this.csrIndices,m=this.csrDistances,w=p[g+1],M=p[g];M<w;M++){var x=y[M],S=h[g]+m[M];if(S<h[x]){h[x]=S,u[x]=g;var A=c[x];A<0&&(A=s(i[x],v),c[x]=A),d.insert(S+A,x)}}else{var I=a[g];if(!I||0===I.length)continue;for(var k=0,L=I.length;k<L;k++){var C=I[k],E=C.node,b=h[g]+C.distance;if(b<h[E]){h[E]=b,u[E]=g;var N=c[E];N<0&&(N=s(i[E],v),c[E]=N),d.insert(b+N,E)}}}}}if(u[n]<0)return null;for(var O=[],F=n;F!==r;)O.push(i[F]),F=u[F];return O.push(i[r]),O.reverse(),{type:"Feature",geometry:{type:"LineString",coordinates:O},properties:{}}},r.getOrCreateIndex=function(t){var e=t[0],r=t[1],n=this.coordinateIndexMap.get(e);void 0===n&&(n=new Map,this.coordinateIndexMap.set(e,n));var i=n.get(r);if(void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),n.set(r,i),this.adjacencyList[i]=[],this.csrOffsets)){var a=this.csrNodeCount;if(i===a){var s=new Int32Array(a+2);s.set(this.csrOffsets,0),s[a+1]=s[a],this.csrOffsets=s,this.csrNodeCount=a+1}}return i},r.ensureScratch=function(t){if(!(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch)){var e=0|t;this.gScoreScratch=new Float64Array(e),this.cameFromScratch=new Int32Array(e),this.visitedScratch=new Uint8Array(e),this.hScratch=new Float64Array(e),this.scratchCapacity=e}},e}();exports.LineStringGraph=k,exports.TerraRoute=C,exports.createCheapRuler=function(t){var e=1/298.257223563,r=e*(2-e),n=Math.PI/180,i=Math.cos(t*n),a=1/(1-r*(1-i*i)),s=Math.sqrt(a),o=6378.137*n,h=o*s*i,u=o*s*a*(1-r);return function(t,e){for(var r=t[0]-e[0];r<-180;)r+=360;for(;r>180;)r-=360;var n=r*h,i=(t[1]-e[1])*u;return Math.sqrt(n*n+i*i)}},exports.haversineDistance=t;
|
|
1
|
+
var t=function(t,s){var e=function(t){return t*Math.PI/180},r=e(t[1]),i=e(t[0]),n=e(s[1]),a=n-r,h=e(s[0])-i,c=Math.sin(a/2)*Math.sin(a/2)+Math.cos(r)*Math.cos(n)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(c),Math.sqrt(1-c))*6371e3/1e3},s=/*#__PURE__*/function(){function t(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}var s=t.prototype;return s.insert=function(t,s){var e=this.length;this.length=e+1;for(var r=t,i=s,n=this.insertCounter++;e>0;){var a=e-1>>>2,h=this.keys[a],c=this.idxs[a];if(r>h||r===h&&n>c)break;this.keys[e]=h,this.values[e]=this.values[a],this.idxs[e]=c,e=a}this.keys[e]=r,this.values[e]=i,this.idxs[e]=n},s.extractMin=function(){var t=this.length;if(0===t)return null;var s=this.values[0],e=t-1;return this.length=e,e>0&&(this.keys[0]=this.keys[e],this.values[0]=this.values[e],this.idxs[0]=this.idxs[e],this.bubbleDown(0)),s},s.size=function(){return this.length},s.bubbleDown=function(t){for(var s=this.length,e=this.keys,r=this.values,i=this.idxs,n=e[t],a=r[t],h=i[t];;){var c=1+(t<<2);if(c>=s)break;var o=c,u=e[c],l=i[c],v=r[c],d=c+1;if(d<s){var f=e[d],g=i[d];(f<u||f===u&&g<l)&&(o=d,u=f,l=g,v=r[d])}var p=c+2;if(p<s){var y=e[p],M=i[p];(y<u||y===u&&M<l)&&(o=p,u=y,l=M,v=r[p])}var I=c+3;if(I<s){var w=e[I],S=i[I];(w<u||w===u&&S<l)&&(o=I,u=w,l=S,v=r[I])}if(!(u<n||u===n&&l<h))break;e[t]=u,r[t]=v,i[t]=l,t=o}e[t]=n,r[t]=a,i[t]=h},t}();exports.TerraRoute=/*#__PURE__*/function(){function e(e){var r,i;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(r=null==e?void 0:e.distanceMeasurement)?r:t,this.heapConstructor=null!=(i=null==e?void 0:e.heap)?i:s}var r=e.prototype;return r.buildRouteGraph=function(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;for(var s=this.coordinateIndexMap,e=this.coordinates,r=this.distanceMeasurement,i=t.features,n=[],a=0,h=i.length;a<h;a++)for(var c=i[a].geometry.coordinates,o=0,u=c.length-1;o<u;o++){var l,v,d=c[o],f=c[o+1],g=d[0],p=d[1],y=f[0],M=f[1],I=s.get(g);void 0===I&&(I=new Map,s.set(g,I));var w=I.get(p);void 0===w&&(w=e.length,e.push(d),I.set(p,w));var S=s.get(y);void 0===S&&(S=new Map,s.set(y,S));var x=S.get(M);void 0===x&&(x=e.length,e.push(f),S.set(M,x)),n[w]=(null!=(l=n[w])?l:0)+1,n[x]=(null!=(v=n[x])?v:0)+1}var m=this.coordinates.length;this.csrNodeCount=m;for(var C=new Int32Array(m+1),k=0;k<m;k++){var b,O=null!=(b=n[k])?b:0;C[k+1]=C[k]+O}for(var N=C[m],A=new Int32Array(N),F=new Float64Array(N),D=C.slice(),L=0,j=i.length;L<j;L++)for(var R=i[L].geometry.coordinates,q=0,P=R.length-1;q<P;q++){var T=R[q],z=R[q+1],E=T[1],G=z[0],U=z[1],V=this.coordinateIndexMap.get(T[0]).get(E),Y=this.coordinateIndexMap.get(G).get(U),_=r(T,z),B=D[V]++;A[B]=Y,F[B]=_,A[B=D[Y]++]=V,F[B]=_}this.csrOffsets=C,this.csrIndices=A,this.csrDistances=F,this.adjacencyList=new Array(m)},r.getRoute=function(t,s){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var e=this.getOrCreateIndex(t.geometry.coordinates),r=this.getOrCreateIndex(s.geometry.coordinates);if(e===r)return null;var i=this.coordinates,n=this.adjacencyList,a=this.distanceMeasurement,h=i.length;this.ensureScratch(h);var c=this.gScoreScratch,o=this.cameFromScratch,u=this.visitedScratch,l=this.hScratch;c.fill(Number.POSITIVE_INFINITY,0,h),o.fill(-1,0,h),u.fill(0,0,h),l.fill(-1,0,h);var v=new this.heapConstructor,d=i[r],f=l[e];for(f<0&&(f=a(i[e],d),l[e]=f),v.insert(f,e),c[e]=0;v.size()>0;){var g=v.extractMin();if(0===u[g]){if(g===r)break;if(u[g]=1,this.csrOffsets&&g<this.csrNodeCount)for(var p=this.csrOffsets,y=this.csrIndices,M=this.csrDistances,I=p[g+1],w=p[g];w<I;w++){var S=y[w],x=c[g]+M[w];if(x<c[S]){c[S]=x,o[S]=g;var m=l[S];m<0&&(m=a(i[S],d),l[S]=m),v.insert(x+m,S)}}else{var C=n[g];if(!C||0===C.length)continue;for(var k=0,b=C.length;k<b;k++){var O=C[k],N=O.node,A=c[g]+O.distance;if(A<c[N]){c[N]=A,o[N]=g;var F=l[N];F<0&&(F=a(i[N],d),l[N]=F),v.insert(A+F,N)}}}}}if(o[r]<0)return null;for(var D=[],L=r;L!==e;)D.push(i[L]),L=o[L];return D.push(i[e]),D.reverse(),{type:"Feature",geometry:{type:"LineString",coordinates:D},properties:{}}},r.getOrCreateIndex=function(t){var s=t[0],e=t[1],r=this.coordinateIndexMap.get(s);void 0===r&&(r=new Map,this.coordinateIndexMap.set(s,r));var i=r.get(e);if(void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),r.set(e,i),this.adjacencyList[i]=[],this.csrOffsets)){var n=this.csrNodeCount;if(i===n){var a=new Int32Array(n+2);a.set(this.csrOffsets,0),a[n+1]=a[n],this.csrOffsets=a,this.csrNodeCount=n+1}}return i},r.ensureScratch=function(t){if(!(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch)){var s=0|t;this.gScoreScratch=new Float64Array(s),this.cameFromScratch=new Int32Array(s),this.visitedScratch=new Uint8Array(s),this.hScratch=new Float64Array(s),this.scratchCapacity=s}},e}(),exports.createCheapRuler=function(t){var s=1/298.257223563,e=s*(2-s),r=Math.PI/180,i=Math.cos(t*r),n=1/(1-e*(1-i*i)),a=Math.sqrt(n),h=6378.137*r,c=h*a*i,o=h*a*n*(1-e);return function(t,s){for(var e=t[0]-s[0];e<-180;)e+=360;for(;e>180;)e-=360;var r=e*c,i=(t[1]-s[1])*o;return Math.sqrt(r*r+i*i)}},exports.haversineDistance=t;
|
|
2
2
|
//# sourceMappingURL=terra-route.cjs.map
|
package/dist/terra-route.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terra-route.cjs","sources":["../src/distance/haversine.ts","../src/graph/methods/connected.ts","../src/graph/methods/unique.ts","../src/test-utils/utils.ts","../src/graph/methods/duplicates.ts","../src/graph/methods/leaf.ts","../src/graph/methods/bounding-box.ts","../src/graph/methods/spatial-index/kdbush.ts","../src/graph/methods/spatial-index/tinyqueue.ts","../src/graph/methods/spatial-index/geokdbush.ts","../src/graph/graph.ts","../src/graph/methods/nodes.ts","../src/graph/methods/unify.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Feature, FeatureCollection, LineString, Position } from 'geojson'\n\n/**\n * Counts the number of connected components in a graph represented by LineString features in a GeoJSON FeatureCollection.\n * Each LineString is treated as an edge in the graph, and connected components are determined by shared coordinates.\n * @param featureCollection - A GeoJSON FeatureCollection containing LineString features\n * @returns The number of connected components in the graph represented by the LineStrings\n */\nexport function graphGetConnectedComponentCount(\n featureCollection: FeatureCollection<LineString>\n): number {\n const features = featureCollection.features\n const numberOfFeatures = features.length\n\n // Map coordinates to feature indices\n const coordinateToFeatureIndices = new Map<string, number[]>()\n\n for (let index = 0; index < numberOfFeatures; index++) {\n const coordinates = features[index].geometry.coordinates\n\n for (const coordinate of coordinates) {\n const key = coordinateKey(coordinate)\n\n if (!coordinateToFeatureIndices.has(key)) {\n coordinateToFeatureIndices.set(key, [])\n }\n\n coordinateToFeatureIndices.get(key)!.push(index)\n }\n }\n\n // Build adjacency list for the graph\n const adjacencyList: number[][] = Array.from({ length: numberOfFeatures }, () => [])\n\n for (const indices of coordinateToFeatureIndices.values()) {\n for (let i = 0; i < indices.length; i++) {\n for (let j = i + 1; j < indices.length; j++) {\n const a = indices[i]\n const b = indices[j]\n adjacencyList[a].push(b)\n adjacencyList[b].push(a)\n }\n }\n }\n\n const visited = new Array<boolean>(numberOfFeatures).fill(false)\n let connectedComponents = 0\n\n for (let index = 0; index < numberOfFeatures; index++) {\n if (!visited[index]) {\n dfs(index, adjacencyList, visited)\n connectedComponents++\n }\n }\n\n return connectedComponents\n}\n\n/**\n * Depth-first search to mark all reachable nodes from the given index.\n * @param index - The current node index to start DFS from.\n * @param adjacencyList - The adjacency list representing the graph.\n * @param visited - An array to keep track of visited nodes.\n */\nfunction dfs(index: number, adjacencyList: number[][], visited: boolean[]): void {\n visited[index] = true\n\n for (const neighbor of adjacencyList[index]) {\n if (!visited[neighbor]) {\n dfs(neighbor, adjacencyList, visited)\n }\n }\n}\n\nfunction coordinateKey(position: Position): string {\n return `${position[0]},${position[1]}`\n}\n\nexport function graphGetConnectedComponents(\n featureCollection: FeatureCollection<LineString>\n): FeatureCollection<LineString>[] {\n const features = featureCollection.features\n const graph: Map<number, Set<number>> = new Map()\n const coordinateMap: Map<string, Set<number>> = new Map()\n\n function coordinateKey(coordinate: Position): string {\n return `${coordinate[0]},${coordinate[1]}`\n }\n\n // Build coordinate map: coordinate string -> Set of feature indices\n for (let index = 0; index < features.length; index++) {\n const coordinates = features[index].geometry.coordinates\n\n for (const coordinate of coordinates) {\n const key = coordinateKey(coordinate)\n\n if (!coordinateMap.has(key)) {\n coordinateMap.set(key, new Set())\n }\n\n coordinateMap.get(key)!.add(index)\n }\n }\n\n // Build adjacency list for graph\n for (let index = 0; index < features.length; index++) {\n graph.set(index, new Set())\n\n const coordinates = features[index].geometry.coordinates\n for (const coordinate of coordinates) {\n const key = coordinateKey(coordinate)\n const neighbors = coordinateMap.get(key)\n\n if (neighbors) {\n for (const neighborIndex of neighbors) {\n if (neighborIndex !== index) {\n graph.get(index)!.add(neighborIndex)\n }\n }\n }\n }\n }\n\n // DFS to find connected components\n const visited = new Set<number>()\n const components: FeatureCollection<LineString>[] = []\n\n function dfs(startIndex: number, currentComponent: Feature<LineString>[]): void {\n const stack: number[] = [startIndex]\n\n while (stack.length > 0) {\n const currentIndex = stack.pop()!\n\n if (visited.has(currentIndex)) {\n continue\n }\n\n visited.add(currentIndex)\n currentComponent.push(features[currentIndex])\n\n const neighbors = graph.get(currentIndex)\n if (neighbors) {\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n stack.push(neighbor)\n }\n }\n }\n }\n }\n\n for (let index = 0; index < features.length; index++) {\n if (!visited.has(index)) {\n const component: Feature<LineString>[] = []\n dfs(index, component)\n components.push({\n type: 'FeatureCollection',\n features: component\n })\n }\n }\n\n\n // Sort components by the number of features in ascending order\n components.sort((a, b) => a.features.length - b.features.length)\n\n return components\n}\n","import {\n Feature,\n FeatureCollection,\n LineString,\n Position\n} from 'geojson';\n\n/**\n * Normalize a segment so that [A, B] is equal to [B, A]\n */\nfunction normalizeSegment(start: Position, end: Position): [Position, Position] {\n const [aLat, aLng] = start;\n const [bLat, bLng] = end;\n\n if (\n aLat < bLat ||\n (aLat === bLat && aLng <= bLng)\n ) {\n return [start, end];\n }\n\n return [end, start];\n}\n\n/**\n * Convert a pair of Positions to a string key for deduplication\n */\nfunction segmentKey(start: Position, end: Position): string {\n const [normalizedStart, normalizedEnd] = normalizeSegment(start, end);\n return JSON.stringify([normalizedStart, normalizedEnd]);\n}\n\n/**\n * Breaks LineStrings in a FeatureCollection into unique single line segments\n */\nexport function graphGetUniqueSegments(\n input: FeatureCollection<LineString>\n): FeatureCollection<LineString> {\n const uniqueSegments = new Map<string, Feature<LineString>>();\n\n for (const feature of input.features) {\n const coordinates = feature.geometry.coordinates;\n\n for (let index = 0; index < coordinates.length - 1; index++) {\n const start = coordinates[index];\n const end = coordinates[index + 1];\n\n const key = segmentKey(start, end);\n\n if (!uniqueSegments.has(key)) {\n const segment: Feature<LineString> = {\n type: 'Feature',\n geometry: {\n type: 'LineString',\n coordinates: [start, end]\n },\n properties: {}\n };\n\n uniqueSegments.set(key, segment);\n }\n }\n }\n\n return {\n type: 'FeatureCollection',\n features: Array.from(uniqueSegments.values())\n };\n}\n","import { Position, Feature, Point, LineString, FeatureCollection } from \"geojson\";\nimport { haversineDistance } from \"../distance/haversine\";\n\n/**\n * Calculates the total length of a LineString route in meters.\n *\n * @param line - A GeoJSON Feature<LineString> representing the route\n * @returns The total length of the route in meters\n */\nexport function routeLength(\n line: Feature<LineString>,\n) {\n const lineCoords = line.geometry.coordinates;\n\n // Calculate the total route distance\n let routeDistance = 0;\n for (let i = 0; i < lineCoords.length - 1; i++) {\n routeDistance += haversineDistance(lineCoords[i], lineCoords[i + 1]);\n }\n return routeDistance\n}\n\n/**\n * Extracts unique coordinates from a FeatureCollection of LineStrings.\n *\n * @param collection - A GeoJSON FeatureCollection of LineStrings\n * @returns An array of unique Position coordinates\n */\nexport function getUniqueCoordinatesFromLineStrings(\n collection: FeatureCollection<LineString>\n): Position[] {\n const seen = new Set<string>();\n const unique: Position[] = [];\n\n for (const feature of collection.features) {\n if (feature.geometry.type !== \"LineString\") {\n continue;\n }\n\n for (const coord of feature.geometry.coordinates) {\n const key = `${coord[0]},${coord[1]}`;\n\n if (!seen.has(key)) {\n seen.add(key);\n unique.push(coord);\n }\n }\n }\n\n return unique;\n}\n\n/**\n * Validates a GeoJSON Feature<LineString> route.\n *\n * @param route - The GeoJSON feature to validate\n * @returns A boolean indicating if it is a valid LineString route\n */\nexport function getReasonIfLineStringInvalid(\n route: Feature<LineString> | null | undefined\n): string | undefined {\n // 1. Must exist\n if (!route) {\n return 'No feature';\n }\n\n // 2. Must be a Feature\n if (route.type !== \"Feature\") {\n return 'Not a Feature';\n }\n\n // 3. Must have a geometry of type LineString\n if (!route.geometry || route.geometry.type !== \"LineString\") {\n return 'Not a LineString';\n }\n\n // 4. Coordinates must be an array with length >= 2\n const coords = route.geometry.coordinates;\n if (!Array.isArray(coords) || coords.length < 2) {\n return `Not enough coordinates: ${coords.length} (${coords})`;\n }\n\n const seen = new Set<string>();\n\n // 5. Validate each coordinate is a valid Position\n // (At minimum, [number, number] or [number, number, number])\n for (const position of coords) {\n if (!Array.isArray(position)) {\n return 'Not a Position; not an array';\n }\n\n // Check numeric values, ignoring optional altitude\n if (\n position.length < 2 ||\n typeof position[0] !== \"number\" ||\n typeof position[1] !== \"number\"\n ) {\n return 'Not a Position; elements are not a numbers';\n }\n\n // 6. Check for duplicates\n const key = `${position[0]},${position[1]}`;\n if (seen.has(key)) {\n return `Duplicate coordinate: ${key}`;\n }\n seen.add(key);\n }\n}\n\n/**\n * Checks if the start and end coordinates of a LineString match the given start and end points.\n * \n * @param line - The LineString feature to check\n * @param start - The start point feature\n * @param end - The end point feature\n * @return True if the start and end coordinates match, false otherwise\n * */\nexport function startAndEndAreCorrect(line: Feature<LineString>, start: Feature<Point>, end: Feature<Point>): boolean {\n const lineCoords = line.geometry.coordinates;\n const startCoords = start.geometry.coordinates;\n const endCoords = end.geometry.coordinates;\n\n // Check if the first coordinate of the LineString matches the start point\n const startMatches = lineCoords[0][0] === startCoords[0] && lineCoords[0][1] === startCoords[1];\n\n // Check if the last coordinate of the LineString matches the end point\n const endMatches = lineCoords[lineCoords.length - 1][0] === endCoords[0] && lineCoords[lineCoords.length - 1][1] === endCoords[1];\n\n return startMatches && endMatches;\n}\n\n/**\n * Checks if the route represented by a LineString is longer than the direct path. \n * In theory, a route should always longer than the direct path if it has more than two points.\n * @param line - The LineString feature representing the route\n * @param start - The start point feature\n * @param end - The end point feature\n * @returns - True if the route is longer than the direct path, false otherwise\n */\nexport function routeIsLongerThanDirectPath(line: Feature<LineString>, start: Feature<Point>, end: Feature<Point>): boolean {\n const lineCoords = line.geometry.coordinates;\n const startCoords = start.geometry.coordinates;\n const endCoords = end.geometry.coordinates;\n\n if (lineCoords.length <= 2) {\n return true;\n }\n\n // Calculate the direct distance between the start and end points\n const directDistance = haversineDistance(startCoords, endCoords);\n\n // Calculate the route distance\n let routeDistance = 0;\n for (let i = 0; i < lineCoords.length - 1; i++) {\n routeDistance += haversineDistance(lineCoords[i], lineCoords[i + 1]);\n }\n\n // If the route distance is 0, it means the start and end points are the same\n if (routeDistance === 0) {\n return true;\n }\n\n if (routeDistance < directDistance) {\n\n // Check if the route distance is very close to the direct distance\n const absoluteDifference = Math.abs(routeDistance - directDistance);\n if (absoluteDifference < 0.000000000001) {\n return true;\n }\n\n return false;\n }\n\n return true\n}\n\n/**\n * Modifies a FeatureCollection of LineStrings to break connections\n * between lines that share coordinates, by adjusting one of the shared\n * coordinates within a given tolerance.\n * \n * @param collection - The input FeatureCollection of LineStrings\n * @param tolerance - The amount by which to offset shared coordinates (in degrees)\n * @returns A new FeatureCollection with modified coordinates\n */\nexport function disconnectLineStrings(\n collection: FeatureCollection<LineString>,\n tolerance: number\n): FeatureCollection<LineString> {\n const seenCoordinates = new Map<string, number>()\n\n function getCoordinateKey(coordinate: Position): string {\n return `${coordinate[0]},${coordinate[1]}`\n }\n\n function offsetCoordinate(coordinate: Position, count: number): Position {\n const offset = count * tolerance\n return [coordinate[0] + offset, coordinate[1] + offset]\n }\n\n const updatedFeatures = collection.features.map((feature) => {\n const updatedCoordinates: Position[] = feature.geometry.coordinates.map((coordinate) => {\n const key = getCoordinateKey(coordinate)\n\n if (seenCoordinates.has(key)) {\n const count = seenCoordinates.get(key)!\n seenCoordinates.set(key, count + 1)\n return offsetCoordinate(coordinate, count + 1)\n }\n\n seenCoordinates.set(key, 0)\n return coordinate\n })\n\n return {\n ...feature,\n geometry: {\n ...feature.geometry,\n coordinates: updatedCoordinates\n }\n }\n })\n\n return {\n ...collection,\n features: updatedFeatures\n }\n}\n","import { FeatureCollection, LineString, Feature, Position } from 'geojson'\n\n/**\n * Are the two coordinate sequences exactly equal (in same order)?\n */\nfunction areCoordinatesEqual(a: Position[], b: Position[]): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n for (let i = 0; i < a.length; i++) {\n if (a[i][0] !== b[i][0] || a[i][1] !== b[i][1]) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Is `sub` a contiguous subsequence of `full`, either forward or reversed?\n */\nfunction isSubsequence(sub: Position[], full: Position[]): boolean {\n const subLength = sub.length\n const fullLength = full.length\n\n if (subLength > fullLength) {\n return false\n }\n\n // check forward\n for (let start = 0; start <= fullLength - subLength; start++) {\n let matches = true\n\n for (let offset = 0; offset < subLength; offset++) {\n if (\n full[start + offset][0] !== sub[offset][0] ||\n full[start + offset][1] !== sub[offset][1]\n ) {\n matches = false\n break\n }\n }\n\n if (matches) {\n return true\n }\n }\n\n // check reversed\n const reversedSub = [...sub].reverse()\n\n for (let start = 0; start <= fullLength - subLength; start++) {\n let matches = true\n\n for (let offset = 0; offset < subLength; offset++) {\n if (\n full[start + offset][0] !== reversedSub[offset][0] ||\n full[start + offset][1] !== reversedSub[offset][1]\n ) {\n matches = false\n break\n }\n }\n\n if (matches) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Remove any LineString that is either\n * - an exact duplicate of an earlier one, or\n * - a contiguous subsequence (in either direction) of any other.\n */\nexport function removeDuplicateAndSubsectionLines(\n collection: FeatureCollection<LineString>\n): FeatureCollection<LineString> {\n const features = collection.features\n const toRemove = new Set<number>()\n\n for (let i = 0; i < features.length; i++) {\n const coordsI = features[i].geometry.coordinates\n\n for (let j = 0; j < features.length; j++) {\n if (i === j) {\n continue\n }\n\n const coordsJ = features[j].geometry.coordinates\n\n // if coordsI are a subsequence of coordsJ, OR exactly equal,\n // then mark coordsI for removal—but only if\n // • coordsI is strictly shorter than coordsJ, or\n // • they’re the same length and this is the later duplicate (i > j)\n if (\n isSubsequence(coordsI, coordsJ) &&\n (\n coordsI.length < coordsJ.length ||\n (coordsI.length === coordsJ.length && i > j)\n )\n ) {\n toRemove.add(i)\n break\n }\n }\n }\n\n const filtered = features.filter((_, index) => !toRemove.has(index))\n return {\n type: 'FeatureCollection',\n features: filtered\n }\n}\n","import { FeatureCollection, LineString, Feature } from 'geojson';\nimport { graphGetUniqueSegments } from './unique';\n\n/**\n * Separates a graph's edges into leaf and non-leaf edges.\n * A leaf edge has a start or end node with degree 1.\n *\n * @param edgesFc - FeatureCollection containing LineString features representing edges of a graph\n * @returns Object containing two FeatureCollections: leafEdges and nonLeafEdges\n */\nexport function getLeafEdges(\n edgesFc: FeatureCollection<LineString>\n): {\n leafEdges: FeatureCollection<LineString>;\n nonLeafEdges: FeatureCollection<LineString>;\n} {\n const edges = graphGetUniqueSegments(edgesFc);\n\n const endpointCountMap: Map<string, number> = new Map();\n\n function coordKey(position: number[]): string {\n return position.join(\",\");\n }\n\n // Count the degree (number of edge endpoints) for each coordinate\n for (let i = 0; i < edges.features.length; i++) {\n const feature = edges.features[i];\n const coordinates = feature.geometry.coordinates;\n\n if (coordinates.length < 2) {\n continue;\n }\n\n const startKey = coordKey(coordinates[0]);\n const endKey = coordKey(coordinates[coordinates.length - 1]);\n\n endpointCountMap.set(\n startKey,\n (endpointCountMap.get(startKey) || 0) + 1\n );\n endpointCountMap.set(\n endKey,\n (endpointCountMap.get(endKey) || 0) + 1\n );\n }\n\n const leafEdgeMap: Map<string, Feature<LineString>> = new Map();\n const nonLeafEdgeMap: Map<string, Feature<LineString>> = new Map();\n\n for (let i = 0; i < edges.features.length; i++) {\n const feature = edges.features[i];\n const coordinates = feature.geometry.coordinates;\n\n if (coordinates.length < 2) {\n continue;\n }\n\n const startKey = coordKey(coordinates[0]);\n const endKey = coordKey(coordinates[coordinates.length - 1]);\n\n const startCount = endpointCountMap.get(startKey) || 0;\n const endCount = endpointCountMap.get(endKey) || 0;\n\n const edgeKey = coordinates.map(coordKey).join(\";\");\n\n if (startCount === 1 || endCount === 1) {\n if (!leafEdgeMap.has(edgeKey)) {\n leafEdgeMap.set(edgeKey, feature);\n }\n } else {\n if (!nonLeafEdgeMap.has(edgeKey)) {\n nonLeafEdgeMap.set(edgeKey, feature);\n }\n }\n }\n\n return {\n leafEdges: {\n type: \"FeatureCollection\",\n features: Array.from(leafEdgeMap.values())\n },\n nonLeafEdges: {\n type: \"FeatureCollection\",\n features: Array.from(nonLeafEdgeMap.values())\n }\n };\n}\n\n","import { Feature, FeatureCollection, LineString, Position } from 'geojson'\n\n/**\n * Type representing a bounding box as [minLng, minLat, maxLng, maxLat]\n */\nexport type BoundingBox = [number, number, number, number]\n\n/**\n * Filters a FeatureCollection of LineString features to only include LineStrings \n * that are completely within the specified bounding box.\n * @param featureCollection - A GeoJSON FeatureCollection containing LineString features\n * @param boundingBox - A bounding box array in the format [minLng, minLat, maxLng, maxLat]\n * @returns A new FeatureCollection<LineString> containing only the LineStrings completely within the bounding box\n */\nexport function getNetworkInBoundingBox(\n featureCollection: FeatureCollection<LineString>,\n boundingBox: BoundingBox\n): FeatureCollection<LineString> {\n const [minLng, minLat, maxLng, maxLat] = boundingBox\n\n // Validate bounding box\n if (minLng >= maxLng || minLat >= maxLat) {\n throw new Error('Invalid bounding box: min values must be less than max values')\n }\n\n const filteredFeatures: Feature<LineString>[] = []\n\n for (const feature of featureCollection.features) {\n if (isLineStringCompletelyWithinBounds(feature, minLng, minLat, maxLng, maxLat)) {\n filteredFeatures.push(feature)\n }\n }\n\n return {\n type: 'FeatureCollection',\n features: filteredFeatures\n }\n}\n\n/**\n * Checks if a LineString feature is completely within the specified bounds.\n * @param lineStringFeature - A GeoJSON Feature<LineString>\n * @param minLng - Minimum longitude\n * @param minLat - Minimum latitude\n * @param maxLng - Maximum longitude\n * @param maxLat - Maximum latitude\n * @returns true if all coordinates of the LineString are within the bounds, false otherwise\n */\nfunction isLineStringCompletelyWithinBounds(\n lineStringFeature: Feature<LineString>,\n minLng: number,\n minLat: number,\n maxLng: number,\n maxLat: number\n): boolean {\n const coordinates = lineStringFeature.geometry.coordinates\n\n for (const coordinate of coordinates) {\n if (!isCoordinateWithinBounds(coordinate, minLng, minLat, maxLng, maxLat)) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Checks if a coordinate is within the specified bounds.\n * @param coordinate - A coordinate position [lng, lat]\n * @param minLng - Minimum longitude\n * @param minLat - Minimum latitude\n * @param maxLng - Maximum longitude\n * @param maxLat - Maximum latitude\n * @returns true if the coordinate is within the bounds, false otherwise\n */\nfunction isCoordinateWithinBounds(\n coordinate: Position,\n minLng: number,\n minLat: number,\n maxLng: number,\n maxLat: number\n): boolean {\n const [lng, lat] = coordinate\n return lng >= minLng && lng <= maxLng && lat >= minLat && lat <= maxLat\n}\n","// Adapted from https://github.com/mourner/kdbush\n\n// ISC License\n\n// Copyright (c) 2018, Vladimir Agafonkin\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\nconst ARRAY_TYPES = [\n Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,\n Int32Array, Uint32Array, Float32Array, Float64Array\n];\n\nconst VERSION = 1;\nconst HEADER_SIZE = 8;\n\nexport class KDBush {\n public data: ArrayBuffer;\n public ids: Uint16Array | Uint32Array;\n public coords: InstanceType<TypedArrayConstructor>;\n private _pos: number;\n private _finished: boolean;\n public numItems: number;\n public nodeSize: number;\n private ArrayType: TypedArrayConstructor;\n private IndexArrayType: typeof Uint16Array | typeof Uint32Array;\n\n constructor(\n numItems: number,\n nodeSize: number = 64,\n ArrayType: TypedArrayConstructor = Float64Array,\n data?: ArrayBuffer\n ) {\n if (isNaN(numItems) || numItems < 0) {\n throw new Error(`Unexpected numItems value: ${numItems}.`);\n }\n\n this.numItems = numItems;\n this.nodeSize = Math.min(Math.max(nodeSize, 2), 65535);\n this.ArrayType = ArrayType;\n this.IndexArrayType = numItems < 65536 ? Uint16Array : Uint32Array;\n\n const arrayTypeIndex = ARRAY_TYPES.indexOf(this.ArrayType);\n const coordsByteSize = numItems * 2 * this.ArrayType.BYTES_PER_ELEMENT;\n const idsByteSize = numItems * this.IndexArrayType.BYTES_PER_ELEMENT;\n const padCoords = (8 - idsByteSize % 8) % 8;\n\n if (arrayTypeIndex < 0) {\n throw new Error(`Unexpected typed array class: ${ArrayType}.`);\n }\n\n if (data) {\n this.data = data;\n this.ids = new (this.IndexArrayType as any)(this.data, HEADER_SIZE, numItems);\n this.coords = new (this.ArrayType as any)(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);\n this._pos = numItems * 2;\n this._finished = true;\n } else {\n this.data = new ArrayBuffer(HEADER_SIZE + coordsByteSize + idsByteSize + padCoords);\n this.ids = new (this.IndexArrayType as any)(this.data, HEADER_SIZE, numItems);\n this.coords = new (this.ArrayType as any)(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);\n this._pos = 0;\n this._finished = false;\n\n new Uint8Array(this.data, 0, 2).set([0xdb, (VERSION << 4) + arrayTypeIndex]);\n new Uint16Array(this.data, 2, 1)[0] = this.nodeSize;\n new Uint32Array(this.data, 4, 1)[0] = this.numItems;\n }\n }\n\n add(x: number, y: number): number {\n const index = this._pos >> 1;\n this.ids[index] = index;\n this.coords[this._pos++] = x;\n this.coords[this._pos++] = y;\n return index;\n }\n\n finish(): this {\n const numAdded = this._pos >> 1;\n if (numAdded !== this.numItems) {\n throw new Error(`Added ${numAdded} items when expected ${this.numItems}.`);\n }\n sort(this.ids, this.coords, this.nodeSize, 0, this.numItems - 1, 0);\n this._finished = true;\n return this;\n }\n}\n\ntype TypedArrayConstructor =\n Int8ArrayConstructor | Uint8ArrayConstructor | Uint8ClampedArrayConstructor |\n Int16ArrayConstructor | Uint16ArrayConstructor |\n Int32ArrayConstructor | Uint32ArrayConstructor |\n Float32ArrayConstructor | Float64ArrayConstructor;\n\nfunction sort(\n ids: Uint16Array | Uint32Array,\n coords: InstanceType<TypedArrayConstructor>,\n nodeSize: number,\n left: number,\n right: number,\n axis: number\n): void {\n if (right - left <= nodeSize) return;\n const m = (left + right) >> 1;\n select(ids, coords, m, left, right, axis);\n sort(ids, coords, nodeSize, left, m - 1, 1 - axis);\n sort(ids, coords, nodeSize, m + 1, right, 1 - axis);\n}\n\nfunction select(\n ids: Uint16Array | Uint32Array,\n coords: InstanceType<TypedArrayConstructor>,\n k: number,\n left: number,\n right: number,\n axis: number\n): void {\n while (right > left) {\n if (right - left > 600) {\n const n = right - left + 1;\n const m = k - left + 1;\n const z = Math.log(n);\n const s = 0.5 * Math.exp(2 * z / 3);\n const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);\n const newLeft = Math.max(left, Math.floor(k - m * s / n + sd));\n const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));\n select(ids, coords, k, newLeft, newRight, axis);\n }\n\n const t = coords[2 * k + axis];\n let i = left;\n let j = right;\n\n swapItem(ids, coords, left, k);\n if (coords[2 * right + axis] > t) {\n swapItem(ids, coords, left, right);\n }\n\n while (i < j) {\n swapItem(ids, coords, i, j);\n i++;\n j--;\n while (coords[2 * i + axis] < t) i++;\n while (coords[2 * j + axis] > t) j--;\n }\n\n if (coords[2 * left + axis] === t) {\n swapItem(ids, coords, left, j);\n } else {\n j++;\n swapItem(ids, coords, j, right);\n }\n\n if (j <= k) left = j + 1;\n if (k <= j) right = j - 1;\n }\n}\n\nfunction swapItem(\n ids: Uint16Array | Uint32Array,\n coords: InstanceType<TypedArrayConstructor>,\n i: number,\n j: number\n): void {\n swap(ids, i, j);\n swap(coords, 2 * i, 2 * j);\n swap(coords, 2 * i + 1, 2 * j + 1);\n}\n\nfunction swap<T extends Uint16Array | Uint32Array | Float32Array | Float64Array | Int8Array | Int16Array | Int32Array | Uint8Array | Uint8ClampedArray>(\n arr: T,\n i: number,\n j: number\n): void {\n const tmp = arr[i];\n arr[i] = arr[j];\n arr[j] = tmp;\n}\n","// Adapted from https://github.com/mourner/kdbush\n\n// ISC License\n\n// Copyright (c) 2017, Vladimir Agafonkin\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\nexport default class TinyQueue<T> {\n\n private data: T[];\n public length: number;\n private compare: (a: T, b: T) => number;\n\n constructor(\n data: T[] = [],\n compare: (a: T, b: T) => number = (a, b) =>\n a < b ? -1 : a > b ? 1 : 0\n ) {\n this.data = data;\n this.length = this.data.length;\n this.compare = compare;\n\n if (this.length > 0) {\n for (let i = (this.length >> 1) - 1; i >= 0; i--) {\n this._down(i);\n }\n }\n }\n\n push(item: T): void {\n this.data.push(item);\n this._up(this.length++);\n }\n\n pop(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n\n const top = this.data[0];\n const bottom = this.data.pop() as T;\n\n this.length--;\n\n if (this.length > 0) {\n this.data[0] = bottom;\n this._down(0);\n }\n\n return top;\n }\n\n peek(): T | undefined {\n return this.data[0];\n }\n\n private _up(pos: number): void {\n const { data, compare } = this;\n const item = data[pos];\n\n while (pos > 0) {\n const parent = (pos - 1) >> 1;\n const current = data[parent];\n if (compare(item, current) >= 0) {\n break;\n }\n data[pos] = current;\n pos = parent;\n }\n\n data[pos] = item;\n }\n\n private _down(pos: number): void {\n const { data, compare } = this;\n const halfLength = this.length >> 1;\n const item = data[pos];\n\n while (pos < halfLength) {\n let bestChild = (pos << 1) + 1;\n const right = bestChild + 1;\n\n if (right < this.length && compare(data[right], data[bestChild]) < 0) {\n bestChild = right;\n }\n\n if (compare(data[bestChild], item) >= 0) {\n break;\n }\n\n data[pos] = data[bestChild];\n pos = bestChild;\n }\n\n data[pos] = item;\n }\n}\n","// Adapted from https://github.com/mourner/geokdbush\n\n// ISC License\n\n// Copyright (c) 2017, Vladimir Agafonkin\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\nimport { KDBush } from './kdbush';\nimport TinyQueue from './tinyqueue';\n\nconst earthRadius = 6371;\nconst rad = Math.PI / 180;\n\n\n// Distance is in kilometers\nexport function around(index: KDBush, lng: number, lat: number, maxResults = Infinity, maxDistanceKm = Infinity) {\n let maxHaverSinDist = 1;\n const result = [];\n\n if (maxResults === undefined) {\n maxResults = Infinity;\n }\n\n if (maxDistanceKm !== undefined) {\n maxHaverSinDist = haverSin(maxDistanceKm / earthRadius);\n }\n\n // a distance-sorted priority queue that will contain both points and kd-tree nodes\n const q = new TinyQueue([], compareDist);\n\n // an object that represents the top kd-tree node (the whole Earth)\n let node = {\n left: 0, // left index in the kd-tree array\n right: index.ids.length - 1, // right index\n axis: 0, // 0 for longitude axis and 1 for latitude axis\n dist: 0, // will hold the lower bound of children's distances to the query point\n minLng: -180, // bounding box of the node\n minLat: -90,\n maxLng: 180,\n maxLat: 90\n };\n\n const cosLat = Math.cos(lat * rad);\n\n while (node) {\n const right = node.right;\n const left = node.left;\n\n if (right - left <= index.nodeSize) { // leaf node\n\n // add all points of the leaf node to the queue\n for (let i = left; i <= right; i++) {\n const id = index.ids[i];\n\n const dist = haverSinDist(lng, lat, index.coords[2 * i], index.coords[2 * i + 1], cosLat);\n q.push({ id, dist });\n }\n\n } else { // not a leaf node (has child nodes)\n\n const m = (left + right) >> 1; // middle index\n const midLng = index.coords[2 * m];\n const midLat = index.coords[2 * m + 1];\n\n // add middle point to the queue\n const id = index.ids[m];\n const dist = haverSinDist(lng, lat, midLng, midLat, cosLat);\n q.push({ id, dist });\n\n\n const nextAxis = (node.axis + 1) % 2;\n\n // first half of the node\n const leftNode = {\n left,\n right: m - 1,\n axis: nextAxis,\n minLng: node.minLng,\n minLat: node.minLat,\n maxLng: node.axis === 0 ? midLng : node.maxLng,\n maxLat: node.axis === 1 ? midLat : node.maxLat,\n dist: 0\n };\n // second half of the node\n const rightNode = {\n left: m + 1,\n right,\n axis: nextAxis,\n minLng: node.axis === 0 ? midLng : node.minLng,\n minLat: node.axis === 1 ? midLat : node.minLat,\n maxLng: node.maxLng,\n maxLat: node.maxLat,\n dist: 0\n };\n\n leftNode.dist = boxDist(lng, lat, cosLat, leftNode);\n rightNode.dist = boxDist(lng, lat, cosLat, rightNode);\n\n // add child nodes to the queue\n q.push(leftNode);\n q.push(rightNode);\n }\n\n // fetch closest points from the queue; they're guaranteed to be closer\n // than all remaining points (both individual and those in kd-tree nodes),\n // since each node's distance is a lower bound of distances to its children\n while (q.length && q.peek().id != null) {\n const candidate = q.pop()!;\n if (candidate.dist > maxHaverSinDist) return result;\n result.push(candidate.id);\n if (result.length === maxResults) return result;\n }\n\n // the next closest kd-tree node\n node = q.pop();\n }\n\n return result;\n}\n\n// lower bound for distance from a location to points inside a bounding box\nfunction boxDist(lng: number, lat: number, cosLat: number, node: any) {\n const minLng = node.minLng;\n const maxLng = node.maxLng;\n const minLat = node.minLat;\n const maxLat = node.maxLat;\n\n // query point is between minimum and maximum longitudes\n if (lng >= minLng && lng <= maxLng) {\n if (lat < minLat) return haverSin((lat - minLat) * rad);\n if (lat > maxLat) return haverSin((lat - maxLat) * rad);\n return 0;\n }\n\n // query point is west or east of the bounding box;\n // calculate the extremum for great circle distance from query point to the closest longitude;\n const haverSinDLng = Math.min(haverSin((lng - minLng) * rad), haverSin((lng - maxLng) * rad));\n const extremumLat = vertexLat(lat, haverSinDLng);\n\n // if extremum is inside the box, return the distance to it\n if (extremumLat > minLat && extremumLat < maxLat) {\n return haverSinDistPartial(haverSinDLng, cosLat, lat, extremumLat);\n }\n // otherwise return the distan e to one of the bbox corners (whichever is closest)\n return Math.min(\n haverSinDistPartial(haverSinDLng, cosLat, lat, minLat),\n haverSinDistPartial(haverSinDLng, cosLat, lat, maxLat)\n );\n}\n\nfunction compareDist(a: any, b: any) {\n return a.dist - b.dist;\n}\n\nfunction haverSin(theta: number) {\n const s = Math.sin(theta / 2);\n return s * s;\n}\n\nfunction haverSinDistPartial(haverSinDLng: number, cosLat1: number, lat1: number, lat2: number) {\n return cosLat1 * Math.cos(lat2 * rad) * haverSinDLng + haverSin((lat1 - lat2) * rad);\n}\n\nfunction haverSinDist(lng1: number, lat1: number, lng2: number, lat2: number, cosLat1: number) {\n const haverSinDLng = haverSin((lng1 - lng2) * rad);\n return haverSinDistPartial(haverSinDLng, cosLat1, lat1, lat2);\n}\n\nexport function distance(lng1: number, lat1: number, lng2: number, lat2: number) {\n const h = haverSinDist(lng1, lat1, lng2, lat2, Math.cos(lat1 * rad));\n return 2 * earthRadius * Math.asin(Math.sqrt(h));\n}\n\nfunction vertexLat(lat: number, haverSinDLng: number) {\n const cosDLng = 1 - 2 * haverSinDLng;\n if (cosDLng <= 0) return lat > 0 ? 90 : -90;\n return Math.atan(Math.tan(lat * rad) / cosDLng) / rad;\n}","import { Feature, FeatureCollection, LineString, Point } from \"geojson\";\nimport { graphGetConnectedComponentCount, graphGetConnectedComponents } from \"./methods/connected\";\nimport { graphGetNodeAndEdgeCount, graphGetNodesAsPoints } from \"./methods/nodes\";\nimport { graphGetUniqueSegments } from \"./methods/unique\";\nimport { routeLength } from \"../test-utils/utils\";\nimport { removeDuplicateAndSubsectionLines } from \"./methods/duplicates\";\nimport { getLeafEdges } from \"./methods/leaf\";\nimport { getNetworkInBoundingBox, BoundingBox } from \"./methods/bounding-box\";\nimport { unifyCloseCoordinates } from \"./methods/unify\";\n\n/**\n * Represents a graph constructed from a GeoJSON FeatureCollection of LineString features.\n * This class provides methods to analyze the graph, including connected components, node and edge counts,\n * and shortest paths. Coordinates in the LineStrings are considered connected if they share identical coordinates.\n */\nexport class LineStringGraph {\n constructor(network: FeatureCollection<LineString>) {\n this.network = network;\n }\n\n private network: FeatureCollection<LineString>;\n\n /**\n * Sets the network for the graph.\n * This method replaces the current network with a new one.\n * @param network A GeoJSON FeatureCollection of LineString features representing the network.\n */\n setNetwork(network: FeatureCollection<LineString>) {\n this.network = network;\n }\n\n /**\n * Gets the current network of the graph.\n * @returns A GeoJSON FeatureCollection of LineString features representing the network.\n */\n getNetwork(): FeatureCollection<LineString> {\n return this.network;\n }\n\n /**\n * Gets a filtered network containing only LineStrings that are completely within the specified bounding box.\n * @param boundingBox A bounding box array in the format [minLng, minLat, maxLng, maxLat]\n * @returns A GeoJSON FeatureCollection of LineString features representing the network filtered by the bounding box.\n */\n getNetworkInBoundingBox(boundingBox: BoundingBox): FeatureCollection<LineString> {\n return getNetworkInBoundingBox(this.network, boundingBox);\n }\n\n /**\n * Gets the network without duplicate or subsection lines. \n * This method processes the network to remove any duplicate lines or lines that are subsections of other lines.\n * @returns A FeatureCollection<LineString> representing the network without duplicate or subsection lines.\n */\n getNetworkWithoutDuplicatesOrSubsections() {\n return removeDuplicateAndSubsectionLines(this.network);\n }\n\n /**\n * Gets the connected components of the graph.\n * @returns An array of FeatureCollection<LineString> representing the connected components.\n */\n getConnectedComponents(): FeatureCollection<LineString>[] {\n return graphGetConnectedComponents(this.network)\n }\n\n /**\n * Gets the count of connected components in the graph.\n * @returns The number of connected components in the graph.\n */\n getConnectedComponentCount(): number {\n return graphGetConnectedComponentCount(this.network);\n }\n\n /**\n * Gets the count of unique nodes and edges in the graph.\n * @returns An object containing the counts of nodes and edges.\n */\n getNodeAndEdgeCount(): { nodeCount: number, edgeCount: number } {\n return graphGetNodeAndEdgeCount(this.network);\n }\n\n /**\n * Gets the unique nodes of the graph as a FeatureCollection of Point features.\n * @returns A FeatureCollection<Point> containing the nodes of the graph.\n */\n getNodes(): FeatureCollection<Point> {\n const nodes = graphGetNodesAsPoints(this.network);\n return {\n type: \"FeatureCollection\",\n features: nodes\n };\n }\n\n /**\n * Gets the count of unique nodes in the graph.\n * @returns The number of unique nodes in the graph.\n */\n getNodeCount(): number {\n const { nodeCount } = this.getNodeAndEdgeCount();\n return nodeCount;\n }\n\n /**\n * Gets the unique edges of the graph as a FeatureCollection of LineString features. Each edge is represented as a LineString.\n * This method ensures that each edge is unique, meaning that edges are not duplicated in the collection. Each linestring only \n * two coordinates, representing the start and end points of the edge.\n * @returns A FeatureCollection<LineString> containing the unique edges of the graph.\n */\n getEdges(): FeatureCollection<LineString> {\n return graphGetUniqueSegments(this.network);\n }\n\n /**\n * Gets the length of the longest edge in the graph based on the length of the LineString.\n * If no edges exist, it returns -1.\n * @returns The length of the longest edge in meters, or 0 if no edges exist.\n */\n getLongestEdgeLength(): number {\n const longestEdge = this.getLongestEdge();\n if (!longestEdge) {\n return -1;\n }\n return routeLength(longestEdge);\n }\n\n /**\n * Gets the length of the shortest edge in the graph based on the length of the LineString.\n * If no edges exist, it returns -1.\n * @returns The length of the shortest edge in meters, or 0 if no edges exist.\n */\n getShortestEdgeLength(): number {\n const shortestEdge = this.getShortestEdge();\n if (!shortestEdge) {\n return -1;\n }\n return routeLength(shortestEdge);\n }\n\n /**\n * Gets the longest edge in the graph based on the length of the LineString.\n * @returns The longest edge as a Feature<LineString> or null if no edges exist.\n */\n getLongestEdge(): Feature<LineString> | null {\n const edges = this.getEdges().features;\n if (edges.length === 0) {\n return null;\n }\n const longestEdges = edges.sort((a, b) => routeLength(a) - routeLength(b));\n return longestEdges[longestEdges.length - 1];\n }\n\n /**\n * Gets the shortest edge in the graph based on the length of the LineString.\n * @returns The shortest edge as a Feature<LineString> or null if no edges exist.\n */\n getShortestEdge(): Feature<LineString> | null {\n const edges = this.getEdges().features;\n if (edges.length === 0) {\n return null;\n }\n const shortestEdges = edges.sort((a, b) => routeLength(a) - routeLength(b));\n return shortestEdges[0];\n }\n\n /**\n * Gets the count of unique edges in the graph.\n * @returns The number of unique edges in the graph.\n */\n getEdgeCount(): number {\n const { edgeCount } = this.getNodeAndEdgeCount();\n return edgeCount;\n }\n\n /**\n * Gets the leaf edges of the graph. A leaf edge is defined as an edge whose start or end node has a degree of 1.\n * Here an edge is defined as a LineString with two coordinates, representing the start and end points.\n * @returns A FeatureCollection<LineString> containing only the leaf edges of the graph.\n */\n getLeafEdges(): FeatureCollection<LineString> {\n return getLeafEdges(this.network).leafEdges;\n }\n\n /**\n * Returns the pruned network, which is the network without the leaf edges.\n * i.e. This method removes all leaf edges from the network, leaving only the non-leaf edges\n * @return A FeatureCollection<LineString> representing the pruned network without leaf edges.\n */\n getPrunedEdges(depth?: number): FeatureCollection<LineString> {\n if (depth && depth > 0) {\n\n let currentEdges = this.network;\n\n for (let i = 0; i < depth; i++) {\n const leafEdges = getLeafEdges(currentEdges);\n currentEdges = leafEdges.nonLeafEdges;\n }\n\n return currentEdges;\n\n }\n return getLeafEdges(this.network).nonLeafEdges;\n }\n\n /**\n * Returns the network where all nodes that are with n meters of each other are unified. \n * The function will avoid unifying coordinates in the same linestring.\n * @param toleranceMeters the tolerance for unifying nodes in meters. \n */\n getUnifiedNetwork(toleranceMeters: number) {\n return unifyCloseCoordinates(this.network, toleranceMeters);\n }\n}","import { Feature, FeatureCollection, LineString, Point, Position } from 'geojson'\n\n/**\n * Counts the unique nodes and edges in a GeoJSON FeatureCollection of LineString features.\n * @param featureCollection - A GeoJSON FeatureCollection containing LineString features\n * @returns An object containing the count of unique nodes and edges\n */\nexport function graphGetNodeAndEdgeCount(\n featureCollection: FeatureCollection<LineString>\n): { nodeCount: number; edgeCount: number } {\n const nodeSet = new Set<string>()\n const edgeSet = new Set<string>()\n\n for (const feature of featureCollection.features) {\n const coordinates = feature.geometry.coordinates\n\n for (const coordinate of coordinates) {\n nodeSet.add(JSON.stringify(coordinate))\n }\n\n for (let i = 0; i < coordinates.length - 1; i++) {\n const coordinateOne = coordinates[i]\n const coordinateTwo = coordinates[i + 1]\n\n const edge = normalizeEdge(coordinateOne, coordinateTwo)\n edgeSet.add(edge)\n }\n }\n\n return {\n nodeCount: nodeSet.size,\n edgeCount: edgeSet.size,\n }\n}\n\nfunction normalizeEdge(coordinateOne: Position, coordinateTwo: Position): string {\n const stringOne = JSON.stringify(coordinateOne)\n const stringTwo = JSON.stringify(coordinateTwo)\n\n if (stringOne < stringTwo) {\n return `${stringOne}|${stringTwo}`\n }\n\n return `${stringTwo}|${stringOne}`\n}\n\n\n/**\n * Converts a FeatureCollection of LineString features into a FeatureCollection of Point features,\n * where each unique coordinate in the LineStrings becomes a Point.\n * @param lines - A GeoJSON FeatureCollection containing LineString features\n * @returns A FeatureCollection of Point features representing unique nodes\n */\nexport function graphGetNodesAsPoints(lines: FeatureCollection<LineString>): Feature<Point>[] {\n const seen = new Set<string>();\n const points: Feature<Point>[] = [];\n\n for (const feature of lines.features) {\n for (const coord of feature.geometry.coordinates) {\n const key = coord.join(',');\n\n if (!seen.has(key)) {\n seen.add(key);\n points.push({\n type: 'Feature',\n geometry: {\n type: 'Point',\n coordinates: coord\n },\n properties: {}\n });\n }\n }\n }\n\n return points;\n}\n","import { FeatureCollection, LineString, Position } from 'geojson';\nimport { haversineDistance } from '../../distance/haversine';\nimport { KDBush } from './spatial-index/kdbush';\nimport { around } from './spatial-index/geokdbush';\n\nexport function unifyCloseCoordinates(\n featureCollection: FeatureCollection<LineString>,\n radiusMeters: number\n): FeatureCollection<LineString> {\n if (featureCollection.features.length < 2) {\n return featureCollection; // No features to process\n }\n\n const globalSeen: Position[] = [];\n const globalSeenSet = new Set<string>(); // Fast O(1) lookup for globalSeen\n const mapping: Map<string, Position> = new Map();\n\n // Build a one-time spatial index with all coordinates for efficient querying\n const allCoords: Position[] = [];\n const coordToGlobalIndex = new Map<string, number>();\n\n // Collect all unique coordinates first\n for (const feature of featureCollection.features) {\n for (const coord of feature.geometry.coordinates) {\n const key = `${coord[0]},${coord[1]}`; // Faster than join\n if (!coordToGlobalIndex.has(key)) {\n coordToGlobalIndex.set(key, allCoords.length);\n allCoords.push(coord);\n }\n }\n }\n\n // Build spatial index once for all coordinates\n let spatialIndex: KDBush = new KDBush(allCoords.length);\n for (const coord of allCoords) {\n spatialIndex.add(coord[0], coord[1]); // lng, lat\n }\n spatialIndex.finish();\n\n function findOrRegisterCoordinate(\n coordinate: Position,\n exclude: Position[],\n futureConflictCheck: Set<string>\n ): Position {\n let closest: Position | null = null;\n let closestDistance = Infinity;\n\n // if (globalSeen.length > 5) {\n // Use spatial index for efficient querying\n const radiusKm = radiusMeters / 1000; // Convert meters to kilometers\n const candidateIndices = around(spatialIndex, coordinate[0], coordinate[1], Infinity, radiusKm);\n\n for (const candidateIndex of candidateIndices) {\n const candidateCoord = allCoords[candidateIndex];\n const candidateKey = `${candidateCoord[0]},${candidateCoord[1]}`;\n\n // Only consider coordinates that are in globalSeen (O(1) lookup)\n if (!globalSeenSet.has(candidateKey)) {\n continue;\n }\n\n if (exclude.includes(candidateCoord)) {\n continue;\n }\n\n if (futureConflictCheck.has(candidateKey)) {\n continue;\n }\n\n const distance = haversineDistance(candidateCoord, coordinate) * 1000; // Convert to meters\n\n if (distance <= radiusMeters && distance < closestDistance) {\n closest = candidateCoord;\n closestDistance = distance;\n }\n }\n\n\n if (closest !== null) {\n return closest;\n }\n\n globalSeen.push(coordinate);\n globalSeenSet.add(`${coordinate[0]},${coordinate[1]}`);\n return coordinate;\n }\n\n const updatedFeatures = featureCollection.features.map((feature) => {\n const localSeen: Position[] = [];\n const updatedCoordinates: Position[] = [];\n const usedKeys = new Set<string>();\n\n for (const coordinate of feature.geometry.coordinates) {\n const key = `${coordinate[0]},${coordinate[1]}`;\n\n if (!mapping.has(key)) {\n const unified = findOrRegisterCoordinate(coordinate, localSeen, usedKeys);\n const unifiedKey = `${unified[0]},${unified[1]}`;\n\n // Avoid inserting a coordinate if it's going to cause duplication later in this feature\n if (usedKeys.has(unifiedKey)) {\n mapping.set(key, coordinate);\n } else {\n mapping.set(key, unified);\n }\n }\n\n const unifiedCoordinate = mapping.get(key)!;\n const unifiedKey = `${unifiedCoordinate[0]},${unifiedCoordinate[1]}`;\n\n if (!usedKeys.has(unifiedKey)) {\n updatedCoordinates.push(unifiedCoordinate);\n usedKeys.add(unifiedKey);\n }\n\n localSeen.push(coordinate);\n }\n\n return {\n ...feature,\n geometry: {\n ...feature.geometry,\n coordinates: updatedCoordinates\n }\n };\n });\n\n return {\n ...featureCollection,\n features: updatedFeatures\n };\n}\n","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { LineStringGraph } from \"./graph/graph\"; // Exported type (not used internally here)\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n const features = network.features; // All LineString features\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n for (let f = 0, fLen = features.length; f < fLen; f++) { // Iterate features\n const feature = features[f]; // Current feature\n const lineCoords = feature.geometry.coordinates; // Coordinates for this LineString\n\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) { // Iterate segment pairs\n const a = lineCoords[i]; // Segment start coord\n const b = lineCoords[i + 1]; // Segment end coord\n\n const lngA = a[0], latA = a[1]; // Keys for A\n const lngB = b[0], latB = b[1]; // Keys for B\n\n // Index A\n let latMapA = coordIndexMapLocal.get(lngA);\n if (latMapA === undefined) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(a);\n latMapA.set(latA, indexA);\n }\n\n // Index B\n let latMapB = coordIndexMapLocal.get(lngB);\n if (latMapB === undefined) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(b);\n latMapB.set(latB, indexB);\n }\n\n // Count degree for both directions\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n }\n }\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const feature = features[f];\n const lineCoords = feature.geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n const a = lineCoords[i];\n const b = lineCoords[i + 1];\n\n const lngA = a[0], latA = a[1];\n const lngB = b[0], latB = b[1];\n\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(lngA)!.get(latA)!;\n const indexB = this.coordinateIndexMap.get(lngB)!.get(latB)!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n // Write A → B\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n // Write B → A\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n }\n }\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n const measureDistance = this.distanceMeasurement; // Alias to distance function\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gScore = this.gScoreScratch!; // gScore pointer\n const cameFrom = this.cameFromScratch!; // cameFrom pointer\n const visited = this.visitedScratch!; // visited pointer\n const hCache = this.hScratch!; // heuristic cache pointer\n\n // Reset only the used range for speed\n gScore.fill(Number.POSITIVE_INFINITY, 0, nodeCount); // Init gScore to +∞\n cameFrom.fill(-1, 0, nodeCount); // Init predecessors to -1 (unknown)\n visited.fill(0, 0, nodeCount); // Init visited flags to 0\n hCache.fill(-1, 0, nodeCount); // Init heuristic cache with sentinel (-1 means unknown)\n\n // Create min-heap (priority queue)\n const openSet = new this.heapConstructor();\n\n // Precompute heuristic for start to prime the queue cost\n const endCoord = coords[endIndex]; // Cache end coordinate for heuristic\n let hStart = hCache[startIndex];\n\n if (hStart < 0) {\n hStart = measureDistance(coords[startIndex], endCoord);\n hCache[startIndex] = hStart;\n }\n\n openSet.insert(hStart, startIndex); // Insert start with f = g(0) + h(start)\n gScore[startIndex] = 0; // g(start) = 0\n\n while (openSet.size() > 0) { // Main A* loop until queue empty\n const current = openSet.extractMin()!; // Pop node with smallest f\n if (visited[current] !== 0) { // Skip if already finalized\n continue;\n }\n if (current === endIndex) { // Early exit if reached goal\n break;\n }\n visited[current] = 1; // Mark as visited\n\n // Prefer CSR neighbors if available for this node, fall back to sparse list for dynamically added nodes\n\n if (this.csrOffsets && current < this.csrNodeCount) { // Use CSR fast path\n const csrOffsets = this.csrOffsets!; // Local CSR offsets (non-null here)\n const csrIndices = this.csrIndices!; // Local CSR neighbors\n const csrDistances = this.csrDistances!; // Local CSR weights\n const startOff = csrOffsets[current]; // Row start for current\n const endOff = csrOffsets[current + 1]; // Row end for current\n\n for (let i = startOff; i < endOff; i++) { // Iterate neighbors in CSR slice\n const nbNode = csrIndices[i]; // Neighbor node id\n const tentativeG = gScore[current] + csrDistances[i]; // g' = g(current) + w(current, nb)\n if (tentativeG < gScore[nbNode]) { // Relaxation check\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n // A* priority = g + h (cache h per node for this query)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Push/update neighbor into open set\n }\n }\n }\n // Fallback: use sparse adjacency (only for nodes added after CSR build)\n else {\n\n const neighbors = adj[current]; // Neighbor list for current\n if (!neighbors || neighbors.length === 0) continue; // No neighbors\n for (let i = 0, n = neighbors.length; i < n; i++) { // Iterate neighbors\n const nb = neighbors[i]; // Neighbor entry\n const nbNode = nb.node; // Neighbor id\n\n const tentativeG = gScore[current] + nb.distance; // g' via current\n if (tentativeG < gScore[nbNode]) { // Relax if better\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n\n // A* priority = g + h (cached)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Enqueue neighbor\n }\n }\n }\n }\n\n // If goal was never reached/relaxed, return null\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path (push + reverse to avoid O(n^2) unshift)\n const path: Position[] = [];\n let cur = endIndex;\n while (cur !== startIndex) {\n path.push(coords[cur]);\n cur = cameFrom[cur];\n }\n // Include start coordinate\n path.push(coords[startIndex]);\n\n // Reverse to get start→end order\n path.reverse();\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance, LineStringGraph } ","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","dfs","index","adjacencyList","visited","_step3","_iterator3","_createForOfIteratorHelperLoose","done","neighbor","value","segmentKey","start","end","_normalizeSegment","aLat","bLat","normalizeSegment","JSON","stringify","graphGetUniqueSegments","input","_step","uniqueSegments","Map","_iterator","features","coordinates","geometry","length","key","has","set","type","properties","Array","from","values","routeLength","line","lineCoords","routeDistance","i","isSubsequence","sub","full","subLength","fullLength","matches","offset","reversedSub","concat","reverse","getLeafEdges","edgesFc","edges","endpointCountMap","coordKey","position","join","startKey","endKey","get","leafEdgeMap","nonLeafEdgeMap","feature","startCount","endCount","edgeKey","map","leafEdges","nonLeafEdges","isLineStringCompletelyWithinBounds","lineStringFeature","minLng","minLat","maxLng","maxLat","_step2","_iterator2","isCoordinateWithinBounds","coordinate","lng","lat","ARRAY_TYPES","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Uint16Array","Int32Array","Uint32Array","Float32Array","Float64Array","KDBush","numItems","nodeSize","ArrayType","data","ids","this","coords","_pos","_finished","IndexArrayType","isNaN","Error","min","max","arrayTypeIndex","indexOf","coordsByteSize","BYTES_PER_ELEMENT","idsByteSize","padCoords","ArrayBuffer","_proto","prototype","add","x","y","finish","numAdded","sort","left","right","axis","m","select","k","n","z","log","s","exp","sd","floor","t","j","swapItem","swap","arr","tmp","TinyQueue","compare","b","_down","push","item","_up","pop","top","bottom","peek","pos","parent","current","halfLength","bestChild","rad","boxDist","cosLat","node","haverSin","haverSinDLng","extremumLat","cosDLng","atan","tan","vertexLat","haverSinDistPartial","compareDist","dist","theta","cosLat1","lat1","lat2","haverSinDist","lng1","lng2","LineStringGraph","network","setNetwork","getNetwork","getNetworkInBoundingBox","boundingBox","featureCollection","filteredFeatures","getNetworkWithoutDuplicatesOrSubsections","collection","toRemove","Set","coordsI","coordsJ","filter","_","removeDuplicateAndSubsectionLines","getConnectedComponents","graph","coordinateMap","coordinateKey","_step4","_iterator4","_step5","_iterator5","neighbors","_iterator6","_step6","neighborIndex","components","startIndex","currentComponent","stack","currentIndex","_step7","_iterator7","component","graphGetConnectedComponents","getConnectedComponentCount","numberOfFeatures","coordinateToFeatureIndices","indices","fill","connectedComponents","graphGetConnectedComponentCount","getNodeAndEdgeCount","nodeSet","edgeSet","edge","coordinateTwo","stringOne","stringTwo","nodeCount","size","edgeCount","graphGetNodeAndEdgeCount","getNodes","lines","seen","points","coord","graphGetNodesAsPoints","getNodeCount","getEdges","getLongestEdgeLength","longestEdge","getLongestEdge","getShortestEdgeLength","shortestEdge","getShortestEdge","longestEdges","getEdgeCount","getPrunedEdges","depth","currentEdges","getUnifiedNetwork","toleranceMeters","radiusMeters","globalSeenSet","mapping","allCoords","coordToGlobalIndex","spatialIndex","_i","_allCoords","findOrRegisterCoordinate","exclude","futureConflictCheck","closest","closestDistance","Infinity","maxResults","maxDistanceKm","maxHaverSinDist","result","undefined","q","id","midLng","midLat","nextAxis","leftNode","rightNode","candidate","around","candidateCoord","candidateKey","includes","distance","updatedFeatures","localSeen","updatedCoordinates","usedKeys","unified","unifiedCoordinate","unifiedKey","_extends","unifyCloseCoordinates","FourAryHeap","keys","idxs","insertCounter","insert","ck","cv","ci","p","pk","pi","extractMin","minValue","last","bubbleDown","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","distanceMeasurement","heapConstructor","coordinateIndexMap","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","scratchCapacity","heap","buildRouteGraph","coordIndexMapLocal","coordsLocal","measureDistance","degree","f","fLen","len","_degree$indexA","_degree$indexB","lngA","latA","lngB","latB","latMapA","indexA","latMapB","indexB","offsets","_degree$_i","deg","totalEdges","distances","cursor","slice","segmentDistance","getRoute","getOrCreateIndex","endIndex","adj","ensureScratch","gScore","cameFrom","hCache","Number","POSITIVE_INFINITY","openSet","endCoord","hStart","endOff","nbNode","tentativeG","hVal","nb","path","cur","latMap","oldCount","newOffsets","capacity","FE","E2","RAD","w2","w","kx","ky","deltaLng","dx","dy"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"terra-route.cjs","sources":["../src/distance/haversine.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n const features = network.features; // All LineString features\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n for (let f = 0, fLen = features.length; f < fLen; f++) { // Iterate features\n const feature = features[f]; // Current feature\n const lineCoords = feature.geometry.coordinates; // Coordinates for this LineString\n\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) { // Iterate segment pairs\n const a = lineCoords[i]; // Segment start coord\n const b = lineCoords[i + 1]; // Segment end coord\n\n const lngA = a[0], latA = a[1]; // Keys for A\n const lngB = b[0], latB = b[1]; // Keys for B\n\n // Index A\n let latMapA = coordIndexMapLocal.get(lngA);\n if (latMapA === undefined) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(a);\n latMapA.set(latA, indexA);\n }\n\n // Index B\n let latMapB = coordIndexMapLocal.get(lngB);\n if (latMapB === undefined) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(b);\n latMapB.set(latB, indexB);\n }\n\n // Count degree for both directions\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n }\n }\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const feature = features[f];\n const lineCoords = feature.geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n const a = lineCoords[i];\n const b = lineCoords[i + 1];\n\n const lngA = a[0], latA = a[1];\n const lngB = b[0], latB = b[1];\n\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(lngA)!.get(latA)!;\n const indexB = this.coordinateIndexMap.get(lngB)!.get(latB)!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n // Write A → B\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n // Write B → A\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n }\n }\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n const measureDistance = this.distanceMeasurement; // Alias to distance function\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gScore = this.gScoreScratch!; // gScore pointer\n const cameFrom = this.cameFromScratch!; // cameFrom pointer\n const visited = this.visitedScratch!; // visited pointer\n const hCache = this.hScratch!; // heuristic cache pointer\n\n // Reset only the used range for speed\n gScore.fill(Number.POSITIVE_INFINITY, 0, nodeCount); // Init gScore to +∞\n cameFrom.fill(-1, 0, nodeCount); // Init predecessors to -1 (unknown)\n visited.fill(0, 0, nodeCount); // Init visited flags to 0\n hCache.fill(-1, 0, nodeCount); // Init heuristic cache with sentinel (-1 means unknown)\n\n // Create min-heap (priority queue)\n const openSet = new this.heapConstructor();\n\n // Precompute heuristic for start to prime the queue cost\n const endCoord = coords[endIndex]; // Cache end coordinate for heuristic\n let hStart = hCache[startIndex];\n\n if (hStart < 0) {\n hStart = measureDistance(coords[startIndex], endCoord);\n hCache[startIndex] = hStart;\n }\n\n openSet.insert(hStart, startIndex); // Insert start with f = g(0) + h(start)\n gScore[startIndex] = 0; // g(start) = 0\n\n while (openSet.size() > 0) { // Main A* loop until queue empty\n const current = openSet.extractMin()!; // Pop node with smallest f\n if (visited[current] !== 0) { // Skip if already finalized\n continue;\n }\n if (current === endIndex) { // Early exit if reached goal\n break;\n }\n visited[current] = 1; // Mark as visited\n\n // Prefer CSR neighbors if available for this node, fall back to sparse list for dynamically added nodes\n\n if (this.csrOffsets && current < this.csrNodeCount) { // Use CSR fast path\n const csrOffsets = this.csrOffsets!; // Local CSR offsets (non-null here)\n const csrIndices = this.csrIndices!; // Local CSR neighbors\n const csrDistances = this.csrDistances!; // Local CSR weights\n const startOff = csrOffsets[current]; // Row start for current\n const endOff = csrOffsets[current + 1]; // Row end for current\n\n for (let i = startOff; i < endOff; i++) { // Iterate neighbors in CSR slice\n const nbNode = csrIndices[i]; // Neighbor node id\n const tentativeG = gScore[current] + csrDistances[i]; // g' = g(current) + w(current, nb)\n if (tentativeG < gScore[nbNode]) { // Relaxation check\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n // A* priority = g + h (cache h per node for this query)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Push/update neighbor into open set\n }\n }\n }\n // Fallback: use sparse adjacency (only for nodes added after CSR build)\n else {\n\n const neighbors = adj[current]; // Neighbor list for current\n if (!neighbors || neighbors.length === 0) continue; // No neighbors\n for (let i = 0, n = neighbors.length; i < n; i++) { // Iterate neighbors\n const nb = neighbors[i]; // Neighbor entry\n const nbNode = nb.node; // Neighbor id\n\n const tentativeG = gScore[current] + nb.distance; // g' via current\n if (tentativeG < gScore[nbNode]) { // Relax if better\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n\n // A* priority = g + h (cached)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Enqueue neighbor\n }\n }\n }\n }\n\n // If goal was never reached/relaxed, return null\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path (push + reverse to avoid O(n^2) unshift)\n const path: Position[] = [];\n let cur = endIndex;\n while (cur !== startIndex) {\n path.push(coords[cur]);\n cur = cameFrom[cur];\n }\n // Include start coordinate\n path.push(coords[startIndex]);\n\n // Reverse to get start→end order\n path.reverse();\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } ","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","FourAryHeap","keys","this","values","idxs","length","insertCounter","_proto","prototype","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","scratchCapacity","heap","buildRouteGraph","coordIndexMapLocal","coordsLocal","measureDistance","features","degree","f","fLen","lineCoords","geometry","len","_degree$indexA","_degree$indexB","b","lngA","latA","lngB","latB","latMapA","get","undefined","set","indexA","push","latMapB","indexB","nodeCount","offsets","Int32Array","_degree$_i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","segmentDistance","pos","Array","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","coords","adj","ensureScratch","gScore","cameFrom","visited","hCache","fill","Number","POSITIVE_INFINITY","openSet","endCoord","hStart","current","endOff","nbNode","tentativeG","hVal","neighbors","nb","node","distance","path","cur","reverse","type","properties","coord","lng","lat","latMap","index","oldCount","newOffsets","capacity","Uint8Array","FE","E2","RAD","cosLat","w2","w","m","kx","ky","deltaLng","dx","dy"],"mappings":"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"}
|
package/dist/terra-route.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { FeatureCollection, LineString, Point, Feature, Position } from "geojson
|
|
|
2
2
|
import { haversineDistance } from "./distance/haversine";
|
|
3
3
|
import { createCheapRuler } from "./distance/cheap-ruler";
|
|
4
4
|
import { HeapConstructor } from "./heap/heap";
|
|
5
|
-
import { LineStringGraph } from "./graph/graph";
|
|
6
5
|
interface Router {
|
|
7
6
|
buildRouteGraph(network: FeatureCollection<LineString>): void;
|
|
8
7
|
getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;
|
|
@@ -49,4 +48,4 @@ declare class TerraRoute implements Router {
|
|
|
49
48
|
private getOrCreateIndex;
|
|
50
49
|
private ensureScratch;
|
|
51
50
|
}
|
|
52
|
-
export { TerraRoute, createCheapRuler, haversineDistance
|
|
51
|
+
export { TerraRoute, createCheapRuler, haversineDistance };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const t=(t,e)=>{const n=t=>t*Math.PI/180,s=n(t[1]),r=n(t[0]),o=n(e[1]),i=o-s,a=n(e[0])-r,h=Math.sin(i/2)*Math.sin(i/2)+Math.cos(s)*Math.cos(o)*Math.sin(a/2)*Math.sin(a/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};function e(t){const e=1/298.257223563,n=e*(2-e),s=Math.PI/180,r=Math.cos(t*s),o=1/(1-n*(1-r*r)),i=Math.sqrt(o),a=6378.137*s,h=a*i*r,c=a*i*o*(1-n);return function(t,e){let n=t[0]-e[0];for(;n<-180;)n+=360;for(;n>180;)n-=360;const s=n*h,r=(t[1]-e[1])*c;return Math.sqrt(s*s+r*r)}}function n(t,e,s){s[t]=!0;for(const r of e[t])s[r]||n(r,e,s)}function s(t,e){const n=JSON.stringify(t),s=JSON.stringify(e);return n<s?`${n}|${s}`:`${s}|${n}`}function r(t,e){const[n,s]=function(t,e){const[n,s]=t,[r,o]=e;return n<r||n===r&&s<=o?[t,e]:[e,t]}(t,e);return JSON.stringify([n,s])}function o(t){const e=new Map;for(const n of t.features){const t=n.geometry.coordinates;for(let n=0;n<t.length-1;n++){const s=t[n],o=t[n+1],i=r(s,o);e.has(i)||e.set(i,{type:"Feature",geometry:{type:"LineString",coordinates:[s,o]},properties:{}})}}return{type:"FeatureCollection",features:Array.from(e.values())}}function i(){return i=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var s in n)({}).hasOwnProperty.call(n,s)&&(t[s]=n[s])}return t},i.apply(null,arguments)}function a(e){const n=e.geometry.coordinates;let s=0;for(let e=0;e<n.length-1;e++)s+=t(n[e],n[e+1]);return s}function h(t,e){const n=t.length,s=e.length;if(n>s)return!1;for(let r=0;r<=s-n;r++){let s=!0;for(let o=0;o<n;o++)if(e[r+o][0]!==t[o][0]||e[r+o][1]!==t[o][1]){s=!1;break}if(s)return!0}const r=[...t].reverse();for(let t=0;t<=s-n;t++){let s=!0;for(let o=0;o<n;o++)if(e[t+o][0]!==r[o][0]||e[t+o][1]!==r[o][1]){s=!1;break}if(s)return!0}return!1}function c(t){const e=o(t),n=new Map;function s(t){return t.join(",")}for(let t=0;t<e.features.length;t++){const r=e.features[t].geometry.coordinates;if(r.length<2)continue;const o=s(r[0]),i=s(r[r.length-1]);n.set(o,(n.get(o)||0)+1),n.set(i,(n.get(i)||0)+1)}const r=new Map,i=new Map;for(let t=0;t<e.features.length;t++){const o=e.features[t],a=o.geometry.coordinates;if(a.length<2)continue;const h=s(a[0]),c=s(a[a.length-1]),u=n.get(h)||0,f=n.get(c)||0,l=a.map(s).join(";");1===u||1===f?r.has(l)||r.set(l,o):i.has(l)||i.set(l,o)}return{leafEdges:{type:"FeatureCollection",features:Array.from(r.values())},nonLeafEdges:{type:"FeatureCollection",features:Array.from(i.values())}}}function u(t,e,n,s,r){const o=t.geometry.coordinates;for(const t of o)if(!f(t,e,n,s,r))return!1;return!0}function f(t,e,n,s,r){const[o,i]=t;return o>=e&&o<=s&&i>=n&&i<=r}const l=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];class d{constructor(t,e=64,n=Float64Array,s){if(this.data=void 0,this.ids=void 0,this.coords=void 0,this._pos=void 0,this._finished=void 0,this.numItems=void 0,this.nodeSize=void 0,this.ArrayType=void 0,this.IndexArrayType=void 0,isNaN(t)||t<0)throw new Error(`Unexpected numItems value: ${t}.`);this.numItems=t,this.nodeSize=Math.min(Math.max(e,2),65535),this.ArrayType=n,this.IndexArrayType=t<65536?Uint16Array:Uint32Array;const r=l.indexOf(this.ArrayType),o=2*t*this.ArrayType.BYTES_PER_ELEMENT,i=t*this.IndexArrayType.BYTES_PER_ELEMENT,a=(8-i%8)%8;if(r<0)throw new Error(`Unexpected typed array class: ${n}.`);s?(this.data=s,this.ids=new this.IndexArrayType(this.data,8,t),this.coords=new this.ArrayType(this.data,8+i+a,2*t),this._pos=2*t,this._finished=!0):(this.data=new ArrayBuffer(8+o+i+a),this.ids=new this.IndexArrayType(this.data,8,t),this.coords=new this.ArrayType(this.data,8+i+a,2*t),this._pos=0,this._finished=!1,new Uint8Array(this.data,0,2).set([219,16+r]),new Uint16Array(this.data,2,1)[0]=this.nodeSize,new Uint32Array(this.data,4,1)[0]=this.numItems)}add(t,e){const n=this._pos>>1;return this.ids[n]=n,this.coords[this._pos++]=t,this.coords[this._pos++]=e,n}finish(){const t=this._pos>>1;if(t!==this.numItems)throw new Error(`Added ${t} items when expected ${this.numItems}.`);return g(this.ids,this.coords,this.nodeSize,0,this.numItems-1,0),this._finished=!0,this}}function g(t,e,n,s,r,o){if(r-s<=n)return;const i=s+r>>1;p(t,e,i,s,r,o),g(t,e,n,s,i-1,1-o),g(t,e,n,i+1,r,1-o)}function p(t,e,n,s,r,o){for(;r>s;){if(r-s>600){const i=r-s+1,a=n-s+1,h=Math.log(i),c=.5*Math.exp(2*h/3),u=.5*Math.sqrt(h*c*(i-c)/i)*(a-i/2<0?-1:1);p(t,e,n,Math.max(s,Math.floor(n-a*c/i+u)),Math.min(r,Math.floor(n+(i-a)*c/i+u)),o)}const i=e[2*n+o];let a=s,h=r;for(y(t,e,s,n),e[2*r+o]>i&&y(t,e,s,r);a<h;){for(y(t,e,a,h),a++,h--;e[2*a+o]<i;)a++;for(;e[2*h+o]>i;)h--}e[2*s+o]===i?y(t,e,s,h):(h++,y(t,e,h,r)),h<=n&&(s=h+1),n<=h&&(r=h-1)}}function y(t,e,n,s){m(t,n,s),m(e,2*n,2*s),m(e,2*n+1,2*s+1)}function m(t,e,n){const s=t[e];t[e]=t[n],t[n]=s}class w{constructor(t=[],e=(t,e)=>t<e?-1:t>e?1:0){if(this.data=void 0,this.length=void 0,this.compare=void 0,this.data=t,this.length=this.data.length,this.compare=e,this.length>0)for(let t=(this.length>>1)-1;t>=0;t--)this._down(t)}push(t){this.data.push(t),this._up(this.length++)}pop(){if(0===this.length)return;const t=this.data[0],e=this.data.pop();return this.length--,this.length>0&&(this.data[0]=e,this._down(0)),t}peek(){return this.data[0]}_up(t){const{data:e,compare:n}=this,s=e[t];for(;t>0;){const r=t-1>>1,o=e[r];if(n(s,o)>=0)break;e[t]=o,t=r}e[t]=s}_down(t){const{data:e,compare:n}=this,s=this.length>>1,r=e[t];for(;t<s;){let s=1+(t<<1);const o=s+1;if(o<this.length&&n(e[o],e[s])<0&&(s=o),n(e[s],r)>=0)break;e[t]=e[s],t=s}e[t]=r}}const M=Math.PI/180;function x(t,e,n,s){const r=s.minLng,o=s.maxLng,i=s.minLat,a=s.maxLat;if(t>=r&&t<=o)return e<i?S((e-i)*M):e>a?S((e-a)*M):0;const h=Math.min(S((t-r)*M),S((t-o)*M)),c=function(t,e){const n=1-2*e;return n<=0?t>0?90:-90:Math.atan(Math.tan(t*M)/n)/M}(e,h);return c>i&&c<a?I(h,n,e,c):Math.min(I(h,n,e,i),I(h,n,e,a))}function v(t,e){return t.dist-e.dist}function S(t){const e=Math.sin(t/2);return e*e}function I(t,e,n,s){return e*Math.cos(s*M)*t+S((n-s)*M)}function A(t,e,n,s,r){return I(S((t-n)*M),r,e,s)}class k{constructor(t){this.network=void 0,this.network=t}setNetwork(t){this.network=t}getNetwork(){return this.network}getNetworkInBoundingBox(t){return function(t,e){const[n,s,r,o]=e;if(n>=r||s>=o)throw new Error("Invalid bounding box: min values must be less than max values");const i=[];for(const e of t.features)u(e,n,s,r,o)&&i.push(e);return{type:"FeatureCollection",features:i}}(this.network,t)}getNetworkWithoutDuplicatesOrSubsections(){return function(t){const e=t.features,n=new Set;for(let t=0;t<e.length;t++){const s=e[t].geometry.coordinates;for(let r=0;r<e.length;r++){if(t===r)continue;const o=e[r].geometry.coordinates;if(h(s,o)&&(s.length<o.length||s.length===o.length&&t>r)){n.add(t);break}}}return{type:"FeatureCollection",features:e.filter((t,e)=>!n.has(e))}}(this.network)}getConnectedComponents(){return function(t){const e=t.features,n=new Map,s=new Map;function r(t){return`${t[0]},${t[1]}`}for(let t=0;t<e.length;t++){const n=e[t].geometry.coordinates;for(const e of n){const n=r(e);s.has(n)||s.set(n,new Set),s.get(n).add(t)}}for(let t=0;t<e.length;t++){n.set(t,new Set);const o=e[t].geometry.coordinates;for(const e of o){const o=r(e),i=s.get(o);if(i)for(const e of i)e!==t&&n.get(t).add(e)}}const o=new Set,i=[];function a(t,s){const r=[t];for(;r.length>0;){const t=r.pop();if(o.has(t))continue;o.add(t),s.push(e[t]);const i=n.get(t);if(i)for(const t of i)o.has(t)||r.push(t)}}for(let t=0;t<e.length;t++)if(!o.has(t)){const e=[];a(t,e),i.push({type:"FeatureCollection",features:e})}return i.sort((t,e)=>t.features.length-e.features.length),i}(this.network)}getConnectedComponentCount(){return function(t){const e=t.features,s=e.length,r=new Map;for(let t=0;t<s;t++){const n=e[t].geometry.coordinates;for(const e of n){const n=`${(o=e)[0]},${o[1]}`;r.has(n)||r.set(n,[]),r.get(n).push(t)}}var o;const i=Array.from({length:s},()=>[]);for(const t of r.values())for(let e=0;e<t.length;e++)for(let n=e+1;n<t.length;n++){const s=t[e],r=t[n];i[s].push(r),i[r].push(s)}const a=new Array(s).fill(!1);let h=0;for(let t=0;t<s;t++)a[t]||(n(t,i,a),h++);return h}(this.network)}getNodeAndEdgeCount(){return function(t){const e=new Set,n=new Set;for(const r of t.features){const t=r.geometry.coordinates;for(const n of t)e.add(JSON.stringify(n));for(let e=0;e<t.length-1;e++){const r=s(t[e],t[e+1]);n.add(r)}}return{nodeCount:e.size,edgeCount:n.size}}(this.network)}getNodes(){return{type:"FeatureCollection",features:function(t){const e=new Set,n=[];for(const s of t.features)for(const t of s.geometry.coordinates){const s=t.join(",");e.has(s)||(e.add(s),n.push({type:"Feature",geometry:{type:"Point",coordinates:t},properties:{}}))}return n}(this.network)}}getNodeCount(){const{nodeCount:t}=this.getNodeAndEdgeCount();return t}getEdges(){return o(this.network)}getLongestEdgeLength(){const t=this.getLongestEdge();return t?a(t):-1}getShortestEdgeLength(){const t=this.getShortestEdge();return t?a(t):-1}getLongestEdge(){const t=this.getEdges().features;if(0===t.length)return null;const e=t.sort((t,e)=>a(t)-a(e));return e[e.length-1]}getShortestEdge(){const t=this.getEdges().features;return 0===t.length?null:t.sort((t,e)=>a(t)-a(e))[0]}getEdgeCount(){const{edgeCount:t}=this.getNodeAndEdgeCount();return t}getLeafEdges(){return c(this.network).leafEdges}getPrunedEdges(t){if(t&&t>0){let e=this.network;for(let n=0;n<t;n++)e=c(e).nonLeafEdges;return e}return c(this.network).nonLeafEdges}getUnifiedNetwork(e){return function(e,n){if(e.features.length<2)return e;const s=new Set,r=new Map,o=[],a=new Map;for(const t of e.features)for(const e of t.geometry.coordinates){const t=`${e[0]},${e[1]}`;a.has(t)||(a.set(t,o.length),o.push(e))}let h=new d(o.length);for(const t of o)h.add(t[0],t[1]);function c(e,r,i){let a=null,c=Infinity;const u=function(t,e,n,s=Infinity,r=Infinity){let o=1;const i=[];void 0===s&&(s=Infinity),void 0!==r&&(o=S(r/6371));const a=new w([],v);let h={left:0,right:t.ids.length-1,axis:0,dist:0,minLng:-180,minLat:-90,maxLng:180,maxLat:90};const c=Math.cos(n*M);for(;h;){const r=h.right,u=h.left;if(r-u<=t.nodeSize)for(let s=u;s<=r;s++){const r=t.ids[s],o=A(e,n,t.coords[2*s],t.coords[2*s+1],c);a.push({id:r,dist:o})}else{const s=u+r>>1,o=t.coords[2*s],i=t.coords[2*s+1],f=t.ids[s],l=A(e,n,o,i,c);a.push({id:f,dist:l});const d=(h.axis+1)%2,g={left:u,right:s-1,axis:d,minLng:h.minLng,minLat:h.minLat,maxLng:0===h.axis?o:h.maxLng,maxLat:1===h.axis?i:h.maxLat,dist:0},p={left:s+1,right:r,axis:d,minLng:0===h.axis?o:h.minLng,minLat:1===h.axis?i:h.minLat,maxLng:h.maxLng,maxLat:h.maxLat,dist:0};g.dist=x(e,n,c,g),p.dist=x(e,n,c,p),a.push(g),a.push(p)}for(;a.length&&null!=a.peek().id;){const t=a.pop();if(t.dist>o)return i;if(i.push(t.id),i.length===s)return i}h=a.pop()}return i}(h,e[0],e[1],Infinity,n/1e3);for(const h of u){const u=o[h],f=`${u[0]},${u[1]}`;if(!s.has(f))continue;if(r.includes(u))continue;if(i.has(f))continue;const l=1e3*t(u,e);l<=n&&l<c&&(a=u,c=l)}return null!==a?a:(s.add(`${e[0]},${e[1]}`),e)}h.finish();const u=e.features.map(t=>{const e=[],n=[],s=new Set;for(const o of t.geometry.coordinates){const t=`${o[0]},${o[1]}`;if(!r.has(t)){const n=c(o,e,s);s.has(`${n[0]},${n[1]}`)?r.set(t,o):r.set(t,n)}const i=r.get(t),a=`${i[0]},${i[1]}`;s.has(a)||(n.push(i),s.add(a)),e.push(o)}return i({},t,{geometry:i({},t.geometry,{coordinates:n})})});return i({},e,{features:u})}(this.network,e)}}class L{constructor(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}insert(t,e){let n=this.length;this.length=n+1;let s=t,r=e,o=this.insertCounter++;for(;n>0;){const t=n-1>>>2,e=this.keys[t],r=this.idxs[t];if(s>e||s===e&&o>r)break;this.keys[n]=e,this.values[n]=this.values[t],this.idxs[n]=r,n=t}this.keys[n]=s,this.values[n]=r,this.idxs[n]=o}extractMin(){const t=this.length;if(0===t)return null;const e=this.values[0],n=t-1;return this.length=n,n>0&&(this.keys[0]=this.keys[n],this.values[0]=this.values[n],this.idxs[0]=this.idxs[n],this.bubbleDown(0)),e}size(){return this.length}bubbleDown(t){const e=this.length,n=this.keys,s=this.values,r=this.idxs,o=n[t],i=s[t],a=r[t];for(;;){const i=1+(t<<2);if(i>=e)break;let h=i,c=n[i],u=r[i],f=s[i];const l=i+1;if(l<e){const t=n[l],e=r[l];(t<c||t===c&&e<u)&&(h=l,c=t,u=e,f=s[l])}const d=i+2;if(d<e){const t=n[d],e=r[d];(t<c||t===c&&e<u)&&(h=d,c=t,u=e,f=s[d])}const g=i+3;if(g<e){const t=n[g],e=r[g];(t<c||t===c&&e<u)&&(h=g,c=t,u=e,f=s[g])}if(!(c<o||c===o&&u<a))break;n[t]=c,s[t]=f,r[t]=u,t=h}n[t]=o,s[t]=i,r[t]=a}}class C{constructor(e){var n,s;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(n=null==e?void 0:e.distanceMeasurement)?n:t,this.heapConstructor=null!=(s=null==e?void 0:e.heap)?s:L}buildRouteGraph(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;const e=this.coordinateIndexMap,n=this.coordinates,s=this.distanceMeasurement,r=t.features,o=[];for(let t=0,s=r.length;t<s;t++){const s=r[t].geometry.coordinates;for(let t=0,r=s.length-1;t<r;t++){var i,a;const r=s[t],h=s[t+1],c=r[0],u=r[1],f=h[0],l=h[1];let d=e.get(c);void 0===d&&(d=new Map,e.set(c,d));let g=d.get(u);void 0===g&&(g=n.length,n.push(r),d.set(u,g));let p=e.get(f);void 0===p&&(p=new Map,e.set(f,p));let y=p.get(l);void 0===y&&(y=n.length,n.push(h),p.set(l,y)),o[g]=(null!=(i=o[g])?i:0)+1,o[y]=(null!=(a=o[y])?a:0)+1}}const h=this.coordinates.length;this.csrNodeCount=h;const c=new Int32Array(h+1);for(let t=0;t<h;t++){var u;const e=null!=(u=o[t])?u:0;c[t+1]=c[t]+e}const f=c[h],l=new Int32Array(f),d=new Float64Array(f),g=c.slice();for(let t=0,e=r.length;t<e;t++){const e=r[t].geometry.coordinates;for(let t=0,n=e.length-1;t<n;t++){const n=e[t],r=e[t+1],o=n[1],i=r[0],a=r[1],h=this.coordinateIndexMap.get(n[0]).get(o),c=this.coordinateIndexMap.get(i).get(a),u=s(n,r);let f=g[h]++;l[f]=c,d[f]=u,f=g[c]++,l[f]=h,d[f]=u}}this.csrOffsets=c,this.csrIndices=l,this.csrDistances=d,this.adjacencyList=new Array(h)}getRoute(t,e){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");const n=this.getOrCreateIndex(t.geometry.coordinates),s=this.getOrCreateIndex(e.geometry.coordinates);if(n===s)return null;const r=this.coordinates,o=this.adjacencyList,i=this.distanceMeasurement,a=r.length;this.ensureScratch(a);const h=this.gScoreScratch,c=this.cameFromScratch,u=this.visitedScratch,f=this.hScratch;h.fill(Number.POSITIVE_INFINITY,0,a),c.fill(-1,0,a),u.fill(0,0,a),f.fill(-1,0,a);const l=new this.heapConstructor,d=r[s];let g=f[n];for(g<0&&(g=i(r[n],d),f[n]=g),l.insert(g,n),h[n]=0;l.size()>0;){const t=l.extractMin();if(0===u[t]){if(t===s)break;if(u[t]=1,this.csrOffsets&&t<this.csrNodeCount){const e=this.csrOffsets,n=this.csrIndices,s=this.csrDistances,o=e[t+1];for(let a=e[t];a<o;a++){const e=n[a],o=h[t]+s[a];if(o<h[e]){h[e]=o,c[e]=t;let n=f[e];n<0&&(n=i(r[e],d),f[e]=n),l.insert(o+n,e)}}}else{const e=o[t];if(!e||0===e.length)continue;for(let n=0,s=e.length;n<s;n++){const s=e[n],o=s.node,a=h[t]+s.distance;if(a<h[o]){h[o]=a,c[o]=t;let e=f[o];e<0&&(e=i(r[o],d),f[o]=e),l.insert(a+e,o)}}}}}if(c[s]<0)return null;const p=[];let y=s;for(;y!==n;)p.push(r[y]),y=c[y];return p.push(r[n]),p.reverse(),{type:"Feature",geometry:{type:"LineString",coordinates:p},properties:{}}}getOrCreateIndex(t){const e=t[0],n=t[1];let s=this.coordinateIndexMap.get(e);void 0===s&&(s=new Map,this.coordinateIndexMap.set(e,s));let r=s.get(n);if(void 0===r&&(r=this.coordinates.length,this.coordinates.push(t),s.set(n,r),this.adjacencyList[r]=[],this.csrOffsets)){const t=this.csrNodeCount;if(r===t){const e=new Int32Array(t+2);e.set(this.csrOffsets,0),e[t+1]=e[t],this.csrOffsets=e,this.csrNodeCount=t+1}}return r}ensureScratch(t){if(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch)return;const e=0|t;this.gScoreScratch=new Float64Array(e),this.cameFromScratch=new Int32Array(e),this.visitedScratch=new Uint8Array(e),this.hScratch=new Float64Array(e),this.scratchCapacity=e}}export{k as LineStringGraph,C as TerraRoute,e as createCheapRuler,t as haversineDistance};
|
|
1
|
+
const t=(t,s)=>{const e=t=>t*Math.PI/180,i=e(t[1]),n=e(t[0]),r=e(s[1]),c=r-i,h=e(s[0])-n,o=Math.sin(c/2)*Math.sin(c/2)+Math.cos(i)*Math.cos(r)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(o),Math.sqrt(1-o))*6371e3/1e3};function s(t){const s=1/298.257223563,e=s*(2-s),i=Math.PI/180,n=Math.cos(t*i),r=1/(1-e*(1-n*n)),c=Math.sqrt(r),h=6378.137*i,o=h*c*n,a=h*c*r*(1-e);return function(t,s){let e=t[0]-s[0];for(;e<-180;)e+=360;for(;e>180;)e-=360;const i=e*o,n=(t[1]-s[1])*a;return Math.sqrt(i*i+n*n)}}class e{constructor(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}insert(t,s){let e=this.length;this.length=e+1;let i=t,n=s,r=this.insertCounter++;for(;e>0;){const t=e-1>>>2,s=this.keys[t],n=this.idxs[t];if(i>s||i===s&&r>n)break;this.keys[e]=s,this.values[e]=this.values[t],this.idxs[e]=n,e=t}this.keys[e]=i,this.values[e]=n,this.idxs[e]=r}extractMin(){const t=this.length;if(0===t)return null;const s=this.values[0],e=t-1;return this.length=e,e>0&&(this.keys[0]=this.keys[e],this.values[0]=this.values[e],this.idxs[0]=this.idxs[e],this.bubbleDown(0)),s}size(){return this.length}bubbleDown(t){const s=this.length,e=this.keys,i=this.values,n=this.idxs,r=e[t],c=i[t],h=n[t];for(;;){const c=1+(t<<2);if(c>=s)break;let o=c,a=e[c],l=n[c],u=i[c];const d=c+1;if(d<s){const t=e[d],s=n[d];(t<a||t===a&&s<l)&&(o=d,a=t,l=s,u=i[d])}const f=c+2;if(f<s){const t=e[f],s=n[f];(t<a||t===a&&s<l)&&(o=f,a=t,l=s,u=i[f])}const g=c+3;if(g<s){const t=e[g],s=n[g];(t<a||t===a&&s<l)&&(o=g,a=t,l=s,u=i[g])}if(!(a<r||a===r&&l<h))break;e[t]=a,i[t]=u,n[t]=l,t=o}e[t]=r,i[t]=c,n[t]=h}}class i{constructor(s){var i,n;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(i=null==s?void 0:s.distanceMeasurement)?i:t,this.heapConstructor=null!=(n=null==s?void 0:s.heap)?n:e}buildRouteGraph(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;const s=this.coordinateIndexMap,e=this.coordinates,i=this.distanceMeasurement,n=t.features,r=[];for(let t=0,i=n.length;t<i;t++){const i=n[t].geometry.coordinates;for(let t=0,n=i.length-1;t<n;t++){var c,h;const n=i[t],o=i[t+1],a=n[0],l=n[1],u=o[0],d=o[1];let f=s.get(a);void 0===f&&(f=new Map,s.set(a,f));let g=f.get(l);void 0===g&&(g=e.length,e.push(n),f.set(l,g));let M=s.get(u);void 0===M&&(M=new Map,s.set(u,M));let p=M.get(d);void 0===p&&(p=e.length,e.push(o),M.set(d,p)),r[g]=(null!=(c=r[g])?c:0)+1,r[p]=(null!=(h=r[p])?h:0)+1}}const o=this.coordinates.length;this.csrNodeCount=o;const a=new Int32Array(o+1);for(let t=0;t<o;t++){var l;const s=null!=(l=r[t])?l:0;a[t+1]=a[t]+s}const u=a[o],d=new Int32Array(u),f=new Float64Array(u),g=a.slice();for(let t=0,s=n.length;t<s;t++){const s=n[t].geometry.coordinates;for(let t=0,e=s.length-1;t<e;t++){const e=s[t],n=s[t+1],r=e[1],c=n[0],h=n[1],o=this.coordinateIndexMap.get(e[0]).get(r),a=this.coordinateIndexMap.get(c).get(h),l=i(e,n);let u=g[o]++;d[u]=a,f[u]=l,u=g[a]++,d[u]=o,f[u]=l}}this.csrOffsets=a,this.csrIndices=d,this.csrDistances=f,this.adjacencyList=new Array(o)}getRoute(t,s){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");const e=this.getOrCreateIndex(t.geometry.coordinates),i=this.getOrCreateIndex(s.geometry.coordinates);if(e===i)return null;const n=this.coordinates,r=this.adjacencyList,c=this.distanceMeasurement,h=n.length;this.ensureScratch(h);const o=this.gScoreScratch,a=this.cameFromScratch,l=this.visitedScratch,u=this.hScratch;o.fill(Number.POSITIVE_INFINITY,0,h),a.fill(-1,0,h),l.fill(0,0,h),u.fill(-1,0,h);const d=new this.heapConstructor,f=n[i];let g=u[e];for(g<0&&(g=c(n[e],f),u[e]=g),d.insert(g,e),o[e]=0;d.size()>0;){const t=d.extractMin();if(0===l[t]){if(t===i)break;if(l[t]=1,this.csrOffsets&&t<this.csrNodeCount){const s=this.csrOffsets,e=this.csrIndices,i=this.csrDistances,r=s[t+1];for(let h=s[t];h<r;h++){const s=e[h],r=o[t]+i[h];if(r<o[s]){o[s]=r,a[s]=t;let e=u[s];e<0&&(e=c(n[s],f),u[s]=e),d.insert(r+e,s)}}}else{const s=r[t];if(!s||0===s.length)continue;for(let e=0,i=s.length;e<i;e++){const i=s[e],r=i.node,h=o[t]+i.distance;if(h<o[r]){o[r]=h,a[r]=t;let s=u[r];s<0&&(s=c(n[r],f),u[r]=s),d.insert(h+s,r)}}}}}if(a[i]<0)return null;const M=[];let p=i;for(;p!==e;)M.push(n[p]),p=a[p];return M.push(n[e]),M.reverse(),{type:"Feature",geometry:{type:"LineString",coordinates:M},properties:{}}}getOrCreateIndex(t){const s=t[0],e=t[1];let i=this.coordinateIndexMap.get(s);void 0===i&&(i=new Map,this.coordinateIndexMap.set(s,i));let n=i.get(e);if(void 0===n&&(n=this.coordinates.length,this.coordinates.push(t),i.set(e,n),this.adjacencyList[n]=[],this.csrOffsets)){const t=this.csrNodeCount;if(n===t){const s=new Int32Array(t+2);s.set(this.csrOffsets,0),s[t+1]=s[t],this.csrOffsets=s,this.csrNodeCount=t+1}}return n}ensureScratch(t){if(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch)return;const s=0|t;this.gScoreScratch=new Float64Array(s),this.cameFromScratch=new Int32Array(s),this.visitedScratch=new Uint8Array(s),this.hScratch=new Float64Array(s),this.scratchCapacity=s}}export{i as TerraRoute,s as createCheapRuler,t as haversineDistance};
|
|
2
2
|
//# sourceMappingURL=terra-route.modern.js.map
|