terra-route 0.0.7 → 0.0.8

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 fast library for routing on GeoJSON LineStrings networks, where LineStrings share identical coordinates. Terra Routes main aim is currently performance - it uses bidirectional A* to help achieve this.
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 - it uses A* to help achieve this.
4
4
 
5
5
  ## Install
6
6
 
@@ -1,2 +1,2 @@
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=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},n=/*#__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,p=e[u].index;(d<h||d===h&&p<c)&&(s=u,h=d,c=p)}if(!(h<a||h===a&&c<i))break;e[t]=e[s],t=s}e[t]=n},t}();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.heap=void 0,this.heap=null!=t&&t.heap?t.heap:n,this.distanceMeasurement=null!=t&&t.distanceMeasurement?t.distanceMeasurement:r}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,r){if(!this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var n=this.coordinateIndex(t.geometry.coordinates),a=this.coordinateIndex(r.geometry.coordinates);if(n===a)return null;var i=new this.heap;i.insert(0,n);for(var o=new Map,s=new Map([[n,0]]),h=new Set;i.size()>0;){var c=i.extractMin();if(c===a)break;h.add(c);for(var u,d=e(this.adjacencyList.get(c)||[]);!(u=d()).done;){var p,f,l=u.value,v=(null!=(p=s.get(c))?p:Infinity)+l.distance;if(v<(null!=(f=s.get(l.node))?f:Infinity)){o.set(l.node,c),s.set(l.node,v);var y=v+this.distanceMeasurement(this.coords[l.node],this.coords[a]);i.insert(y,l.node)}}}if(!o.has(a))return null;for(var M=[],g=a;void 0!==g;)M.unshift(this.coords[g]),g=o.get(g);return{type:"Feature",geometry:{type:"LineString",coordinates:M},properties:{}}},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=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=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},n=/*#__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(t>i.key||t===i.key&&r.index>i.index)break;this.heap[n]=i,n=a}this.heap[n]=r},e.extractMin=function(){var t=this.heap,e=t.length;if(0===e)return null;var r=t[0],n=t.pop();return e>1&&(t[0]=n,this.bubbleDown(0)),r.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],u=o+1;if(u<r){var c=e[u];(c.key<h.key||c.key===h.key&&c.index<h.index)&&(s=u,h=c)}if(!(h.key<a||h.key===a&&h.index<i))break;e[t]=h,t=s}e[t]=n},t}();exports.TerraRoute=/*#__PURE__*/function(){function t(t){var e,a;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.distanceMeasurement=null!=(e=null==t?void 0:t.distanceMeasurement)?e:r,this.heapConstructor=null!=(a=null==t?void 0:t.heap)?a:n}var a=t.prototype;return a.buildRouteGraph=function(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[];for(var r,n=this.coordinateIndexMap,a=this.coordinates,i=this.adjacencyList,o=this.distanceMeasurement,s=e(t.features);!(r=s()).done;)for(var h=r.value.geometry.coordinates,u=0;u<h.length-1;u++){var c=h[u],d=c[0],l=c[1],f=h[u+1],v=f[0],p=f[1],y=n.get(d);y||(y=new Map,n.set(d,y));var M=y.get(l);void 0===M&&(M=a.length,a.push(h[u]),y.set(l,M),i[M]=[]);var g=n.get(v);g||(g=new Map,n.set(v,g));var b=g.get(p);void 0===b&&(b=a.length,a.push(h[u+1]),g.set(p,b),i[b]=[]);var x=o(h[u],h[u+1]);i[M].push({node:b,distance:x}),i[b].push({node:M,distance:x})}},a.getRoute=function(t,r){if(!this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var n=this.getOrCreateIndex(t.geometry.coordinates),a=this.getOrCreateIndex(r.geometry.coordinates);if(n===a)return null;var i=new this.heapConstructor;i.insert(0,n);var o=this.coordinates.length,s=new Array(o).fill(Infinity),h=new Array(o).fill(-1),u=new Array(o).fill(!1);for(s[n]=0;i.size()>0;){var c=i.extractMin();if(!u[c]){if(c===a)break;u[c]=!0;for(var d,l=e(this.adjacencyList[c]||[]);!(d=l()).done;){var f=d.value,v=s[c]+f.distance;if(v<s[f.node]){s[f.node]=v,h[f.node]=c;var p=this.distanceMeasurement(this.coordinates[f.node],this.coordinates[a]);i.insert(v+p,f.node)}}}}if(h[a]<0)return null;for(var y=[],M=a;M!==n;)y.unshift(this.coordinates[M]),M=h[M];return y.unshift(this.coordinates[n]),{type:"Feature",geometry:{type:"LineString",coordinates:y},properties:{}}},a.getOrCreateIndex=function(t){var e=t[0],r=t[1],n=this.coordinateIndexMap.get(e);n||(n=new Map,this.coordinateIndexMap.set(e,n));var a=n.get(r);return void 0===a&&(a=this.coordinates.length,this.coordinates.push(t),n.set(r,a),this.adjacencyList[a]=[]),a},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,u=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])*u;return Math.sqrt(n*n+a*a)}},exports.haversineDistance=r;
2
2
  //# sourceMappingURL=terra-route.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.cjs","sources":["../src/distance/haversine.ts","../src/heap/min-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\nexport class MinHeap implements Heap {\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 // eslint-disable-next-line no-constant-condition\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}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\nimport { MinHeap } from \"./heap/min-heap\";\nimport { HeapConstructor } from \"./heap/heap\";\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 private heap: HeapConstructor;\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(options?: {\n distanceMeasurement?: (positionA: Position, positionB: Position) => number,\n heap?: HeapConstructor\n }) {\n this.heap = options?.heap ? options.heap : MinHeap\n this.distanceMeasurement = options?.distanceMeasurement ? options.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 index = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, index);\n\n return index;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into 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 aIndex = this.coordinateIndex(coords[i]);\n const bIndex = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIndex)) this.adjacencyList.set(aIndex, []);\n if (!this.adjacencyList.has(bIndex)) this.adjacencyList.set(bIndex, []);\n\n this.adjacencyList.get(aIndex)!.push({ node: bIndex, distance });\n this.adjacencyList.get(bIndex)!.push({ node: aIndex, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>,\n end: Feature<Point>\n ): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n const startIndex = this.coordinateIndex(start.geometry.coordinates);\n const endIndex = this.coordinateIndex(end.geometry.coordinates);\n\n if (startIndex === endIndex) {\n return null;\n }\n\n const openSet = new this.heap();\n openSet.insert(0, startIndex);\n\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIndex, 0]]);\n const visited = new Set<number>();\n\n while (openSet.size() > 0) {\n // Extract the node with the smallest fScore\n const current = openSet.extractMin()!;\n\n // If we've reached the end node, we're done\n if (current === endIndex) {\n break;\n }\n\n visited.add(current);\n\n // Explore neighbors\n for (const neighbor of this.adjacencyList.get(current) || []) {\n // Tentative cost from start to this neighbor\n const tentativeG = (gScore.get(current) ?? Infinity) + neighbor.distance;\n\n // If this path to neighbor is better, record it\n if (tentativeG < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeG);\n\n // Calculate fScore: gScore + heuristic distance to the end\n const fScore =\n tentativeG +\n this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIndex]);\n\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n // If we never set a path to the end node, there's no route\n if (!cameFrom.has(endIndex)) {\n return null;\n }\n\n // Reconstruct the path from end node to start node\n const path: Position[] = [];\n let node = endIndex;\n\n while (node !== undefined) {\n path.unshift(this.coords[node]);\n node = cameFrom.get(node)!;\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","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","TerraRoute","options","network","distanceMeasurement","adjacencyList","Map","coords","coordMap","coordinateIndex","coord","lng","lat","has","set","latMap","get","buildRouteGraph","_step","_iterator","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIndex","bIndex","distance","getRoute","start","end","Error","startIndex","endIndex","openSet","cameFrom","gScore","visited","Set","current","add","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeG","Infinity","fScore","path","undefined","unshift","type","properties","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy"],"mappings":"oyBAGa,IAAAA,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,ECvBaK,eAAO,WAAA,SAAAA,IAAAC,KACRC,KAA6D,GAC7DC,KAAAA,cAAgB,CAAC,KAAAC,EAAAJ,EAAAK,UAoFxB,OApFwBD,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,GASf,IARA,IAAQT,EAASD,KAATC,KACFU,EAASV,EAAKU,OAEdH,EAAOP,EAAKS,GACZW,EAAUb,EAAKF,IACfgB,EAAYd,EAAKC,QAGV,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,CAtFe,mCCwBhB,WAAA,SAAA+B,EAAYC,GAGX/B,KAfOgC,aACAC,EAAAA,KAAAA,gCACAC,cAAwE,IAAIC,SAC5EC,OAAqB,GACrBC,KAAAA,SAA6C,IAAIF,IACjDlC,KAAAA,UAWJ,EAAAD,KAAKC,KAAc,MAAP8B,GAAAA,EAAS9B,KAAO8B,EAAQ9B,KAAOF,EAC3CC,KAAKiC,0BAAsBF,GAAAA,EAASE,oBAAsBF,EAAQE,oBAAsBnD,CAC5F,CAAC,IAAAqB,EAAA2B,EAAA1B,iBAAAD,EASOmC,gBAAA,SAAgBC,GACpB,IAAOC,EAAYD,KAAPE,EAAOF,EACnB,GAAKvC,KAAKqC,SAASK,IAAIF,IAAMxC,KAAKqC,SAASM,IAAIH,EAAK,IAAIL,KAExD,IAAMS,EAAS5C,KAAKqC,SAASQ,IAAIL,GACjC,GAAII,EAAOF,IAAID,GACX,OAAOG,EAAOC,IAAIJ,GAGtB,IAAMhC,EAAQT,KAAKoC,OAAOzB,OAI1B,OAHAX,KAAKoC,OAAOxB,KAAK2B,GACjBK,EAAOD,IAAIF,EAAKhC,GAETA,CACX,EAACN,EAUM2C,gBAAA,SAAgBd,GACnBhC,KAAKgC,QAAUA,EACfhC,KAAKkC,cAAgB,IAAIC,IACzBnC,KAAKoC,OAAS,GACdpC,KAAKqC,SAAW,IAAIF,IAEpB,IAAA,IAA2CY,EAA3CC,EAAAC,EAAsBjD,KAAKgC,QAAQkB,YAAQH,EAAAC,KAAAG,MAEvC,IAFyC,IACnCf,EADQW,EAAAxC,MACS6C,SAASC,YACvBC,EAAI,EAAGA,EAAIlB,EAAOzB,OAAS,EAAG2C,IAAK,CACxC,IAAMC,EAASvD,KAAKsC,gBAAgBF,EAAOkB,IACrCE,EAASxD,KAAKsC,gBAAgBF,EAAOkB,EAAI,IACzCG,EAAWzD,KAAKiC,oBAAoBG,EAAOkB,GAAIlB,EAAOkB,EAAI,IAE3DtD,KAAKkC,cAAcQ,IAAIa,IAASvD,KAAKkC,cAAcS,IAAIY,EAAQ,IAC/DvD,KAAKkC,cAAcQ,IAAIc,IAASxD,KAAKkC,cAAcS,IAAIa,EAAQ,IAEpExD,KAAKkC,cAAcW,IAAIU,GAAS3C,KAAK,CAAEJ,KAAMgD,EAAQC,SAAAA,IACrDzD,KAAKkC,cAAcW,IAAIW,GAAS5C,KAAK,CAAEJ,KAAM+C,EAAQE,SAAAA,GACzD,CAER,EAACtD,EAWMuD,SAAA,SACHC,EACAC,GAEA,IAAK5D,KAAKgC,QACN,MAAU,IAAA6B,MAAM,kEAGpB,IAAMC,EAAa9D,KAAKsC,gBAAgBqB,EAAMP,SAASC,aACjDU,EAAW/D,KAAKsC,gBAAgBsB,EAAIR,SAASC,aAEnD,GAAIS,IAAeC,EACf,OAAO,KAGX,IAAMC,EAAU,IAAQhE,KAACC,KACzB+D,EAAQ3D,OAAO,EAAGyD,GAMlB,IAJA,IAAMG,EAAW,IAAI9B,IACf+B,EAAS,IAAI/B,IAAoB,CAAC,CAAC2B,EAAY,KAC/CK,EAAU,IAAIC,IAEbJ,EAAQ5C,OAAS,GAAG,CAEvB,IAAMiD,EAAUL,EAAQjD,aAGxB,GAAIsD,IAAYN,EACZ,MAGJI,EAAQG,IAAID,GAGZ,IAAA,IAA4DE,EAA5DC,EAAAvB,EAAuBjD,KAAKkC,cAAcW,IAAIwB,IAAY,MAAEE,EAAAC,KAAArB,MAAE,KAAAsB,EAAAC,EAAnDC,EAAQJ,EAAAhE,MAETqE,UAAaH,EAACP,EAAOrB,IAAIwB,IAAQI,EAAII,UAAYF,EAASlB,SAGhE,GAAImB,GAAuC,OAA7BF,EAAIR,EAAOrB,IAAI8B,EAASnE,OAAKkE,EAAIG,UAAW,CACtDZ,EAAStB,IAAIgC,EAASnE,KAAM6D,GAC5BH,EAAOvB,IAAIgC,EAASnE,KAAMoE,GAG1B,IAAME,EACFF,EACA5E,KAAKiC,oBAAoBjC,KAAKoC,OAAOuC,EAASnE,MAAOR,KAAKoC,OAAO2B,IAErEC,EAAQ3D,OAAOyE,EAAQH,EAASnE,KACpC,CACJ,CACJ,CAGA,IAAKyD,EAASvB,IAAIqB,GACd,OACJ,KAMA,IAHA,IAAMgB,EAAmB,GACrBvE,EAAOuD,OAEKiB,IAATxE,GACHuE,EAAKE,QAAQjF,KAAKoC,OAAO5B,IACzBA,EAAOyD,EAASpB,IAAIrC,GAGxB,MAAO,CACH0E,KAAM,UACN9B,SAAU,CAAE8B,KAAM,aAAc7B,YAAa0B,GAC7CI,WAAY,CAAA,EAEpB,EAACrD,CAAA,CA9ID,4BCME,SAA2BW,GAC7B,IACM2C,EAAK,EAAI,cACTC,EAAKD,GAAM,EAAIA,GACfE,EAAMnG,KAAKC,GAAK,IAEhBmG,EAASpG,KAAKS,IAAI6C,EAAM6C,GACxBE,EAAK,GAAK,EAAIH,GAAM,EAAIE,EAASA,IACjCE,EAAItG,KAAKW,KAAK0F,GAEdE,EATK,SASDJ,EACJK,EAAKD,EAAID,EAAIF,EACbK,EAAKF,EAAID,EAAID,GAAM,EAAIH,GAE7B,OAAgB,SAAS3F,EAAamG,GAGlC,IAFA,IAAIC,EAAWpG,EAAE,GAAKmG,EAAE,GAEjBC,GAAY,KAAKA,GAAY,IACpC,KAAOA,EAAW,KAAKA,GAAY,IAEnC,IAAMC,EAAKD,EAAWH,EAChBK,GAAMtG,EAAE,GAAKmG,EAAE,IAAMD,EAE3B,OAAOzG,KAAKW,KAAKiG,EAAKA,EAAKC,EAAKA,EACpC,CACJ"}
1
+ {"version":3,"file":"terra-route.cjs","sources":["../src/distance/haversine.ts","../src/heap/min-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\nexport class MinHeap implements Heap {\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 currentIndex = this.heap.length;\n this.heap.push(node);\n\n while (currentIndex > 0) {\n const parentIndex = (currentIndex - 1) >>> 1;\n const parent = this.heap[parentIndex];\n\n if (\n key > parent.key ||\n (key === parent.key && node.index > parent.index)\n ) {\n break;\n }\n\n this.heap[currentIndex] = parent;\n currentIndex = parentIndex;\n }\n\n this.heap[currentIndex] = node;\n }\n\n extractMin(): number | null {\n const heap = this.heap;\n const length = heap.length;\n\n if (length === 0) {\n return null;\n }\n\n const minNode = heap[0];\n const endNode = heap.pop()!;\n\n if (length > 1) {\n 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(index: number): void {\n const heap = this.heap;\n const length = heap.length;\n const node = heap[index];\n const nodeKey = node.key;\n const nodeIndex = node.index;\n\n while (true) {\n const leftChildIndex = (index << 1) + 1;\n if (leftChildIndex >= length) {\n break;\n }\n\n let smallestIndex = leftChildIndex;\n let smallest = heap[leftChildIndex];\n\n const rightChildIndex = leftChildIndex + 1;\n if (rightChildIndex < length) {\n const right = heap[rightChildIndex];\n if (\n right.key < smallest.key ||\n (right.key === smallest.key && right.index < smallest.index)\n ) {\n smallestIndex = rightChildIndex;\n smallest = right;\n }\n }\n\n if (\n smallest.key < nodeKey ||\n (smallest.key === nodeKey && smallest.index < nodeIndex)\n ) {\n heap[index] = smallest;\n index = smallestIndex;\n } else {\n break;\n }\n }\n\n heap[index] = node;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\nimport { MinHeap } from \"./heap/min-heap\";\nimport { HeapConstructor } from \"./heap/heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null;\n private distanceMeasurement: (a: Position, b: Position) => number;\n private heapConstructor: HeapConstructor;\n\n // Map from longitude → (map from latitude → index)\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map();\n private coordinates: Position[] = [];\n private adjacencyList: Array<Array<{ node: number; distance: number }>> = [];\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number;\n heap?: HeapConstructor;\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance;\n this.heapConstructor = options?.heap ?? MinHeap;\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 public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network;\n\n // Reset everything\n this.coordinateIndexMap = new Map();\n this.coordinates = [];\n this.adjacencyList = [];\n\n // Hoist to locals for speed\n const coordIndexMapLocal = this.coordinateIndexMap;\n const coordsLocal = this.coordinates;\n const adjListLocal = this.adjacencyList;\n const measureDistance = this.distanceMeasurement;\n\n for (const feature of network.features) {\n const lineCoords = feature.geometry.coordinates;\n\n for (let i = 0; i < lineCoords.length - 1; i++) {\n const [lngA, latA] = lineCoords[i];\n const [lngB, latB] = lineCoords[i + 1];\n\n // get or assign index for A \n let latMapA = coordIndexMapLocal.get(lngA);\n if (!latMapA) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(lineCoords[i]);\n latMapA.set(latA, indexA);\n adjListLocal[indexA] = [];\n }\n\n // get or assign index for B \n let latMapB = coordIndexMapLocal.get(lngB);\n if (!latMapB) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(lineCoords[i + 1]);\n latMapB.set(latB, indexB);\n adjListLocal[indexB] = [];\n }\n\n // record the bidirectional edge \n const segmentDistance = measureDistance(lineCoords[i], lineCoords[i + 1]);\n adjListLocal[indexA].push({ node: indexB, distance: segmentDistance });\n adjListLocal[indexB].push({ node: indexA, distance: segmentDistance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>,\n end: Feature<Point>\n ): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // ensure start/end are in the index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates);\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates);\n\n if (startIndex === endIndex) {\n return null;\n }\n\n const openSet = new this.heapConstructor();\n openSet.insert(0, startIndex);\n\n const nodeCount = this.coordinates.length;\n const gScore = new Array<number>(nodeCount).fill(Infinity);\n const cameFrom = new Array<number>(nodeCount).fill(-1);\n const visited = new Array<boolean>(nodeCount).fill(false);\n\n gScore[startIndex] = 0;\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n if (visited[current]) {\n continue;\n }\n if (current === endIndex) {\n break;\n }\n visited[current] = true;\n\n for (const neighbor of this.adjacencyList[current] || []) {\n const tentativeG = gScore[current] + neighbor.distance;\n if (tentativeG < gScore[neighbor.node]) {\n gScore[neighbor.node] = tentativeG;\n cameFrom[neighbor.node] = current;\n const heuristic = this.distanceMeasurement(\n this.coordinates[neighbor.node],\n this.coordinates[endIndex]\n );\n openSet.insert(tentativeG + heuristic, neighbor.node);\n }\n }\n }\n\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path\n const path: Position[] = [];\n let current = endIndex;\n while (current !== startIndex) {\n path.unshift(this.coordinates[current]);\n current = cameFrom[current];\n }\n path.unshift(this.coordinates[startIndex]);\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number {\n const [lng, lat] = coord;\n let latMap = this.coordinateIndexMap.get(lng);\n if (!latMap) {\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n let index = latMap.get(lat);\n if (index === undefined) {\n index = this.coordinates.length;\n this.coordinates.push(coord);\n latMap.set(lat, index);\n // ensure adjacencyList covers this new node\n this.adjacencyList[index] = [];\n }\n return index;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","MinHeap","heap","this","insertCounter","_proto","prototype","insert","key","value","node","index","currentIndex","length","push","parentIndex","parent","extractMin","minNode","endNode","pop","bubbleDown","size","nodeKey","nodeIndex","leftChildIndex","smallestIndex","smallest","rightChildIndex","right","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","buildRouteGraph","_step","coordIndexMapLocal","coordsLocal","adjListLocal","measureDistance","_iterator","_createForOfIteratorHelperLoose","features","done","lineCoords","geometry","i","_lineCoords$i","lngA","latA","_lineCoords","lngB","latB","latMapA","get","set","indexA","undefined","latMapB","indexB","segmentDistance","distance","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","openSet","nodeCount","gScore","Array","fill","Infinity","cameFrom","visited","current","_iterator2","_step2","neighbor","tentativeG","heuristic","path","unshift","type","properties","coord","lng","lat","latMap","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy"],"mappings":"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"}
@@ -2,27 +2,19 @@ import { FeatureCollection, LineString, Point, Feature, Position } from "geojson
2
2
  import { haversineDistance } from "./distance/haversine";
3
3
  import { createCheapRuler } from "./distance/cheap-ruler";
4
4
  import { HeapConstructor } from "./heap/heap";
5
- /**
6
- * TerraRoute is a routing utility for finding the shortest path
7
- * between two geographic points over a given GeoJSON LineString network.
8
- *
9
- * The class builds an internal graph structure based on the provided network,
10
- * then applies A* algorithm to compute the shortest route.
11
- */
12
- declare class TerraRoute {
5
+ interface Router {
6
+ buildRouteGraph(network: FeatureCollection<LineString>): void;
7
+ getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;
8
+ }
9
+ declare class TerraRoute implements Router {
13
10
  private network;
14
11
  private distanceMeasurement;
12
+ private heapConstructor;
13
+ private coordinateIndexMap;
14
+ private coordinates;
15
15
  private adjacencyList;
16
- private coords;
17
- private coordMap;
18
- private heap;
19
- /**
20
- * Creates a new instance of TerraRoute.
21
- *
22
- * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).
23
- */
24
16
  constructor(options?: {
25
- distanceMeasurement?: (positionA: Position, positionB: Position) => number;
17
+ distanceMeasurement?: (a: Position, b: Position) => number;
26
18
  heap?: HeapConstructor;
27
19
  });
28
20
  /**
@@ -32,15 +24,6 @@ declare class TerraRoute {
32
24
  * @param coord - A GeoJSON Position array representing [longitude, latitude].
33
25
  * @returns A unique numeric index for the coordinate.
34
26
  */
35
- private coordinateIndex;
36
- /**
37
- * Builds the internal graph representation (adjacency list) from the input network.
38
- * Each LineString segment is translated into graph edges with associated distances.
39
- * Assumes that the network is a connected graph of LineStrings with shared coordinates. Calling this
40
- * method with a new network overwrite any existing network and reset all internal data structures.
41
- *
42
- * @param network - A GeoJSON FeatureCollection of LineStrings representing the road network.
43
- */
44
27
  buildRouteGraph(network: FeatureCollection<LineString>): void;
45
28
  /**
46
29
  * Computes the shortest route between two points in the network using the A* algorithm.
@@ -52,5 +35,9 @@ declare class TerraRoute {
52
35
  * @throws Error if the network has not been built yet with buildRouteGraph(network).
53
36
  */
54
37
  getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;
38
+ /**
39
+ * Helper to index start/end in getRoute.
40
+ */
41
+ private getOrCreateIndex;
55
42
  }
56
43
  export { TerraRoute, createCheapRuler, haversineDistance };
@@ -1,2 +1,2 @@
1
- const t=(t,e)=>{const s=t=>t*Math.PI/180,n=s(t[1]),i=s(t[0]),o=s(e[1]),a=o-n,r=s(e[0])-i,h=Math.sin(a/2)*Math.sin(a/2)+Math.cos(n)*Math.cos(o)*Math.sin(r/2)*Math.sin(r/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};function e(t){const e=1/298.257223563,s=e*(2-e),n=Math.PI/180,i=Math.cos(t*n),o=1/(1-s*(1-i*i)),a=Math.sqrt(o),r=6378.137*n,h=r*a*i,c=r*a*o*(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,i=(t[1]-e[1])*c;return Math.sqrt(n*n+i*i)}}class s{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],i=n.key,o=n.index;for(;;){const n=1+(t<<1);if(n>=s)break;let a=n,r=e[n].key,h=e[n].index;const c=n+1;if(c<s){const t=e[c].key,s=e[c].index;(t<r||t===r&&s<h)&&(a=c,r=t,h=s)}if(!(r<i||r===i&&h<o))break;e[t]=e[a],t=a}e[t]=n}}class n{constructor(e){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.heap=void 0,this.heap=null!=e&&e.heap?e.heap:s,this.distanceMeasurement=null!=e&&e.distanceMeasurement?e.distanceMeasurement:t}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 i=this.coords.length;return this.coords.push(t),n.set(s,i),i}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]),i=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:i}),this.adjacencyList.get(n).push({node:s,distance:i})}}}getRoute(t,e){if(!this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");const s=this.coordinateIndex(t.geometry.coordinates),n=this.coordinateIndex(e.geometry.coordinates);if(s===n)return null;const i=new this.heap;i.insert(0,s);const o=new Map,a=new Map([[s,0]]),r=new Set;for(;i.size()>0;){const t=i.extractMin();if(t===n)break;r.add(t);for(const e of this.adjacencyList.get(t)||[]){var h,c;const s=(null!=(h=a.get(t))?h:Infinity)+e.distance;if(s<(null!=(c=a.get(e.node))?c:Infinity)){o.set(e.node,t),a.set(e.node,s);const r=s+this.distanceMeasurement(this.coords[e.node],this.coords[n]);i.insert(r,e.node)}}}if(!o.has(n))return null;const d=[];let u=n;for(;void 0!==u;)d.unshift(this.coords[u]),u=o.get(u);return{type:"Feature",geometry:{type:"LineString",coordinates:d},properties:{}}}}export{n as TerraRoute,e as createCheapRuler,t as haversineDistance};
1
+ const t=(t,e)=>{const n=t=>t*Math.PI/180,s=n(t[1]),i=n(t[0]),o=n(e[1]),r=o-s,a=n(e[0])-i,h=Math.sin(r/2)*Math.sin(r/2)+Math.cos(s)*Math.cos(o)*Math.sin(a/2)*Math.sin(a/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};function e(t){const e=1/298.257223563,n=e*(2-e),s=Math.PI/180,i=Math.cos(t*s),o=1/(1-n*(1-i*i)),r=Math.sqrt(o),a=6378.137*s,h=a*r*i,c=a*r*o*(1-n);return function(t,e){let n=t[0]-e[0];for(;n<-180;)n+=360;for(;n>180;)n-=360;const s=n*h,i=(t[1]-e[1])*c;return Math.sqrt(s*s+i*i)}}class n{constructor(){this.heap=[],this.insertCounter=0}insert(t,e){const n={key:t,value:e,index:this.insertCounter++};let s=this.heap.length;for(this.heap.push(n);s>0;){const e=s-1>>>1,i=this.heap[e];if(t>i.key||t===i.key&&n.index>i.index)break;this.heap[s]=i,s=e}this.heap[s]=n}extractMin(){const t=this.heap,e=t.length;if(0===e)return null;const n=t[0],s=t.pop();return e>1&&(t[0]=s,this.bubbleDown(0)),n.value}size(){return this.heap.length}bubbleDown(t){const e=this.heap,n=e.length,s=e[t],i=s.key,o=s.index;for(;;){const s=1+(t<<1);if(s>=n)break;let r=s,a=e[s];const h=s+1;if(h<n){const t=e[h];(t.key<a.key||t.key===a.key&&t.index<a.index)&&(r=h,a=t)}if(!(a.key<i||a.key===i&&a.index<o))break;e[t]=a,t=r}e[t]=s}}class s{constructor(e){var s,i;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.distanceMeasurement=null!=(s=null==e?void 0:e.distanceMeasurement)?s:t,this.heapConstructor=null!=(i=null==e?void 0:e.heap)?i:n}buildRouteGraph(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[];const e=this.coordinateIndexMap,n=this.coordinates,s=this.adjacencyList,i=this.distanceMeasurement;for(const o of t.features){const t=o.geometry.coordinates;for(let o=0;o<t.length-1;o++){const[r,a]=t[o],[h,c]=t[o+1];let d=e.get(r);d||(d=new Map,e.set(r,d));let l=d.get(a);void 0===l&&(l=n.length,n.push(t[o]),d.set(a,l),s[l]=[]);let u=e.get(h);u||(u=new Map,e.set(h,u));let p=u.get(c);void 0===p&&(p=n.length,n.push(t[o+1]),u.set(c,p),s[p]=[]);const f=i(t[o],t[o+1]);s[l].push({node:p,distance:f}),s[p].push({node:l,distance:f})}}}getRoute(t,e){if(!this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");const n=this.getOrCreateIndex(t.geometry.coordinates),s=this.getOrCreateIndex(e.geometry.coordinates);if(n===s)return null;const i=new this.heapConstructor;i.insert(0,n);const o=this.coordinates.length,r=new Array(o).fill(Infinity),a=new Array(o).fill(-1),h=new Array(o).fill(!1);for(r[n]=0;i.size()>0;){const t=i.extractMin();if(!h[t]){if(t===s)break;h[t]=!0;for(const e of this.adjacencyList[t]||[]){const n=r[t]+e.distance;if(n<r[e.node]){r[e.node]=n,a[e.node]=t;const o=this.distanceMeasurement(this.coordinates[e.node],this.coordinates[s]);i.insert(n+o,e.node)}}}}if(a[s]<0)return null;const c=[];let d=s;for(;d!==n;)c.unshift(this.coordinates[d]),d=a[d];return c.unshift(this.coordinates[n]),{type:"Feature",geometry:{type:"LineString",coordinates:c},properties:{}}}getOrCreateIndex(t){const[e,n]=t;let s=this.coordinateIndexMap.get(e);s||(s=new Map,this.coordinateIndexMap.set(e,s));let i=s.get(n);return void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),s.set(n,i),this.adjacencyList[i]=[]),i}}export{s as TerraRoute,e as createCheapRuler,t as haversineDistance};
2
2
  //# sourceMappingURL=terra-route.modern.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.modern.js","sources":["../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/heap/min-heap.ts","../src/terra-route.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n return distance / 1000;\n}\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 { Heap } from \"./heap\";\n\nexport class MinHeap implements Heap {\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 // eslint-disable-next-line no-constant-condition\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}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\nimport { MinHeap } from \"./heap/min-heap\";\nimport { HeapConstructor } from \"./heap/heap\";\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 private heap: HeapConstructor;\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(options?: {\n distanceMeasurement?: (positionA: Position, positionB: Position) => number,\n heap?: HeapConstructor\n }) {\n this.heap = options?.heap ? options.heap : MinHeap\n this.distanceMeasurement = options?.distanceMeasurement ? options.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 index = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, index);\n\n return index;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into 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 aIndex = this.coordinateIndex(coords[i]);\n const bIndex = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIndex)) this.adjacencyList.set(aIndex, []);\n if (!this.adjacencyList.has(bIndex)) this.adjacencyList.set(bIndex, []);\n\n this.adjacencyList.get(aIndex)!.push({ node: bIndex, distance });\n this.adjacencyList.get(bIndex)!.push({ node: aIndex, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>,\n end: Feature<Point>\n ): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n const startIndex = this.coordinateIndex(start.geometry.coordinates);\n const endIndex = this.coordinateIndex(end.geometry.coordinates);\n\n if (startIndex === endIndex) {\n return null;\n }\n\n const openSet = new this.heap();\n openSet.insert(0, startIndex);\n\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIndex, 0]]);\n const visited = new Set<number>();\n\n while (openSet.size() > 0) {\n // Extract the node with the smallest fScore\n const current = openSet.extractMin()!;\n\n // If we've reached the end node, we're done\n if (current === endIndex) {\n break;\n }\n\n visited.add(current);\n\n // Explore neighbors\n for (const neighbor of this.adjacencyList.get(current) || []) {\n // Tentative cost from start to this neighbor\n const tentativeG = (gScore.get(current) ?? Infinity) + neighbor.distance;\n\n // If this path to neighbor is better, record it\n if (tentativeG < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeG);\n\n // Calculate fScore: gScore + heuristic distance to the end\n const fScore =\n tentativeG +\n this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIndex]);\n\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n // If we never set a path to the end node, there's no route\n if (!cameFrom.has(endIndex)) {\n return null;\n }\n\n // Reconstruct the path from end node to start node\n const path: Position[] = [];\n let node = endIndex;\n\n while (node !== undefined) {\n path.unshift(this.coords[node]);\n node = cameFrom.get(node)!;\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }"],"names":["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","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","TerraRoute","options","network","distanceMeasurement","adjacencyList","Map","coords","coordMap","coordinateIndex","coord","lng","has","set","latMap","get","buildRouteGraph","feature","features","geometry","coordinates","i","aIndex","bIndex","distance","getRoute","start","end","Error","startIndex","endIndex","openSet","cameFrom","gScore","visited","Set","current","add","neighbor","_gScore$get","_gScore$get2","tentativeG","Infinity","fScore","path","undefined","unshift","type","properties"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.modern.js","sources":["../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/heap/min-heap.ts","../src/terra-route.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n return distance / 1000;\n}\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 { Heap } from \"./heap\";\n\nexport class MinHeap implements Heap {\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 currentIndex = this.heap.length;\n this.heap.push(node);\n\n while (currentIndex > 0) {\n const parentIndex = (currentIndex - 1) >>> 1;\n const parent = this.heap[parentIndex];\n\n if (\n key > parent.key ||\n (key === parent.key && node.index > parent.index)\n ) {\n break;\n }\n\n this.heap[currentIndex] = parent;\n currentIndex = parentIndex;\n }\n\n this.heap[currentIndex] = node;\n }\n\n extractMin(): number | null {\n const heap = this.heap;\n const length = heap.length;\n\n if (length === 0) {\n return null;\n }\n\n const minNode = heap[0];\n const endNode = heap.pop()!;\n\n if (length > 1) {\n 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(index: number): void {\n const heap = this.heap;\n const length = heap.length;\n const node = heap[index];\n const nodeKey = node.key;\n const nodeIndex = node.index;\n\n while (true) {\n const leftChildIndex = (index << 1) + 1;\n if (leftChildIndex >= length) {\n break;\n }\n\n let smallestIndex = leftChildIndex;\n let smallest = heap[leftChildIndex];\n\n const rightChildIndex = leftChildIndex + 1;\n if (rightChildIndex < length) {\n const right = heap[rightChildIndex];\n if (\n right.key < smallest.key ||\n (right.key === smallest.key && right.index < smallest.index)\n ) {\n smallestIndex = rightChildIndex;\n smallest = right;\n }\n }\n\n if (\n smallest.key < nodeKey ||\n (smallest.key === nodeKey && smallest.index < nodeIndex)\n ) {\n heap[index] = smallest;\n index = smallestIndex;\n } else {\n break;\n }\n }\n\n heap[index] = node;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\nimport { MinHeap } from \"./heap/min-heap\";\nimport { HeapConstructor } from \"./heap/heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null;\n private distanceMeasurement: (a: Position, b: Position) => number;\n private heapConstructor: HeapConstructor;\n\n // Map from longitude → (map from latitude → index)\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map();\n private coordinates: Position[] = [];\n private adjacencyList: Array<Array<{ node: number; distance: number }>> = [];\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number;\n heap?: HeapConstructor;\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance;\n this.heapConstructor = options?.heap ?? MinHeap;\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 public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network;\n\n // Reset everything\n this.coordinateIndexMap = new Map();\n this.coordinates = [];\n this.adjacencyList = [];\n\n // Hoist to locals for speed\n const coordIndexMapLocal = this.coordinateIndexMap;\n const coordsLocal = this.coordinates;\n const adjListLocal = this.adjacencyList;\n const measureDistance = this.distanceMeasurement;\n\n for (const feature of network.features) {\n const lineCoords = feature.geometry.coordinates;\n\n for (let i = 0; i < lineCoords.length - 1; i++) {\n const [lngA, latA] = lineCoords[i];\n const [lngB, latB] = lineCoords[i + 1];\n\n // get or assign index for A \n let latMapA = coordIndexMapLocal.get(lngA);\n if (!latMapA) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(lineCoords[i]);\n latMapA.set(latA, indexA);\n adjListLocal[indexA] = [];\n }\n\n // get or assign index for B \n let latMapB = coordIndexMapLocal.get(lngB);\n if (!latMapB) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(lineCoords[i + 1]);\n latMapB.set(latB, indexB);\n adjListLocal[indexB] = [];\n }\n\n // record the bidirectional edge \n const segmentDistance = measureDistance(lineCoords[i], lineCoords[i + 1]);\n adjListLocal[indexA].push({ node: indexB, distance: segmentDistance });\n adjListLocal[indexB].push({ node: indexA, distance: segmentDistance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>,\n end: Feature<Point>\n ): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // ensure start/end are in the index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates);\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates);\n\n if (startIndex === endIndex) {\n return null;\n }\n\n const openSet = new this.heapConstructor();\n openSet.insert(0, startIndex);\n\n const nodeCount = this.coordinates.length;\n const gScore = new Array<number>(nodeCount).fill(Infinity);\n const cameFrom = new Array<number>(nodeCount).fill(-1);\n const visited = new Array<boolean>(nodeCount).fill(false);\n\n gScore[startIndex] = 0;\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n if (visited[current]) {\n continue;\n }\n if (current === endIndex) {\n break;\n }\n visited[current] = true;\n\n for (const neighbor of this.adjacencyList[current] || []) {\n const tentativeG = gScore[current] + neighbor.distance;\n if (tentativeG < gScore[neighbor.node]) {\n gScore[neighbor.node] = tentativeG;\n cameFrom[neighbor.node] = current;\n const heuristic = this.distanceMeasurement(\n this.coordinates[neighbor.node],\n this.coordinates[endIndex]\n );\n openSet.insert(tentativeG + heuristic, neighbor.node);\n }\n }\n }\n\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path\n const path: Position[] = [];\n let current = endIndex;\n while (current !== startIndex) {\n path.unshift(this.coordinates[current]);\n current = cameFrom[current];\n }\n path.unshift(this.coordinates[startIndex]);\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number {\n const [lng, lat] = coord;\n let latMap = this.coordinateIndexMap.get(lng);\n if (!latMap) {\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n let index = latMap.get(lat);\n if (index === undefined) {\n index = this.coordinates.length;\n this.coordinates.push(coord);\n latMap.set(lat, index);\n // ensure adjacencyList covers this new node\n this.adjacencyList[index] = [];\n }\n return index;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }"],"names":["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","MinHeap","constructor","heap","this","insertCounter","insert","key","value","node","index","currentIndex","length","push","parentIndex","parent","extractMin","minNode","endNode","pop","bubbleDown","size","nodeKey","nodeIndex","leftChildIndex","smallestIndex","smallest","rightChildIndex","right","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","buildRouteGraph","coordIndexMapLocal","coordsLocal","adjListLocal","measureDistance","feature","features","lineCoords","geometry","i","lngA","latA","lngB","latB","latMapA","get","set","indexA","undefined","latMapB","indexB","segmentDistance","distance","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","openSet","nodeCount","gScore","Array","fill","Infinity","cameFrom","visited","current","neighbor","tentativeG","heuristic","path","unshift","type","properties","coord","lng","latMap"],"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 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 n=function(t,e){var n=function(t){return t*Math.PI/180},r=n(t[1]),a=n(t[0]),i=n(e[1]),o=i-r,s=n(e[0])-a,h=Math.sin(o/2)*Math.sin(o/2)+Math.cos(r)*Math.cos(i)*Math.sin(s/2)*Math.sin(s/2);return 2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h))*6371e3/1e3};function r(t){var e=1/298.257223563,n=e*(2-e),r=Math.PI/180,a=Math.cos(t*r),i=1/(1-n*(1-a*a)),o=Math.sqrt(i),s=6378.137*r,h=s*o*a,c=s*o*i*(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,a=(t[1]-e[1])*c;return Math.sqrt(r*r+a*a)}}var a=/*#__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 a=r-1>>>1,i=this.heap[a];if(n.key>i.key||n.key===i.key&&n.index>i.index)break;this.heap[r]=i,r=a}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],a=r.key,i=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<a||h===a&&c<i))break;e[t]=e[s],t=s}e[t]=r},t}(),i=/*#__PURE__*/function(){function t(t){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.heap=void 0,this.heap=null!=t&&t.heap?t.heap:a,this.distanceMeasurement=null!=t&&t.distanceMeasurement?t.distanceMeasurement:n}var r=t.prototype;return r.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 a=this.coords.length;return this.coords.push(t),r.set(n,a),a},r.buildRouteGraph=function(t){this.network=t,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map;for(var n,r=e(this.network.features);!(n=r()).done;)for(var a=n.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})}},r.getRoute=function(t,n){if(!this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var r=this.coordinateIndex(t.geometry.coordinates),a=this.coordinateIndex(n.geometry.coordinates);if(r===a)return null;var i=new this.heap;i.insert(0,r);for(var o=new Map,s=new Map([[r,0]]),h=new Set;i.size()>0;){var c=i.extractMin();if(c===a)break;h.add(c);for(var u,d=e(this.adjacencyList.get(c)||[]);!(u=d()).done;){var f,p,l=u.value,v=(null!=(f=s.get(c))?f:Infinity)+l.distance;if(v<(null!=(p=s.get(l.node))?p:Infinity)){o.set(l.node,c),s.set(l.node,v);var y=v+this.distanceMeasurement(this.coords[l.node],this.coords[a]);i.insert(y,l.node)}}}if(!o.has(a))return null;for(var M=[],g=a;void 0!==g;)M.unshift(this.coords[g]),g=o.get(g);return{type:"Feature",geometry:{type:"LineString",coordinates:M},properties:{}}},t}();export{i as TerraRoute,r as createCheapRuler,n 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=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 n(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,u=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])*u;return Math.sqrt(n*n+i*i)}}var i=/*#__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(t>a.key||t===a.key&&r.index>a.index)break;this.heap[n]=a,n=i}this.heap[n]=r},e.extractMin=function(){var t=this.heap,e=t.length;if(0===e)return null;var r=t[0],n=t.pop();return e>1&&(t[0]=n,this.bubbleDown(0)),r.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],u=o+1;if(u<r){var c=e[u];(c.key<h.key||c.key===h.key&&c.index<h.index)&&(s=u,h=c)}if(!(h.key<i||h.key===i&&h.index<a))break;e[t]=h,t=s}e[t]=n},t}(),a=/*#__PURE__*/function(){function t(t){var e,n;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.distanceMeasurement=null!=(e=null==t?void 0:t.distanceMeasurement)?e:r,this.heapConstructor=null!=(n=null==t?void 0:t.heap)?n:i}var n=t.prototype;return n.buildRouteGraph=function(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[];for(var r,n=this.coordinateIndexMap,i=this.coordinates,a=this.adjacencyList,o=this.distanceMeasurement,s=e(t.features);!(r=s()).done;)for(var h=r.value.geometry.coordinates,u=0;u<h.length-1;u++){var c=h[u],d=c[0],l=c[1],f=h[u+1],v=f[0],p=f[1],y=n.get(d);y||(y=new Map,n.set(d,y));var M=y.get(l);void 0===M&&(M=i.length,i.push(h[u]),y.set(l,M),a[M]=[]);var g=n.get(v);g||(g=new Map,n.set(v,g));var b=g.get(p);void 0===b&&(b=i.length,i.push(h[u+1]),g.set(p,b),a[b]=[]);var m=o(h[u],h[u+1]);a[M].push({node:b,distance:m}),a[b].push({node:M,distance:m})}},n.getRoute=function(t,r){if(!this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var n=this.getOrCreateIndex(t.geometry.coordinates),i=this.getOrCreateIndex(r.geometry.coordinates);if(n===i)return null;var a=new this.heapConstructor;a.insert(0,n);var o=this.coordinates.length,s=new Array(o).fill(Infinity),h=new Array(o).fill(-1),u=new Array(o).fill(!1);for(s[n]=0;a.size()>0;){var c=a.extractMin();if(!u[c]){if(c===i)break;u[c]=!0;for(var d,l=e(this.adjacencyList[c]||[]);!(d=l()).done;){var f=d.value,v=s[c]+f.distance;if(v<s[f.node]){s[f.node]=v,h[f.node]=c;var p=this.distanceMeasurement(this.coordinates[f.node],this.coordinates[i]);a.insert(v+p,f.node)}}}}if(h[i]<0)return null;for(var y=[],M=i;M!==n;)y.unshift(this.coordinates[M]),M=h[M];return y.unshift(this.coordinates[n]),{type:"Feature",geometry:{type:"LineString",coordinates:y},properties:{}}},n.getOrCreateIndex=function(t){var e=t[0],r=t[1],n=this.coordinateIndexMap.get(e);n||(n=new Map,this.coordinateIndexMap.set(e,n));var i=n.get(r);return void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),n.set(r,i),this.adjacencyList[i]=[]),i},t}();export{a as TerraRoute,n as createCheapRuler,r as haversineDistance};
2
2
  //# sourceMappingURL=terra-route.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.module.js","sources":["../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/heap/min-heap.ts","../src/terra-route.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n return distance / 1000;\n}\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 { Heap } from \"./heap\";\n\nexport class MinHeap implements Heap {\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 // eslint-disable-next-line no-constant-condition\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}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\nimport { MinHeap } from \"./heap/min-heap\";\nimport { HeapConstructor } from \"./heap/heap\";\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 private heap: HeapConstructor;\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(options?: {\n distanceMeasurement?: (positionA: Position, positionB: Position) => number,\n heap?: HeapConstructor\n }) {\n this.heap = options?.heap ? options.heap : MinHeap\n this.distanceMeasurement = options?.distanceMeasurement ? options.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 index = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, index);\n\n return index;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into 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 aIndex = this.coordinateIndex(coords[i]);\n const bIndex = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIndex)) this.adjacencyList.set(aIndex, []);\n if (!this.adjacencyList.has(bIndex)) this.adjacencyList.set(bIndex, []);\n\n this.adjacencyList.get(aIndex)!.push({ node: bIndex, distance });\n this.adjacencyList.get(bIndex)!.push({ node: aIndex, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>,\n end: Feature<Point>\n ): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n const startIndex = this.coordinateIndex(start.geometry.coordinates);\n const endIndex = this.coordinateIndex(end.geometry.coordinates);\n\n if (startIndex === endIndex) {\n return null;\n }\n\n const openSet = new this.heap();\n openSet.insert(0, startIndex);\n\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIndex, 0]]);\n const visited = new Set<number>();\n\n while (openSet.size() > 0) {\n // Extract the node with the smallest fScore\n const current = openSet.extractMin()!;\n\n // If we've reached the end node, we're done\n if (current === endIndex) {\n break;\n }\n\n visited.add(current);\n\n // Explore neighbors\n for (const neighbor of this.adjacencyList.get(current) || []) {\n // Tentative cost from start to this neighbor\n const tentativeG = (gScore.get(current) ?? Infinity) + neighbor.distance;\n\n // If this path to neighbor is better, record it\n if (tentativeG < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeG);\n\n // Calculate fScore: gScore + heuristic distance to the end\n const fScore =\n tentativeG +\n this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIndex]);\n\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n // If we never set a path to the end node, there's no route\n if (!cameFrom.has(endIndex)) {\n return null;\n }\n\n // Reconstruct the path from end node to start node\n const path: Position[] = [];\n let node = endIndex;\n\n while (node !== undefined) {\n path.unshift(this.coords[node]);\n node = cameFrom.get(node)!;\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }"],"names":["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","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","TerraRoute","options","network","distanceMeasurement","adjacencyList","Map","coords","coordMap","coordinateIndex","coord","lng","has","set","latMap","get","buildRouteGraph","_step","_iterator","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIndex","bIndex","distance","getRoute","start","end","Error","startIndex","endIndex","openSet","cameFrom","gScore","visited","Set","current","add","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeG","Infinity","fScore","path","undefined","unshift","type","properties"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.module.js","sources":["../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/heap/min-heap.ts","../src/terra-route.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n return distance / 1000;\n}\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 { Heap } from \"./heap\";\n\nexport class MinHeap implements Heap {\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 currentIndex = this.heap.length;\n this.heap.push(node);\n\n while (currentIndex > 0) {\n const parentIndex = (currentIndex - 1) >>> 1;\n const parent = this.heap[parentIndex];\n\n if (\n key > parent.key ||\n (key === parent.key && node.index > parent.index)\n ) {\n break;\n }\n\n this.heap[currentIndex] = parent;\n currentIndex = parentIndex;\n }\n\n this.heap[currentIndex] = node;\n }\n\n extractMin(): number | null {\n const heap = this.heap;\n const length = heap.length;\n\n if (length === 0) {\n return null;\n }\n\n const minNode = heap[0];\n const endNode = heap.pop()!;\n\n if (length > 1) {\n 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(index: number): void {\n const heap = this.heap;\n const length = heap.length;\n const node = heap[index];\n const nodeKey = node.key;\n const nodeIndex = node.index;\n\n while (true) {\n const leftChildIndex = (index << 1) + 1;\n if (leftChildIndex >= length) {\n break;\n }\n\n let smallestIndex = leftChildIndex;\n let smallest = heap[leftChildIndex];\n\n const rightChildIndex = leftChildIndex + 1;\n if (rightChildIndex < length) {\n const right = heap[rightChildIndex];\n if (\n right.key < smallest.key ||\n (right.key === smallest.key && right.index < smallest.index)\n ) {\n smallestIndex = rightChildIndex;\n smallest = right;\n }\n }\n\n if (\n smallest.key < nodeKey ||\n (smallest.key === nodeKey && smallest.index < nodeIndex)\n ) {\n heap[index] = smallest;\n index = smallestIndex;\n } else {\n break;\n }\n }\n\n heap[index] = node;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\nimport { MinHeap } from \"./heap/min-heap\";\nimport { HeapConstructor } from \"./heap/heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null;\n private distanceMeasurement: (a: Position, b: Position) => number;\n private heapConstructor: HeapConstructor;\n\n // Map from longitude → (map from latitude → index)\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map();\n private coordinates: Position[] = [];\n private adjacencyList: Array<Array<{ node: number; distance: number }>> = [];\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number;\n heap?: HeapConstructor;\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance;\n this.heapConstructor = options?.heap ?? MinHeap;\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 public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network;\n\n // Reset everything\n this.coordinateIndexMap = new Map();\n this.coordinates = [];\n this.adjacencyList = [];\n\n // Hoist to locals for speed\n const coordIndexMapLocal = this.coordinateIndexMap;\n const coordsLocal = this.coordinates;\n const adjListLocal = this.adjacencyList;\n const measureDistance = this.distanceMeasurement;\n\n for (const feature of network.features) {\n const lineCoords = feature.geometry.coordinates;\n\n for (let i = 0; i < lineCoords.length - 1; i++) {\n const [lngA, latA] = lineCoords[i];\n const [lngB, latB] = lineCoords[i + 1];\n\n // get or assign index for A \n let latMapA = coordIndexMapLocal.get(lngA);\n if (!latMapA) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(lineCoords[i]);\n latMapA.set(latA, indexA);\n adjListLocal[indexA] = [];\n }\n\n // get or assign index for B \n let latMapB = coordIndexMapLocal.get(lngB);\n if (!latMapB) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(lineCoords[i + 1]);\n latMapB.set(latB, indexB);\n adjListLocal[indexB] = [];\n }\n\n // record the bidirectional edge \n const segmentDistance = measureDistance(lineCoords[i], lineCoords[i + 1]);\n adjListLocal[indexA].push({ node: indexB, distance: segmentDistance });\n adjListLocal[indexB].push({ node: indexA, distance: segmentDistance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>,\n end: Feature<Point>\n ): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // ensure start/end are in the index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates);\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates);\n\n if (startIndex === endIndex) {\n return null;\n }\n\n const openSet = new this.heapConstructor();\n openSet.insert(0, startIndex);\n\n const nodeCount = this.coordinates.length;\n const gScore = new Array<number>(nodeCount).fill(Infinity);\n const cameFrom = new Array<number>(nodeCount).fill(-1);\n const visited = new Array<boolean>(nodeCount).fill(false);\n\n gScore[startIndex] = 0;\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n if (visited[current]) {\n continue;\n }\n if (current === endIndex) {\n break;\n }\n visited[current] = true;\n\n for (const neighbor of this.adjacencyList[current] || []) {\n const tentativeG = gScore[current] + neighbor.distance;\n if (tentativeG < gScore[neighbor.node]) {\n gScore[neighbor.node] = tentativeG;\n cameFrom[neighbor.node] = current;\n const heuristic = this.distanceMeasurement(\n this.coordinates[neighbor.node],\n this.coordinates[endIndex]\n );\n openSet.insert(tentativeG + heuristic, neighbor.node);\n }\n }\n }\n\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path\n const path: Position[] = [];\n let current = endIndex;\n while (current !== startIndex) {\n path.unshift(this.coordinates[current]);\n current = cameFrom[current];\n }\n path.unshift(this.coordinates[startIndex]);\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number {\n const [lng, lat] = coord;\n let latMap = this.coordinateIndexMap.get(lng);\n if (!latMap) {\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n let index = latMap.get(lat);\n if (index === undefined) {\n index = this.coordinates.length;\n this.coordinates.push(coord);\n latMap.set(lat, index);\n // ensure adjacencyList covers this new node\n this.adjacencyList[index] = [];\n }\n return index;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }"],"names":["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","MinHeap","heap","this","insertCounter","_proto","prototype","insert","key","value","node","index","currentIndex","length","push","parentIndex","parent","extractMin","minNode","endNode","pop","bubbleDown","size","nodeKey","nodeIndex","leftChildIndex","smallestIndex","smallest","rightChildIndex","right","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","buildRouteGraph","_step","coordIndexMapLocal","coordsLocal","adjListLocal","measureDistance","_iterator","_createForOfIteratorHelperLoose","features","done","lineCoords","geometry","i","_lineCoords$i","lngA","latA","_lineCoords","lngB","latB","latMapA","get","set","indexA","undefined","latMapB","indexB","segmentDistance","distance","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","openSet","nodeCount","gScore","Array","fill","Infinity","cameFrom","visited","current","_iterator2","_step2","neighbor","tentativeG","heuristic","path","unshift","type","properties","coord","lng","latMap"],"mappings":"oyBAGa,IAAAA,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,ECOM,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,CCvDa,IAAAC,eAAOA,WAAAA,SAAAA,SACRC,KAA6D,GAAEC,KAC/DC,cAAgB,CAAC,CAAAC,IAAAA,EAAAJ,EAAAK,UAwFxB,OAxFwBD,EAEzBE,OAAA,SAAOC,EAAaC,GAChB,IAAMC,EAAO,CAAEF,IAAAA,EAAKC,MAAAA,EAAOE,MAAOR,KAAKC,iBACnCQ,EAAeT,KAAKD,KAAKW,OAG7B,IAFAV,KAAKD,KAAKY,KAAKJ,GAERE,EAAe,GAAG,CACrB,IAAMG,EAAeH,EAAe,IAAO,EACrCI,EAASb,KAAKD,KAAKa,GAEzB,GACIP,EAAMQ,EAAOR,KACZA,IAAQQ,EAAOR,KAAOE,EAAKC,MAAQK,EAAOL,MAE3C,MAGJR,KAAKD,KAAKU,GAAgBI,EAC1BJ,EAAeG,CACnB,CAEAZ,KAAKD,KAAKU,GAAgBF,CAC9B,EAACL,EAEDY,WAAA,WACI,IAAMf,EAAOC,KAAKD,KACZW,EAASX,EAAKW,OAEpB,GAAe,IAAXA,EACA,OACJ,KAEA,IAAMK,EAAUhB,EAAK,GACfiB,EAAUjB,EAAKkB,MAOrB,OALIP,EAAS,IACTX,EAAK,GAAKiB,EACVhB,KAAKkB,WAAW,IAGbH,EAAQT,KACnB,EAACJ,EAEDiB,KAAA,WACI,OAAOnB,KAAKD,KAAKW,MACrB,EAACR,EAEOgB,WAAA,SAAWV,GAOf,IANA,IAAMT,EAAOC,KAAKD,KACZW,EAASX,EAAKW,OACdH,EAAOR,EAAKS,GACZY,EAAUb,EAAKF,IACfgB,EAAYd,EAAKC,QAEV,CACT,IAAMc,EAAgC,GAAdd,GAAS,GACjC,GAAIc,GAAkBZ,EAClB,MAGJ,IAAIa,EAAgBD,EAChBE,EAAWzB,EAAKuB,GAEdG,EAAkBH,EAAiB,EACzC,GAAIG,EAAkBf,EAAQ,CAC1B,IAAMgB,EAAQ3B,EAAK0B,IAEfC,EAAMrB,IAAMmB,EAASnB,KACpBqB,EAAMrB,MAAQmB,EAASnB,KAAOqB,EAAMlB,MAAQgB,EAAShB,SAEtDe,EAAgBE,EAChBD,EAAWE,EAEnB,CAEA,KACIF,EAASnB,IAAMe,GACdI,EAASnB,MAAQe,GAAWI,EAAShB,MAAQa,GAK9C,MAHAtB,EAAKS,GAASgB,EACdhB,EAAQe,CAIhB,CAEAxB,EAAKS,GAASD,CAClB,EAACT,CAAA,CA1FeA,GCSd6B,eAAU,WAUZ,SAAAA,EAAYC,GAGXC,IAAAA,EAAAC,EAZOC,KAAAA,QAAgD,KAAI/B,KACpDgC,yBAAmB,EAAAhC,KACnBiC,qBAAe,EAAAjC,KAGfkC,mBAAuD,IAAIC,IAAKnC,KAChEoC,YAA0B,GAAEpC,KAC5BqC,cAAkE,GAMtErC,KAAKgC,oBAAkDH,OAA/BA,EAAU,MAAPD,OAAO,EAAPA,EAASI,qBAAmBH,EAAI/D,EAC3DkC,KAAKiC,gBAA+B,OAAhBH,EAAGF,MAAAA,OAAAA,EAAAA,EAAS7B,MAAI+B,EAAIhC,CAC5C,CAAC,IAAAI,EAAAyB,EAAAxB,UAoKAwB,OApKAzB,EASMoC,gBAAA,SAAgBP,GACnB/B,KAAK+B,QAAUA,EAGf/B,KAAKkC,mBAAqB,IAAIC,IAC9BnC,KAAKoC,YAAc,GACnBpC,KAAKqC,cAAgB,GAQrB,IALA,IAKsCE,EALhCC,EAAqBxC,KAAKkC,mBAC1BO,EAAczC,KAAKoC,YACnBM,EAAe1C,KAAKqC,cACpBM,EAAkB3C,KAAKgC,oBAE7BY,EAAAC,EAAsBd,EAAQe,YAAQP,EAAAK,KAAAG,MAGlC,IAHO,IACDC,EADQT,EAAAjC,MACa2C,SAASb,YAE3Bc,EAAI,EAAGA,EAAIF,EAAWtC,OAAS,EAAGwC,IAAK,CAC5C,IAAAC,EAAqBH,EAAWE,GAAzBE,EAAID,EAAA,GAAEE,EAAIF,EAAA,GACjBG,EAAqBN,EAAWE,EAAI,GAA7BK,EAAID,EAAA,GAAEE,EAAIF,EAGjB,GAAIG,EAAUjB,EAAmBkB,IAAIN,GAChCK,IACDA,EAAU,IAAItB,IACdK,EAAmBmB,IAAIP,EAAMK,IAEjC,IAAIG,EAASH,EAAQC,IAAIL,QACVQ,IAAXD,IACAA,EAASnB,EAAY/B,OACrB+B,EAAY9B,KAAKqC,EAAWE,IAC5BO,EAAQE,IAAIN,EAAMO,GAClBlB,EAAakB,GAAU,IAI3B,IAAIE,EAAUtB,EAAmBkB,IAAIH,GAChCO,IACDA,EAAU,IAAI3B,IACdK,EAAmBmB,IAAIJ,EAAMO,IAEjC,IAAIC,EAASD,EAAQJ,IAAIF,QACVK,IAAXE,IACAA,EAAStB,EAAY/B,OACrB+B,EAAY9B,KAAKqC,EAAWE,EAAI,IAChCY,EAAQH,IAAIH,EAAMO,GAClBrB,EAAaqB,GAAU,IAI3B,IAAMC,EAAkBrB,EAAgBK,EAAWE,GAAIF,EAAWE,EAAI,IACtER,EAAakB,GAAQjD,KAAK,CAAEJ,KAAMwD,EAAQE,SAAUD,IACpDtB,EAAaqB,GAAQpD,KAAK,CAAEJ,KAAMqD,EAAQK,SAAUD,GACxD,CAER,EAAC9D,EAWMgE,SAAA,SACHC,EACAC,GAEA,IAAKpE,KAAK+B,QACN,MAAM,IAAIsC,MAAM,kEAIpB,IAAMC,EAAatE,KAAKuE,iBAAiBJ,EAAMlB,SAASb,aAClDoC,EAAWxE,KAAKuE,iBAAiBH,EAAInB,SAASb,aAEpD,GAAIkC,IAAeE,EACf,OACJ,KAEA,IAAMC,EAAU,IAAIzE,KAAKiC,gBACzBwC,EAAQrE,OAAO,EAAGkE,GAElB,IAAMI,EAAY1E,KAAKoC,YAAY1B,OAC7BiE,EAAS,IAAIC,MAAcF,GAAWG,KAAKC,UAC3CC,EAAW,IAAIH,MAAcF,GAAWG,MAAM,GAC9CG,EAAU,IAAIJ,MAAeF,GAAWG,MAAK,GAInD,IAFAF,EAAOL,GAAc,EAEdG,EAAQtD,OAAS,GAAG,CACvB,IAAM8D,EAAUR,EAAQ3D,aACxB,IAAIkE,EAAQC,GAAZ,CAGA,GAAIA,IAAYT,EACZ,MAEJQ,EAAQC,IAAW,EAEnB,IAAAC,IAAwDC,EAAxDD,EAAArC,EAAuB7C,KAAKqC,cAAc4C,IAAY,MAAEE,EAAAD,KAAAnC,MAAE,CAAA,IAA/CqC,EAAQD,EAAA7E,MACT+E,EAAaV,EAAOM,GAAWG,EAASnB,SAC9C,GAAIoB,EAAaV,EAAOS,EAAS7E,MAAO,CACpCoE,EAAOS,EAAS7E,MAAQ8E,EACxBN,EAASK,EAAS7E,MAAQ0E,EAC1B,IAAMK,EAAYtF,KAAKgC,oBACnBhC,KAAKoC,YAAYgD,EAAS7E,MAC1BP,KAAKoC,YAAYoC,IAErBC,EAAQrE,OAAOiF,EAAaC,EAAWF,EAAS7E,KACpD,CACJ,CAjBA,CAkBJ,CAEA,GAAIwE,EAASP,GAAY,EACrB,OAAO,KAMX,IAFA,IAAMe,EAAmB,GACrBN,EAAUT,EACPS,IAAYX,GACfiB,EAAKC,QAAQxF,KAAKoC,YAAY6C,IAC9BA,EAAUF,EAASE,GAIvB,OAFAM,EAAKC,QAAQxF,KAAKoC,YAAYkC,IAEvB,CACHmB,KAAM,UACNxC,SAAU,CAAEwC,KAAM,aAAcrD,YAAamD,GAC7CG,WAAY,GAEpB,EAACxF,EAKOqE,iBAAA,SAAiBoB,GACrB,IAAOC,EAAYD,EAAK,GAAZ3G,EAAO2G,EAAK,GACpBE,EAAS7F,KAAKkC,mBAAmBwB,IAAIkC,GACpCC,IACDA,EAAS,IAAI1D,IACbnC,KAAKkC,mBAAmByB,IAAIiC,EAAKC,IAErC,IAAIrF,EAAQqF,EAAOnC,IAAI1E,GAQvB,YAPc6E,IAAVrD,IACAA,EAAQR,KAAKoC,YAAY1B,OACzBV,KAAKoC,YAAYzB,KAAKgF,GACtBE,EAAOlC,IAAI3E,EAAKwB,GAEhBR,KAAKqC,cAAc7B,GAAS,IAEzBA,CACX,EAACmB,CAAA,CApLW"}
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e||self).terraRoute={})}(this,function(e){function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function n(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 r=function(e,t){var n=function(e){return e*Math.PI/180},r=n(e[1]),i=n(e[0]),a=n(t[1]),o=a-r,s=n(t[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},i=/*#__PURE__*/function(){function e(){this.heap=[],this.insertCounter=0}var t=e.prototype;return t.insert=function(e,t){var n={key:e,value:t,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},t.extractMin=function(){var e=this.heap.length;if(0===e)return null;var t=this.heap[0],n=this.heap.pop();return e>1&&(this.heap[0]=n,this.bubbleDown(0)),t.value},t.size=function(){return this.heap.length},t.bubbleDown=function(e){for(var t=this.heap,n=t.length,r=t[e],i=r.key,a=r.index;;){var o=1+(e<<1);if(o>=n)break;var s=o,h=t[o].key,u=t[o].index,c=o+1;if(c<n){var d=t[c].key,f=t[c].index;(d<h||d===h&&f<u)&&(s=c,h=d,u=f)}if(!(h<i||h===i&&u<a))break;t[e]=t[s],e=s}t[e]=r},e}();e.TerraRoute=/*#__PURE__*/function(){function e(e){this.network=void 0,this.distanceMeasurement=void 0,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map,this.heap=void 0,this.heap=null!=e&&e.heap?e.heap:i,this.distanceMeasurement=null!=e&&e.distanceMeasurement?e.distanceMeasurement:r}var t=e.prototype;return t.coordinateIndex=function(e){var t=e[0],n=e[1];this.coordMap.has(t)||this.coordMap.set(t,new Map);var r=this.coordMap.get(t);if(r.has(n))return r.get(n);var i=this.coords.length;return this.coords.push(e),r.set(n,i),i},t.buildRouteGraph=function(e){this.network=e,this.adjacencyList=new Map,this.coords=[],this.coordMap=new Map;for(var t,r=n(this.network.features);!(t=r()).done;)for(var i=t.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})}},t.getRoute=function(e,t){if(!this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var r=this.coordinateIndex(e.geometry.coordinates),i=this.coordinateIndex(t.geometry.coordinates);if(r===i)return null;var a=new this.heap;a.insert(0,r);for(var o=new Map,s=new Map([[r,0]]),h=new Set;a.size()>0;){var u=a.extractMin();if(u===i)break;h.add(u);for(var c,d=n(this.adjacencyList.get(u)||[]);!(c=d()).done;){var f,p,l=c.value,v=(null!=(f=s.get(u))?f:Infinity)+l.distance;if(v<(null!=(p=s.get(l.node))?p:Infinity)){o.set(l.node,u),s.set(l.node,v);var y=v+this.distanceMeasurement(this.coords[l.node],this.coords[i]);a.insert(y,l.node)}}}if(!o.has(i))return null;for(var M=[],g=i;void 0!==g;)M.unshift(this.coords[g]),g=o.get(g);return{type:"Feature",geometry:{type:"LineString",coordinates:M},properties:{}}},e}(),e.createCheapRuler=function(e){var t=1/298.257223563,n=t*(2-t),r=Math.PI/180,i=Math.cos(e*r),a=1/(1-n*(1-i*i)),o=Math.sqrt(a),s=6378.137*r,h=s*o*i,u=s*o*a*(1-n);return function(e,t){for(var n=e[0]-t[0];n<-180;)n+=360;for(;n>180;)n-=360;var r=n*h,i=(e[1]-t[1])*u;return Math.sqrt(r*r+i*i)}},e.haversineDistance=r});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e||self).terraRoute={})}(this,function(e){function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function n(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 r=function(e,t){var n=function(e){return e*Math.PI/180},r=n(e[1]),i=n(e[0]),a=n(t[1]),o=a-r,s=n(t[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},i=/*#__PURE__*/function(){function e(){this.heap=[],this.insertCounter=0}var t=e.prototype;return t.insert=function(e,t){var n={key:e,value:t,index:this.insertCounter++},r=this.heap.length;for(this.heap.push(n);r>0;){var i=r-1>>>1,a=this.heap[i];if(e>a.key||e===a.key&&n.index>a.index)break;this.heap[r]=a,r=i}this.heap[r]=n},t.extractMin=function(){var e=this.heap,t=e.length;if(0===t)return null;var n=e[0],r=e.pop();return t>1&&(e[0]=r,this.bubbleDown(0)),n.value},t.size=function(){return this.heap.length},t.bubbleDown=function(e){for(var t=this.heap,n=t.length,r=t[e],i=r.key,a=r.index;;){var o=1+(e<<1);if(o>=n)break;var s=o,h=t[o],u=o+1;if(u<n){var d=t[u];(d.key<h.key||d.key===h.key&&d.index<h.index)&&(s=u,h=d)}if(!(h.key<i||h.key===i&&h.index<a))break;t[e]=h,e=s}t[e]=r},e}();e.TerraRoute=/*#__PURE__*/function(){function e(e){var t,n;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.distanceMeasurement=null!=(t=null==e?void 0:e.distanceMeasurement)?t:r,this.heapConstructor=null!=(n=null==e?void 0:e.heap)?n:i}var t=e.prototype;return t.buildRouteGraph=function(e){this.network=e,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[];for(var t,r=this.coordinateIndexMap,i=this.coordinates,a=this.adjacencyList,o=this.distanceMeasurement,s=n(e.features);!(t=s()).done;)for(var h=t.value.geometry.coordinates,u=0;u<h.length-1;u++){var d=h[u],c=d[0],f=d[1],l=h[u+1],p=l[0],v=l[1],y=r.get(c);y||(y=new Map,r.set(c,y));var g=y.get(f);void 0===g&&(g=i.length,i.push(h[u]),y.set(f,g),a[g]=[]);var M=r.get(p);M||(M=new Map,r.set(p,M));var b=M.get(v);void 0===b&&(b=i.length,i.push(h[u+1]),M.set(v,b),a[b]=[]);var m=o(h[u],h[u+1]);a[g].push({node:b,distance:m}),a[b].push({node:g,distance:m})}},t.getRoute=function(e,t){if(!this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var r=this.getOrCreateIndex(e.geometry.coordinates),i=this.getOrCreateIndex(t.geometry.coordinates);if(r===i)return null;var a=new this.heapConstructor;a.insert(0,r);var o=this.coordinates.length,s=new Array(o).fill(Infinity),h=new Array(o).fill(-1),u=new Array(o).fill(!1);for(s[r]=0;a.size()>0;){var d=a.extractMin();if(!u[d]){if(d===i)break;u[d]=!0;for(var c,f=n(this.adjacencyList[d]||[]);!(c=f()).done;){var l=c.value,p=s[d]+l.distance;if(p<s[l.node]){s[l.node]=p,h[l.node]=d;var v=this.distanceMeasurement(this.coordinates[l.node],this.coordinates[i]);a.insert(p+v,l.node)}}}}if(h[i]<0)return null;for(var y=[],g=i;g!==r;)y.unshift(this.coordinates[g]),g=h[g];return y.unshift(this.coordinates[r]),{type:"Feature",geometry:{type:"LineString",coordinates:y},properties:{}}},t.getOrCreateIndex=function(e){var t=e[0],n=e[1],r=this.coordinateIndexMap.get(t);r||(r=new Map,this.coordinateIndexMap.set(t,r));var i=r.get(n);return void 0===i&&(i=this.coordinates.length,this.coordinates.push(e),r.set(n,i),this.adjacencyList[i]=[]),i},e}(),e.createCheapRuler=function(e){var t=1/298.257223563,n=t*(2-t),r=Math.PI/180,i=Math.cos(e*r),a=1/(1-n*(1-i*i)),o=Math.sqrt(a),s=6378.137*r,h=s*o*i,u=s*o*a*(1-n);return function(e,t){for(var n=e[0]-t[0];n<-180;)n+=360;for(;n>180;)n-=360;var r=n*h,i=(e[1]-t[1])*u;return Math.sqrt(r*r+i*i)}},e.haversineDistance=r});
2
2
  //# sourceMappingURL=terra-route.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.umd.js","sources":["../src/distance/haversine.ts","../src/heap/min-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\nexport class MinHeap implements Heap {\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 // eslint-disable-next-line no-constant-condition\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}","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\nimport { MinHeap } from \"./heap/min-heap\";\nimport { HeapConstructor } from \"./heap/heap\";\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 private heap: HeapConstructor;\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(options?: {\n distanceMeasurement?: (positionA: Position, positionB: Position) => number,\n heap?: HeapConstructor\n }) {\n this.heap = options?.heap ? options.heap : MinHeap\n this.distanceMeasurement = options?.distanceMeasurement ? options.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 index = this.coords.length;\n this.coords.push(coord);\n latMap.set(lat, index);\n\n return index;\n }\n\n /**\n * Builds the internal graph representation (adjacency list) from the input network.\n * Each LineString segment is translated into 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 aIndex = this.coordinateIndex(coords[i]);\n const bIndex = this.coordinateIndex(coords[i + 1]);\n const distance = this.distanceMeasurement(coords[i], coords[i + 1]);\n\n if (!this.adjacencyList.has(aIndex)) this.adjacencyList.set(aIndex, []);\n if (!this.adjacencyList.has(bIndex)) this.adjacencyList.set(bIndex, []);\n\n this.adjacencyList.get(aIndex)!.push({ node: bIndex, distance });\n this.adjacencyList.get(bIndex)!.push({ node: aIndex, distance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>,\n end: Feature<Point>\n ): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n const startIndex = this.coordinateIndex(start.geometry.coordinates);\n const endIndex = this.coordinateIndex(end.geometry.coordinates);\n\n if (startIndex === endIndex) {\n return null;\n }\n\n const openSet = new this.heap();\n openSet.insert(0, startIndex);\n\n const cameFrom = new Map<number, number>();\n const gScore = new Map<number, number>([[startIndex, 0]]);\n const visited = new Set<number>();\n\n while (openSet.size() > 0) {\n // Extract the node with the smallest fScore\n const current = openSet.extractMin()!;\n\n // If we've reached the end node, we're done\n if (current === endIndex) {\n break;\n }\n\n visited.add(current);\n\n // Explore neighbors\n for (const neighbor of this.adjacencyList.get(current) || []) {\n // Tentative cost from start to this neighbor\n const tentativeG = (gScore.get(current) ?? Infinity) + neighbor.distance;\n\n // If this path to neighbor is better, record it\n if (tentativeG < (gScore.get(neighbor.node) ?? Infinity)) {\n cameFrom.set(neighbor.node, current);\n gScore.set(neighbor.node, tentativeG);\n\n // Calculate fScore: gScore + heuristic distance to the end\n const fScore =\n tentativeG +\n this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIndex]);\n\n openSet.insert(fScore, neighbor.node);\n }\n }\n }\n\n // If we never set a path to the end node, there's no route\n if (!cameFrom.has(endIndex)) {\n return null;\n }\n\n // Reconstruct the path from end node to start node\n const path: Position[] = [];\n let node = endIndex;\n\n while (node !== undefined) {\n path.unshift(this.coords[node]);\n node = cameFrom.get(node)!;\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","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","TerraRoute","options","network","distanceMeasurement","adjacencyList","Map","coords","coordMap","coordinateIndex","coord","lng","lat","has","set","latMap","get","buildRouteGraph","_step","_iterator","_createForOfIteratorHelperLoose","features","done","geometry","coordinates","i","aIndex","bIndex","distance","getRoute","start","end","Error","startIndex","endIndex","openSet","cameFrom","gScore","visited","Set","current","add","_step2","_iterator2","_gScore$get","_gScore$get2","neighbor","tentativeG","Infinity","fScore","path","undefined","unshift","type","properties","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/distance/haversine.ts","../src/heap/min-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\nexport class MinHeap implements Heap {\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 currentIndex = this.heap.length;\n this.heap.push(node);\n\n while (currentIndex > 0) {\n const parentIndex = (currentIndex - 1) >>> 1;\n const parent = this.heap[parentIndex];\n\n if (\n key > parent.key ||\n (key === parent.key && node.index > parent.index)\n ) {\n break;\n }\n\n this.heap[currentIndex] = parent;\n currentIndex = parentIndex;\n }\n\n this.heap[currentIndex] = node;\n }\n\n extractMin(): number | null {\n const heap = this.heap;\n const length = heap.length;\n\n if (length === 0) {\n return null;\n }\n\n const minNode = heap[0];\n const endNode = heap.pop()!;\n\n if (length > 1) {\n 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(index: number): void {\n const heap = this.heap;\n const length = heap.length;\n const node = heap[index];\n const nodeKey = node.key;\n const nodeIndex = node.index;\n\n while (true) {\n const leftChildIndex = (index << 1) + 1;\n if (leftChildIndex >= length) {\n break;\n }\n\n let smallestIndex = leftChildIndex;\n let smallest = heap[leftChildIndex];\n\n const rightChildIndex = leftChildIndex + 1;\n if (rightChildIndex < length) {\n const right = heap[rightChildIndex];\n if (\n right.key < smallest.key ||\n (right.key === smallest.key && right.index < smallest.index)\n ) {\n smallestIndex = rightChildIndex;\n smallest = right;\n }\n }\n\n if (\n smallest.key < nodeKey ||\n (smallest.key === nodeKey && smallest.index < nodeIndex)\n ) {\n heap[index] = smallest;\n index = smallestIndex;\n } else {\n break;\n }\n }\n\n heap[index] = node;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\";\nimport { haversineDistance } from \"./distance/haversine\";\nimport { createCheapRuler } from \"./distance/cheap-ruler\";\nimport { MinHeap } from \"./heap/min-heap\";\nimport { HeapConstructor } from \"./heap/heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null;\n private distanceMeasurement: (a: Position, b: Position) => number;\n private heapConstructor: HeapConstructor;\n\n // Map from longitude → (map from latitude → index)\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map();\n private coordinates: Position[] = [];\n private adjacencyList: Array<Array<{ node: number; distance: number }>> = [];\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number;\n heap?: HeapConstructor;\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance;\n this.heapConstructor = options?.heap ?? MinHeap;\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 public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network;\n\n // Reset everything\n this.coordinateIndexMap = new Map();\n this.coordinates = [];\n this.adjacencyList = [];\n\n // Hoist to locals for speed\n const coordIndexMapLocal = this.coordinateIndexMap;\n const coordsLocal = this.coordinates;\n const adjListLocal = this.adjacencyList;\n const measureDistance = this.distanceMeasurement;\n\n for (const feature of network.features) {\n const lineCoords = feature.geometry.coordinates;\n\n for (let i = 0; i < lineCoords.length - 1; i++) {\n const [lngA, latA] = lineCoords[i];\n const [lngB, latB] = lineCoords[i + 1];\n\n // get or assign index for A \n let latMapA = coordIndexMapLocal.get(lngA);\n if (!latMapA) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(lineCoords[i]);\n latMapA.set(latA, indexA);\n adjListLocal[indexA] = [];\n }\n\n // get or assign index for B \n let latMapB = coordIndexMapLocal.get(lngB);\n if (!latMapB) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(lineCoords[i + 1]);\n latMapB.set(latB, indexB);\n adjListLocal[indexB] = [];\n }\n\n // record the bidirectional edge \n const segmentDistance = measureDistance(lineCoords[i], lineCoords[i + 1]);\n adjListLocal[indexA].push({ node: indexB, distance: segmentDistance });\n adjListLocal[indexB].push({ node: indexA, distance: segmentDistance });\n }\n }\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>,\n end: Feature<Point>\n ): Feature<LineString> | null {\n if (!this.network) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // ensure start/end are in the index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates);\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates);\n\n if (startIndex === endIndex) {\n return null;\n }\n\n const openSet = new this.heapConstructor();\n openSet.insert(0, startIndex);\n\n const nodeCount = this.coordinates.length;\n const gScore = new Array<number>(nodeCount).fill(Infinity);\n const cameFrom = new Array<number>(nodeCount).fill(-1);\n const visited = new Array<boolean>(nodeCount).fill(false);\n\n gScore[startIndex] = 0;\n\n while (openSet.size() > 0) {\n const current = openSet.extractMin()!;\n if (visited[current]) {\n continue;\n }\n if (current === endIndex) {\n break;\n }\n visited[current] = true;\n\n for (const neighbor of this.adjacencyList[current] || []) {\n const tentativeG = gScore[current] + neighbor.distance;\n if (tentativeG < gScore[neighbor.node]) {\n gScore[neighbor.node] = tentativeG;\n cameFrom[neighbor.node] = current;\n const heuristic = this.distanceMeasurement(\n this.coordinates[neighbor.node],\n this.coordinates[endIndex]\n );\n openSet.insert(tentativeG + heuristic, neighbor.node);\n }\n }\n }\n\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path\n const path: Position[] = [];\n let current = endIndex;\n while (current !== startIndex) {\n path.unshift(this.coordinates[current]);\n current = cameFrom[current];\n }\n path.unshift(this.coordinates[startIndex]);\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number {\n const [lng, lat] = coord;\n let latMap = this.coordinateIndexMap.get(lng);\n if (!latMap) {\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n let index = latMap.get(lat);\n if (index === undefined) {\n index = this.coordinates.length;\n this.coordinates.push(coord);\n latMap.set(lat, index);\n // ensure adjacencyList covers this new node\n this.adjacencyList[index] = [];\n }\n return index;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance }","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","MinHeap","heap","this","insertCounter","_proto","prototype","insert","key","value","node","index","currentIndex","length","push","parentIndex","parent","extractMin","minNode","endNode","pop","bubbleDown","size","nodeKey","nodeIndex","leftChildIndex","smallestIndex","smallest","rightChildIndex","right","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","buildRouteGraph","_step","coordIndexMapLocal","coordsLocal","adjListLocal","measureDistance","_iterator","_createForOfIteratorHelperLoose","features","done","lineCoords","geometry","i","_lineCoords$i","lngA","latA","_lineCoords","lngB","latB","latMapA","get","set","indexA","undefined","latMapB","indexB","segmentDistance","distance","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","openSet","nodeCount","gScore","Array","fill","Infinity","cameFrom","visited","current","_iterator2","_step2","neighbor","tentativeG","heuristic","path","unshift","type","properties","coord","lng","lat","latMap","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terra-route",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "A library for routing along GeoJSON LineString networks",
5
5
  "scripts": {
6
6
  "docs": "typedoc",
@@ -6,29 +6,40 @@ export class MinHeap implements Heap {
6
6
 
7
7
  insert(key: number, value: number): void {
8
8
  const node = { key, value, index: this.insertCounter++ };
9
- let idx = this.heap.length;
9
+ let currentIndex = this.heap.length;
10
10
  this.heap.push(node);
11
11
 
12
- // Optimized Bubble Up
13
- while (idx > 0) {
14
- const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)
15
- const parent = this.heap[parentIdx];
16
- if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;
17
- this.heap[idx] = parent;
18
- idx = parentIdx;
12
+ while (currentIndex > 0) {
13
+ const parentIndex = (currentIndex - 1) >>> 1;
14
+ const parent = this.heap[parentIndex];
15
+
16
+ if (
17
+ key > parent.key ||
18
+ (key === parent.key && node.index > parent.index)
19
+ ) {
20
+ break;
21
+ }
22
+
23
+ this.heap[currentIndex] = parent;
24
+ currentIndex = parentIndex;
19
25
  }
20
- this.heap[idx] = node;
26
+
27
+ this.heap[currentIndex] = node;
21
28
  }
22
29
 
23
30
  extractMin(): number | null {
24
- const length = this.heap.length;
25
- if (length === 0) return null;
31
+ const heap = this.heap;
32
+ const length = heap.length;
33
+
34
+ if (length === 0) {
35
+ return null;
36
+ }
26
37
 
27
- const minNode = this.heap[0];
28
- const endNode = this.heap.pop()!;
38
+ const minNode = heap[0];
39
+ const endNode = heap.pop()!;
29
40
 
30
41
  if (length > 1) {
31
- this.heap[0] = endNode;
42
+ heap[0] = endNode;
32
43
  this.bubbleDown(0);
33
44
  }
34
45
 
@@ -39,52 +50,45 @@ export class MinHeap implements Heap {
39
50
  return this.heap.length;
40
51
  }
41
52
 
42
- private bubbleDown(idx: number): void {
43
- const { heap } = this;
53
+ private bubbleDown(index: number): void {
54
+ const heap = this.heap;
44
55
  const length = heap.length;
45
- // Grab the parent node once, then move it down only if needed
46
- const node = heap[idx];
56
+ const node = heap[index];
47
57
  const nodeKey = node.key;
48
58
  const nodeIndex = node.index;
49
59
 
50
- // eslint-disable-next-line no-constant-condition
51
60
  while (true) {
52
- // Calculate left and right child indexes
53
- const leftIdx = (idx << 1) + 1;
54
- if (leftIdx >= length) {
55
- // No children => we’re already in place
61
+ const leftChildIndex = (index << 1) + 1;
62
+ if (leftChildIndex >= length) {
56
63
  break;
57
64
  }
58
65
 
59
- // Assume left child is the smaller one by default
60
- let smallestIdx = leftIdx;
61
- let smallestKey = heap[leftIdx].key;
62
- let smallestIndex = heap[leftIdx].index;
63
-
64
- const rightIdx = leftIdx + 1;
65
- if (rightIdx < length) {
66
- // Compare left child vs. right child
67
- const rightKey = heap[rightIdx].key;
68
- const rightIndex = heap[rightIdx].index;
69
- if (rightKey < smallestKey || (rightKey === smallestKey && rightIndex < smallestIndex)) {
70
- smallestIdx = rightIdx;
71
- smallestKey = rightKey;
72
- smallestIndex = rightIndex;
66
+ let smallestIndex = leftChildIndex;
67
+ let smallest = heap[leftChildIndex];
68
+
69
+ const rightChildIndex = leftChildIndex + 1;
70
+ if (rightChildIndex < length) {
71
+ const right = heap[rightChildIndex];
72
+ if (
73
+ right.key < smallest.key ||
74
+ (right.key === smallest.key && right.index < smallest.index)
75
+ ) {
76
+ smallestIndex = rightChildIndex;
77
+ smallest = right;
73
78
  }
74
79
  }
75
80
 
76
- // Compare the smaller child with the parent
77
- if (smallestKey < nodeKey || (smallestKey === nodeKey && smallestIndex < nodeIndex)) {
78
- // Swap the smaller child up
79
- heap[idx] = heap[smallestIdx];
80
- idx = smallestIdx;
81
+ if (
82
+ smallest.key < nodeKey ||
83
+ (smallest.key === nodeKey && smallest.index < nodeIndex)
84
+ ) {
85
+ heap[index] = smallest;
86
+ index = smallestIndex;
81
87
  } else {
82
- // We’re in the correct position now, so stop
83
88
  break;
84
89
  }
85
90
  }
86
91
 
87
- // Place the original node in its final position
88
- heap[idx] = node;
92
+ heap[index] = node;
89
93
  }
90
- }
94
+ }
@@ -4,32 +4,27 @@ import { createCheapRuler } from "./distance/cheap-ruler";
4
4
  import { MinHeap } from "./heap/min-heap";
5
5
  import { HeapConstructor } from "./heap/heap";
6
6
 
7
- /**
8
- * TerraRoute is a routing utility for finding the shortest path
9
- * between two geographic points over a given GeoJSON LineString network.
10
- *
11
- * The class builds an internal graph structure based on the provided network,
12
- * then applies A* algorithm to compute the shortest route.
13
- */
14
- class TerraRoute {
15
- private network: FeatureCollection<LineString> | undefined;
16
- private distanceMeasurement: (positionA: Position, positionB: Position) => number;
17
- private adjacencyList: Map<number, Array<{ node: number; distance: number }>> = new Map();
18
- private coords: Position[] = []
19
- private coordMap: Map<number, Map<number, number>> = new Map();
20
- private heap: HeapConstructor;
7
+ interface Router {
8
+ buildRouteGraph(network: FeatureCollection<LineString>): void;
9
+ getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;
10
+ }
11
+
12
+ class TerraRoute implements Router {
13
+ private network: FeatureCollection<LineString> | null = null;
14
+ private distanceMeasurement: (a: Position, b: Position) => number;
15
+ private heapConstructor: HeapConstructor;
16
+
17
+ // Map from longitude (map from latitude index)
18
+ private coordinateIndexMap: Map<number, Map<number, number>> = new Map();
19
+ private coordinates: Position[] = [];
20
+ private adjacencyList: Array<Array<{ node: number; distance: number }>> = [];
21
21
 
22
- /**
23
- * Creates a new instance of TerraRoute.
24
- *
25
- * @param distanceMeasurement - Optional custom distance measurement function (defaults to haversine distance).
26
- */
27
22
  constructor(options?: {
28
- distanceMeasurement?: (positionA: Position, positionB: Position) => number,
29
- heap?: HeapConstructor
23
+ distanceMeasurement?: (a: Position, b: Position) => number;
24
+ heap?: HeapConstructor;
30
25
  }) {
31
- this.heap = options?.heap ? options.heap : MinHeap
32
- this.distanceMeasurement = options?.distanceMeasurement ? options.distanceMeasurement : haversineDistance;
26
+ this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance;
27
+ this.heapConstructor = options?.heap ?? MinHeap;
33
28
  }
34
29
 
35
30
  /**
@@ -39,48 +34,59 @@ class TerraRoute {
39
34
  * @param coord - A GeoJSON Position array representing [longitude, latitude].
40
35
  * @returns A unique numeric index for the coordinate.
41
36
  */
42
- private coordinateIndex(coord: Position): number {
43
- const [lng, lat] = coord;
44
- if (!this.coordMap.has(lng)) this.coordMap.set(lng, new Map());
45
-
46
- const latMap = this.coordMap.get(lng)!;
47
- if (latMap.has(lat)) {
48
- return latMap.get(lat)!;
49
- }
37
+ public buildRouteGraph(network: FeatureCollection<LineString>): void {
38
+ this.network = network;
50
39
 
51
- const index = this.coords.length;
52
- this.coords.push(coord);
53
- latMap.set(lat, index);
40
+ // Reset everything
41
+ this.coordinateIndexMap = new Map();
42
+ this.coordinates = [];
43
+ this.adjacencyList = [];
44
+
45
+ // Hoist to locals for speed
46
+ const coordIndexMapLocal = this.coordinateIndexMap;
47
+ const coordsLocal = this.coordinates;
48
+ const adjListLocal = this.adjacencyList;
49
+ const measureDistance = this.distanceMeasurement;
50
+
51
+ for (const feature of network.features) {
52
+ const lineCoords = feature.geometry.coordinates;
53
+
54
+ for (let i = 0; i < lineCoords.length - 1; i++) {
55
+ const [lngA, latA] = lineCoords[i];
56
+ const [lngB, latB] = lineCoords[i + 1];
57
+
58
+ // get or assign index for A
59
+ let latMapA = coordIndexMapLocal.get(lngA);
60
+ if (!latMapA) {
61
+ latMapA = new Map<number, number>();
62
+ coordIndexMapLocal.set(lngA, latMapA);
63
+ }
64
+ let indexA = latMapA.get(latA);
65
+ if (indexA === undefined) {
66
+ indexA = coordsLocal.length;
67
+ coordsLocal.push(lineCoords[i]);
68
+ latMapA.set(latA, indexA);
69
+ adjListLocal[indexA] = [];
70
+ }
54
71
 
55
- return index;
56
- }
72
+ // get or assign index for B
73
+ let latMapB = coordIndexMapLocal.get(lngB);
74
+ if (!latMapB) {
75
+ latMapB = new Map<number, number>();
76
+ coordIndexMapLocal.set(lngB, latMapB);
77
+ }
78
+ let indexB = latMapB.get(latB);
79
+ if (indexB === undefined) {
80
+ indexB = coordsLocal.length;
81
+ coordsLocal.push(lineCoords[i + 1]);
82
+ latMapB.set(latB, indexB);
83
+ adjListLocal[indexB] = [];
84
+ }
57
85
 
58
- /**
59
- * Builds the internal graph representation (adjacency list) from the input network.
60
- * Each LineString segment is translated into graph edges with associated distances.
61
- * Assumes that the network is a connected graph of LineStrings with shared coordinates. Calling this
62
- * method with a new network overwrite any existing network and reset all internal data structures.
63
- *
64
- * @param network - A GeoJSON FeatureCollection of LineStrings representing the road network.
65
- */
66
- public buildRouteGraph(network: FeatureCollection<LineString>): void {
67
- this.network = network;
68
- this.adjacencyList = new Map();
69
- this.coords = [];
70
- this.coordMap = new Map();
71
-
72
- for (const feature of this.network.features) {
73
- const coords = feature.geometry.coordinates;
74
- for (let i = 0; i < coords.length - 1; i++) {
75
- const aIndex = this.coordinateIndex(coords[i]);
76
- const bIndex = this.coordinateIndex(coords[i + 1]);
77
- const distance = this.distanceMeasurement(coords[i], coords[i + 1]);
78
-
79
- if (!this.adjacencyList.has(aIndex)) this.adjacencyList.set(aIndex, []);
80
- if (!this.adjacencyList.has(bIndex)) this.adjacencyList.set(bIndex, []);
81
-
82
- this.adjacencyList.get(aIndex)!.push({ node: bIndex, distance });
83
- this.adjacencyList.get(bIndex)!.push({ node: aIndex, distance });
86
+ // record the bidirectional edge
87
+ const segmentDistance = measureDistance(lineCoords[i], lineCoords[i + 1]);
88
+ adjListLocal[indexA].push({ node: indexB, distance: segmentDistance });
89
+ adjListLocal[indexB].push({ node: indexA, distance: segmentDistance });
84
90
  }
85
91
  }
86
92
  }
@@ -102,64 +108,60 @@ class TerraRoute {
102
108
  throw new Error("Network not built. Please call buildRouteGraph(network) first.");
103
109
  }
104
110
 
105
- const startIndex = this.coordinateIndex(start.geometry.coordinates);
106
- const endIndex = this.coordinateIndex(end.geometry.coordinates);
111
+ // ensure start/end are in the index maps
112
+ const startIndex = this.getOrCreateIndex(start.geometry.coordinates);
113
+ const endIndex = this.getOrCreateIndex(end.geometry.coordinates);
107
114
 
108
115
  if (startIndex === endIndex) {
109
116
  return null;
110
117
  }
111
118
 
112
- const openSet = new this.heap();
119
+ const openSet = new this.heapConstructor();
113
120
  openSet.insert(0, startIndex);
114
121
 
115
- const cameFrom = new Map<number, number>();
116
- const gScore = new Map<number, number>([[startIndex, 0]]);
117
- const visited = new Set<number>();
122
+ const nodeCount = this.coordinates.length;
123
+ const gScore = new Array<number>(nodeCount).fill(Infinity);
124
+ const cameFrom = new Array<number>(nodeCount).fill(-1);
125
+ const visited = new Array<boolean>(nodeCount).fill(false);
126
+
127
+ gScore[startIndex] = 0;
118
128
 
119
129
  while (openSet.size() > 0) {
120
- // Extract the node with the smallest fScore
121
130
  const current = openSet.extractMin()!;
122
-
123
- // If we've reached the end node, we're done
131
+ if (visited[current]) {
132
+ continue;
133
+ }
124
134
  if (current === endIndex) {
125
135
  break;
126
136
  }
127
-
128
- visited.add(current);
129
-
130
- // Explore neighbors
131
- for (const neighbor of this.adjacencyList.get(current) || []) {
132
- // Tentative cost from start to this neighbor
133
- const tentativeG = (gScore.get(current) ?? Infinity) + neighbor.distance;
134
-
135
- // If this path to neighbor is better, record it
136
- if (tentativeG < (gScore.get(neighbor.node) ?? Infinity)) {
137
- cameFrom.set(neighbor.node, current);
138
- gScore.set(neighbor.node, tentativeG);
139
-
140
- // Calculate fScore: gScore + heuristic distance to the end
141
- const fScore =
142
- tentativeG +
143
- this.distanceMeasurement(this.coords[neighbor.node], this.coords[endIndex]);
144
-
145
- openSet.insert(fScore, neighbor.node);
137
+ visited[current] = true;
138
+
139
+ for (const neighbor of this.adjacencyList[current] || []) {
140
+ const tentativeG = gScore[current] + neighbor.distance;
141
+ if (tentativeG < gScore[neighbor.node]) {
142
+ gScore[neighbor.node] = tentativeG;
143
+ cameFrom[neighbor.node] = current;
144
+ const heuristic = this.distanceMeasurement(
145
+ this.coordinates[neighbor.node],
146
+ this.coordinates[endIndex]
147
+ );
148
+ openSet.insert(tentativeG + heuristic, neighbor.node);
146
149
  }
147
150
  }
148
151
  }
149
152
 
150
- // If we never set a path to the end node, there's no route
151
- if (!cameFrom.has(endIndex)) {
153
+ if (cameFrom[endIndex] < 0) {
152
154
  return null;
153
155
  }
154
156
 
155
- // Reconstruct the path from end node to start node
157
+ // Reconstruct path
156
158
  const path: Position[] = [];
157
- let node = endIndex;
158
-
159
- while (node !== undefined) {
160
- path.unshift(this.coords[node]);
161
- node = cameFrom.get(node)!;
159
+ let current = endIndex;
160
+ while (current !== startIndex) {
161
+ path.unshift(this.coordinates[current]);
162
+ current = cameFrom[current];
162
163
  }
164
+ path.unshift(this.coordinates[startIndex]);
163
165
 
164
166
  return {
165
167
  type: "Feature",
@@ -168,6 +170,26 @@ class TerraRoute {
168
170
  };
169
171
  }
170
172
 
173
+ /**
174
+ * Helper to index start/end in getRoute.
175
+ */
176
+ private getOrCreateIndex(coord: Position): number {
177
+ const [lng, lat] = coord;
178
+ let latMap = this.coordinateIndexMap.get(lng);
179
+ if (!latMap) {
180
+ latMap = new Map<number, number>();
181
+ this.coordinateIndexMap.set(lng, latMap);
182
+ }
183
+ let index = latMap.get(lat);
184
+ if (index === undefined) {
185
+ index = this.coordinates.length;
186
+ this.coordinates.push(coord);
187
+ latMap.set(lat, index);
188
+ // ensure adjacencyList covers this new node
189
+ this.adjacencyList[index] = [];
190
+ }
191
+ return index;
192
+ }
171
193
  }
172
194
 
173
195
  export { TerraRoute, createCheapRuler, haversineDistance }