
Searoute
io.github.mayurrawtePublished by GitHub account mayurrawtev0.1.1Updated Nov 3, 2026
Real shortest sea routes between ports: distance, canals/straits, GeoJSON. UN/LOCODE aware.
Overview
Computes real shortest sea routes between ports, returning distance, canal and strait passages, and GeoJSON geometry.
- What you can do
- Compute a shortest sea route between two points or UN/LOCODE port codes, with distance, duration and passages
- Return alternative routings, such as avoiding Suez or Panama, and multi-leg port rotations
- Report route geometry as GeoJSON lines suitable for map rendering
- When to use it
- Useful when an assistant has to answer shipping-distance, ETA or routing questions — for example how far Shanghai is from Rotterdam by sea, or what a route looks like when a canal is avoided — instead of guessing great-circle figures.
- Requirements
- Runs locally over stdio via the npm package @searoute-ts/mcp, normally started with npx, so Node.js is needed. No accounts, API keys, environment variables or headers are declared. The bundled maritime network works offline; fetching a network or port dataset from a URL is optional and needs network access.
Installation
In SourceWeft
- Open Searoute in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
searoute-ts
Shortest sea route between any two points on Earth. A TypeScript / JavaScript library for maritime route planning, port-to-port distance, ETA estimation, and shipping-lane visualisation — powered by the 2025 Eurostat maritime network.
[npm version] [npm downloads] [CI] [license] [types]
🗺️ Try the interactive demo — click two points on a map and see the route, with all options live. (source)
📏 Port-to-port sea distances — distance, sailing time and canal passages for common routes between major ports.
Works from plain JavaScript too — the package ships compiled
.jsplus.d.tsdeclarations. The-tsin the name is for searchability, not a language requirement.
Why searoute-ts
- 🚢 Realistic shipping routes, not great-circle lines through Eurasia.
- 🗺️ Returns GeoJSON — drop straight into Leaflet, Mapbox, deck.gl, MapLibre.
- 🌊 2025 Eurostat marnet with explicit Suez, Panama, Bab-el-Mandeb, Malacca, Gibraltar, Dover, Kiel, Corinth, Bering, Magellan, NW/NE Passage labels.
- 🚫 Canal & strait restrictions — force Cape of Good Hope during a Red Sea disruption with one option.
- 📦 Vessel-draft gating — auto-block Panama (15.2 m), Suez (20.1 m), Kiel (7 m), Corinth (7.3 m) when the vessel exceeds the canal limit.
- 🛤️ K-shortest alternatives —
seaRouteAlternativesreturns the baseline plus up to N realistic alternatives. - 🧭 Multi-leg waypoints —
seaRouteMultifor port rotations and itineraries. - ⏱️ ETA from speed —
speedKnots→durationHours. - 🛠️ Modern toolchain — TypeScript 5, ESM + CJS dual build, types included, Node 18+, zero peer deps.
Quick examples
Basic — shortest route
With ETA and units
Red Sea / Suez disruption — force Cape of Good Hope
Vessel-aware — Ultra Large Container Ship
Port codes (UN/LOCODE)
The ~1 600-port dataset lives behind the searoute-ts/ports subpath so the core
stays lean — importing it registers the resolver. You can also resolve codes
yourself:
Unknown codes throw UnknownPortError. See Port codes below for provenance.
Load the port dataset from a CDN instead of bundling it
Don't want to bundle the ~135 KB dataset? Fetch it at runtime with loadPorts —
the analog of loadNetwork.
The dataset also ships as a raw dist/ports.json, so jsDelivr/unpkg serve it
versioned for free:
(dist/ports.json ships from the release that adds port codes onward — pin any
version at or after it for reproducibility.)
loadPorts registers the fetched dataset (so code strings resolve) and returns
it. It uses the global fetch (Node ≥18 / browsers); pass { fetch } to override.
Multi-leg / port rotation
Alternative routes (Yen-style canal permutation)
Fetch the network from a URL instead of bundling it (optional)
The network is bundled by default, so seaRoute works offline with zero setup.
If you'd rather not ship the ~1 MB network (e.g. to trim a browser bundle,
or to use an updated network without upgrading the package), fetch it at
runtime and pass it via the existing network option:
Only the fetch is async — seaRoute itself stays synchronous. loadNetwork
uses the global fetch (Node ≥18 and all browsers); pass { fetch } to supply
your own. This is purely opt-in; nothing changes if you don't use it.
Which option should I use?
Versioning the hosted network
You choose the version by choosing the URL:
-
@latest/ rolling — the GitHub Pages URL above always serves the current network. Convenient, but it can change under you. -
Pinned & immutable — because the package is on npm, jsDelivr and unpkg serve every published version automatically, with immutable per-version URLs:
A pinned URL never changes, so your routes stay reproducible. (These standalone-JSON CDN paths land with the package once the network ships as a separate asset — see issue #10; until then, use the GitHub Pages URL.)
For production, prefer a pinned URL (or just the bundled default) so your distances don't shift when the network is updated.
Higher-resolution networks (optional)
The bundled network is Eurostat's 100 km marnet_plus. Eurostat also
publishes finer resolutions, which give more accurate coastal routing and
shorter-hop fidelity at the cost of a larger download and slightly slower
first-route graph construction. Two moderate resolutions ship as subpath
exports so you only pay for them if you import them:
Like the bundled default, each variant ships once as a shared
dist/data/marnet-<res>.cjs asset that both the CJS and ESM builds load at
runtime, so importing a variant doesn't duplicate the network across builds.
The 10 km and 5 km networks are large enough that bundling them would dominate
the install, so they are not shipped in the package. Generate them from the
Eurostat source with scripts/build-marnet.cjs (the script header documents the
GDAL conversion), host the resulting JSON, and load it with
loadNetwork
— or pass any FeatureCollection<LineString> to the network option directly.
Output shape
length runs between the two snapped network vertices. It does not include
the legs from your inputs to the network (originSnapKm, destinationSnapKm,
always in km), even with appendOriginDestination: true, which only adds the
raw points to the line. For a door-to-door figure, add them yourself:
On hops shorter than the network resolution (~100 km by default, ~20 km with
searoute-ts/marnet-20km), the snapped vertices can sit closer together than
the inputs, so length can come out below greatCircleLength
(detourRatio < 1). If both inputs snap to the same vertex, NoRouteError is
thrown.
Full options
Inputs can be [lon, lat] arrays, GeoJSON Feature<Point>, bare Point objects,
or a UN/LOCODE string (e.g. 'CNSHA') once searoute-ts/ports is imported.
Antimeridian (dateline) handling
Routes that cross the ±180° meridian (e.g. Yokohama → LA) come back wrapped to
[-180, 180] by default, which many map renderers draw as a straight streak
across the whole map. Pass antimeridian to get map-ready geometry:
'unwrap' shifts longitudes by multiples of 360° so the line never jumps the
dateline (ideal for MapLibre/Leaflet/Deck.gl). 'split' cuts the route into a
MultiLineString at ±180°, keeping every coordinate in range. Both apply to
seaRoute and seaRouteMulti; properties.length is unchanged either way.
Forcing routes through a passage (via)
restrictions blocks a passage; via requires one — the inverse. Use
it to compare explicit routings, e.g. "via Suez" against "via Cape of Good Hope",
or to force a Pacific + Panama routing between Asia and Europe:
via accepts the same passage names as restrictions and visits multiple
passages in the order given. It routes origin → passage → destination through
each passage's location using the multi-leg machinery, so it composes with the
other options. A passage named in via is never blocked out from under the
requirement (via: ['northeast'] reaches the Northeast Passage without also
needing allowArctic). Naming the same passage in both via and restrictions
is a contradiction and throws NoRouteError.
Emissions & ECA/SECA reporting
Opt in with emissions: true for two rough estimates on properties:
ecaKm— how much of the route lies inside ECA/SECA emission-control areas (Baltic, North Sea, Mediterranean, North American and US Caribbean), which drives fuel-type/cost. The zones ship behind thesearoute-ts/ecasubpath export (to keep the core lean); importing it registers them. They are bounding-box approximations of the IMO MARPOL Annex VI areas — good for estimates, not compliance. Swap in higher-fidelity polygons withregisterEcaZones.co2eTonnes— a deliberately simpledistance × vessel-class factorestimate, not a certified figure. Factors are derived transparently from a representative fuel burn and the IMO HFO CO₂ conversion (seeVESSEL_CLASSES); override withco2eFactorKgPerKm. GLEC recommends inflating shortest-path distance by ~15 % for real-world deviations — passglecInflation: 0.15.
Restrictable passages
The first twelve are natively labelled in the Eurostat marnet (exact match
on the feature's pass attribute). The remaining four are detected via
bounding boxes.
The Northwest and Northeast Passages are mathematically the shortest path for
many Asia ↔ Europe routes but are ice-blocked most of the year, so they are
blocked by default. Opt in with allowArctic: true.
Validated against industry distances
12 real-world lanes within ±10% of published Searoutes / Sea-Distances figures.
All checks pass in the test suite.
Errors
SnapFailedError— input cannot be projected onto the network withinmaxSnapDistanceKm. Carries.side: 'origin' | 'destination'and.distanceKm: number.NoRouteError— no path exists between the snapped origin and destination (e.g. all viable canals blocked).
API reference
Port codes (UN/LOCODE)
Origins and destinations may be given as UN/LOCODE strings (e.g. 'CNSHA')
instead of coordinates. The port dataset ships behind the searoute-ts/ports
subpath export, so consumers only pay for it if they use it — importing the
subpath (for any of its exports, or purely for its side effect) registers a
resolver into the core so seaRoute('CNSHA', 'NLRTM') works.
- ~1 600 seaports, keyed by UN/LOCODE (primary codes and aliases).
- Source: marchah/sea-ports (MIT),
itself derived from UN/LOCODE. Regenerate with
scripts/build-ports.cjs. - Coordinates are approximate (port-city granularity) — the routing engine snaps them onto the network anyway, so this is fine for distance/visualisation.
- Unknown or malformed codes throw
UnknownPortError.
Use from an AI agent (MCP)
A companion Model Context Protocol server,
@searoute-ts/mcp (source),
lets AI agents (Claude Desktop, the claude CLI, etc.) compute real sea routes
instead of guessing — asking "how far is Shanghai to Rotterdam by sea, avoiding
Suez?" calls the library directly. It exposes two tools, sea_route and
sea_route_alternatives, and accepts port codes ('CNSHA') or coordinates.
Or add it to any MCP client config:
See the server's README
for the full tool reference. For the rail leg, add
@railroute-ts/mcp alongside it
(claude mcp add railroute -- npx -y @railroute-ts/mcp).
Multimodal: add the rail leg (railroute-ts)
Sea distance is rarely the whole shipment. The sibling library
railroute-ts routes over the
OpenStreetMap rail network (Europe bundled, same API shape, same GeoJSON
output), so a port-to-inland quote or a GLEC/CountEmissions-style report is one
extra call:
npm install railroute-ts — docs & interactive demo.
Both libraries also ship MCP servers, so an AI agent can chain sea_route →
rail_route for door-to-door distance (see below).
How it works
A two-page deep-dive (graph data, snapping, Dijkstra, restrictions, antimeridian fix, draft logic, alternatives) is in DOCS.md.
FAQ
Is this for navigation? No. The routes are network paths suitable for visualisation and rough distance/duration estimates, not for piloting ships.
Does it support weather routing? No. For weather-aware routing see VISIR-2.
Why are my Asia→Europe routes going through Bering Strait? They aren't,
by default — the Northwest and Northeast Passages are blocked. Pass
allowArctic: true to enable them.
Can I use my own network? Yes — seaRoute(origin, destination, { network }).
Useful for inland waterways or AIS-derived custom graphs. For higher-resolution
Eurostat data (5/10/20/50 km), see
Higher-resolution networks — 20 km and
50 km ship as subpath exports.
Does it handle the Red Sea / Suez crisis? Yes — pass
restrictions: ['suez', 'babelmandeb'] to force Cape of Good Hope routing.
Cannot find module '…/searoute-ts/dist/lib/utils' on 1.x? 1.x (up to
1.2.1) shipped an ESM build with extensionless imports under "main", so plain
Node can't load it without patching. Fixed in 2.0: install searoute-ts@^2,
because a ^1 range never picks it up. When upgrading, use the named
import { seaRoute }. Failures now throw NoRouteError / SnapFailedError
instead of returning null, and nautical-mile lengths are ~24 % smaller,
because 1.x over-counted them. See the 2.0.0 migration notes in the
CHANGELOG.
Is the great-circle distance correct across the antimeridian? Yes — the marnet has been normalised so the Pacific is a connected graph, and all distances use haversine internally.
What's the bundle size? What you import at runtime is small: the core plus
the bundled 100 km marnet (~1.1 MB JSON, shipped once as a shared
dist/data/marnet.cjs asset both builds load, rather than inlined into each).
Tree-shakeable, so the optional searoute-ts/marnet-20km / marnet-50km
networks only load if you import them. They do add to the npm tarball, though —
including them the package is ~1.1 MB packed / ~7 MB unpacked (each variant is a
single shared asset, not duplicated per build). If you need the finer networks
without the install cost, generate and host them and use loadNetwork instead.
Credits
- Maritime network — Eurostat searoute v3.5 (EUPL-1.2). Oak Ridge National Labs Global Shipping Lane Network enriched with European AIS data.
- Dijkstra —
geojson-path-finder@2by Per Liedman. - Inspired by
searoute-py(Apache-2.0). - Original JS port — @johnx25bd.
- Geospatial primitives — Turf.js.
License
MIT © Mayur Rawte
Source: README.md at commit f837ac6
Tools
0Version history
1- v0.1.1LatestSep 16, 2026
Score
50% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 days25 pts
Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.
- Auth declared10 pts
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts