⚠️ Disclaimer: This project is entirely vibe coded.
A .NET 8 wrapper for OSRM (Open Source Routing Machine) using native C interop. SharpOSRM exposes OSRM's routing engine and data pipeline as idiomatic C# APIs, distributed as a cross-platform NuGet package that bundles the native libsharposrm shared library for Linux (x64), macOS (x64/arm64), and Windows (x64).
⚠️ TBB malloc proxy notice: The native library links TBB'stbbmalloc_proxy, which replaces the process'smalloc/free/reallocwith TBB's scalable allocator globally. This improves OSRM's routing performance but can cause crashes and memory corruption in other native libraries (e.g. SQLite, custom native dependencies) loaded in the same process. If your application uses other native libraries that are incompatible with a replaced malloc, build from source with-DSTRIP_TBBMALLOC=ONto disable the proxy at the cost of reduced routing performance.
OSRM is a high-performance routing engine that operates on OpenStreetMap data. SharpOSRM gives you:
- Data pipeline — prepare raw OSM data (
.osm.pbf) into a routable dataset using the four OSRM pipeline stages: Extract, Partition, Customize (MLD), Contract (CH). - Routing engine — load a prepared dataset and issue routing queries in-process without any HTTP server.
- Seven routing services — Route, Table, Nearest, Match, Trip, Tile, and a FlatBuffers variant of each.
All native calls cross a thin C bridge (libsharposrm) that wraps the OSRM C++ API. The C# side uses LibraryImport P/Invoke with SafeHandle-based lifetime management. All blocking OSRM calls have corresponding *Async overloads that offload to the thread pool.
- .NET 8
- The
SharposrmNuGet package (includes native libraries for all supported platforms — no separate OSRM installation needed)
For building from source, see CI/CD and Testing Guide.
dotnet add package Sharposrm
The NuGet package includes prebuilt native libraries for:
| Runtime ID | Library | Platform |
|---|---|---|
linux-x64 |
libsharposrm.so |
Ubuntu 22.04+ |
osx-x64 |
libsharposrm.dylib |
macOS 13+ (Intel) |
osx-arm64 |
libsharposrm.dylib |
macOS 15+ (Apple Silicon) |
win-x64 |
sharposrm.dll |
Windows Server 2025 |
OSRM cannot route until OSM data has been processed through the pipeline. You need an .osm.pbf file (download from Geofabrik or OSM extracts) and a routing profile Lua file.
Contraction Hierarchies (CH) — faster queries, longer upfront processing:
using Sharposrm;
using Sharposrm.Pipeline;
// Extract
OsrmPipeline.Extract(new ExtractorConfig
{
InputPath = "/data/monaco.osm.pbf",
ProfilePath = "/profiles/car.lua",
OutputPath = "/data/monaco", // base path, OSRM appends extensions
RequestedThreads = 4,
});
// Contract (CH)
// Note: run on a thread with a large stack — OSRM's contractor is deeply recursive.
var thread = new Thread(() =>
{
OsrmPipeline.Contract(new ContractorConfig
{
BasePath = "/data/monaco",
RequestedThreads = 4,
});
}, maxStackSize: 8 * 1024 * 1024);
thread.Start();
thread.Join();Multi-Level Dijkstra (MLD) — more flexible, faster preprocessing:
OsrmPipeline.Extract(new ExtractorConfig
{
InputPath = "/data/monaco.osm.pbf",
ProfilePath = "/profiles/car.lua",
OutputPath = "/data/monaco-mld",
RequestedThreads = 4,
});
OsrmPipeline.Partition(new PartitionerConfig
{
BasePath = "/data/monaco-mld",
RequestedThreads = 4,
});
OsrmPipeline.Customize(new CustomizerConfig
{
BasePath = "/data/monaco-mld",
RequestedThreads = 4,
});Important: All pipeline
RequestedThreadsmust be ≥ 1. Setting 0 crashes OSRM's TBB backend.
await using var engine = await OsrmEngine.CreateAsync(new EngineConfig
{
StoragePath = "/data/monaco", // base path produced by the pipeline
Algorithm = Algorithm.CH, // or Algorithm.MLD
});OsrmEngine is IAsyncDisposable. Engine creation loads the dataset into memory — for large datasets this can take several seconds. CreateAsync offloads this to the thread pool so it doesn't block an async context.
Compute a route between two or more waypoints:
using Sharposrm.Route;
var response = engine.Route(new RouteParameters
{
Coordinates = new[]
{
(7.41337, 43.72956), // (longitude, latitude)
(7.41983, 43.73115),
},
Steps = true,
Annotations = true,
AnnotationTypes = AnnotationsType.Duration | AnnotationsType.Speed,
Geometries = GeometriesType.Polyline,
Overview = OverviewType.Simplified,
Alternatives = false,
GenerateHints = true,
});
Console.WriteLine($"Distance: {response.Routes![0].Distance}m");
Console.WriteLine($"Duration: {response.Routes[0].Duration}s");Compute a duration/distance matrix between multiple origins and destinations:
using Sharposrm.Table;
var response = engine.Table(new TableParameters
{
Coordinates = new[]
{
(7.41337, 43.72956),
(7.41983, 43.73115),
(7.42500, 43.73500),
},
// Optional: separate Sources/Destinations index lists for one-to-many queries
});
// Durations[i][j] is null when the pair cannot be routed
double? duration = response.Durations![0][1];Snap a coordinate to the nearest road network node:
using Sharposrm.Nearest;
var response = engine.Nearest(new NearestParameters
{
Coordinates = new[] { (7.41337, 43.72956) },
Number = 3, // return 3 nearest snapping candidates
});Map-match a GPS trace to the road network:
using Sharposrm.Match;
var response = engine.Match(new MatchParameters
{
Coordinates = gpsTrace, // IReadOnlyList<(double, double)>
Timestamps = timestamps, // optional, Unix epoch seconds
Radiuses = radiuses, // optional, per-point search radius in metres
});Solve a Travelling Salesman Problem (shortest round trip through all waypoints):
using Sharposrm.Trip;
var response = engine.Trip(new TripParameters
{
Coordinates = waypoints,
RoundTrip = true,
Source = TripSourceType.First,
Destination = TripDestinationType.Last,
});Fetch a Mapbox Vector Tile for a given tile coordinate (returns raw MVT bytes):
using Sharposrm.Tile;
byte[] mvtData = engine.Tile(new TileParameters
{
X = 33660,
Y = 22961,
Zoom = 15,
});Every service except Tile has a *Flatbuffers() method that returns raw byte[] instead of a deserialized response object, for use cases where you need the FlatBuffers wire format directly:
byte[] fbBytes = engine.RouteFlatbuffers(routeParameters);
byte[] fbBytes = engine.TableFlatbuffers(tableParameters);
// etc.| Property | Type | Default | Description |
|---|---|---|---|
StoragePath |
string |
(required) | Base path to the prepared .osrm dataset |
Algorithm |
Algorithm |
CH |
CH or MLD |
UseSharedMemory |
bool |
false |
Load from shared memory (requires osrm-datastore) |
UseMmap |
bool |
true |
Use memory-mapped file I/O |
MaxLocationsTrip |
int |
-1 (unlimited) |
Per-engine cap for Trip service |
MaxLocationsViaroute |
int |
-1 |
Per-engine cap for Route service |
MaxLocationsDistanceTable |
int |
-1 |
Per-engine cap for Table service |
MaxLocationsMapMatching |
int |
-1 |
Per-engine cap for Match service |
MaxResultsNearest |
int |
-1 |
Per-engine cap for Nearest service |
MaxAlternatives |
int |
3 |
Maximum alternative routes |
DisableRouteSteps |
bool |
false |
Strip step data from all responses |
DisableRouteGeometry |
bool |
false |
Strip geometry from all responses |
All routing services throw OsrmException on failure. The exception message contains OSRM's error code and message (e.g. "OSRM error: NoRoute — No route found between input coordinates").
try
{
var response = engine.Route(parameters);
}
catch (OsrmException ex)
{
Console.WriteLine(ex.Message);
}OsrmEngineinstances are not thread-safe. Use one engine per thread, or synchronise externally.- Each
*Asyncmethod callsTask.Run(...)— it offloads the blocking native call to the thread pool. - OSRM's CH contraction is deeply recursive. Always run
OsrmPipeline.Contracton a thread with a stack of at least 8 MB (see example above).
SharpOSRM is licensed under the MIT License.
This package statically links OSRM, which is licensed under the BSD 2-Clause License. See THIRD-PARTY-NOTICES.txt for the full OSRM copyright notice.