auto-sea-way

July 8, 2026 · View on GitHub

Open source maritime auto-routing. Generates a global water-surface routing graph from OpenStreetMap land polygon data using H3 hexagonal grid indexing. Pure Rust.

San Francisco to Mykolaiv — maritime route computed through Panama Canal, Atlantic, Mediterranean, and Black Sea

San Francisco to Mykolaiv (9,768 nm) — computed route through the Panama Canal, across the Atlantic, through the Mediterranean and into the Black Sea. More benchmark routes in bench-routes.geojson.

Why auto-sea-way?

If you're building a maritime application — fleet tracking, voyage planning, logistics optimization — you need a way to compute realistic sea routes between coordinates. The alternatives are:

  • Commercial SaaS APIs — subscription pricing, closed-source, no self-hosting option, vendor lock-in
  • Open-source libraries (eurostat/searoute, searoute-py, scgraph) — route on pre-curated shipping lane networks (~4K edges), no coastline detail, can't distinguish a harbor entrance from open ocean

auto-sea-way takes a different approach: it generates a high-resolution routing graph algorithmically from OpenStreetMap land polygons using H3 hexagonal indexing. The result is ~40M navigable cells with adaptive resolution — coarse in open ocean (fast), fine near coastlines and through narrow passages like Suez and Panama (accurate).

Ship it as a single binary + graph file. Self-hosted, no third-party API keys, no rate limits.

Quick Start

# Start the routing server (graph file included in image)
docker run -e ASW_API_KEY=changeme -p 3000:3000 ghcr.io/auto-sea-way/asw:0.6.1-full

Wait for the /ready endpoint to return 200 (~60-90s while the graph loads), then query a route:

curl -H 'X-Api-Key: changeme' \
  'http://localhost:3000/route?from=36.85,28.27&to=36.39,25.46'

Returns a GeoJSON LineString. See API Endpoints for all available routes and Deployment Guide for Docker Compose, Kubernetes, and bare-metal examples.

H3 hexagonal grid at Marmaris Bay — adaptive resolution from open water to coastline

Adaptive H3 hexagonal grid at Marmaris Bay — coarse cells in open water, fine resolution along the coastline.

How It Works

  1. Read OSM land polygons shapefile
  2. Generate H3 hexagonal grid over ocean areas (adaptive cascade: res-3 deep ocean through res-9 shoreline, up to res-13 in passage corridors)
  3. Classify cells as navigable using hierarchical elimination and polygon intersection
  4. Build routing graph edges between adjacent navigable cells (same-resolution + cross-resolution)
  5. Refine passage corridors (Suez, Panama, Bosphorus, etc.) to higher resolutions for accurate navigation
  6. Serialize graph to compact binary format (bitcode + zstd-19, sorted H3 indices for O(log n) spatial lookup)

Comparison with Alternatives

auto-sea-wayscgrapheurostat/searoutesearoute-pyCommercial SaaS APIs
Routing graphGenerated from OSM data (~40M cells)Pre-curated shipping lane network (marnet)Static hand-drawn (~4K edges)Static hand-drawn (~4K edges)Proprietary
Coastline detailAdaptive res-3→res-13None — routes along lane waypointsFixed low resolutionFixed low resolutionVaries
Narrow passagesSuez, Panama, Bosphorus, etc.Only if in curated datasetApproximateApproximateUsually yes
Arbitrary coordinatesYesSnaps to nearest lane node (KD-tree)Ports + coordsPorts + coordsVaries
Self-hostedYes — single binaryYes — Python libraryYes — Java libraryYes — Python libraryNo
API server includedYes (HTTP/JSON)NoNoNoYes
Multi-modalMaritime onlyMaritime, road, rail, customMaritime onlyMaritime onlyVaries
LanguageRustPython (optional C++ extension)JavaPython
LicenseMIT / Apache 2.0MITEUPLMITProprietary
StatusActiveActiveInactive (last commit 2023)Maintained

Routing Benchmarks

20 routes, 50 iterations each. Graph v3 format (bitcode + H3 binary search). Graphs built with v2 must be rebuilt — v2 files are rejected at load time.

Routes start and end at the exact requested coordinates; distances count only the water segments (overland connectors for pins placed on land are excluded).

Sailing Routes

RouteDistanceP50P95Hops
English Channel22.1nm0.3ms0.3ms33>4
Aegean Hop25.3nm0.8ms0.8ms54>6
Strait of Gibraltar29.4nm0.8ms0.8ms63>5
Baltic Crossing42.0nm1.5ms1.5ms53>5
Balearic Sea127.6nm2.2ms2.2ms114>7
Florida Strait89.0nm0.5ms0.5ms22>4
Malacca Route534.3nm39.1ms39.3ms497>21
Tasman Sea1265.1nm57.3ms59.9ms408>17
South Atlantic3272.3nm30.8ms31.9ms392>8
North Atlantic3040.5nm869ms1.07s682>18

Passage Transits

RouteDistanceP50P95Hops
Suez Canal141.2nm13.1ms13.3ms1155>24
Panama Canal53.5nm76.0ms79.2ms1029>54
Kiel Canal84.2nm39.0ms41.3ms1976>57
Corinth Canal6.5nm1.5ms1.6ms364>8
Bosphorus32.7nm1.8ms1.9ms163>11
Dardanelles45.1nm1.4ms1.5ms116>6
Malacca Strait28.9nm1.4ms1.4ms89>6
Singapore Strait27.2nm1.0ms1.1ms46>5
Messina Strait16.1nm0.6ms0.7ms68>6
Dover Strait18.4nm0.4ms0.5ms17>5

API Endpoints

EndpointAuthPurpose
GET /route?from=lat,lon&to=lat,lonRequiredCompute maritime route, returns GeoJSON LineString
GET /infoRequiredGraph metadata: node/edge counts, version
GET /healthNoneLiveness probe (always 200)
GET /readyNoneReadiness probe (503 during graph load, 200 when ready)

Protected endpoints require an X-Api-Key header matching the configured ASW_API_KEY. Requests with a missing or invalid key receive 401 Unauthorized.

/route parameters:

  • from, tolat,lon coordinates
  • shore_buffer (optional, nautical miles, 05.0, default 0) — soft clearance: the router strongly prefers water at least this far from the coastline, but can still enter harbors/coves when there is no alternative; not a hard guarantee. The response echoes the requested value as shore_buffer_nm

/route response: distance_nm counts only water segments. When a requested point sits on land, the geometry still starts/ends exactly there, and the overland connector segments are listed in land_legs (segment indices into geometry.coordinates) so clients can render them differently — they contribute nothing to distance_nm. Land detection is a coastline-crossing test, not point-in-polygon: a segment that lies entirely inland on one landmass, never touching a coastline, is not detected. land_legs covers pins near the shore, not arbitrary points deep inland.

Packages

Docker Images

Hosted on GitHub Container Registry:

ImageTagDescription
ghcr.io/auto-sea-way/aswlatest, 0.6.1Slim image — bring your own graph file or auto-download via ASW_GRAPH_URL
ghcr.io/auto-sea-way/aswlatest-full, 0.6.1-fullFull image — graph file included (~740 MB)

Both images are available for linux/amd64 and linux/arm64.

# Full image — zero config, graph included (~740 MB)
docker run -e ASW_API_KEY=your-secret -p 3000:3000 ghcr.io/auto-sea-way/asw:0.6.1-full

# Slim image — auto-download graph on first start (cached in volume)
docker run -e ASW_API_KEY=your-secret \
  -e ASW_GRAPH_URL=https://github.com/auto-sea-way/asw/releases/download/v0.6.1/asw.graph \
  -v asw-data:/data -p 3000:3000 ghcr.io/auto-sea-way/asw:0.6.1

# Slim image — mounted graph file
docker run -e ASW_API_KEY=your-secret \
  -v /path/to/asw.graph:/data/asw.graph -p 3000:3000 ghcr.io/auto-sea-way/asw:0.6.1

The full planet graph needs ~4.1 GiB RSS right after load (measured, Linux), growing with query coverage as A* buffer pages are touched — 4.3 GiB measured after a globally diverse route mix, ~4.8 GiB hard ceiling. Plan for ~5 GiB total. A 4 GB instance with a generous swap file still works but pages under load; an 8 GB instance is recommended. Graph loading takes ~60-90s; wait for /ready to return 200 before sending route queries.

See Deployment Guide for Docker Compose, Kubernetes, and bare-metal examples.

Pre-built Binaries

Download from GitHub Releases:

PlatformBinary
Linux x86_64asw-linux-amd64
Linux ARM64asw-linux-arm64
macOS x86_64asw-darwin-amd64
macOS ARM64 (Apple Silicon)asw-darwin-arm64

Each release also includes the pre-built asw.graph file and SHA256SUMS for verification.

Full Planet Build

Built on Hetzner ccx53 (32 dedicated vCPU, 128 GB RAM) in ~5 hours:

MetricValue
Nodes39,412,823
Edges299,517,836
Graph file size717 MB
Connectivity100% (single connected component after build-time pruning)
Server memory (RSS)~4.1 GiB after load, 4.3 GiB measured under global traffic (~4.8 GiB ceiling)
Server memory (total)plan for ~5 GiB (needs swap below 8 GB)
Minimum instance4 GB RAM + swap (pages under load), recommended 8 GB
asw cloud build --output export/asw.graph

CLI Reference

# Local build
asw build --shp land-polygons-split-4326 --bbox marmaris --output export/marmaris.graph

# Cloud build (full pipeline)
asw cloud build --bbox marmaris --output export/marmaris.graph --keep-server

# Server management
asw cloud provision
asw cloud status
asw cloud teardown

# Serve routing API (requires ASW_API_KEY in .env or --api-key)
asw serve --graph export/asw.graph --host 0.0.0.0 --port 3000

# Export as GeoJSON for visualization
asw geojson --graph export/asw.graph --bbox marmaris --coastline --output export/asw.geojson

# Benchmark routing performance (20 fixed routes, 50 iterations each)
asw bench --graph export/asw.graph --output export/bench.json
asw bench --compare export/bench.json          # compare against a saved baseline
asw bench --shore-buffer 1.0                   # benchmark with the shore-clearance penalty applied

Bbox supports presets (dev, dev-small, marmaris) or min_lon,min_lat,max_lon,max_lat.

Architecture

Rust workspace with 5 crates:

crates/
├── asw-core      # Graph data structures, H3 utilities, routing (A*)
├── asw-build     # Graph builder: shapefiles → H3 grid → edges
├── asw-serve     # HTTP API server (axum)
├── asw-cloud     # Hetzner provisioning + SSH/SCP + remote build pipeline
└── asw-cli       # CLI entry point

Building from Source

Requires Rust (see rust-toolchain.toml for the pinned version):

cargo build --release -p asw-cli

Environment Variables

VariableDefaultDescription
ASW_PORT3000Server listen port
ASW_HOST0.0.0.0Bind address
ASW_GRAPHexport/asw.graphPath to graph file
ASW_GRAPH_URLURL to download graph if file is missing
ASW_API_KEYRequired. API key for authenticating /route and /info requests
HETZNER_TOKENHetzner API token for cloud builds

Known Limitations

  • No depth data. Routing treats all water as navigable — there is no bathymetry or draft-clearance check. This is generally fine for small craft like sailing boats but may route larger vessels through shallow areas. The shore_buffer parameter partially mitigates this by keeping routes off headlands and uncharted near-shore hazards, but it is not a substitute for nautical charts.

Data Sources

Geographic data is derived from OpenStreetMap, © OpenStreetMap contributors, available under the Open Database License (ODbL) v1.0.

DatasetSizeLicense
OSM land polygons~900MBODbL
Geofabrik regional extracts (canal water polygons)variesODbL

License

Licensed under either of

at your option.

Changelog

See CHANGELOG.md for a detailed list of changes in each release.