terra-route
Version:
A library for routing along GeoJSON LineString networks
1 lines • 50.5 kB
Source Map (JSON)
{"version":3,"file":"terra-route.cjs","sources":["../src/distance/haversine.ts","../src/heap/min-heap.ts","../src/graph/methods/connected.ts","../src/graph/methods/unique-segments.ts","../src/test-utils/utils.ts","../src/graph/graph.ts","../src/graph/methods/nodes.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n 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 { Feature, FeatureCollection, LineString, Position } from 'geojson'\n\n/**\n * Counts the number of connected components in a graph represented by LineString features in a GeoJSON FeatureCollection.\n * Each LineString is treated as an edge in the graph, and connected components are determined by shared coordinates.\n * @param featureCollection - A GeoJSON FeatureCollection containing LineString features\n * @returns The number of connected components in the graph represented by the LineStrings\n */\nexport function graphGetConnectedComponentCount(\n featureCollection: FeatureCollection<LineString>\n): number {\n const features = featureCollection.features\n const numberOfFeatures = features.length\n\n // Map coordinates to feature indices\n const coordinateToFeatureIndices = new Map<string, number[]>()\n\n for (let index = 0; index < numberOfFeatures; index++) {\n const coordinates = features[index].geometry.coordinates\n\n for (const coordinate of coordinates) {\n const key = coordinateKey(coordinate)\n\n if (!coordinateToFeatureIndices.has(key)) {\n coordinateToFeatureIndices.set(key, [])\n }\n\n coordinateToFeatureIndices.get(key)!.push(index)\n }\n }\n\n // Build adjacency list for the graph\n const adjacencyList: number[][] = Array.from({ length: numberOfFeatures }, () => [])\n\n for (const indices of coordinateToFeatureIndices.values()) {\n for (let i = 0; i < indices.length; i++) {\n for (let j = i + 1; j < indices.length; j++) {\n const a = indices[i]\n const b = indices[j]\n adjacencyList[a].push(b)\n adjacencyList[b].push(a)\n }\n }\n }\n\n const visited = new Array<boolean>(numberOfFeatures).fill(false)\n let connectedComponents = 0\n\n for (let index = 0; index < numberOfFeatures; index++) {\n if (!visited[index]) {\n dfs(index, adjacencyList, visited)\n connectedComponents++\n }\n }\n\n return connectedComponents\n}\n\n/**\n * Depth-first search to mark all reachable nodes from the given index.\n * @param index - The current node index to start DFS from.\n * @param adjacencyList - The adjacency list representing the graph.\n * @param visited - An array to keep track of visited nodes.\n */\nfunction dfs(index: number, adjacencyList: number[][], visited: boolean[]): void {\n visited[index] = true\n\n for (const neighbor of adjacencyList[index]) {\n if (!visited[neighbor]) {\n dfs(neighbor, adjacencyList, visited)\n }\n }\n}\n\nfunction coordinateKey(position: Position): string {\n return `${position[0]},${position[1]}`\n}\n\nexport function graphGetConnectedComponents(\n featureCollection: FeatureCollection<LineString>\n): FeatureCollection<LineString>[] {\n const features = featureCollection.features\n const graph: Map<number, Set<number>> = new Map()\n const coordinateMap: Map<string, Set<number>> = new Map()\n\n function coordinateKey(coordinate: Position): string {\n return `${coordinate[0]},${coordinate[1]}`\n }\n\n // Build coordinate map: coordinate string -> Set of feature indices\n for (let index = 0; index < features.length; index++) {\n const coordinates = features[index].geometry.coordinates\n\n for (const coordinate of coordinates) {\n const key = coordinateKey(coordinate)\n\n if (!coordinateMap.has(key)) {\n coordinateMap.set(key, new Set())\n }\n\n coordinateMap.get(key)!.add(index)\n }\n }\n\n // Build adjacency list for graph\n for (let index = 0; index < features.length; index++) {\n graph.set(index, new Set())\n\n const coordinates = features[index].geometry.coordinates\n for (const coordinate of coordinates) {\n const key = coordinateKey(coordinate)\n const neighbors = coordinateMap.get(key)\n\n if (neighbors) {\n for (const neighborIndex of neighbors) {\n if (neighborIndex !== index) {\n graph.get(index)!.add(neighborIndex)\n }\n }\n }\n }\n }\n\n // DFS to find connected components\n const visited = new Set<number>()\n const components: FeatureCollection<LineString>[] = []\n\n function dfs(startIndex: number, currentComponent: Feature<LineString>[]): void {\n const stack: number[] = [startIndex]\n\n while (stack.length > 0) {\n const currentIndex = stack.pop()!\n\n if (visited.has(currentIndex)) {\n continue\n }\n\n visited.add(currentIndex)\n currentComponent.push(features[currentIndex])\n\n const neighbors = graph.get(currentIndex)\n if (neighbors) {\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n stack.push(neighbor)\n }\n }\n }\n }\n }\n\n for (let index = 0; index < features.length; index++) {\n if (!visited.has(index)) {\n const component: Feature<LineString>[] = []\n dfs(index, component)\n components.push({\n type: 'FeatureCollection',\n features: component\n })\n }\n }\n\n\n // Sort components by the number of features in ascending order\n components.sort((a, b) => a.features.length - b.features.length)\n\n return components\n}\n","import {\n Feature,\n FeatureCollection,\n LineString,\n Position\n} from 'geojson';\n\n/**\n * Normalize a segment so that [A, B] is equal to [B, A]\n */\nfunction normalizeSegment(start: Position, end: Position): [Position, Position] {\n const [aLat, aLng] = start;\n const [bLat, bLng] = end;\n\n if (\n aLat < bLat ||\n (aLat === bLat && aLng <= bLng)\n ) {\n return [start, end];\n }\n\n return [end, start];\n}\n\n/**\n * Convert a pair of Positions to a string key for deduplication\n */\nfunction segmentKey(start: Position, end: Position): string {\n const [normalizedStart, normalizedEnd] = normalizeSegment(start, end);\n return JSON.stringify([normalizedStart, normalizedEnd]);\n}\n\n/**\n * Breaks LineStrings in a FeatureCollection into unique single line segments\n */\nexport function graphGetUniqueSegments(\n input: FeatureCollection<LineString>\n): FeatureCollection<LineString> {\n const uniqueSegments = new Map<string, Feature<LineString>>();\n\n for (const feature of input.features) {\n const coordinates = feature.geometry.coordinates;\n\n for (let index = 0; index < coordinates.length - 1; index++) {\n const start = coordinates[index];\n const end = coordinates[index + 1];\n\n const key = segmentKey(start, end);\n\n if (!uniqueSegments.has(key)) {\n const segment: Feature<LineString> = {\n type: 'Feature',\n geometry: {\n type: 'LineString',\n coordinates: [start, end]\n },\n properties: {}\n };\n\n uniqueSegments.set(key, segment);\n }\n }\n }\n\n return {\n type: 'FeatureCollection',\n features: Array.from(uniqueSegments.values())\n };\n}\n","import { Position, Feature, Point, LineString, FeatureCollection } from \"geojson\";\nimport { haversineDistance } from \"../terra-route\";\n\n/**\n * Calculates the total length of a LineString route in meters.\n *\n * @param line - A GeoJSON Feature<LineString> representing the route\n * @returns The total length of the route in meters\n */\nexport function routeLength(\n line: Feature<LineString>,\n) {\n const lineCoords = line.geometry.coordinates;\n\n // Calculate the total route distance\n let routeDistance = 0;\n for (let i = 0; i < lineCoords.length - 1; i++) {\n routeDistance += haversineDistance(lineCoords[i], lineCoords[i + 1]);\n }\n return routeDistance\n}\n\n/**\n * Extracts unique coordinates from a FeatureCollection of LineStrings.\n *\n * @param collection - A GeoJSON FeatureCollection of LineStrings\n * @returns An array of unique Position coordinates\n */\nexport function getUniqueCoordinatesFromLineStrings(\n collection: FeatureCollection<LineString>\n): Position[] {\n const seen = new Set<string>();\n const unique: Position[] = [];\n\n for (const feature of collection.features) {\n if (feature.geometry.type !== \"LineString\") {\n continue;\n }\n\n for (const coord of feature.geometry.coordinates) {\n const key = `${coord[0]},${coord[1]}`;\n\n if (!seen.has(key)) {\n seen.add(key);\n unique.push(coord);\n }\n }\n }\n\n return unique;\n}\n\n/**\n * Validates a GeoJSON Feature<LineString> route.\n *\n * @param route - The GeoJSON feature to validate\n * @returns A boolean indicating if it is a valid LineString route\n */\nexport function getReasonIfLineStringInvalid(\n route: Feature<LineString> | null | undefined\n): string | undefined {\n // 1. Must exist\n if (!route) {\n return 'No feature';\n }\n\n // 2. Must be a Feature\n if (route.type !== \"Feature\") {\n return 'Not a Feature';\n }\n\n // 3. Must have a geometry of type LineString\n if (!route.geometry || route.geometry.type !== \"LineString\") {\n return 'Not a LineString';\n }\n\n // 4. Coordinates must be an array with length >= 2\n const coords = route.geometry.coordinates;\n if (!Array.isArray(coords) || coords.length < 2) {\n return `Not enough coordinates: ${coords.length} (${coords})`;\n }\n\n const seen = new Set<string>();\n\n // 5. Validate each coordinate is a valid Position\n // (At minimum, [number, number] or [number, number, number])\n for (const position of coords) {\n if (!Array.isArray(position)) {\n return 'Not a Position; not an array';\n }\n\n // Check numeric values, ignoring optional altitude\n if (\n position.length < 2 ||\n typeof position[0] !== \"number\" ||\n typeof position[1] !== \"number\"\n ) {\n return 'Not a Position; elements are not a numbers';\n }\n\n // 6. Check for duplicates\n const key = `${position[0]},${position[1]}`;\n if (seen.has(key)) {\n return `Duplicate coordinate: ${key}`;\n }\n seen.add(key);\n }\n}\n\n/**\n * Checks if the start and end coordinates of a LineString match the given start and end points.\n * \n * @param line - The LineString feature to check\n * @param start - The start point feature\n * @param end - The end point feature\n * @return True if the start and end coordinates match, false otherwise\n * */\nexport function startAndEndAreCorrect(line: Feature<LineString>, start: Feature<Point>, end: Feature<Point>): boolean {\n const lineCoords = line.geometry.coordinates;\n const startCoords = start.geometry.coordinates;\n const endCoords = end.geometry.coordinates;\n\n // Check if the first coordinate of the LineString matches the start point\n const startMatches = lineCoords[0][0] === startCoords[0] && lineCoords[0][1] === startCoords[1];\n\n // Check if the last coordinate of the LineString matches the end point\n const endMatches = lineCoords[lineCoords.length - 1][0] === endCoords[0] && lineCoords[lineCoords.length - 1][1] === endCoords[1];\n\n return startMatches && endMatches;\n}\n\n/**\n * Checks if the route represented by a LineString is longer than the direct path. \n * In theory, a route should always longer than the direct path if it has more than two points.\n * @param line - The LineString feature representing the route\n * @param start - The start point feature\n * @param end - The end point feature\n * @returns - True if the route is longer than the direct path, false otherwise\n */\nexport function routeIsLongerThanDirectPath(line: Feature<LineString>, start: Feature<Point>, end: Feature<Point>): boolean {\n const lineCoords = line.geometry.coordinates;\n const startCoords = start.geometry.coordinates;\n const endCoords = end.geometry.coordinates;\n\n if (lineCoords.length <= 2) {\n return true;\n }\n\n // Calculate the direct distance between the start and end points\n const directDistance = haversineDistance(startCoords, endCoords);\n\n // Calculate the route distance\n let routeDistance = 0;\n for (let i = 0; i < lineCoords.length - 1; i++) {\n routeDistance += haversineDistance(lineCoords[i], lineCoords[i + 1]);\n }\n\n // If the route distance is 0, it means the start and end points are the same\n if (routeDistance === 0) {\n return true;\n }\n\n if (routeDistance < directDistance) {\n\n // Check if the route distance is very close to the direct distance\n const absoluteDifference = Math.abs(routeDistance - directDistance);\n if (absoluteDifference < 0.000000000001) {\n return true;\n }\n\n return false;\n }\n\n return true\n}\n\n/**\n * Modifies a FeatureCollection of LineStrings to break connections\n * between lines that share coordinates, by adjusting one of the shared\n * coordinates within a given tolerance.\n * \n * @param collection - The input FeatureCollection of LineStrings\n * @param tolerance - The amount by which to offset shared coordinates (in degrees)\n * @returns A new FeatureCollection with modified coordinates\n */\nexport function disconnectLineStrings(\n collection: FeatureCollection<LineString>,\n tolerance: number\n): FeatureCollection<LineString> {\n const seenCoordinates = new Map<string, number>()\n\n function getCoordinateKey(coordinate: Position): string {\n return `${coordinate[0]},${coordinate[1]}`\n }\n\n function offsetCoordinate(coordinate: Position, count: number): Position {\n const offset = count * tolerance\n return [coordinate[0] + offset, coordinate[1] + offset]\n }\n\n const updatedFeatures = collection.features.map((feature) => {\n const updatedCoordinates: Position[] = feature.geometry.coordinates.map((coordinate) => {\n const key = getCoordinateKey(coordinate)\n\n if (seenCoordinates.has(key)) {\n const count = seenCoordinates.get(key)!\n seenCoordinates.set(key, count + 1)\n return offsetCoordinate(coordinate, count + 1)\n }\n\n seenCoordinates.set(key, 0)\n return coordinate\n })\n\n return {\n ...feature,\n geometry: {\n ...feature.geometry,\n coordinates: updatedCoordinates\n }\n }\n })\n\n return {\n ...collection,\n features: updatedFeatures\n }\n}\n","import { Feature, FeatureCollection, LineString, Point } from \"geojson\";\nimport { graphGetConnectedComponentCount, graphGetConnectedComponents } from \"./methods/connected\";\nimport { graphGetNodeAndEdgeCount, graphGetNodesAsPoints } from \"./methods/nodes\";\nimport { graphGetUniqueSegments } from \"./methods/unique-segments\";\nimport { routeLength } from \"../test-utils/utils\";\n\n/**\n * Represents a graph constructed from a GeoJSON FeatureCollection of LineString features.\n * This class provides methods to analyze the graph, including connected components, node and edge counts,\n * and shortest paths. Coordinates in the LineStrings are considered connected if they share identical coordinates.\n */\nexport class LineStringGraph {\n constructor(network: FeatureCollection<LineString>) {\n this.network = network;\n }\n\n private network: FeatureCollection<LineString>;\n\n /**\n * Sets the network for the graph.\n * This method replaces the current network with a new one.\n * @param network A GeoJSON FeatureCollection of LineString features representing the network.\n */\n setNetwork(network: FeatureCollection<LineString>) {\n this.network = network;\n }\n\n /**\n * Gets the current network of the graph.\n * @returns A GeoJSON FeatureCollection of LineString features representing the network.\n */\n getNetwork(): FeatureCollection<LineString> {\n return this.network;\n }\n\n /**\n * Gets the connected components of the graph.\n * @returns An array of FeatureCollection<LineString> representing the connected components.\n */\n getConnectedComponents(): FeatureCollection<LineString>[] {\n return graphGetConnectedComponents(this.network)\n }\n\n /**\n * Gets the count of connected components in the graph.\n * @returns The number of connected components in the graph.\n */\n getConnectedComponentCount(): number {\n return graphGetConnectedComponentCount(this.network);\n }\n\n /**\n * Gets the count of unique nodes and edges in the graph.\n * @returns An object containing the counts of nodes and edges.\n */\n getNodeAndEdgeCount(): { nodeCount: number, edgeCount: number } {\n return graphGetNodeAndEdgeCount(this.network);\n }\n\n /**\n * Gets the unique nodes of the graph as a FeatureCollection of Point features.\n * @returns A FeatureCollection<Point> containing the nodes of the graph.\n */\n getNodes(): FeatureCollection<Point> {\n const nodes = graphGetNodesAsPoints(this.network);\n return {\n type: \"FeatureCollection\",\n features: nodes\n };\n }\n\n /**\n * Gets the count of unique nodes in the graph.\n * @returns The number of unique nodes in the graph.\n */\n getNodeCount(): number {\n const { nodeCount } = this.getNodeAndEdgeCount();\n return nodeCount;\n }\n\n /**\n * Gets the unique edges of the graph as a FeatureCollection of LineString features. Each edge is represented as a LineString.\n * This method ensures that each edge is unique, meaning that edges are not duplicated in the collection. Each linestring only \n * two coordinates, representing the start and end points of the edge.\n * @returns A FeatureCollection<LineString> containing the unique edges of the graph.\n */\n getEdges(): FeatureCollection<LineString> {\n return graphGetUniqueSegments(this.network);\n }\n\n /**\n * Gets the length of the longest edge in the graph based on the length of the LineString.\n * If no edges exist, it returns -1.\n * @returns The length of the longest edge in meters, or 0 if no edges exist.\n */\n getLongestEdgeLength(): number {\n const longestEdge = this.getLongestEdge();\n if (!longestEdge) {\n return -1;\n }\n return routeLength(longestEdge);\n }\n\n /**\n * Gets the length of the shortest edge in the graph based on the length of the LineString.\n * If no edges exist, it returns -1.\n * @returns The length of the shortest edge in meters, or 0 if no edges exist.\n */\n getShortestEdgeLength(): number {\n const shortestEdge = this.getShortestEdge();\n if (!shortestEdge) {\n return -1;\n }\n return routeLength(shortestEdge);\n }\n\n /**\n * Gets the longest edge in the graph based on the length of the LineString.\n * @returns The longest edge as a Feature<LineString> or null if no edges exist.\n */\n getLongestEdge(): Feature<LineString> | null {\n const edges = this.getEdges().features;\n if (edges.length === 0) {\n return null;\n }\n const longestEdges = edges.sort((a, b) => routeLength(a) - routeLength(b));\n return longestEdges[longestEdges.length - 1];\n }\n\n /**\n * Gets the shortest edge in the graph based on the length of the LineString.\n * @returns The shortest edge as a Feature<LineString> or null if no edges exist.\n */\n getShortestEdge(): Feature<LineString> | null {\n const edges = this.getEdges().features;\n if (edges.length === 0) {\n return null;\n }\n const shortestEdges = edges.sort((a, b) => routeLength(a) - routeLength(b));\n return shortestEdges[0];\n }\n\n /**\n * Gets the count of unique edges in the graph.\n * @returns The number of unique edges in the graph.\n */\n getEdgeCount(): number {\n const { edgeCount } = this.getNodeAndEdgeCount();\n return edgeCount;\n }\n}\n","import { Feature, FeatureCollection, LineString, Point, Position } from 'geojson'\n\n/**\n * Counts the unique nodes and edges in a GeoJSON FeatureCollection of LineString features.\n * @param featureCollection - A GeoJSON FeatureCollection containing LineString features\n * @returns An object containing the count of unique nodes and edges\n */\nexport function graphGetNodeAndEdgeCount(\n featureCollection: FeatureCollection<LineString>\n): { nodeCount: number; edgeCount: number } {\n const nodeSet = new Set<string>()\n const edgeSet = new Set<string>()\n\n for (const feature of featureCollection.features) {\n const coordinates = feature.geometry.coordinates\n\n for (const coordinate of coordinates) {\n nodeSet.add(JSON.stringify(coordinate))\n }\n\n for (let i = 0; i < coordinates.length - 1; i++) {\n const coordinateOne = coordinates[i]\n const coordinateTwo = coordinates[i + 1]\n\n const edge = normalizeEdge(coordinateOne, coordinateTwo)\n edgeSet.add(edge)\n }\n }\n\n return {\n nodeCount: nodeSet.size,\n edgeCount: edgeSet.size,\n }\n}\n\nfunction normalizeEdge(coordinateOne: Position, coordinateTwo: Position): string {\n const stringOne = JSON.stringify(coordinateOne)\n const stringTwo = JSON.stringify(coordinateTwo)\n\n if (stringOne < stringTwo) {\n return `${stringOne}|${stringTwo}`\n }\n\n return `${stringTwo}|${stringOne}`\n}\n\n\n/**\n * Converts a FeatureCollection of LineString features into a FeatureCollection of Point features,\n * where each unique coordinate in the LineStrings becomes a Point.\n * @param lines - A GeoJSON FeatureCollection containing LineString features\n * @returns A FeatureCollection of Point features representing unique nodes\n */\nexport function graphGetNodesAsPoints(lines: FeatureCollection<LineString>): Feature<Point>[] {\n const seen = new Set<string>();\n const points: Feature<Point>[] = [];\n\n for (const feature of lines.features) {\n for (const coord of feature.geometry.coordinates) {\n const key = coord.join(',');\n\n if (!seen.has(key)) {\n seen.add(key);\n points.push({\n type: 'Feature',\n geometry: {\n type: 'Point',\n coordinates: coord\n },\n properties: {}\n });\n }\n }\n }\n\n return points;\n}\n","import { FeatureCollection, LineString, 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\";\nimport { LineStringGraph } from \"./graph/graph\";\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, LineStringGraph }","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","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","dfs","adjacencyList","visited","_step3","_iterator3","_createForOfIteratorHelperLoose","done","neighbor","segmentKey","start","end","_normalizeSegment","aLat","bLat","normalizeSegment","JSON","stringify","routeLength","line","lineCoords","geometry","coordinates","routeDistance","i","LineStringGraph","network","setNetwork","getNetwork","getConnectedComponents","featureCollection","features","graph","Map","coordinateMap","coordinateKey","coordinate","_step4","_iterator4","has","set","Set","get","add","_step5","_iterator5","neighbors","_iterator6","_step6","neighborIndex","components","startIndex","currentComponent","stack","_step7","_iterator7","component","type","sort","b","graphGetConnectedComponents","getConnectedComponentCount","position","numberOfFeatures","coordinateToFeatureIndices","_step","_iterator","_step2","Array","from","_iterator2","values","indices","j","fill","connectedComponents","graphGetConnectedComponentCount","getNodeAndEdgeCount","nodeSet","edgeSet","edge","coordinateTwo","stringOne","stringTwo","nodeCount","edgeCount","graphGetNodeAndEdgeCount","getNodes","lines","seen","points","coord","join","properties","graphGetNodesAsPoints","getNodeCount","getEdges","input","uniqueSegments","graphGetUniqueSegments","getLongestEdgeLength","longestEdge","getLongestEdge","getShortestEdgeLength","shortestEdge","getShortestEdge","edges","longestEdges","getEdgeCount","TerraRoute","options","_options$distanceMeas","_options$heap","distanceMeasurement","heapConstructor","coordinateIndexMap","buildRouteGraph","coordIndexMapLocal","coordsLocal","adjListLocal","measureDistance","_lineCoords$i","lngA","latA","_lineCoords","lngB","latB","latMapA","indexA","undefined","latMapB","indexB","segmentDistance","distance","getRoute","Error","getOrCreateIndex","endIndex","openSet","gScore","Infinity","cameFrom","current","tentativeG","heuristic","path","unshift","lng","lat","latMap","FE","E2","RAD","cosLat","w2","w","m","kx","ky","deltaLng","dx","dy"],"mappings":"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