A blazingly fast, highly-customizable, and memory-efficient routing engine for OpenStreetMap data, built with Rust 🦀 and TypeScript 🔷. Designed for performance-critical applications like generating GTFS shapes.txt
, batch routing, and geospatial analysis.
- High-Performance Rust Core: All heavy lifting (graph processing, routing) is done in native Rust code via Neon for maximum speed.
- Customizable Routing Profiles: Define your own routing rules! Create profiles for cars, trains, pedestrians, or anything you can imagine based on OSM tags (
highway
,railway
, etc.) and assign custom penalties. - Advanced A* Routing: Utilizes a highly optimized A* algorithm with precomputed backward costs (ALT heuristic) for incredibly fast routing, especially across multiple waypoints.
- Intelligent Binary Caching: Downloads and parses OSM data from the Overpass API once, then stores a hyper-optimized binary representation of the graph for near-instantaneous loads on subsequent runs.
- Asynchronous Batch Processing: A built-in
RouteQueue
leverages a parallel thread pool to process thousands of routes concurrently, with progress tracking. - Geospatial Queries: Quickly find all nodes or ways within a given radius from a specific point.
- Helper Utilities: Comes with tools to
simplifyShape
(Ramer-Douglas-Peucker) andoffsetShape
for post-processing route geometries.
npm install sparkling-osm-router
The package will build the Rust core during the postinstall
step.
Here's a complete example of loading a graph for a specific area, defining a car routing profile, finding the nearest nodes to your start/end points, and calculating a route.
import { Graph, Location } from "sparkling-osm-router";
const FRANKFURT_AIRPORT_AREA: Location[] = [
[8.529, 50.062],
[8.59, 50.062],
[8.59, 50.02],
[8.529, 50.02],
];
const graph = new Graph({
filePath: "./cache/fra-airport.bin",
ttlDays: 7, // Re-download data from Overpass if cache is older than 7 days
overpassGraph: {
bounds: FRANKFURT_AIRPORT_AREA,
query: ["way[highway]"], // Fetch all ways with a 'highway' tag
},
});
const carProfile = new graph.Profile({
id: "car",
key: "highway", // The OSM tag to base penalties on
penalties: [
[["motorway", "motorway_link"], 1], // Lowest penalty = fastest
[["trunk", "trunk_link"], 1.1],
[["primary", "primary_link"], 1.2],
[["secondary", "secondary_link"], 1.5],
[["tertiary", "tertiary_link"], 1.8],
["residential", 2.5],
["service", 5],
["default", 30], // Penalty for any 'highway' tag not listed above (not recommended)
],
});
await graph.loadGraph();
const startPoint: Location = [8.5705, 50.0522]; // Frankfurt Airport Terminal 1
const endPoint: Location = [8.5401, 50.0471]; // The Squaire
const startNodeId = carProfile.getNearestNode(startPoint);
const endNodeId = carProfile.getNearestNode(endPoint);
if (!startNodeId || !endNodeId) {
throw new Error("Could not find nearest nodes for start or end point.");
}
const route = await carProfile.getRoute([startNodeId, endNodeId]);
console.log("Route found:", carProfile.getShape(route));
graph.unloadGraph();
Initializes a new graph instance.
options.filePath
:string
- Path to the binary cache file. The directory will be created if it doesn't exist.options.ttlDays
:number
- Time-to-live for the cache file. If the file is older than this, it will be rebuilt.options.overpassGraph.bounds
:Location[]
- A polygon defining the geographical area to query.options.overpassGraph.query
:string[]
- An array of Overpass query parts (e.g.,way[highway]
,way[railway]
).options.overpassGraph.ignoreTurnRestrictions?
:boolean
- Set totrue
to disable turn restriction processing. Defaults tofalse
.
Loads the graph from the binary cache or builds it from the Overpass API if needed. Returns the numerical graphId
.
Removes the graph and all associated route queues from memory. Returns true
if successful. It's crucial to call this when you're done to free up resources.
Represents a set of rules for routing on a Graph
.
Creates a new profile associated with the parent graph.
options.id
:string
- A unique ID for this profile (e.g., "car", "pedestrian").options.key
:"highway" | "railway"
- The primary OSM tag key to use for penalties.options.penalties
:[string | string[], number][]
- An array defining the cost for traversing ways with specific tag values. Lower penalty is better. Adefault
key can be used as a fallback.options.accessTags?
:string[]
- Additional tags to check for access rights (e.g.,motorcar
).options.onewayTags?
:string[]
- Additional tags to check for oneway streets.options.exceptTags?
:string[]
- Vehicle types to consider forexcept
tags on turn restrictions.
Calculates the optimal route through a series of OSM node IDs.
Finds the closest routable node in the graph to the given [lon, lat]
coordinates.
Retrieves the full data for a single OSM node, including its ID, location, and tags.
Converts a RouteResult
object into an array of [lon, lat]
coordinates, forming the route's geometry.
Finds all OSM nodes within a specified radius.
Finds all OSM ways that fall at least partially within a specified radius.
Creates a dedicated queue for high-throughput batch routing using this profile.
Manages batch processing of many route requests in parallel.
Adds a new routing task to the queue. routeId
is a custom identifier you provide to track the result.
Starts processing the queue. This is the main execution method.
The callback
function (id: string, result: RouteResult | null, error?: Error) => void
is called for each completed route.
// Example of using the RouteQueue
const queue = carProfile.createRouteQueue(true); // true enables a CLI progress bar
queue.enqueueRoute("route-1", [123, 456]);
queue.enqueueRoute("route-2", [789, 101]);
const results = new Map();
await queue.awaitAll((id, result, error) => {
if (error) {
console.error(`Error processing ${id}:`, error);
} else {
console.log(`Completed ${id}`);
results.set(id, result);
}
});
console.log("All routes processed!");
Returns the current status of the queue ({ queuedTasks, activeTasks, isEmpty }
).
Clears the queue. Can only be called when not processing.
Simplifies a route's geometry using the Ramer-Douglas-Peucker algorithm to reduce the number of points while preserving the general shape.
Creates a new shape that is a parallel offset of the original, useful for visualizing distinct directions on a two-way road.