SharpNinja.Valhalla
1.1.0
dotnet add package SharpNinja.Valhalla --version 1.1.0
NuGet\Install-Package SharpNinja.Valhalla -Version 1.1.0
<PackageReference Include="SharpNinja.Valhalla" Version="1.1.0" />
<PackageVersion Include="SharpNinja.Valhalla" Version="1.1.0" />
<PackageReference Include="SharpNinja.Valhalla" />
paket add SharpNinja.Valhalla --version 1.1.0
#r "nuget: SharpNinja.Valhalla, 1.1.0"
#:package SharpNinja.Valhalla@1.1.0
#addin nuget:?package=SharpNinja.Valhalla&version=1.1.0
#tool nuget:?package=SharpNinja.Valhalla&version=1.1.0
SharpNinja.Valhalla
Embedded, in-process C# port of the Valhalla OSM routing engine: Baldr graph reader, Sif costing, Thor route engine, Odin directions, and a Mjolnir tile builder, exposed behind a provider-neutral routing client.
There is no server process and no HTTP hop. The library reads Valhalla tiles from a local directory and computes routes entirely in-process, which makes it a fit for embedding routing directly into a desktop or mobile app.
Why
Valhalla is normally run as a standalone service (valhalla_service + a tile directory built by valhalla_build_tiles) that clients call over HTTP. SharpNinja.Valhalla ports the parts of that pipeline needed to go from "a .osm.pbf extract" to "a route between two points" as plain, dependency-light C#, so a .NET app can:
- Read tiles built by stock Valhalla, or build its own tiles on-device from an OSM extract.
- Compute a route (auto or truck costing) without a network call.
- Get maneuver/shape output usable for turn-by-turn UI.
Modules
Each module is a fairly direct port of the corresponding Valhalla C++ module, with the same responsibilities and name:
| Module | Ported from | Responsibility | Source | Tests |
|---|---|---|---|---|
Baldr |
valhalla/baldr |
Graph tile reader: tiles, directed edges, nodes, admin areas, traffic, restrictions, sign/street name info | src/SharpNinja.Valhalla/Baldr/ (39 files) |
32 files |
Midgard |
valhalla/midgard |
Geometry primitives: points, polylines, tiling math, distance approximation, encoded-shape helpers | src/SharpNinja.Valhalla/Midgard/ (18 files) |
10 files |
Loki |
valhalla/loki |
Location correlation: snapping input coordinates onto the graph, closest-edge search | src/SharpNinja.Valhalla/Loki/ (4 files) |
3 files |
Sif |
valhalla/sif |
Costing models: DynamicCost, AutoCost, TruckCost, edge labels |
src/SharpNinja.Valhalla/Sif/ (8 files) |
3 files |
Thor |
valhalla/thor |
Path algorithms: unidirectional and bidirectional A*, trip-leg building | src/SharpNinja.Valhalla/Thor/ (10 files) |
8 files |
Odin |
valhalla/odin |
Maneuver building, directions-leg assembly, and en-US narrative prose (NarrativeBuilder + embedded locale dictionaries; see Known gaps for remaining parity depth) |
src/SharpNinja.Valhalla/Odin/ |
tests under Odin/ |
Mjolnir |
valhalla/mjolnir |
Tile builder: OSM PBF parsing, graph construction, enhancement, shortcuts, restrictions | src/SharpNinja.Valhalla/Mjolnir/ (33 files) |
14 files |
Osm |
- | This package's own on-device provisioning seam (tile-set building, extract retrieval abstractions) | src/SharpNinja.Valhalla/Osm/ (4 files) |
- |
On top of these, the package root exposes the public, provider-neutral surface: IOsmRoutingClient, EmbeddedValhallaRoutingClient, EmbeddedValhallaGraphReaderFactory, GeoCoordinate, IEncodedPolylineDecoder, ValhallaPolylineDecoder, and OsmRoutingErrorCodes.
Requirements
- .NET 10 SDK
- A local Valhalla tile directory (built by stock Valhalla's
valhalla_build_tiles, or built on-device from a.osm.pbfextract with this package'sMjolnir/Osmtypes)
Install
dotnet add package SharpNinja.Valhalla
Quickstart
The routing client is provider-neutral by design (IOsmRoutingClient) so callers do not depend on Valhalla-specific DTOs. EmbeddedValhallaRoutingClient is the in-process implementation that drives the ported engine directly against a local tile directory.
using Microsoft.Extensions.DependencyInjection;
using SharpNinja.Valhalla;
var services = new ServiceCollection();
services.AddLogging();
services.AddSingleton<EmbeddedValhallaGraphReaderFactory>();
services.AddSingleton<IOsmTileDirectoryProvider>(new FixedTileDirectoryProvider("/path/to/valhalla_tiles"));
services.AddSingleton<IOsmRoutingClient, EmbeddedValhallaRoutingClient>();
using var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<IOsmRoutingClient>();
var request = new OsmRouteRequest(
Endpoint: null,
Origin: new GeoCoordinate(47.6062, -122.3321), // Seattle
Destination: new GeoCoordinate(45.5152, -122.6784), // Portland
Costing: OsmRouteCostings.Auto);
var result = await client.CalculateRouteAsync(request);
if (result.Error is not null)
{
Console.WriteLine($"Routing failed: {result.Error}");
}
else
{
var route = result.Routes[0];
Console.WriteLine($"{route.DistanceMeters / 1000:F1} km, {route.DurationSeconds / 60} min, {route.Maneuvers.Count} maneuvers");
}
// A minimal IOsmTileDirectoryProvider for a directory that never changes.
sealed class FixedTileDirectoryProvider(string path) : IOsmTileDirectoryProvider
{
public Task<string?> GetTileDirectoryAsync(CancellationToken cancellationToken = default)
=> Task.FromResult<string?>(path);
}
Notes on the shapes above:
EmbeddedValhallaGraphReaderFactorycaches oneGraphReaderper tile directory (tile loading is expensive) and hands out aLeasewith a lock gate, because the ported tile cache is not thread-safe.EmbeddedValhallaRoutingClientalready serializes on that gate internally, so callers just awaitCalculateRouteAsyncas normal.IOsmTileDirectoryProvideris host-supplied on purpose: the package does not care whether the tile directory comes from settings, is downloaded on demand, or is bundled with the app.- Truck routing is available via
OsmRouteCostings.TruckandOsmRouteRequest.TruckOptions(height/width/length/weight/axle count); omittingTruckOptionsfalls back to stock Valhalla's default truck profile. OsmRouteRequest.ComputeAlternativeRoutesis currently a no-op (see Known gaps).
Decoding an encoded shape
Route candidates already include decoded RoutePoints, but the raw encoded polyline6 string is also available (OsmRouteCandidate.EncodedPolyline) if you need to decode it yourself, e.g. for a cached/serialized route:
IEncodedPolylineDecoder decoder = new ValhallaPolylineDecoder();
IReadOnlyList<GeoCoordinate> points = decoder.Decode(route.EncodedPolyline);
Building tiles on-device
If you don't already have a Valhalla tile directory, the Osm/Mjolnir types can build one on-device from an .osm.pbf extract:
IOsmExtractSource- retrieves the extract (the only network step; entirely opt-in)ITileSetBuilder/MjolnirTileSetBuilder- builds tiles from the extract via the portedMjolnir.TileBuilderIOnDeviceTileProvisioner- orchestrates the two: returns the existing tile directory if tiles are already present, otherwise retrieves the extract and builds tiles, propagating the extract source's error verbatim on failure
var result = await provisioner.EnsureTilesAsync();
if (result.Success)
{
// result.TileDirectory now has usable tiles
}
else
{
// result.Error is one of OsmRoutingErrorCodes
}
Error codes
IOsmRoutingClient implementations report failures as one of the canonical OsmRoutingErrorCodes string constants:
| Code | Meaning |
|---|---|
not_configured |
No tile directory configured, or it's missing/empty on disk |
auth_error |
Authentication failure (reserved for HTTP-backed clients) |
rate_limit |
Rate limited (reserved for HTTP-backed clients) |
transport |
Tile I/O or access failure reading the local tile directory |
parse |
No route found (no snap, no path, or an engine-internal failure building directions) |
http_error |
Generic HTTP failure (reserved for HTTP-backed clients) |
invalid_source |
The configured OSM extract source is present but invalid (e.g. non-HTTPS URL) |
Known gaps
Both original behavior gaps versus a full Valhalla HTTP service are now closed for the surfaced routing behavior:
- Maneuver narrative text.
OsmRouteManeuver.Instructioncarries en-US written turn-by-turn prose produced by the ported OdinNarrativeBuilder(all driving maneuver families). Remaining upstream-parity depth that the current DTO does not surface - spoken/verbal strings, localized length/time, additional-locale grammar, and the transit/pedestrian/bike-share/indoor maneuver families - is being ported in later slices. - Alternate routes. When
OsmRouteRequest.ComputeAlternativeRoutesis set and no via/through points are supplied, the engine computes multiple distinct routes (bidirectional A* with the portedalternates.hsharing/stretch viability filters and therecost.hforward recost pass) andOsmRouteResult.Routescarries them primary-first, then by ascending cost. Via routes stay on the single-leg axis. Small maps may still yield a single route when no viable alternate exists.
Build
Run from the repo root; both scripts self-locate so they work regardless of caller cwd.
.\build.ps1 Pack
./build.sh Pack
The build is a Nuke target chain: Restore -> Compile -> Test -> Pack -> Publish.
- Local default target:
Pack(producesartifacts/nuget/*.nupkg) - CI (Azure Pipelines) runs
Publish, which chains through the rest and pushes to nuget.org using theNUGET_API_KEYpipeline secret
Common targets:
.\build.ps1 Test # run the test suite (trx output under artifacts/test-results)
.\build.ps1 Pack -Configuration Release
.\build.ps1 Clean # delete bin/obj and artifacts output
Testing
The test suite is xUnit and mirrors the src/ module layout 1:1 (76 test files across Baldr, Loki, Midgard, Mjolnir, Odin, Sif, and Thor), plus a BaldrMonacoParityTests suite that checks the ported Baldr reader against reference behavior.
dotnet test tests/SharpNinja.Valhalla.Tests/SharpNinja.Valhalla.Tests.csproj
or via the Nuke build (.\build.ps1 Test), which also drops .trx results under artifacts/test-results for CI publishing.
Repository layout
src/SharpNinja.Valhalla/ the engine (packable library)
Baldr/ Midgard/ Loki/ Sif/ Thor/ Odin/ Mjolnir/ Osm/ ported modules (see table above)
*.cs public routing surface (client, factory, coordinate, polyline decoder, error codes)
tests/SharpNinja.Valhalla.Tests/ xUnit test suite, same module layout
build/ Nuke build project (Build.cs)
SharpNinja.Valhalla.slnx solution file
build.ps1 / build.sh bootstrap scripts that invoke the Nuke build
License
MIT, matching upstream Valhalla's license. See LICENSE and ACKNOWLEDGMENTS.md for upstream and third-party attribution.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.