terra-route 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Terra Route
2
2
 
3
- Terra Route aims to be a performant library for routing on GeoJSON LineStrings, where LineStrings share identical coordinates. Terra Routes main aim is performance.
3
+ Terra Route aims to be a fast library for routing on GeoJSON LineStrings networks, where LineStrings share identical coordinates. Terra Routes main aim is currently performance.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,8 +8,14 @@ Terra Route aims to be a performant library for routing on GeoJSON LineStrings,
8
8
  npm install terra-route
9
9
  ```
10
10
 
11
+ ## Docs
12
+
13
+ [API Docs can be found here](https://jameslmilner.github.io/terra-route/)
14
+
11
15
  ## Example
12
16
 
17
+ Here is a short example of how to use the TerraRoute class:
18
+
13
19
  ```typescript
14
20
  import { FeatureCollection, LineString, Point, Feature } from "geojson";
15
21
  import { TerraRoute } from "terra-route";
@@ -63,8 +69,11 @@ const endPoint: Feature<Point> = {
63
69
  properties: {},
64
70
  };
65
71
 
66
- // Initialize TerraRoute
67
- const router = new TerraRoute(network);
72
+ // Initialize TerraRoute instance
73
+ const router = new TerraRoute();
74
+
75
+ // We must build the route graph first before calling getRoute
76
+ router.buildRouteGraph(network);
68
77
 
69
78
  // Get shortest route
70
79
  const route = router.getRoute(startPoint, endPoint);
@@ -72,6 +81,20 @@ const route = router.getRoute(startPoint, endPoint);
72
81
  console.log("Shortest route:", JSON.stringify(route, null, 2));
73
82
  ```
74
83
 
84
+ ## Benchmarks
85
+
86
+ You can run the benchmarks yourself using:
87
+
88
+ ```
89
+ npm run benchmark
90
+ ```
91
+
92
+ Using default Haversine distance, Terra Route is approximately 1.6x faster than GeoJSON Path Finder with Haversine distance. If you pass in the CheapRuler distance metric (you can use the exposed `createCheapRuler` function), it is about 3x faster.
93
+
94
+ ## Limitations
95
+
96
+ Terra Route does not currently support weighting functions.
97
+
75
98
  ## Acknowledgements
76
99
 
77
100
  Terra Route is inspired by the the prior art of [geojson-path-finder](https://github.com/perliedman/geojson-path-finder/) and we use this library to help benchmark Terra Routes performance.
@@ -1,2 +1,2 @@
1
- function t(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=Array(e);n<e;n++)r[n]=t[n];return r}function e(e,n){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(r)return(r=r.call(e)).next.bind(r);if(Array.isArray(e)||(r=function(e,n){if(e){if("string"==typeof e)return t(e,n);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?t(e,n):void 0}}(e))||n&&e&&"number"==typeof e.length){r&&(e=r);var i=0;return function(){return i>=e.length?{done:!0}:{done:!1,value:e[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.")}var n=/*#__PURE__*/function(){function t(){this.heap=[],this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var n={key:t,value:e,index:this.insertCounter++},r=this.heap.length;for(this.heap.push(n);r>0;){var i=r-1>>>1,a=this.heap[i];if(n.key>a.key||n.key===a.key&&n.index>a.index)break;this.heap[r]=a,r=i}this.heap[r]=n},e.extractMin=function(){var t=this.heap.length;if(0===t)return null;var e=this.heap[0],n=this.heap.pop();return t>1&&(this.heap[0]=n,this.bubbleDown(0)),e.value},e.size=function(){return this.heap.length},e.bubbleDown=function(t){for(var e=this.heap.length,n=this.heap[t];;){var r=1+(t<<1),i=r+1,a=t;if(r<e&&(this.heap[r].key<this.heap[a].key||this.heap[r].key===this.heap[a].key&&this.heap[r].index<this.heap[a].index)&&(a=r),i<e&&(this.heap[i].key<this.heap[a].key||this.heap[i].key===this.heap[a].key&&this.heap[i].index<this.heap[a].index)&&(a=i),a===t)break;this.heap[t]=this.heap[a],this.heap[a]=n,t=a}},t}(),r=function(t,e){var n=function(t){return t*Math.PI/180},r=n(t[1]),i=n(t[0]),a=n(e[1]),s=a-r,o=n(e[0])-i,h=Math.sin(s/2)*Math.sin(s/2)+Math.cos(r)*Math.cos(a)*Math.sin(o/2)*Math.sin(o/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};exports.TerraRoute=/*#__PURE__*/function(){function t(t,e){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=void 0,this.coords=void 0,this.coordMap=void 0,this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.distanceMeasurement=e||r,this.buildNetworkGraph()}var i=t.prototype;return i.coordinateIndex=function(t){var e=t[0],n=t[1];this.coordMap.has(e)||this.coordMap.set(e,new Map);var r=this.coordMap.get(e);if(r.has(n))return r.get(n);var i=this.coords.length;return this.coords.push(t),r.set(n,i),i},i.buildNetworkGraph=function(){for(var t,n=e(this.network.features);!(t=n()).done;)for(var r=t.value.geometry.coordinates,i=0;i<r.length-1;i++){var a=this.coordinateIndex(r[i]),s=this.coordinateIndex(r[i+1]),o=this.distanceMeasurement(r[i],r[i+1]);this.adjacencyList.has(a)||this.adjacencyList.set(a,[]),this.adjacencyList.has(s)||this.adjacencyList.set(s,[]),this.adjacencyList.get(a).push({node:s,distance:o}),this.adjacencyList.get(s).push({node:a,distance:o})}},i.getRoute=function(t,r){var i=this.coordinateIndex(t.geometry.coordinates),a=this.coordinateIndex(r.geometry.coordinates),s=new n;s.insert(0,i);for(var o=new Map,h=new Map([[i,0]]);s.size()>0;){var c=s.extractMin();if(c===a){for(var d=[],u=c;void 0!==u;)d.unshift(this.coords[u]),u=o.get(u);return{type:"Feature",geometry:{type:"LineString",coordinates:d},properties:{}}}for(var p,f=e(this.adjacencyList.get(c)||[]);!(p=f()).done;){var v,y,l=p.value,M=(null!=(v=h.get(c))?v:Infinity)+l.distance;if(M<(null!=(y=h.get(l.node))?y:Infinity)){o.set(l.node,c),h.set(l.node,M);var g=M+this.distanceMeasurement(this.coords[l.node],this.coords[a]);s.insert(g,l.node)}}}return null},t}(),exports.createCheapRuler=function(t){var e=1/298.257223563,n=e*(2-e),r=Math.PI/180,i=Math.cos(t*r),a=1/(1-n*(1-i*i)),s=Math.sqrt(a),o=6378.137*r,h=o*s*i,c=o*s*a*(1-n);return function(t,e){for(var n=t[0]-e[0];n<-180;)n+=360;for(;n>180;)n-=360;var r=n*h,i=(t[1]-e[1])*c;return Math.sqrt(r*r+i*i)}},exports.haversineDistance=r;
1
+ function t(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 e(e,r){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,r){if(e){if("string"==typeof e)return t(e,r);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?t(e,r):void 0}}(e))||r&&e&&"number"==typeof e.length){n&&(e=n);var a=0;return function(){return a>=e.length?{done:!0}:{done:!1,value:e[a++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r=/*#__PURE__*/function(){function t(){this.heap=[],this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var r={key:t,value:e,index:this.insertCounter++},n=this.heap.length;for(this.heap.push(r);n>0;){var a=n-1>>>1,i=this.heap[a];if(r.key>i.key||r.key===i.key&&r.index>i.index)break;this.heap[n]=i,n=a}this.heap[n]=r},e.extractMin=function(){var t=this.heap.length;if(0===t)return null;var e=this.heap[0],r=this.heap.pop();return t>1&&(this.heap[0]=r,this.bubbleDown(0)),e.value},e.size=function(){return this.heap.length},e.bubbleDown=function(t){for(var e=this.heap,r=e.length,n=e[t],a=n.key,i=n.index;;){var o=1+(t<<1);if(o>=r)break;var s=o,h=e[o].key,c=e[o].index,u=o+1;if(u<r){var d=e[u].key,f=e[u].index;(d<h||d===h&&f<c)&&(s=u,h=d,c=f)}if(!(h<a||h===a&&c<i))break;e[t]=e[s],t=s}e[t]=n},t}(),n=function(t,e){var r=function(t){return t*Math.PI/180},n=r(t[1]),a=r(t[0]),i=r(e[1]),o=i-n,s=r(e[0])-a,h=Math.sin(o/2)*Math.sin(o/2)+Math.cos(n)*Math.cos(i)*Math.sin(s/2)*Math.sin(s/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};exports.TerraRoute=/*#__PURE__*/function(){function t(t){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.distanceMeasurement=t||n}var a=t.prototype;return a.coordinateIndex=function(t){var e=t[0],r=t[1];this.coordMap.has(e)||this.coordMap.set(e,new Map);var n=this.coordMap.get(e);if(n.has(r))return n.get(r);var a=this.coords.length;return this.coords.push(t),n.set(r,a),a},a.buildRouteGraph=function(t){this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map;for(var r,n=e(this.network.features);!(r=n()).done;)for(var a=r.value.geometry.coordinates,i=0;i<a.length-1;i++){var o=this.coordinateIndex(a[i]),s=this.coordinateIndex(a[i+1]),h=this.distanceMeasurement(a[i],a[i+1]);this.adjacencyList.has(o)||this.adjacencyList.set(o,[]),this.adjacencyList.has(s)||this.adjacencyList.set(s,[]),this.adjacencyList.get(o).push({node:s,distance:h}),this.adjacencyList.get(s).push({node:o,distance:h})}},a.getRoute=function(t,n){if(!this.network)throw new Error("Network not built. Please call buildNetworkGraph(network) first.");var a=this.coordinateIndex(t.geometry.coordinates),i=this.coordinateIndex(n.geometry.coordinates);if(a===i)return null;var o=new r;o.insert(0,a);for(var s=new Map,h=new Map([[a,0]]);o.size()>0;){var c=o.extractMin();if(c===i){for(var u=[],d=c;void 0!==d;)u.unshift(this.coords[d]),d=s.get(d);return{type:"Feature",geometry:{type:"LineString",coordinates:u},properties:{}}}for(var f,p=e(this.adjacencyList.get(c)||[]);!(f=p()).done;){var l,v,y=f.value,M=(null!=(l=h.get(c))?l:Infinity)+y.distance;if(M<(null!=(v=h.get(y.node))?v:Infinity)){s.set(y.node,c),h.set(y.node,M);var g=M+this.distanceMeasurement(this.coords[y.node],this.coords[i]);o.insert(g,y.node)}}}return null},t}(),exports.createCheapRuler=function(t){var e=1/298.257223563,r=e*(2-e),n=Math.PI/180,a=Math.cos(t*n),i=1/(1-r*(1-a*a)),o=Math.sqrt(i),s=6378.137*n,h=s*o*a,c=s*o*i*(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,a=(t[1]-e[1])*c;return Math.sqrt(n*n+a*a)}},exports.haversineDistance=n;
2
2
  //# sourceMappingURL=terra-route.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.cjs","sources":["../src/min-heap.ts","../src/distance/haversine.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["export class MinHeap {\n private heap: Array<{ key: number; value: number; index: number }> = [];\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n const node = { key, value, index: this.insertCounter++ };\n let idx = this.heap.length;\n this.heap.push(node);\n\n // Optimized Bubble Up\n while (idx > 0) {\n const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)\n const parent = this.heap[parentIdx];\n if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;\n this.heap[idx] = parent;\n idx = parentIdx;\n }\n this.heap[idx] = node;\n }\n\n extractMin(): number | null {\n const length = this.heap.length;\n if (length === 0) return null;\n\n const minNode = this.heap[0];\n const endNode = this.heap.pop()!;\n\n if (length > 1) {\n this.heap[0] = endNode;\n this.bubbleDown(0);\n }\n\n return minNode.value;\n }\n\n size(): number {\n return this.heap.length;\n }\n\n private bubbleDown(idx: number): void {\n const length = this.heap.length;\n const node = this.heap[idx];\n\n while (true) {\n const leftIdx = (idx << 1) + 1;\n const rightIdx = leftIdx + 1;\n let smallestIdx = idx;\n\n if (\n leftIdx < length &&\n (this.heap[leftIdx].key < this.heap[smallestIdx].key ||\n (this.heap[leftIdx].key === this.heap[smallestIdx].key &&\n this.heap[leftIdx].index < this.heap[smallestIdx].index))\n ) {\n smallestIdx = leftIdx;\n }\n\n if (\n rightIdx < length &&\n (this.heap[rightIdx].key < this.heap[smallestIdx].key ||\n (this.heap[rightIdx].key === this.heap[smallestIdx].key &&\n this.heap[rightIdx].index < this.heap[smallestIdx].index))\n ) {\n smallestIdx = rightIdx;\n }\n\n if (smallestIdx === idx) break;\n\n this.heap[idx] = this.heap[smallestIdx];\n this.heap[smallestIdx] = node;\n\n idx = smallestIdx;\n }\n }\n}\n","import { Position } from \"geojson\";\n\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 return distance / 1000;\n}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { MinHeap } from \"./min-heap\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\n\n/**\n * TerraRoute is a routing utility for finding the shortest path\n * between two geographic points over a given GeoJSON LineString network.\n *\n * The class builds an internal graph structure based on the provided network,\n * then applies A* algorithm to compute the shortest route.\n */\nclass TerraRoute {\n private network: FeatureCollection<LineString>;\n private distanceMeasurement: (positionA: Position, positionB: Position) => number;\n private adjacencyList: Map<number, Array<{ node: number; distance: number }>>;\n private coords: Position[];\n private coordMap: Map<number, Map<number, number>>;\n\n /**\n * Creates a new instance of TerraRoute.\n * \n * @param network - A GeoJSON FeatureCollection of LineStrings representing the route network.\n * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).\n */\n constructor(\n network: FeatureCollection<LineString>,\n distanceMeasurement?: (positionA: Position, positionB: Position) => number\n ) {\n this.network = network;\n this.adjacencyList = new Map();\n this.coords = [];\n this.coordMap = new Map();\n this.distanceMeasurement = distanceMeasurement ? distanceMeasurement : haversineDistance;\n\n // \n this.buildNetworkGraph();\n }\n\n /**\n * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.\n * Otherwise, assigns a new index and stores the coordinate.\n * \n * @param coord - A GeoJSON Position array representing [longitude, latitude].\n * @returns A unique numeric index for the coordinate.\n */\n private coordinateIndex(coord: Position): number {\n const [lng, lat] = coord;\n if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());\n\n const latMap = this.coordMap.get(lng)!;\n if (latMap.has(lat)) {\n return latMap.get(lat)!;\n }\n\n const idx = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, idx);\n\n return idx;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into bidirectional graph edges with associated distances.\n * Assumes that the network is a connected graph of LineStrings with shared coordinates.\n */\n private buildNetworkGraph(): void {\n for (const feature of this.network.features) {\n const coords = feature.geometry.coordinates;\n for (let i = 0; i < coords.length - 1; i++) {\n const aIdx = this.coordinateIndex(coords[i]);\n const bIdx = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIdx)) this.adjacencyList.set(aIdx, []);\n if (!this.adjacencyList.has(bIdx)) this.adjacencyList.set(bIdx, []);\n\n this.adjacencyList.get(aIdx)!.push({ node: bIdx, distance });\n this.adjacencyList.get(bIdx)!.push({ node: aIdx, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using 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 public getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null {\n const startIdx = this.coordinateIndex(start.geometry.coordinates);\n const endIdx = this.coordinateIndex(end.geometry.coordinates);\n\n const openSet = new MinHeap();\n openSet.insert(0, startIdx);\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIdx, 0]]);\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n\n if (current === endIdx) {\n const path: Position[] = [];\n let currNode: number | undefined = current;\n while (currNode !== undefined) {\n path.unshift(this.coords[currNode]);\n currNode = cameFrom.get(currNode);\n }\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n for (const neighbor of this.adjacencyList.get(current) || []) {\n const tentativeGScore = (gScore.get(current) ?? Infinity) + neighbor.distance;\n if (tentativeGScore < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeGScore);\n const fScore = tentativeGScore + this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIdx]);\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n return null;\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":["MinHeap","this","heap","insertCounter","_proto","prototype","insert","key","value","node","index","idx","length","push","parentIdx","parent","extractMin","minNode","endNode","pop","bubbleDown","size","leftIdx","rightIdx","smallestIdx","haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","TerraRoute","network","distanceMeasurement","adjacencyList","coords","coordMap","Map","buildNetworkGraph","coordinateIndex","coord","lng","lat","has","set","latMap","get","_step","_iterator","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIdx","bIdx","distance","getRoute","start","end","startIdx","endIdx","openSet","cameFrom","gScore","current","path","currNode","undefined","unshift","type","properties","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeGScore","Infinity","fScore","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.cjs","sources":["../src/min-heap.ts","../src/distance/haversine.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["export class MinHeap {\n private heap: Array<{ key: number; value: number; index: number }> = [];\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n const node = { key, value, index: this.insertCounter++ };\n let idx = this.heap.length;\n this.heap.push(node);\n\n // Optimized Bubble Up\n while (idx > 0) {\n const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)\n const parent = this.heap[parentIdx];\n if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;\n this.heap[idx] = parent;\n idx = parentIdx;\n }\n this.heap[idx] = node;\n }\n\n extractMin(): number | null {\n const length = this.heap.length;\n if (length === 0) return null;\n\n const minNode = this.heap[0];\n const endNode = this.heap.pop()!;\n\n if (length > 1) {\n this.heap[0] = endNode;\n this.bubbleDown(0);\n }\n\n return minNode.value;\n }\n\n size(): number {\n return this.heap.length;\n }\n\n private bubbleDown(idx: number): void {\n const { heap } = this;\n const length = heap.length;\n // Grab the parent node once, then move it down only if needed\n const node = heap[idx];\n const nodeKey = node.key;\n const nodeIndex = node.index;\n\n while (true) {\n // Calculate left and right child indexes\n const leftIdx = (idx << 1) + 1;\n if (leftIdx >= length) {\n // No children => we’re already in place\n break;\n }\n\n // Assume left child is the smaller one by default\n let smallestIdx = leftIdx;\n let smallestKey = heap[leftIdx].key;\n let smallestIndex = heap[leftIdx].index;\n\n const rightIdx = leftIdx + 1;\n if (rightIdx < length) {\n // Compare left child vs. right child\n const rightKey = heap[rightIdx].key;\n const rightIndex = heap[rightIdx].index;\n if (rightKey < smallestKey || (rightKey === smallestKey && rightIndex < smallestIndex)) {\n smallestIdx = rightIdx;\n smallestKey = rightKey;\n smallestIndex = rightIndex;\n }\n }\n\n // Compare the smaller child with the parent\n if (smallestKey < nodeKey || (smallestKey === nodeKey && smallestIndex < nodeIndex)) {\n // Swap the smaller child up\n heap[idx] = heap[smallestIdx];\n idx = smallestIdx;\n } else {\n // We’re in the correct position now, so stop\n break;\n }\n }\n\n // Place the original node in its final position\n heap[idx] = node;\n }\n}\n","import { Position } from \"geojson\";\n\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 return distance / 1000;\n}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { MinHeap } from \"./min-heap\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\n\n/**\n * TerraRoute is a routing utility for finding the shortest path\n * between two geographic points over a given GeoJSON LineString network.\n *\n * The class builds an internal graph structure based on the provided network,\n * then applies A* algorithm to compute the shortest route.\n */\nclass TerraRoute {\n private network: FeatureCollection<LineString> | undefined;\n private distanceMeasurement: (positionA: Position, positionB: Position) => number;\n private adjacencyList: Map<number, Array<{ node: number; distance: number }>> = new Map();\n private coords: Position[] = []\n private coordMap: Map<number, Map<number, number>> = new Map();\n\n /**\n * Creates a new instance of TerraRoute.\n * \n * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).\n */\n constructor(\n distanceMeasurement?: (positionA: Position, positionB: Position) => number\n ) {\n this.distanceMeasurement = distanceMeasurement ? distanceMeasurement : haversineDistance;\n }\n\n /**\n * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.\n * Otherwise, assigns a new index and stores the coordinate.\n * \n * @param coord - A GeoJSON Position array representing [longitude, latitude].\n * @returns A unique numeric index for the coordinate.\n */\n private coordinateIndex(coord: Position): number {\n const [lng, lat] = coord;\n if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());\n\n const latMap = this.coordMap.get(lng)!;\n if (latMap.has(lat)) {\n return latMap.get(lat)!;\n }\n\n const idx = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, idx);\n\n return idx;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into bidirectional graph edges with associated distances.\n * Assumes that the network is a connected graph of LineStrings with shared coordinates. Calling this \n * method with a new network overwrite any existing network and reset all internal data structures.\n * \n * @param network - A GeoJSON FeatureCollection of LineStrings representing the road network.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network;\n this.adjacencyList = new Map();\n this.coords = [];\n this.coordMap = new Map();\n\n for (const feature of this.network.features) {\n const coords = feature.geometry.coordinates;\n for (let i = 0; i < coords.length - 1; i++) {\n const aIdx = this.coordinateIndex(coords[i]);\n const bIdx = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIdx)) this.adjacencyList.set(aIdx, []);\n if (!this.adjacencyList.has(bIdx)) this.adjacencyList.set(bIdx, []);\n\n this.adjacencyList.get(aIdx)!.push({ node: bIdx, distance });\n this.adjacencyList.get(bIdx)!.push({ node: aIdx, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using 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(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildNetworkGraph(network) first.\");\n }\n\n const startIdx = this.coordinateIndex(start.geometry.coordinates);\n const endIdx = this.coordinateIndex(end.geometry.coordinates);\n\n if (startIdx === endIdx) {\n return null;\n }\n\n const openSet = new MinHeap();\n openSet.insert(0, startIdx);\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIdx, 0]]);\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n\n if (current === endIdx) {\n const path: Position[] = [];\n let currNode: number | undefined = current;\n while (currNode !== undefined) {\n path.unshift(this.coords[currNode]);\n currNode = cameFrom.get(currNode);\n }\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n const neighbors = this.adjacencyList.get(current) || [];\n\n for (const neighbor of neighbors) {\n const tentativeGScore = (gScore.get(current) ?? Infinity) + neighbor.distance;\n if (tentativeGScore < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeGScore);\n const fScore = tentativeGScore + this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIdx]);\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n return null;\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":["MinHeap","this","heap","insertCounter","_proto","prototype","insert","key","value","node","index","idx","length","push","parentIdx","parent","extractMin","minNode","endNode","pop","bubbleDown","size","nodeKey","nodeIndex","leftIdx","smallestIdx","smallestKey","smallestIndex","rightIdx","rightKey","rightIndex","haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","TerraRoute","distanceMeasurement","network","adjacencyList","Map","coords","coordMap","coordinateIndex","coord","lng","lat","has","set","latMap","get","buildRouteGraph","_iterator","_step","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIdx","bIdx","distance","getRoute","start","end","Error","startIdx","endIdx","openSet","cameFrom","gScore","current","path","currNode","undefined","unshift","type","properties","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeGScore","Infinity","fScore","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy"],"mappings":"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"}
@@ -17,10 +17,9 @@ declare class TerraRoute {
17
17
  /**
18
18
  * Creates a new instance of TerraRoute.
19
19
  *
20
- * @param network - A GeoJSON FeatureCollection of LineStrings representing the route network.
21
20
  * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).
22
21
  */
23
- constructor(network: FeatureCollection<LineString>, distanceMeasurement?: (positionA: Position, positionB: Position) => number);
22
+ constructor(distanceMeasurement?: (positionA: Position, positionB: Position) => number);
24
23
  /**
25
24
  * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.
26
25
  * Otherwise, assigns a new index and stores the coordinate.
@@ -32,15 +31,20 @@ declare class TerraRoute {
32
31
  /**
33
32
  * Builds the internal graph representation (adjacency list) from the input network.
34
33
  * Each LineString segment is translated into bidirectional graph edges with associated distances.
35
- * Assumes that the network is a connected graph of LineStrings with shared coordinates.
34
+ * Assumes that the network is a connected graph of LineStrings with shared coordinates. Calling this
35
+ * method with a new network overwrite any existing network and reset all internal data structures.
36
+ *
37
+ * @param network - A GeoJSON FeatureCollection of LineStrings representing the road network.
36
38
  */
37
- private buildNetworkGraph;
39
+ buildRouteGraph(network: FeatureCollection<LineString>): void;
38
40
  /**
39
41
  * Computes the shortest route between two points in the network using A* algorithm.
40
42
  *
41
43
  * @param start - A GeoJSON Point Feature representing the start location.
42
44
  * @param end - A GeoJSON Point Feature representing the end location.
43
45
  * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.
46
+ *
47
+ * @throws Error if the network has not been built yet with buildRouteGraph(network).
44
48
  */
45
49
  getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;
46
50
  }
@@ -1,2 +1,2 @@
1
- class t{constructor(){this.heap=[],this.insertCounter=0}insert(t,e){const s={key:t,value:e,index:this.insertCounter++};let i=this.heap.length;for(this.heap.push(s);i>0;){const t=i-1>>>1,e=this.heap[t];if(s.key>e.key||s.key===e.key&&s.index>e.index)break;this.heap[i]=e,i=t}this.heap[i]=s}extractMin(){const t=this.heap.length;if(0===t)return null;const e=this.heap[0],s=this.heap.pop();return t>1&&(this.heap[0]=s,this.bubbleDown(0)),e.value}size(){return this.heap.length}bubbleDown(t){const e=this.heap.length,s=this.heap[t];for(;;){const i=1+(t<<1),n=i+1;let h=t;if(i<e&&(this.heap[i].key<this.heap[h].key||this.heap[i].key===this.heap[h].key&&this.heap[i].index<this.heap[h].index)&&(h=i),n<e&&(this.heap[n].key<this.heap[h].key||this.heap[n].key===this.heap[h].key&&this.heap[n].index<this.heap[h].index)&&(h=n),h===t)break;this.heap[t]=this.heap[h],this.heap[h]=s,t=h}}}const e=(t,e)=>{const s=t=>t*Math.PI/180,i=s(t[1]),n=s(t[0]),h=s(e[1]),o=h-i,a=s(e[0])-n,r=Math.sin(o/2)*Math.sin(o/2)+Math.cos(i)*Math.cos(h)*Math.sin(a/2)*Math.sin(a/2);return 2*Math.atan2(Math.sqrt(r),Math.sqrt(1-r))*6371e3/1e3};function s(t){const e=1/298.257223563,s=e*(2-e),i=Math.PI/180,n=Math.cos(t*i),h=1/(1-s*(1-n*n)),o=Math.sqrt(h),a=6378.137*i,r=a*o*n,c=a*o*h*(1-s);return function(t,e){let s=t[0]-e[0];for(;s<-180;)s+=360;for(;s>180;)s-=360;const i=s*r,n=(t[1]-e[1])*c;return Math.sqrt(i*i+n*n)}}class i{constructor(t,s){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=void 0,this.coords=void 0,this.coordMap=void 0,this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.distanceMeasurement=s||e,this.buildNetworkGraph()}coordinateIndex(t){const[e,s]=t;this.coordMap.has(e)||this.coordMap.set(e,new Map);const i=this.coordMap.get(e);if(i.has(s))return i.get(s);const n=this.coords.length;return this.coords.push(t),i.set(s,n),n}buildNetworkGraph(){for(const t of this.network.features){const e=t.geometry.coordinates;for(let t=0;t<e.length-1;t++){const s=this.coordinateIndex(e[t]),i=this.coordinateIndex(e[t+1]),n=this.distanceMeasurement(e[t],e[t+1]);this.adjacencyList.has(s)||this.adjacencyList.set(s,[]),this.adjacencyList.has(i)||this.adjacencyList.set(i,[]),this.adjacencyList.get(s).push({node:i,distance:n}),this.adjacencyList.get(i).push({node:s,distance:n})}}}getRoute(e,s){const i=this.coordinateIndex(e.geometry.coordinates),n=this.coordinateIndex(s.geometry.coordinates),h=new t;h.insert(0,i);const o=new Map,a=new Map([[i,0]]);for(;h.size()>0;){const t=h.extractMin();if(t===n){const e=[];let s=t;for(;void 0!==s;)e.unshift(this.coords[s]),s=o.get(s);return{type:"Feature",geometry:{type:"LineString",coordinates:e},properties:{}}}for(const e of this.adjacencyList.get(t)||[]){var r,c;const s=(null!=(r=a.get(t))?r:Infinity)+e.distance;if(s<(null!=(c=a.get(e.node))?c:Infinity)){o.set(e.node,t),a.set(e.node,s);const i=s+this.distanceMeasurement(this.coords[e.node],this.coords[n]);h.insert(i,e.node)}}}return null}}export{i as TerraRoute,s as createCheapRuler,e as haversineDistance};
1
+ class t{constructor(){this.heap=[],this.insertCounter=0}insert(t,e){const s={key:t,value:e,index:this.insertCounter++};let n=this.heap.length;for(this.heap.push(s);n>0;){const t=n-1>>>1,e=this.heap[t];if(s.key>e.key||s.key===e.key&&s.index>e.index)break;this.heap[n]=e,n=t}this.heap[n]=s}extractMin(){const t=this.heap.length;if(0===t)return null;const e=this.heap[0],s=this.heap.pop();return t>1&&(this.heap[0]=s,this.bubbleDown(0)),e.value}size(){return this.heap.length}bubbleDown(t){const{heap:e}=this,s=e.length,n=e[t],o=n.key,i=n.index;for(;;){const n=1+(t<<1);if(n>=s)break;let r=n,a=e[n].key,h=e[n].index;const c=n+1;if(c<s){const t=e[c].key,s=e[c].index;(t<a||t===a&&s<h)&&(r=c,a=t,h=s)}if(!(a<o||a===o&&h<i))break;e[t]=e[r],t=r}e[t]=n}}const e=(t,e)=>{const s=t=>t*Math.PI/180,n=s(t[1]),o=s(t[0]),i=s(e[1]),r=i-n,a=s(e[0])-o,h=Math.sin(r/2)*Math.sin(r/2)+Math.cos(n)*Math.cos(i)*Math.sin(a/2)*Math.sin(a/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};function s(t){const e=1/298.257223563,s=e*(2-e),n=Math.PI/180,o=Math.cos(t*n),i=1/(1-s*(1-o*o)),r=Math.sqrt(i),a=6378.137*n,h=a*r*o,c=a*r*i*(1-s);return function(t,e){let s=t[0]-e[0];for(;s<-180;)s+=360;for(;s>180;)s-=360;const n=s*h,o=(t[1]-e[1])*c;return Math.sqrt(n*n+o*o)}}class n{constructor(t){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.distanceMeasurement=t||e}coordinateIndex(t){const[e,s]=t;this.coordMap.has(e)||this.coordMap.set(e,new Map);const n=this.coordMap.get(e);if(n.has(s))return n.get(s);const o=this.coords.length;return this.coords.push(t),n.set(s,o),o}buildRouteGraph(t){this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map;for(const t of this.network.features){const e=t.geometry.coordinates;for(let t=0;t<e.length-1;t++){const s=this.coordinateIndex(e[t]),n=this.coordinateIndex(e[t+1]),o=this.distanceMeasurement(e[t],e[t+1]);this.adjacencyList.has(s)||this.adjacencyList.set(s,[]),this.adjacencyList.has(n)||this.adjacencyList.set(n,[]),this.adjacencyList.get(s).push({node:n,distance:o}),this.adjacencyList.get(n).push({node:s,distance:o})}}}getRoute(e,s){if(!this.network)throw new Error("Network not built. Please call buildNetworkGraph(network) first.");const n=this.coordinateIndex(e.geometry.coordinates),o=this.coordinateIndex(s.geometry.coordinates);if(n===o)return null;const i=new t;i.insert(0,n);const r=new Map,a=new Map([[n,0]]);for(;i.size()>0;){const t=i.extractMin();if(t===o){const e=[];let s=t;for(;void 0!==s;)e.unshift(this.coords[s]),s=r.get(s);return{type:"Feature",geometry:{type:"LineString",coordinates:e},properties:{}}}const e=this.adjacencyList.get(t)||[];for(const s of e){var h,c;const e=(null!=(h=a.get(t))?h:Infinity)+s.distance;if(e<(null!=(c=a.get(s.node))?c:Infinity)){r.set(s.node,t),a.set(s.node,e);const n=e+this.distanceMeasurement(this.coords[s.node],this.coords[o]);i.insert(n,s.node)}}}return null}}export{n as TerraRoute,s as createCheapRuler,e as haversineDistance};
2
2
  //# sourceMappingURL=terra-route.modern.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.modern.js","sources":["../src/min-heap.ts","../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/terra-route.ts"],"sourcesContent":["export class MinHeap {\n private heap: Array<{ key: number; value: number; index: number }> = [];\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n const node = { key, value, index: this.insertCounter++ };\n let idx = this.heap.length;\n this.heap.push(node);\n\n // Optimized Bubble Up\n while (idx > 0) {\n const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)\n const parent = this.heap[parentIdx];\n if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;\n this.heap[idx] = parent;\n idx = parentIdx;\n }\n this.heap[idx] = node;\n }\n\n extractMin(): number | null {\n const length = this.heap.length;\n if (length === 0) return null;\n\n const minNode = this.heap[0];\n const endNode = this.heap.pop()!;\n\n if (length > 1) {\n this.heap[0] = endNode;\n this.bubbleDown(0);\n }\n\n return minNode.value;\n }\n\n size(): number {\n return this.heap.length;\n }\n\n private bubbleDown(idx: number): void {\n const length = this.heap.length;\n const node = this.heap[idx];\n\n while (true) {\n const leftIdx = (idx << 1) + 1;\n const rightIdx = leftIdx + 1;\n let smallestIdx = idx;\n\n if (\n leftIdx < length &&\n (this.heap[leftIdx].key < this.heap[smallestIdx].key ||\n (this.heap[leftIdx].key === this.heap[smallestIdx].key &&\n this.heap[leftIdx].index < this.heap[smallestIdx].index))\n ) {\n smallestIdx = leftIdx;\n }\n\n if (\n rightIdx < length &&\n (this.heap[rightIdx].key < this.heap[smallestIdx].key ||\n (this.heap[rightIdx].key === this.heap[smallestIdx].key &&\n this.heap[rightIdx].index < this.heap[smallestIdx].index))\n ) {\n smallestIdx = rightIdx;\n }\n\n if (smallestIdx === idx) break;\n\n this.heap[idx] = this.heap[smallestIdx];\n this.heap[smallestIdx] = node;\n\n idx = smallestIdx;\n }\n }\n}\n","import { Position } from \"geojson\";\n\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 return distance / 1000;\n}","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}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { MinHeap } from \"./min-heap\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\n\n/**\n * TerraRoute is a routing utility for finding the shortest path\n * between two geographic points over a given GeoJSON LineString network.\n *\n * The class builds an internal graph structure based on the provided network,\n * then applies A* algorithm to compute the shortest route.\n */\nclass TerraRoute {\n private network: FeatureCollection<LineString>;\n private distanceMeasurement: (positionA: Position, positionB: Position) => number;\n private adjacencyList: Map<number, Array<{ node: number; distance: number }>>;\n private coords: Position[];\n private coordMap: Map<number, Map<number, number>>;\n\n /**\n * Creates a new instance of TerraRoute.\n * \n * @param network - A GeoJSON FeatureCollection of LineStrings representing the route network.\n * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).\n */\n constructor(\n network: FeatureCollection<LineString>,\n distanceMeasurement?: (positionA: Position, positionB: Position) => number\n ) {\n this.network = network;\n this.adjacencyList = new Map();\n this.coords = [];\n this.coordMap = new Map();\n this.distanceMeasurement = distanceMeasurement ? distanceMeasurement : haversineDistance;\n\n // \n this.buildNetworkGraph();\n }\n\n /**\n * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.\n * Otherwise, assigns a new index and stores the coordinate.\n * \n * @param coord - A GeoJSON Position array representing [longitude, latitude].\n * @returns A unique numeric index for the coordinate.\n */\n private coordinateIndex(coord: Position): number {\n const [lng, lat] = coord;\n if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());\n\n const latMap = this.coordMap.get(lng)!;\n if (latMap.has(lat)) {\n return latMap.get(lat)!;\n }\n\n const idx = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, idx);\n\n return idx;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into bidirectional graph edges with associated distances.\n * Assumes that the network is a connected graph of LineStrings with shared coordinates.\n */\n private buildNetworkGraph(): void {\n for (const feature of this.network.features) {\n const coords = feature.geometry.coordinates;\n for (let i = 0; i < coords.length - 1; i++) {\n const aIdx = this.coordinateIndex(coords[i]);\n const bIdx = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIdx)) this.adjacencyList.set(aIdx, []);\n if (!this.adjacencyList.has(bIdx)) this.adjacencyList.set(bIdx, []);\n\n this.adjacencyList.get(aIdx)!.push({ node: bIdx, distance });\n this.adjacencyList.get(bIdx)!.push({ node: aIdx, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using 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 public getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null {\n const startIdx = this.coordinateIndex(start.geometry.coordinates);\n const endIdx = this.coordinateIndex(end.geometry.coordinates);\n\n const openSet = new MinHeap();\n openSet.insert(0, startIdx);\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIdx, 0]]);\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n\n if (current === endIdx) {\n const path: Position[] = [];\n let currNode: number | undefined = current;\n while (currNode !== undefined) {\n path.unshift(this.coords[currNode]);\n currNode = cameFrom.get(currNode);\n }\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n for (const neighbor of this.adjacencyList.get(current) || []) {\n const tentativeGScore = (gScore.get(current) ?? Infinity) + neighbor.distance;\n if (tentativeGScore < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeGScore);\n const fScore = tentativeGScore + this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIdx]);\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n return null;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }"],"names":["MinHeap","constructor","heap","insertCounter","insert","key","value","node","index","this","idx","length","push","parentIdx","parent","extractMin","minNode","endNode","pop","bubbleDown","size","leftIdx","rightIdx","smallestIdx","haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","createCheapRuler","lat","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy","TerraRoute","network","distanceMeasurement","adjacencyList","coords","coordMap","Map","buildNetworkGraph","coordinateIndex","coord","lng","has","set","latMap","get","feature","features","geometry","coordinates","i","aIdx","bIdx","distance","getRoute","start","end","startIdx","endIdx","openSet","cameFrom","gScore","current","path","currNode","undefined","unshift","type","properties","neighbor","_gScore$get","_gScore$get2","tentativeGScore","Infinity","fScore"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.modern.js","sources":["../src/min-heap.ts","../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/terra-route.ts"],"sourcesContent":["export class MinHeap {\n private heap: Array<{ key: number; value: number; index: number }> = [];\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n const node = { key, value, index: this.insertCounter++ };\n let idx = this.heap.length;\n this.heap.push(node);\n\n // Optimized Bubble Up\n while (idx > 0) {\n const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)\n const parent = this.heap[parentIdx];\n if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;\n this.heap[idx] = parent;\n idx = parentIdx;\n }\n this.heap[idx] = node;\n }\n\n extractMin(): number | null {\n const length = this.heap.length;\n if (length === 0) return null;\n\n const minNode = this.heap[0];\n const endNode = this.heap.pop()!;\n\n if (length > 1) {\n this.heap[0] = endNode;\n this.bubbleDown(0);\n }\n\n return minNode.value;\n }\n\n size(): number {\n return this.heap.length;\n }\n\n private bubbleDown(idx: number): void {\n const { heap } = this;\n const length = heap.length;\n // Grab the parent node once, then move it down only if needed\n const node = heap[idx];\n const nodeKey = node.key;\n const nodeIndex = node.index;\n\n while (true) {\n // Calculate left and right child indexes\n const leftIdx = (idx << 1) + 1;\n if (leftIdx >= length) {\n // No children => we’re already in place\n break;\n }\n\n // Assume left child is the smaller one by default\n let smallestIdx = leftIdx;\n let smallestKey = heap[leftIdx].key;\n let smallestIndex = heap[leftIdx].index;\n\n const rightIdx = leftIdx + 1;\n if (rightIdx < length) {\n // Compare left child vs. right child\n const rightKey = heap[rightIdx].key;\n const rightIndex = heap[rightIdx].index;\n if (rightKey < smallestKey || (rightKey === smallestKey && rightIndex < smallestIndex)) {\n smallestIdx = rightIdx;\n smallestKey = rightKey;\n smallestIndex = rightIndex;\n }\n }\n\n // Compare the smaller child with the parent\n if (smallestKey < nodeKey || (smallestKey === nodeKey && smallestIndex < nodeIndex)) {\n // Swap the smaller child up\n heap[idx] = heap[smallestIdx];\n idx = smallestIdx;\n } else {\n // We’re in the correct position now, so stop\n break;\n }\n }\n\n // Place the original node in its final position\n heap[idx] = node;\n }\n}\n","import { Position } from \"geojson\";\n\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 return distance / 1000;\n}","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}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { MinHeap } from \"./min-heap\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\n\n/**\n * TerraRoute is a routing utility for finding the shortest path\n * between two geographic points over a given GeoJSON LineString network.\n *\n * The class builds an internal graph structure based on the provided network,\n * then applies A* algorithm to compute the shortest route.\n */\nclass TerraRoute {\n private network: FeatureCollection<LineString> | undefined;\n private distanceMeasurement: (positionA: Position, positionB: Position) => number;\n private adjacencyList: Map<number, Array<{ node: number; distance: number }>> = new Map();\n private coords: Position[] = []\n private coordMap: Map<number, Map<number, number>> = new Map();\n\n /**\n * Creates a new instance of TerraRoute.\n * \n * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).\n */\n constructor(\n distanceMeasurement?: (positionA: Position, positionB: Position) => number\n ) {\n this.distanceMeasurement = distanceMeasurement ? distanceMeasurement : haversineDistance;\n }\n\n /**\n * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.\n * Otherwise, assigns a new index and stores the coordinate.\n * \n * @param coord - A GeoJSON Position array representing [longitude, latitude].\n * @returns A unique numeric index for the coordinate.\n */\n private coordinateIndex(coord: Position): number {\n const [lng, lat] = coord;\n if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());\n\n const latMap = this.coordMap.get(lng)!;\n if (latMap.has(lat)) {\n return latMap.get(lat)!;\n }\n\n const idx = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, idx);\n\n return idx;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into bidirectional graph edges with associated distances.\n * Assumes that the network is a connected graph of LineStrings with shared coordinates. Calling this \n * method with a new network overwrite any existing network and reset all internal data structures.\n * \n * @param network - A GeoJSON FeatureCollection of LineStrings representing the road network.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network;\n this.adjacencyList = new Map();\n this.coords = [];\n this.coordMap = new Map();\n\n for (const feature of this.network.features) {\n const coords = feature.geometry.coordinates;\n for (let i = 0; i < coords.length - 1; i++) {\n const aIdx = this.coordinateIndex(coords[i]);\n const bIdx = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIdx)) this.adjacencyList.set(aIdx, []);\n if (!this.adjacencyList.has(bIdx)) this.adjacencyList.set(bIdx, []);\n\n this.adjacencyList.get(aIdx)!.push({ node: bIdx, distance });\n this.adjacencyList.get(bIdx)!.push({ node: aIdx, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using 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(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildNetworkGraph(network) first.\");\n }\n\n const startIdx = this.coordinateIndex(start.geometry.coordinates);\n const endIdx = this.coordinateIndex(end.geometry.coordinates);\n\n if (startIdx === endIdx) {\n return null;\n }\n\n const openSet = new MinHeap();\n openSet.insert(0, startIdx);\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIdx, 0]]);\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n\n if (current === endIdx) {\n const path: Position[] = [];\n let currNode: number | undefined = current;\n while (currNode !== undefined) {\n path.unshift(this.coords[currNode]);\n currNode = cameFrom.get(currNode);\n }\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n const neighbors = this.adjacencyList.get(current) || [];\n\n for (const neighbor of neighbors) {\n const tentativeGScore = (gScore.get(current) ?? Infinity) + neighbor.distance;\n if (tentativeGScore < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeGScore);\n const fScore = tentativeGScore + this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIdx]);\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n return null;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }"],"names":["MinHeap","constructor","heap","insertCounter","insert","key","value","node","index","this","idx","length","push","parentIdx","parent","extractMin","minNode","endNode","pop","bubbleDown","size","nodeKey","nodeIndex","leftIdx","smallestIdx","smallestKey","smallestIndex","rightIdx","rightKey","rightIndex","haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","createCheapRuler","lat","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy","TerraRoute","distanceMeasurement","network","adjacencyList","Map","coords","coordMap","coordinateIndex","coord","lng","has","set","latMap","get","buildRouteGraph","feature","features","geometry","coordinates","i","aIdx","bIdx","distance","getRoute","start","end","Error","startIdx","endIdx","openSet","cameFrom","gScore","current","path","currNode","undefined","unshift","type","properties","neighbors","neighbor","_gScore$get","_gScore$get2","tentativeGScore","Infinity","fScore"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- function t(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=Array(e);n<e;n++)r[n]=t[n];return r}function e(e,n){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(r)return(r=r.call(e)).next.bind(r);if(Array.isArray(e)||(r=function(e,n){if(e){if("string"==typeof e)return t(e,n);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?t(e,n):void 0}}(e))||n&&e&&"number"==typeof e.length){r&&(e=r);var i=0;return function(){return i>=e.length?{done:!0}:{done:!1,value:e[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.")}var n=/*#__PURE__*/function(){function t(){this.heap=[],this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var n={key:t,value:e,index:this.insertCounter++},r=this.heap.length;for(this.heap.push(n);r>0;){var i=r-1>>>1,a=this.heap[i];if(n.key>a.key||n.key===a.key&&n.index>a.index)break;this.heap[r]=a,r=i}this.heap[r]=n},e.extractMin=function(){var t=this.heap.length;if(0===t)return null;var e=this.heap[0],n=this.heap.pop();return t>1&&(this.heap[0]=n,this.bubbleDown(0)),e.value},e.size=function(){return this.heap.length},e.bubbleDown=function(t){for(var e=this.heap.length,n=this.heap[t];;){var r=1+(t<<1),i=r+1,a=t;if(r<e&&(this.heap[r].key<this.heap[a].key||this.heap[r].key===this.heap[a].key&&this.heap[r].index<this.heap[a].index)&&(a=r),i<e&&(this.heap[i].key<this.heap[a].key||this.heap[i].key===this.heap[a].key&&this.heap[i].index<this.heap[a].index)&&(a=i),a===t)break;this.heap[t]=this.heap[a],this.heap[a]=n,t=a}},t}(),r=function(t,e){var n=function(t){return t*Math.PI/180},r=n(t[1]),i=n(t[0]),a=n(e[1]),s=a-r,o=n(e[0])-i,h=Math.sin(s/2)*Math.sin(s/2)+Math.cos(r)*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 i(t){var e=1/298.257223563,n=e*(2-e),r=Math.PI/180,i=Math.cos(t*r),a=1/(1-n*(1-i*i)),s=Math.sqrt(a),o=6378.137*r,h=o*s*i,c=o*s*a*(1-n);return function(t,e){for(var n=t[0]-e[0];n<-180;)n+=360;for(;n>180;)n-=360;var r=n*h,i=(t[1]-e[1])*c;return Math.sqrt(r*r+i*i)}}var a=/*#__PURE__*/function(){function t(t,e){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=void 0,this.coords=void 0,this.coordMap=void 0,this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.distanceMeasurement=e||r,this.buildNetworkGraph()}var i=t.prototype;return i.coordinateIndex=function(t){var e=t[0],n=t[1];this.coordMap.has(e)||this.coordMap.set(e,new Map);var r=this.coordMap.get(e);if(r.has(n))return r.get(n);var i=this.coords.length;return this.coords.push(t),r.set(n,i),i},i.buildNetworkGraph=function(){for(var t,n=e(this.network.features);!(t=n()).done;)for(var r=t.value.geometry.coordinates,i=0;i<r.length-1;i++){var a=this.coordinateIndex(r[i]),s=this.coordinateIndex(r[i+1]),o=this.distanceMeasurement(r[i],r[i+1]);this.adjacencyList.has(a)||this.adjacencyList.set(a,[]),this.adjacencyList.has(s)||this.adjacencyList.set(s,[]),this.adjacencyList.get(a).push({node:s,distance:o}),this.adjacencyList.get(s).push({node:a,distance:o})}},i.getRoute=function(t,r){var i=this.coordinateIndex(t.geometry.coordinates),a=this.coordinateIndex(r.geometry.coordinates),s=new n;s.insert(0,i);for(var o=new Map,h=new Map([[i,0]]);s.size()>0;){var c=s.extractMin();if(c===a){for(var d=[],u=c;void 0!==u;)d.unshift(this.coords[u]),u=o.get(u);return{type:"Feature",geometry:{type:"LineString",coordinates:d},properties:{}}}for(var p,f=e(this.adjacencyList.get(c)||[]);!(p=f()).done;){var v,y,l=p.value,M=(null!=(v=h.get(c))?v:Infinity)+l.distance;if(M<(null!=(y=h.get(l.node))?y:Infinity)){o.set(l.node,c),h.set(l.node,M);var g=M+this.distanceMeasurement(this.coords[l.node],this.coords[a]);s.insert(g,l.node)}}}return null},t}();export{a as TerraRoute,i as createCheapRuler,r as haversineDistance};
1
+ function t(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 e(e,r){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,r){if(e){if("string"==typeof e)return t(e,r);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?t(e,r):void 0}}(e))||r&&e&&"number"==typeof e.length){n&&(e=n);var i=0;return function(){return i>=e.length?{done:!0}:{done:!1,value:e[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.")}var r=/*#__PURE__*/function(){function t(){this.heap=[],this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var r={key:t,value:e,index:this.insertCounter++},n=this.heap.length;for(this.heap.push(r);n>0;){var i=n-1>>>1,a=this.heap[i];if(r.key>a.key||r.key===a.key&&r.index>a.index)break;this.heap[n]=a,n=i}this.heap[n]=r},e.extractMin=function(){var t=this.heap.length;if(0===t)return null;var e=this.heap[0],r=this.heap.pop();return t>1&&(this.heap[0]=r,this.bubbleDown(0)),e.value},e.size=function(){return this.heap.length},e.bubbleDown=function(t){for(var e=this.heap,r=e.length,n=e[t],i=n.key,a=n.index;;){var o=1+(t<<1);if(o>=r)break;var s=o,h=e[o].key,c=e[o].index,u=o+1;if(u<r){var d=e[u].key,f=e[u].index;(d<h||d===h&&f<c)&&(s=u,h=d,c=f)}if(!(h<i||h===i&&c<a))break;e[t]=e[s],t=s}e[t]=n},t}(),n=function(t,e){var r=function(t){return t*Math.PI/180},n=r(t[1]),i=r(t[0]),a=r(e[1]),o=a-n,s=r(e[0])-i,h=Math.sin(o/2)*Math.sin(o/2)+Math.cos(n)*Math.cos(a)*Math.sin(s/2)*Math.sin(s/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};function i(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)),o=Math.sqrt(a),s=6378.137*n,h=s*o*i,c=s*o*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])*c;return Math.sqrt(n*n+i*i)}}var a=/*#__PURE__*/function(){function t(t){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.distanceMeasurement=t||n}var i=t.prototype;return i.coordinateIndex=function(t){var e=t[0],r=t[1];this.coordMap.has(e)||this.coordMap.set(e,new Map);var n=this.coordMap.get(e);if(n.has(r))return n.get(r);var i=this.coords.length;return this.coords.push(t),n.set(r,i),i},i.buildRouteGraph=function(t){this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map;for(var r,n=e(this.network.features);!(r=n()).done;)for(var i=r.value.geometry.coordinates,a=0;a<i.length-1;a++){var o=this.coordinateIndex(i[a]),s=this.coordinateIndex(i[a+1]),h=this.distanceMeasurement(i[a],i[a+1]);this.adjacencyList.has(o)||this.adjacencyList.set(o,[]),this.adjacencyList.has(s)||this.adjacencyList.set(s,[]),this.adjacencyList.get(o).push({node:s,distance:h}),this.adjacencyList.get(s).push({node:o,distance:h})}},i.getRoute=function(t,n){if(!this.network)throw new Error("Network not built. Please call buildNetworkGraph(network) first.");var i=this.coordinateIndex(t.geometry.coordinates),a=this.coordinateIndex(n.geometry.coordinates);if(i===a)return null;var o=new r;o.insert(0,i);for(var s=new Map,h=new Map([[i,0]]);o.size()>0;){var c=o.extractMin();if(c===a){for(var u=[],d=c;void 0!==d;)u.unshift(this.coords[d]),d=s.get(d);return{type:"Feature",geometry:{type:"LineString",coordinates:u},properties:{}}}for(var f,p=e(this.adjacencyList.get(c)||[]);!(f=p()).done;){var l,v,y=f.value,M=(null!=(l=h.get(c))?l:Infinity)+y.distance;if(M<(null!=(v=h.get(y.node))?v:Infinity)){s.set(y.node,c),h.set(y.node,M);var g=M+this.distanceMeasurement(this.coords[y.node],this.coords[a]);o.insert(g,y.node)}}}return null},t}();export{a as TerraRoute,i as createCheapRuler,n as haversineDistance};
2
2
  //# sourceMappingURL=terra-route.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.module.js","sources":["../src/min-heap.ts","../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/terra-route.ts"],"sourcesContent":["export class MinHeap {\n private heap: Array<{ key: number; value: number; index: number }> = [];\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n const node = { key, value, index: this.insertCounter++ };\n let idx = this.heap.length;\n this.heap.push(node);\n\n // Optimized Bubble Up\n while (idx > 0) {\n const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)\n const parent = this.heap[parentIdx];\n if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;\n this.heap[idx] = parent;\n idx = parentIdx;\n }\n this.heap[idx] = node;\n }\n\n extractMin(): number | null {\n const length = this.heap.length;\n if (length === 0) return null;\n\n const minNode = this.heap[0];\n const endNode = this.heap.pop()!;\n\n if (length > 1) {\n this.heap[0] = endNode;\n this.bubbleDown(0);\n }\n\n return minNode.value;\n }\n\n size(): number {\n return this.heap.length;\n }\n\n private bubbleDown(idx: number): void {\n const length = this.heap.length;\n const node = this.heap[idx];\n\n while (true) {\n const leftIdx = (idx << 1) + 1;\n const rightIdx = leftIdx + 1;\n let smallestIdx = idx;\n\n if (\n leftIdx < length &&\n (this.heap[leftIdx].key < this.heap[smallestIdx].key ||\n (this.heap[leftIdx].key === this.heap[smallestIdx].key &&\n this.heap[leftIdx].index < this.heap[smallestIdx].index))\n ) {\n smallestIdx = leftIdx;\n }\n\n if (\n rightIdx < length &&\n (this.heap[rightIdx].key < this.heap[smallestIdx].key ||\n (this.heap[rightIdx].key === this.heap[smallestIdx].key &&\n this.heap[rightIdx].index < this.heap[smallestIdx].index))\n ) {\n smallestIdx = rightIdx;\n }\n\n if (smallestIdx === idx) break;\n\n this.heap[idx] = this.heap[smallestIdx];\n this.heap[smallestIdx] = node;\n\n idx = smallestIdx;\n }\n }\n}\n","import { Position } from \"geojson\";\n\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 return distance / 1000;\n}","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}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { MinHeap } from \"./min-heap\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\n\n/**\n * TerraRoute is a routing utility for finding the shortest path\n * between two geographic points over a given GeoJSON LineString network.\n *\n * The class builds an internal graph structure based on the provided network,\n * then applies A* algorithm to compute the shortest route.\n */\nclass TerraRoute {\n private network: FeatureCollection<LineString>;\n private distanceMeasurement: (positionA: Position, positionB: Position) => number;\n private adjacencyList: Map<number, Array<{ node: number; distance: number }>>;\n private coords: Position[];\n private coordMap: Map<number, Map<number, number>>;\n\n /**\n * Creates a new instance of TerraRoute.\n * \n * @param network - A GeoJSON FeatureCollection of LineStrings representing the route network.\n * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).\n */\n constructor(\n network: FeatureCollection<LineString>,\n distanceMeasurement?: (positionA: Position, positionB: Position) => number\n ) {\n this.network = network;\n this.adjacencyList = new Map();\n this.coords = [];\n this.coordMap = new Map();\n this.distanceMeasurement = distanceMeasurement ? distanceMeasurement : haversineDistance;\n\n // \n this.buildNetworkGraph();\n }\n\n /**\n * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.\n * Otherwise, assigns a new index and stores the coordinate.\n * \n * @param coord - A GeoJSON Position array representing [longitude, latitude].\n * @returns A unique numeric index for the coordinate.\n */\n private coordinateIndex(coord: Position): number {\n const [lng, lat] = coord;\n if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());\n\n const latMap = this.coordMap.get(lng)!;\n if (latMap.has(lat)) {\n return latMap.get(lat)!;\n }\n\n const idx = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, idx);\n\n return idx;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into bidirectional graph edges with associated distances.\n * Assumes that the network is a connected graph of LineStrings with shared coordinates.\n */\n private buildNetworkGraph(): void {\n for (const feature of this.network.features) {\n const coords = feature.geometry.coordinates;\n for (let i = 0; i < coords.length - 1; i++) {\n const aIdx = this.coordinateIndex(coords[i]);\n const bIdx = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIdx)) this.adjacencyList.set(aIdx, []);\n if (!this.adjacencyList.has(bIdx)) this.adjacencyList.set(bIdx, []);\n\n this.adjacencyList.get(aIdx)!.push({ node: bIdx, distance });\n this.adjacencyList.get(bIdx)!.push({ node: aIdx, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using 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 public getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null {\n const startIdx = this.coordinateIndex(start.geometry.coordinates);\n const endIdx = this.coordinateIndex(end.geometry.coordinates);\n\n const openSet = new MinHeap();\n openSet.insert(0, startIdx);\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIdx, 0]]);\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n\n if (current === endIdx) {\n const path: Position[] = [];\n let currNode: number | undefined = current;\n while (currNode !== undefined) {\n path.unshift(this.coords[currNode]);\n currNode = cameFrom.get(currNode);\n }\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n for (const neighbor of this.adjacencyList.get(current) || []) {\n const tentativeGScore = (gScore.get(current) ?? Infinity) + neighbor.distance;\n if (tentativeGScore < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeGScore);\n const fScore = tentativeGScore + this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIdx]);\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n return null;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }"],"names":["MinHeap","this","heap","insertCounter","_proto","prototype","insert","key","value","node","index","idx","length","push","parentIdx","parent","extractMin","minNode","endNode","pop","bubbleDown","size","leftIdx","rightIdx","smallestIdx","haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","createCheapRuler","lat","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy","TerraRoute","network","distanceMeasurement","adjacencyList","coords","coordMap","Map","buildNetworkGraph","coordinateIndex","coord","lng","has","set","latMap","get","_step","_iterator","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIdx","bIdx","distance","getRoute","start","end","startIdx","endIdx","openSet","cameFrom","gScore","current","path","currNode","undefined","unshift","type","properties","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeGScore","Infinity","fScore"],"mappings":"oyBAAa,IAAAA,eAAO,WAAA,SAAAA,IAAAC,KACRC,KAA6D,GAC7DC,KAAAA,cAAgB,CAAC,KAAAC,EAAAJ,EAAAK,UAuExB,OAvEwBD,EAEzBE,OAAA,SAAOC,EAAaC,GAChB,IAAMC,EAAO,CAAEF,IAAAA,EAAKC,MAAAA,EAAOE,MAAOT,KAAKE,iBACnCQ,EAAMV,KAAKC,KAAKU,OAIpB,IAHAX,KAAKC,KAAKW,KAAKJ,GAGRE,EAAM,GAAG,CACZ,IAAMG,EAAaH,EAAM,IAAO,EAC1BI,EAASd,KAAKC,KAAKY,GACzB,GAAIL,EAAKF,IAAMQ,EAAOR,KAAQE,EAAKF,MAAQQ,EAAOR,KAAOE,EAAKC,MAAQK,EAAOL,MAAQ,MACrFT,KAAKC,KAAKS,GAAOI,EACjBJ,EAAMG,CACV,CACAb,KAAKC,KAAKS,GAAOF,CACrB,EAACL,EAEDY,WAAA,WACI,IAAMJ,EAASX,KAAKC,KAAKU,OACzB,GAAe,IAAXA,EAAc,OAAO,KAEzB,IAAMK,EAAUhB,KAAKC,KAAK,GACpBgB,EAAUjB,KAAKC,KAAKiB,MAO1B,OALIP,EAAS,IACTX,KAAKC,KAAK,GAAKgB,EACfjB,KAAKmB,WAAW,IAGbH,EAAQT,KACnB,EAACJ,EAEDiB,KAAA,WACI,OAAOpB,KAAKC,KAAKU,MACrB,EAACR,EAEOgB,WAAA,SAAWT,GAIf,IAHA,IAAMC,EAASX,KAAKC,KAAKU,OACnBH,EAAOR,KAAKC,KAAKS,KAEV,CACT,IAAMW,EAAuB,GAAZX,GAAO,GAClBY,EAAWD,EAAU,EACvBE,EAAcb,EAoBlB,GAjBIW,EAAUV,IACTX,KAAKC,KAAKoB,GAASf,IAAMN,KAAKC,KAAKsB,GAAajB,KAC5CN,KAAKC,KAAKoB,GAASf,MAAQN,KAAKC,KAAKsB,GAAajB,KAC/CN,KAAKC,KAAKoB,GAASZ,MAAQT,KAAKC,KAAKsB,GAAad,SAE1Dc,EAAcF,GAIdC,EAAWX,IACVX,KAAKC,KAAKqB,GAAUhB,IAAMN,KAAKC,KAAKsB,GAAajB,KAC7CN,KAAKC,KAAKqB,GAAUhB,MAAQN,KAAKC,KAAKsB,GAAajB,KAChDN,KAAKC,KAAKqB,GAAUb,MAAQT,KAAKC,KAAKsB,GAAad,SAE3Dc,EAAcD,GAGdC,IAAgBb,EAAK,MAEzBV,KAAKC,KAAKS,GAAOV,KAAKC,KAAKsB,GAC3BvB,KAAKC,KAAKsB,GAAef,EAEzBE,EAAMa,CACV,CACJ,EAACxB,CAAA,CAzEe,GCEPyB,EAAoB,SAACC,EAAoBC,GAClD,IAAMC,EAAY,SAACC,GAAgB,OAAMA,EAAWC,KAAKC,GAAM,GAAG,EAE5DC,EAASJ,EAAUF,EAAS,IAC5BO,EAAYL,EAAUF,EAAS,IAC/BQ,EAASN,EAAUD,EAAS,IAE5BQ,EAAWD,EAASF,EACpBI,EAFYR,EAAUD,EAAS,IAELM,EAE1BI,EACFP,KAAKQ,IAAIH,EAAW,GAAKL,KAAKQ,IAAIH,EAAW,GAC7CL,KAAKS,IAAIP,GACTF,KAAKS,IAAIL,GACTJ,KAAKQ,IAAIF,EAAc,GACvBN,KAAKQ,IAAIF,EAAc,GAM3B,OALU,EAAIN,KAAKU,MAAMV,KAAKW,KAAKJ,GAAIP,KAAKW,KAAK,EAAIJ,IAEtC,OAGG,GACtB,ECQM,SAAUK,EAAiBC,GAC7B,IACMC,EAAK,EAAI,cACTC,EAAKD,GAAM,EAAIA,GACfE,EAAMhB,KAAKC,GAAK,IAEhBgB,EAASjB,KAAKS,IAAII,EAAMG,GACxBE,EAAK,GAAK,EAAIH,GAAM,EAAIE,EAASA,IACjCE,EAAInB,KAAKW,KAAKO,GAEdE,EATK,SASDJ,EACJK,EAAKD,EAAID,EAAIF,EACbK,EAAKF,EAAID,EAAID,GAAM,EAAIH,GAE7B,OAAgB,SAASR,EAAagB,GAGlC,IAFA,IAAIC,EAAWjB,EAAE,GAAKgB,EAAE,GAEjBC,GAAY,KAAKA,GAAY,IACpC,KAAOA,EAAW,KAAKA,GAAY,IAEnC,IAAMC,EAAKD,EAAWH,EAChBK,GAAMnB,EAAE,GAAKgB,EAAE,IAAMD,EAE3B,OAAOtB,KAAKW,KAAKc,EAAKA,EAAKC,EAAKA,EACpC,CACJ,CC7CM,IAAAC,0BAaF,SAAAA,EACIC,EACAC,GAA0E1D,KAdtEyD,aACAC,EAAAA,KAAAA,gCACAC,mBAAa,EAAA3D,KACb4D,YACAC,EAAAA,KAAAA,gBAYJ7D,KAAKyD,QAAUA,EACfzD,KAAK2D,cAAgB,IAAIG,IACzB9D,KAAK4D,OAAS,GACd5D,KAAK6D,SAAW,IAAIC,IACpB9D,KAAK0D,oBAAsBA,GAA4ClC,EAGvExB,KAAK+D,mBACT,CAAC,IAAA5D,EAAAqD,EAAApD,UA4FA,OA5FAD,EASO6D,gBAAA,SAAgBC,GACpB,IAAOC,EAAYD,EAAK,GAAZvB,EAAOuB,KACdjE,KAAK6D,SAASM,IAAID,IAAMlE,KAAK6D,SAASO,IAAIF,EAAK,IAAIJ,KAExD,IAAMO,EAASrE,KAAK6D,SAASS,IAAIJ,GACjC,GAAIG,EAAOF,IAAIzB,GACX,OAAO2B,EAAOC,IAAI5B,GAGtB,IAAMhC,EAAMV,KAAK4D,OAAOjD,OAIxB,OAHAX,KAAK4D,OAAOhD,KAAKqD,GACjBI,EAAOD,IAAI1B,EAAKhC,GAETA,CACX,EAACP,EAOO4D,kBAAA,WACJ,IAAA,IAA2CQ,EAA3CC,EAAAC,EAAsBzE,KAAKyD,QAAQiB,YAAQH,EAAAC,KAAAG,MAEvC,QADMf,EADQW,EAAAhE,MACSqE,SAASC,YACvBC,EAAI,EAAGA,EAAIlB,EAAOjD,OAAS,EAAGmE,IAAK,CACxC,IAAMC,EAAO/E,KAAKgE,gBAAgBJ,EAAOkB,IACnCE,EAAOhF,KAAKgE,gBAAgBJ,EAAOkB,EAAI,IACvCG,EAAWjF,KAAK0D,oBAAoBE,EAAOkB,GAAIlB,EAAOkB,EAAI,IAE3D9E,KAAK2D,cAAcQ,IAAIY,IAAO/E,KAAK2D,cAAcS,IAAIW,EAAM,IAC3D/E,KAAK2D,cAAcQ,IAAIa,IAAOhF,KAAK2D,cAAcS,IAAIY,EAAM,IAEhEhF,KAAK2D,cAAcW,IAAIS,GAAOnE,KAAK,CAAEJ,KAAMwE,EAAMC,SAAAA,IACjDjF,KAAK2D,cAAcW,IAAIU,GAAOpE,KAAK,CAAEJ,KAAMuE,EAAME,SAAAA,GACrD,CAER,EAAC9E,EASM+E,SAAA,SAASC,EAAuBC,GACnC,IAAMC,EAAWrF,KAAKgE,gBAAgBmB,EAAMP,SAASC,aAC/CS,EAAStF,KAAKgE,gBAAgBoB,EAAIR,SAASC,aAE3CU,EAAU,IAAIxF,EACpBwF,EAAQlF,OAAO,EAAGgF,GAIlB,IAHA,IAAMG,EAAW,IAAI1B,IACf2B,EAAS,IAAI3B,IAAoB,CAAC,CAACuB,EAAU,KAE5CE,EAAQnE,OAAS,GAAG,CACvB,IAAMsE,EAAUH,EAAQxE,aAExB,GAAI2E,IAAYJ,EAAQ,CAGpB,IAFA,IAAMK,EAAmB,GACrBC,EAA+BF,OACfG,IAAbD,GACHD,EAAKG,QAAQ9F,KAAK4D,OAAOgC,IACzBA,EAAWJ,EAASlB,IAAIsB,GAE5B,MAAO,CACHG,KAAM,UACNnB,SAAU,CAAEmB,KAAM,aAAclB,YAAac,GAC7CK,WAAY,CAAA,EAEpB,CAEA,QAA4DC,EAA5DC,EAAAzB,EAAuBzE,KAAK2D,cAAcW,IAAIoB,IAAY,MAAEO,EAAAC,KAAAvB,MAAE,CAAAwB,IAAAA,EAAAC,EAAnDC,EAAQJ,EAAA1F,MACT+F,UAAkBH,EAACV,EAAOnB,IAAIoB,IAAQS,EAAII,UAAYF,EAASpB,SACrE,GAAIqB,GAA4C,OAA7BF,EAAIX,EAAOnB,IAAI+B,EAAS7F,OAAK4F,EAAIG,UAAW,CAC3Df,EAASpB,IAAIiC,EAAS7F,KAAMkF,GAC5BD,EAAOrB,IAAIiC,EAAS7F,KAAM8F,GAC1B,IAAME,EAASF,EAAkBtG,KAAK0D,oBAAoB1D,KAAK4D,OAAOyC,EAAS7F,MAAOR,KAAK4D,OAAO0B,IAClGC,EAAQlF,OAAOmG,EAAQH,EAAS7F,KACpC,CACJ,CACJ,CAEA,WACJ,EAACgD,CAAA"}
1
+ {"version":3,"file":"terra-route.module.js","sources":["../src/min-heap.ts","../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/terra-route.ts"],"sourcesContent":["export class MinHeap {\n private heap: Array<{ key: number; value: number; index: number }> = [];\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n const node = { key, value, index: this.insertCounter++ };\n let idx = this.heap.length;\n this.heap.push(node);\n\n // Optimized Bubble Up\n while (idx > 0) {\n const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)\n const parent = this.heap[parentIdx];\n if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;\n this.heap[idx] = parent;\n idx = parentIdx;\n }\n this.heap[idx] = node;\n }\n\n extractMin(): number | null {\n const length = this.heap.length;\n if (length === 0) return null;\n\n const minNode = this.heap[0];\n const endNode = this.heap.pop()!;\n\n if (length > 1) {\n this.heap[0] = endNode;\n this.bubbleDown(0);\n }\n\n return minNode.value;\n }\n\n size(): number {\n return this.heap.length;\n }\n\n private bubbleDown(idx: number): void {\n const { heap } = this;\n const length = heap.length;\n // Grab the parent node once, then move it down only if needed\n const node = heap[idx];\n const nodeKey = node.key;\n const nodeIndex = node.index;\n\n while (true) {\n // Calculate left and right child indexes\n const leftIdx = (idx << 1) + 1;\n if (leftIdx >= length) {\n // No children => we’re already in place\n break;\n }\n\n // Assume left child is the smaller one by default\n let smallestIdx = leftIdx;\n let smallestKey = heap[leftIdx].key;\n let smallestIndex = heap[leftIdx].index;\n\n const rightIdx = leftIdx + 1;\n if (rightIdx < length) {\n // Compare left child vs. right child\n const rightKey = heap[rightIdx].key;\n const rightIndex = heap[rightIdx].index;\n if (rightKey < smallestKey || (rightKey === smallestKey && rightIndex < smallestIndex)) {\n smallestIdx = rightIdx;\n smallestKey = rightKey;\n smallestIndex = rightIndex;\n }\n }\n\n // Compare the smaller child with the parent\n if (smallestKey < nodeKey || (smallestKey === nodeKey && smallestIndex < nodeIndex)) {\n // Swap the smaller child up\n heap[idx] = heap[smallestIdx];\n idx = smallestIdx;\n } else {\n // We’re in the correct position now, so stop\n break;\n }\n }\n\n // Place the original node in its final position\n heap[idx] = node;\n }\n}\n","import { Position } from \"geojson\";\n\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 return distance / 1000;\n}","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}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { MinHeap } from \"./min-heap\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\n\n/**\n * TerraRoute is a routing utility for finding the shortest path\n * between two geographic points over a given GeoJSON LineString network.\n *\n * The class builds an internal graph structure based on the provided network,\n * then applies A* algorithm to compute the shortest route.\n */\nclass TerraRoute {\n private network: FeatureCollection<LineString> | undefined;\n private distanceMeasurement: (positionA: Position, positionB: Position) => number;\n private adjacencyList: Map<number, Array<{ node: number; distance: number }>> = new Map();\n private coords: Position[] = []\n private coordMap: Map<number, Map<number, number>> = new Map();\n\n /**\n * Creates a new instance of TerraRoute.\n * \n * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).\n */\n constructor(\n distanceMeasurement?: (positionA: Position, positionB: Position) => number\n ) {\n this.distanceMeasurement = distanceMeasurement ? distanceMeasurement : haversineDistance;\n }\n\n /**\n * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.\n * Otherwise, assigns a new index and stores the coordinate.\n * \n * @param coord - A GeoJSON Position array representing [longitude, latitude].\n * @returns A unique numeric index for the coordinate.\n */\n private coordinateIndex(coord: Position): number {\n const [lng, lat] = coord;\n if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());\n\n const latMap = this.coordMap.get(lng)!;\n if (latMap.has(lat)) {\n return latMap.get(lat)!;\n }\n\n const idx = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, idx);\n\n return idx;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into bidirectional graph edges with associated distances.\n * Assumes that the network is a connected graph of LineStrings with shared coordinates. Calling this \n * method with a new network overwrite any existing network and reset all internal data structures.\n * \n * @param network - A GeoJSON FeatureCollection of LineStrings representing the road network.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network;\n this.adjacencyList = new Map();\n this.coords = [];\n this.coordMap = new Map();\n\n for (const feature of this.network.features) {\n const coords = feature.geometry.coordinates;\n for (let i = 0; i < coords.length - 1; i++) {\n const aIdx = this.coordinateIndex(coords[i]);\n const bIdx = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIdx)) this.adjacencyList.set(aIdx, []);\n if (!this.adjacencyList.has(bIdx)) this.adjacencyList.set(bIdx, []);\n\n this.adjacencyList.get(aIdx)!.push({ node: bIdx, distance });\n this.adjacencyList.get(bIdx)!.push({ node: aIdx, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using 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(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildNetworkGraph(network) first.\");\n }\n\n const startIdx = this.coordinateIndex(start.geometry.coordinates);\n const endIdx = this.coordinateIndex(end.geometry.coordinates);\n\n if (startIdx === endIdx) {\n return null;\n }\n\n const openSet = new MinHeap();\n openSet.insert(0, startIdx);\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIdx, 0]]);\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n\n if (current === endIdx) {\n const path: Position[] = [];\n let currNode: number | undefined = current;\n while (currNode !== undefined) {\n path.unshift(this.coords[currNode]);\n currNode = cameFrom.get(currNode);\n }\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n const neighbors = this.adjacencyList.get(current) || [];\n\n for (const neighbor of neighbors) {\n const tentativeGScore = (gScore.get(current) ?? Infinity) + neighbor.distance;\n if (tentativeGScore < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeGScore);\n const fScore = tentativeGScore + this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIdx]);\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n return null;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }"],"names":["MinHeap","this","heap","insertCounter","_proto","prototype","insert","key","value","node","index","idx","length","push","parentIdx","parent","extractMin","minNode","endNode","pop","bubbleDown","size","nodeKey","nodeIndex","leftIdx","smallestIdx","smallestKey","smallestIndex","rightIdx","rightKey","rightIndex","haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","createCheapRuler","lat","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy","TerraRoute","distanceMeasurement","network","adjacencyList","Map","coords","coordMap","coordinateIndex","coord","lng","has","set","latMap","get","buildRouteGraph","_iterator","_step","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIdx","bIdx","distance","getRoute","start","end","Error","startIdx","endIdx","openSet","cameFrom","gScore","current","path","currNode","undefined","unshift","type","properties","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeGScore","Infinity","fScore"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t||self).terraRoute={})}(this,function(t){function e(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=Array(e);n<e;n++)i[n]=t[n];return i}function n(t,n){var i="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(i)return(i=i.call(t)).next.bind(i);if(Array.isArray(t)||(i=function(t,n){if(t){if("string"==typeof t)return e(t,n);var i={}.toString.call(t).slice(8,-1);return"Object"===i&&t.constructor&&(i=t.constructor.name),"Map"===i||"Set"===i?Array.from(t):"Arguments"===i||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i)?e(t,n):void 0}}(t))||n&&t&&"number"==typeof t.length){i&&(t=i);var r=0;return function(){return r>=t.length?{done:!0}:{done:!1,value:t[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i=/*#__PURE__*/function(){function t(){this.heap=[],this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var n={key:t,value:e,index:this.insertCounter++},i=this.heap.length;for(this.heap.push(n);i>0;){var r=i-1>>>1,a=this.heap[r];if(n.key>a.key||n.key===a.key&&n.index>a.index)break;this.heap[i]=a,i=r}this.heap[i]=n},e.extractMin=function(){var t=this.heap.length;if(0===t)return null;var e=this.heap[0],n=this.heap.pop();return t>1&&(this.heap[0]=n,this.bubbleDown(0)),e.value},e.size=function(){return this.heap.length},e.bubbleDown=function(t){for(var e=this.heap.length,n=this.heap[t];;){var i=1+(t<<1),r=i+1,a=t;if(i<e&&(this.heap[i].key<this.heap[a].key||this.heap[i].key===this.heap[a].key&&this.heap[i].index<this.heap[a].index)&&(a=i),r<e&&(this.heap[r].key<this.heap[a].key||this.heap[r].key===this.heap[a].key&&this.heap[r].index<this.heap[a].index)&&(a=r),a===t)break;this.heap[t]=this.heap[a],this.heap[a]=n,t=a}},t}(),r=function(t,e){var n=function(t){return t*Math.PI/180},i=n(t[1]),r=n(t[0]),a=n(e[1]),o=a-i,s=n(e[0])-r,h=Math.sin(o/2)*Math.sin(o/2)+Math.cos(i)*Math.cos(a)*Math.sin(s/2)*Math.sin(s/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};t.TerraRoute=/*#__PURE__*/function(){function t(t,e){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=void 0,this.coords=void 0,this.coordMap=void 0,this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.distanceMeasurement=e||r,this.buildNetworkGraph()}var e=t.prototype;return e.coordinateIndex=function(t){var e=t[0],n=t[1];this.coordMap.has(e)||this.coordMap.set(e,new Map);var i=this.coordMap.get(e);if(i.has(n))return i.get(n);var r=this.coords.length;return this.coords.push(t),i.set(n,r),r},e.buildNetworkGraph=function(){for(var t,e=n(this.network.features);!(t=e()).done;)for(var i=t.value.geometry.coordinates,r=0;r<i.length-1;r++){var a=this.coordinateIndex(i[r]),o=this.coordinateIndex(i[r+1]),s=this.distanceMeasurement(i[r],i[r+1]);this.adjacencyList.has(a)||this.adjacencyList.set(a,[]),this.adjacencyList.has(o)||this.adjacencyList.set(o,[]),this.adjacencyList.get(a).push({node:o,distance:s}),this.adjacencyList.get(o).push({node:a,distance:s})}},e.getRoute=function(t,e){var r=this.coordinateIndex(t.geometry.coordinates),a=this.coordinateIndex(e.geometry.coordinates),o=new i;o.insert(0,r);for(var s=new Map,h=new Map([[r,0]]);o.size()>0;){var c=o.extractMin();if(c===a){for(var d=[],u=c;void 0!==u;)d.unshift(this.coords[u]),u=s.get(u);return{type:"Feature",geometry:{type:"LineString",coordinates:d},properties:{}}}for(var p,f=n(this.adjacencyList.get(c)||[]);!(p=f()).done;){var y,l,v=p.value,M=(null!=(y=h.get(c))?y:Infinity)+v.distance;if(M<(null!=(l=h.get(v.node))?l:Infinity)){s.set(v.node,c),h.set(v.node,M);var g=M+this.distanceMeasurement(this.coords[v.node],this.coords[a]);o.insert(g,v.node)}}}return null},t}(),t.createCheapRuler=function(t){var e=1/298.257223563,n=e*(2-e),i=Math.PI/180,r=Math.cos(t*i),a=1/(1-n*(1-r*r)),o=Math.sqrt(a),s=6378.137*i,h=s*o*r,c=s*o*a*(1-n);return function(t,e){for(var n=t[0]-e[0];n<-180;)n+=360;for(;n>180;)n-=360;var i=n*h,r=(t[1]-e[1])*c;return Math.sqrt(i*i+r*r)}},t.haversineDistance=r});
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t||self).terraRoute={})}(this,function(t){function e(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=Array(e);n<e;n++)r[n]=t[n];return r}function n(t,n){var r="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(r)return(r=r.call(t)).next.bind(r);if(Array.isArray(t)||(r=function(t,n){if(t){if("string"==typeof t)return e(t,n);var r={}.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?e(t,n):void 0}}(t))||n&&t&&"number"==typeof t.length){r&&(t=r);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.")}var r=/*#__PURE__*/function(){function t(){this.heap=[],this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var n={key:t,value:e,index:this.insertCounter++},r=this.heap.length;for(this.heap.push(n);r>0;){var i=r-1>>>1,a=this.heap[i];if(n.key>a.key||n.key===a.key&&n.index>a.index)break;this.heap[r]=a,r=i}this.heap[r]=n},e.extractMin=function(){var t=this.heap.length;if(0===t)return null;var e=this.heap[0],n=this.heap.pop();return t>1&&(this.heap[0]=n,this.bubbleDown(0)),e.value},e.size=function(){return this.heap.length},e.bubbleDown=function(t){for(var e=this.heap,n=e.length,r=e[t],i=r.key,a=r.index;;){var o=1+(t<<1);if(o>=n)break;var s=o,h=e[o].key,c=e[o].index,u=o+1;if(u<n){var d=e[u].key,f=e[u].index;(d<h||d===h&&f<c)&&(s=u,h=d,c=f)}if(!(h<i||h===i&&c<a))break;e[t]=e[s],t=s}e[t]=r},t}(),i=function(t,e){var n=function(t){return t*Math.PI/180},r=n(t[1]),i=n(t[0]),a=n(e[1]),o=a-r,s=n(e[0])-i,h=Math.sin(o/2)*Math.sin(o/2)+Math.cos(r)*Math.cos(a)*Math.sin(s/2)*Math.sin(s/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};t.TerraRoute=/*#__PURE__*/function(){function t(t){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.distanceMeasurement=t||i}var e=t.prototype;return e.coordinateIndex=function(t){var e=t[0],n=t[1];this.coordMap.has(e)||this.coordMap.set(e,new Map);var r=this.coordMap.get(e);if(r.has(n))return r.get(n);var i=this.coords.length;return this.coords.push(t),r.set(n,i),i},e.buildRouteGraph=function(t){this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map;for(var e,r=n(this.network.features);!(e=r()).done;)for(var i=e.value.geometry.coordinates,a=0;a<i.length-1;a++){var o=this.coordinateIndex(i[a]),s=this.coordinateIndex(i[a+1]),h=this.distanceMeasurement(i[a],i[a+1]);this.adjacencyList.has(o)||this.adjacencyList.set(o,[]),this.adjacencyList.has(s)||this.adjacencyList.set(s,[]),this.adjacencyList.get(o).push({node:s,distance:h}),this.adjacencyList.get(s).push({node:o,distance:h})}},e.getRoute=function(t,e){if(!this.network)throw new Error("Network not built. Please call buildNetworkGraph(network) first.");var i=this.coordinateIndex(t.geometry.coordinates),a=this.coordinateIndex(e.geometry.coordinates);if(i===a)return null;var o=new r;o.insert(0,i);for(var s=new Map,h=new Map([[i,0]]);o.size()>0;){var c=o.extractMin();if(c===a){for(var u=[],d=c;void 0!==d;)u.unshift(this.coords[d]),d=s.get(d);return{type:"Feature",geometry:{type:"LineString",coordinates:u},properties:{}}}for(var f,p=n(this.adjacencyList.get(c)||[]);!(f=p()).done;){var l,v,y=f.value,M=(null!=(l=h.get(c))?l:Infinity)+y.distance;if(M<(null!=(v=h.get(y.node))?v:Infinity)){s.set(y.node,c),h.set(y.node,M);var g=M+this.distanceMeasurement(this.coords[y.node],this.coords[a]);o.insert(g,y.node)}}}return null},t}(),t.createCheapRuler=function(t){var e=1/298.257223563,n=e*(2-e),r=Math.PI/180,i=Math.cos(t*r),a=1/(1-n*(1-i*i)),o=Math.sqrt(a),s=6378.137*r,h=s*o*i,c=s*o*a*(1-n);return function(t,e){for(var n=t[0]-e[0];n<-180;)n+=360;for(;n>180;)n-=360;var r=n*h,i=(t[1]-e[1])*c;return Math.sqrt(r*r+i*i)}},t.haversineDistance=i});
2
2
  //# sourceMappingURL=terra-route.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.umd.js","sources":["../src/min-heap.ts","../src/distance/haversine.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["export class MinHeap {\n private heap: Array<{ key: number; value: number; index: number }> = [];\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n const node = { key, value, index: this.insertCounter++ };\n let idx = this.heap.length;\n this.heap.push(node);\n\n // Optimized Bubble Up\n while (idx > 0) {\n const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)\n const parent = this.heap[parentIdx];\n if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;\n this.heap[idx] = parent;\n idx = parentIdx;\n }\n this.heap[idx] = node;\n }\n\n extractMin(): number | null {\n const length = this.heap.length;\n if (length === 0) return null;\n\n const minNode = this.heap[0];\n const endNode = this.heap.pop()!;\n\n if (length > 1) {\n this.heap[0] = endNode;\n this.bubbleDown(0);\n }\n\n return minNode.value;\n }\n\n size(): number {\n return this.heap.length;\n }\n\n private bubbleDown(idx: number): void {\n const length = this.heap.length;\n const node = this.heap[idx];\n\n while (true) {\n const leftIdx = (idx << 1) + 1;\n const rightIdx = leftIdx + 1;\n let smallestIdx = idx;\n\n if (\n leftIdx < length &&\n (this.heap[leftIdx].key < this.heap[smallestIdx].key ||\n (this.heap[leftIdx].key === this.heap[smallestIdx].key &&\n this.heap[leftIdx].index < this.heap[smallestIdx].index))\n ) {\n smallestIdx = leftIdx;\n }\n\n if (\n rightIdx < length &&\n (this.heap[rightIdx].key < this.heap[smallestIdx].key ||\n (this.heap[rightIdx].key === this.heap[smallestIdx].key &&\n this.heap[rightIdx].index < this.heap[smallestIdx].index))\n ) {\n smallestIdx = rightIdx;\n }\n\n if (smallestIdx === idx) break;\n\n this.heap[idx] = this.heap[smallestIdx];\n this.heap[smallestIdx] = node;\n\n idx = smallestIdx;\n }\n }\n}\n","import { Position } from \"geojson\";\n\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 return distance / 1000;\n}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { MinHeap } from \"./min-heap\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\n\n/**\n * TerraRoute is a routing utility for finding the shortest path\n * between two geographic points over a given GeoJSON LineString network.\n *\n * The class builds an internal graph structure based on the provided network,\n * then applies A* algorithm to compute the shortest route.\n */\nclass TerraRoute {\n private network: FeatureCollection<LineString>;\n private distanceMeasurement: (positionA: Position, positionB: Position) => number;\n private adjacencyList: Map<number, Array<{ node: number; distance: number }>>;\n private coords: Position[];\n private coordMap: Map<number, Map<number, number>>;\n\n /**\n * Creates a new instance of TerraRoute.\n * \n * @param network - A GeoJSON FeatureCollection of LineStrings representing the route network.\n * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).\n */\n constructor(\n network: FeatureCollection<LineString>,\n distanceMeasurement?: (positionA: Position, positionB: Position) => number\n ) {\n this.network = network;\n this.adjacencyList = new Map();\n this.coords = [];\n this.coordMap = new Map();\n this.distanceMeasurement = distanceMeasurement ? distanceMeasurement : haversineDistance;\n\n // \n this.buildNetworkGraph();\n }\n\n /**\n * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.\n * Otherwise, assigns a new index and stores the coordinate.\n * \n * @param coord - A GeoJSON Position array representing [longitude, latitude].\n * @returns A unique numeric index for the coordinate.\n */\n private coordinateIndex(coord: Position): number {\n const [lng, lat] = coord;\n if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());\n\n const latMap = this.coordMap.get(lng)!;\n if (latMap.has(lat)) {\n return latMap.get(lat)!;\n }\n\n const idx = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, idx);\n\n return idx;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into bidirectional graph edges with associated distances.\n * Assumes that the network is a connected graph of LineStrings with shared coordinates.\n */\n private buildNetworkGraph(): void {\n for (const feature of this.network.features) {\n const coords = feature.geometry.coordinates;\n for (let i = 0; i < coords.length - 1; i++) {\n const aIdx = this.coordinateIndex(coords[i]);\n const bIdx = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIdx)) this.adjacencyList.set(aIdx, []);\n if (!this.adjacencyList.has(bIdx)) this.adjacencyList.set(bIdx, []);\n\n this.adjacencyList.get(aIdx)!.push({ node: bIdx, distance });\n this.adjacencyList.get(bIdx)!.push({ node: aIdx, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using 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 public getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null {\n const startIdx = this.coordinateIndex(start.geometry.coordinates);\n const endIdx = this.coordinateIndex(end.geometry.coordinates);\n\n const openSet = new MinHeap();\n openSet.insert(0, startIdx);\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIdx, 0]]);\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n\n if (current === endIdx) {\n const path: Position[] = [];\n let currNode: number | undefined = current;\n while (currNode !== undefined) {\n path.unshift(this.coords[currNode]);\n currNode = cameFrom.get(currNode);\n }\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n for (const neighbor of this.adjacencyList.get(current) || []) {\n const tentativeGScore = (gScore.get(current) ?? Infinity) + neighbor.distance;\n if (tentativeGScore < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeGScore);\n const fScore = tentativeGScore + this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIdx]);\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n return null;\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":["MinHeap","this","heap","insertCounter","_proto","prototype","insert","key","value","node","index","idx","length","push","parentIdx","parent","extractMin","minNode","endNode","pop","bubbleDown","size","leftIdx","rightIdx","smallestIdx","haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","TerraRoute","network","distanceMeasurement","adjacencyList","coords","coordMap","Map","buildNetworkGraph","coordinateIndex","coord","lng","lat","has","set","latMap","get","_step","_iterator","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIdx","bIdx","distance","getRoute","start","end","startIdx","endIdx","openSet","cameFrom","gScore","current","path","currNode","undefined","unshift","type","properties","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeGScore","Infinity","fScore","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.umd.js","sources":["../src/min-heap.ts","../src/distance/haversine.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["export class MinHeap {\n private heap: Array<{ key: number; value: number; index: number }> = [];\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n const node = { key, value, index: this.insertCounter++ };\n let idx = this.heap.length;\n this.heap.push(node);\n\n // Optimized Bubble Up\n while (idx > 0) {\n const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)\n const parent = this.heap[parentIdx];\n if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;\n this.heap[idx] = parent;\n idx = parentIdx;\n }\n this.heap[idx] = node;\n }\n\n extractMin(): number | null {\n const length = this.heap.length;\n if (length === 0) return null;\n\n const minNode = this.heap[0];\n const endNode = this.heap.pop()!;\n\n if (length > 1) {\n this.heap[0] = endNode;\n this.bubbleDown(0);\n }\n\n return minNode.value;\n }\n\n size(): number {\n return this.heap.length;\n }\n\n private bubbleDown(idx: number): void {\n const { heap } = this;\n const length = heap.length;\n // Grab the parent node once, then move it down only if needed\n const node = heap[idx];\n const nodeKey = node.key;\n const nodeIndex = node.index;\n\n while (true) {\n // Calculate left and right child indexes\n const leftIdx = (idx << 1) + 1;\n if (leftIdx >= length) {\n // No children => we’re already in place\n break;\n }\n\n // Assume left child is the smaller one by default\n let smallestIdx = leftIdx;\n let smallestKey = heap[leftIdx].key;\n let smallestIndex = heap[leftIdx].index;\n\n const rightIdx = leftIdx + 1;\n if (rightIdx < length) {\n // Compare left child vs. right child\n const rightKey = heap[rightIdx].key;\n const rightIndex = heap[rightIdx].index;\n if (rightKey < smallestKey || (rightKey === smallestKey && rightIndex < smallestIndex)) {\n smallestIdx = rightIdx;\n smallestKey = rightKey;\n smallestIndex = rightIndex;\n }\n }\n\n // Compare the smaller child with the parent\n if (smallestKey < nodeKey || (smallestKey === nodeKey && smallestIndex < nodeIndex)) {\n // Swap the smaller child up\n heap[idx] = heap[smallestIdx];\n idx = smallestIdx;\n } else {\n // We’re in the correct position now, so stop\n break;\n }\n }\n\n // Place the original node in its final position\n heap[idx] = node;\n }\n}\n","import { Position } from \"geojson\";\n\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 return distance / 1000;\n}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { MinHeap } from \"./min-heap\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\n\n/**\n * TerraRoute is a routing utility for finding the shortest path\n * between two geographic points over a given GeoJSON LineString network.\n *\n * The class builds an internal graph structure based on the provided network,\n * then applies A* algorithm to compute the shortest route.\n */\nclass TerraRoute {\n private network: FeatureCollection<LineString> | undefined;\n private distanceMeasurement: (positionA: Position, positionB: Position) => number;\n private adjacencyList: Map<number, Array<{ node: number; distance: number }>> = new Map();\n private coords: Position[] = []\n private coordMap: Map<number, Map<number, number>> = new Map();\n\n /**\n * Creates a new instance of TerraRoute.\n * \n * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).\n */\n constructor(\n distanceMeasurement?: (positionA: Position, positionB: Position) => number\n ) {\n this.distanceMeasurement = distanceMeasurement ? distanceMeasurement : haversineDistance;\n }\n\n /**\n * Converts a coordinate into a unique index. If the coordinate already exists, returns its index.\n * Otherwise, assigns a new index and stores the coordinate.\n * \n * @param coord - A GeoJSON Position array representing [longitude, latitude].\n * @returns A unique numeric index for the coordinate.\n */\n private coordinateIndex(coord: Position): number {\n const [lng, lat] = coord;\n if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());\n\n const latMap = this.coordMap.get(lng)!;\n if (latMap.has(lat)) {\n return latMap.get(lat)!;\n }\n\n const idx = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, idx);\n\n return idx;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into bidirectional graph edges with associated distances.\n * Assumes that the network is a connected graph of LineStrings with shared coordinates. Calling this \n * method with a new network overwrite any existing network and reset all internal data structures.\n * \n * @param network - A GeoJSON FeatureCollection of LineStrings representing the road network.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network;\n this.adjacencyList = new Map();\n this.coords = [];\n this.coordMap = new Map();\n\n for (const feature of this.network.features) {\n const coords = feature.geometry.coordinates;\n for (let i = 0; i < coords.length - 1; i++) {\n const aIdx = this.coordinateIndex(coords[i]);\n const bIdx = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIdx)) this.adjacencyList.set(aIdx, []);\n if (!this.adjacencyList.has(bIdx)) this.adjacencyList.set(bIdx, []);\n\n this.adjacencyList.get(aIdx)!.push({ node: bIdx, distance });\n this.adjacencyList.get(bIdx)!.push({ node: aIdx, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using 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(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildNetworkGraph(network) first.\");\n }\n\n const startIdx = this.coordinateIndex(start.geometry.coordinates);\n const endIdx = this.coordinateIndex(end.geometry.coordinates);\n\n if (startIdx === endIdx) {\n return null;\n }\n\n const openSet = new MinHeap();\n openSet.insert(0, startIdx);\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIdx, 0]]);\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n\n if (current === endIdx) {\n const path: Position[] = [];\n let currNode: number | undefined = current;\n while (currNode !== undefined) {\n path.unshift(this.coords[currNode]);\n currNode = cameFrom.get(currNode);\n }\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n const neighbors = this.adjacencyList.get(current) || [];\n\n for (const neighbor of neighbors) {\n const tentativeGScore = (gScore.get(current) ?? Infinity) + neighbor.distance;\n if (tentativeGScore < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeGScore);\n const fScore = tentativeGScore + this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIdx]);\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n return null;\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":["MinHeap","this","heap","insertCounter","_proto","prototype","insert","key","value","node","index","idx","length","push","parentIdx","parent","extractMin","minNode","endNode","pop","bubbleDown","size","nodeKey","nodeIndex","leftIdx","smallestIdx","smallestKey","smallestIndex","rightIdx","rightKey","rightIndex","haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","TerraRoute","distanceMeasurement","network","adjacencyList","Map","coords","coordMap","coordinateIndex","coord","lng","lat","has","set","latMap","get","buildRouteGraph","_iterator","_step","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIdx","bIdx","distance","getRoute","start","end","Error","startIdx","endIdx","openSet","cameFrom","gScore","current","path","currNode","undefined","unshift","type","properties","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeGScore","Infinity","fScore","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy"],"mappings":"wgCAAa,IAAAA,eAAO,WAAA,SAAAA,IAAAC,KACRC,KAA6D,GAC7DC,KAAAA,cAAgB,CAAC,KAAAC,EAAAJ,EAAAK,UAmFxB,OAnFwBD,EAEzBE,OAAA,SAAOC,EAAaC,GAChB,IAAMC,EAAO,CAAEF,IAAAA,EAAKC,MAAAA,EAAOE,MAAOT,KAAKE,iBACnCQ,EAAMV,KAAKC,KAAKU,OAIpB,IAHAX,KAAKC,KAAKW,KAAKJ,GAGRE,EAAM,GAAG,CACZ,IAAMG,EAAaH,EAAM,IAAO,EAC1BI,EAASd,KAAKC,KAAKY,GACzB,GAAIL,EAAKF,IAAMQ,EAAOR,KAAQE,EAAKF,MAAQQ,EAAOR,KAAOE,EAAKC,MAAQK,EAAOL,MAAQ,MACrFT,KAAKC,KAAKS,GAAOI,EACjBJ,EAAMG,CACV,CACAb,KAAKC,KAAKS,GAAOF,CACrB,EAACL,EAEDY,WAAA,WACI,IAAMJ,EAASX,KAAKC,KAAKU,OACzB,GAAe,IAAXA,EAAc,OAAW,KAE7B,IAAMK,EAAUhB,KAAKC,KAAK,GACpBgB,EAAUjB,KAAKC,KAAKiB,MAO1B,OALIP,EAAS,IACTX,KAAKC,KAAK,GAAKgB,EACfjB,KAAKmB,WAAW,IAGbH,EAAQT,KACnB,EAACJ,EAEDiB,KAAA,WACI,OAAWpB,KAACC,KAAKU,MACrB,EAACR,EAEOgB,WAAA,SAAWT,GAQf,IAPA,IAAQT,EAASD,KAATC,KACFU,EAASV,EAAKU,OAEdH,EAAOP,EAAKS,GACZW,EAAUb,EAAKF,IACfgB,EAAYd,EAAKC,QAEV,CAET,IAAMc,EAAuB,GAAZb,GAAO,GACxB,GAAIa,GAAWZ,EAEX,MAIJ,IAAIa,EAAcD,EACdE,EAAcxB,EAAKsB,GAASjB,IAC5BoB,EAAgBzB,EAAKsB,GAASd,MAE5BkB,EAAWJ,EAAU,EAC3B,GAAII,EAAWhB,EAAQ,CAEnB,IAAMiB,EAAW3B,EAAK0B,GAAUrB,IAC1BuB,EAAa5B,EAAK0B,GAAUlB,OAC9BmB,EAAWH,GAAgBG,IAAaH,GAAeI,EAAaH,KACpEF,EAAcG,EACdF,EAAcG,EACdF,EAAgBG,EAExB,CAGA,KAAIJ,EAAcJ,GAAYI,IAAgBJ,GAAWK,EAAgBJ,GAMrE,MAJArB,EAAKS,GAAOT,EAAKuB,GACjBd,EAAMc,CAKd,CAGAvB,EAAKS,GAAOF,CAChB,EAACT,CAAA,CArFe,GCEP+B,EAAoB,SAACC,EAAoBC,GAClD,IAAMC,EAAY,SAACC,GAAgB,OAAMA,EAAWC,KAAKC,GAAM,GAAG,EAE5DC,EAASJ,EAAUF,EAAS,IAC5BO,EAAYL,EAAUF,EAAS,IAC/BQ,EAASN,EAAUD,EAAS,IAE5BQ,EAAWD,EAASF,EACpBI,EAFYR,EAAUD,EAAS,IAELM,EAE1BI,EACFP,KAAKQ,IAAIH,EAAW,GAAKL,KAAKQ,IAAIH,EAAW,GAC7CL,KAAKS,IAAIP,GACTF,KAAKS,IAAIL,GACTJ,KAAKQ,IAAIF,EAAc,GACvBN,KAAKQ,IAAIF,EAAc,GAM3B,OALU,EAAIN,KAAKU,MAAMV,KAAKW,KAAKJ,GAAIP,KAAKW,KAAK,EAAIJ,IAEtC,OAGG,GACtB,4BCZgB,WAYZ,SAAAK,EACIC,GAZIC,KAAAA,oBACAD,yBAAmB,EAAAhD,KACnBkD,cAAwE,IAAIC,IAAKnD,KACjFoD,OAAqB,QACrBC,SAA6C,IAAIF,IAUrDnD,KAAKgD,oBAAsBA,GAA4ClB,CAC3E,CAAC,IAAA3B,EAAA4C,EAAA3C,UAgHA,OAhHAD,EASOmD,gBAAA,SAAgBC,GACpB,IAAOC,EAAYD,KAAPE,EAAOF,EACnB,GAAKvD,KAAKqD,SAASK,IAAIF,IAAMxD,KAAKqD,SAASM,IAAIH,EAAK,IAAIL,KAExD,IAAMS,EAAS5D,KAAKqD,SAASQ,IAAIL,GACjC,GAAII,EAAOF,IAAID,GACX,OAAOG,EAAOC,IAAIJ,GAGtB,IAAM/C,EAAMV,KAAKoD,OAAOzC,OAIxB,OAHAX,KAAKoD,OAAOxC,KAAK2C,GACjBK,EAAOD,IAAIF,EAAK/C,GAETA,CACX,EAACP,EAUM2D,gBAAA,SAAgBb,GACnBjD,KAAKiD,QAAUA,EACfjD,KAAKkD,cAAgB,IAAIC,IACzBnD,KAAKoD,OAAS,GACdpD,KAAKqD,SAAW,IAAIF,IAEpB,IAAAY,IAA2CC,EAA3CD,EAAAE,EAAsBjE,KAAKiD,QAAQiB,YAAQF,EAAAD,KAAAI,MAEvC,IAFyC,IACnCf,EADQY,EAAAzD,MACS6D,SAASC,YACvBC,EAAI,EAAGA,EAAIlB,EAAOzC,OAAS,EAAG2D,IAAK,CACxC,IAAMC,EAAOvE,KAAKsD,gBAAgBF,EAAOkB,IACnCE,EAAOxE,KAAKsD,gBAAgBF,EAAOkB,EAAI,IACvCG,EAAWzE,KAAKgD,oBAAoBI,EAAOkB,GAAIlB,EAAOkB,EAAI,IAE3DtE,KAAKkD,cAAcQ,IAAIa,IAAOvE,KAAKkD,cAAcS,IAAIY,EAAM,IAC3DvE,KAAKkD,cAAcQ,IAAIc,IAAOxE,KAAKkD,cAAcS,IAAIa,EAAM,IAEhExE,KAAKkD,cAAcW,IAAIU,GAAO3D,KAAK,CAAEJ,KAAMgE,EAAMC,SAAAA,IACjDzE,KAAKkD,cAAcW,IAAIW,GAAO5D,KAAK,CAAEJ,KAAM+D,EAAME,SAAAA,GACrD,CAER,EAACtE,EAWMuE,SAAA,SAASC,EAAuBC,GACnC,IAAK5E,KAAKiD,QACN,MAAU,IAAA4B,MAAM,oEAGpB,IAAMC,EAAW9E,KAAKsD,gBAAgBqB,EAAMP,SAASC,aAC/CU,EAAS/E,KAAKsD,gBAAgBsB,EAAIR,SAASC,aAEjD,GAAIS,IAAaC,EACb,YAGJ,IAAMC,EAAU,IAAIjF,EACpBiF,EAAQ3E,OAAO,EAAGyE,GAIlB,IAHA,IAAMG,EAAW,IAAI9B,IACf+B,EAAS,IAAI/B,IAAoB,CAAC,CAAC2B,EAAU,KAE5CE,EAAQ5D,OAAS,GAAG,CACvB,IAAM+D,EAAUH,EAAQjE,aAExB,GAAIoE,IAAYJ,EAAQ,CAGpB,IAFA,IAAMK,EAAmB,GACrBC,EAA+BF,OACfG,IAAbD,GACHD,EAAKG,QAAQvF,KAAKoD,OAAOiC,IACzBA,EAAWJ,EAASpB,IAAIwB,GAE5B,MAAO,CACHG,KAAM,UACNpB,SAAU,CAAEoB,KAAM,aAAcnB,YAAae,GAC7CK,WAAY,CAAA,EAEpB,CAIA,IAFA,IAEgCC,EAAhCC,EAAA1B,EAFkBjE,KAAKkD,cAAcW,IAAIsB,IAAY,MAErBO,EAAAC,KAAAxB,MAAE,KAAAyB,EAAAC,EAAvBC,EAAQJ,EAAAnF,MACTwF,GAAsCH,OAApBA,EAACV,EAAOrB,IAAIsB,IAAQS,EAAII,UAAYF,EAASrB,SACrE,GAAIsB,GAA4C,OAA7BF,EAAIX,EAAOrB,IAAIiC,EAAStF,OAAKqF,EAAIG,UAAW,CAC3Df,EAAStB,IAAImC,EAAStF,KAAM2E,GAC5BD,EAAOvB,IAAImC,EAAStF,KAAMuF,GAC1B,IAAME,EAASF,EAAkB/F,KAAKgD,oBAAoBhD,KAAKoD,OAAO0C,EAAStF,MAAOR,KAAKoD,OAAO2B,IAClGC,EAAQ3E,OAAO4F,EAAQH,EAAStF,KACpC,CACJ,CACJ,CAEA,WACJ,EAACuC,CAAA,CAhIW,sBCoBV,SAA2BU,GAC7B,IACMyC,EAAK,EAAI,cACTC,EAAKD,GAAM,EAAIA,GACfE,EAAMjE,KAAKC,GAAK,IAEhBiE,EAASlE,KAAKS,IAAIa,EAAM2C,GACxBE,EAAK,GAAK,EAAIH,GAAM,EAAIE,EAASA,IACjCE,EAAIpE,KAAKW,KAAKwD,GAEdE,EATK,SASDJ,EACJK,EAAKD,EAAID,EAAIF,EACbK,EAAKF,EAAID,EAAID,GAAM,EAAIH,GAE7B,OAAgB,SAASzD,EAAaiE,GAGlC,IAFA,IAAIC,EAAWlE,EAAE,GAAKiE,EAAE,GAEjBC,GAAY,KAAKA,GAAY,IACpC,KAAOA,EAAW,KAAKA,GAAY,IAEnC,IAAMC,EAAKD,EAAWH,EAChBK,GAAMpE,EAAE,GAAKiE,EAAE,IAAMD,EAE3B,OAAOvE,KAAKW,KAAK+D,EAAKA,EAAKC,EAAKA,EACpC,CACJ"}