{
  "markdown": "# searoute-ts\n\n> **Shortest sea route between any two points on Earth.** A TypeScript / JavaScript\n> library for maritime route planning, port-to-port distance, ETA estimation, and\n> shipping-lane visualisation — powered by the 2025 Eurostat maritime network.\n\n[![npm version](https://img.shields.io/npm/v/searoute-ts.svg?style=flat)](https://www.npmjs.com/package/searoute-ts)\n[![npm downloads](https://img.shields.io/npm/dw/searoute-ts.svg?style=flat)](https://www.npmjs.com/package/searoute-ts)\n[![CI](https://github.com/mayurrawte/searoute-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/mayurrawte/searoute-ts/actions/workflows/ci.yml)\n[![license](https://img.shields.io/npm/l/searoute-ts.svg?style=flat)](https://github.com/mayurrawte/searoute-ts/blob/main/LICENSE)\n[![types](https://img.shields.io/npm/types/searoute-ts.svg?style=flat)](https://www.npmjs.com/package/searoute-ts)\n\n```bash\nnpm install searoute-ts\n```\n\n```ts\nimport { seaRoute } from 'searoute-ts';\n\nconst route = seaRoute([121.5, 31.0], [4.4, 51.9]);\n// Shanghai → Rotterdam → GeoJSON LineString, ~10,664 nm via Suez Canal\n```\n\n**🗺️ [Try the interactive demo](https://mayurrawte.github.io/searoute-ts/)** — click two points on a map and see the route, with all options live. ([source](https://github.com/mayurrawte/searoute-ts/tree/main/examples/web-demo))\n\n> Works from plain JavaScript too — the package ships compiled `.js` plus\n> `.d.ts` declarations. The `-ts` in the name is for searchability, not a\n> language requirement.\n\n---\n\n## Why searoute-ts\n\n- 🚢 **Realistic shipping routes**, not great-circle lines through Eurasia.\n- 🗺️ **Returns GeoJSON** — drop straight into Leaflet, Mapbox, deck.gl, MapLibre.\n- 🌊 **2025 Eurostat marnet** with explicit Suez, Panama, Bab-el-Mandeb,\n  Malacca, Gibraltar, Dover, Kiel, Corinth, Bering, Magellan, NW/NE Passage labels.\n- 🚫 **Canal & strait restrictions** — force Cape of Good Hope during a Red Sea\n  disruption with one option.\n- 📦 **Vessel-draft gating** — auto-block Panama (15.2 m), Suez (20.1 m), Kiel\n  (7 m), Corinth (7.3 m) when the vessel exceeds the canal limit.\n- 🛤️ **K-shortest alternatives** — `seaRouteAlternatives` returns the baseline\n  plus up to N realistic alternatives.\n- 🧭 **Multi-leg waypoints** — `seaRouteMulti` for port rotations and itineraries.\n- ⏱️ **ETA from speed** — `speedKnots` → `durationHours`.\n- 🛠️ **Modern toolchain** — TypeScript 5, ESM + CJS dual build, types included,\n  Node 18+, zero peer deps.\n\n## Quick examples\n\n### Basic — shortest route\n\n```ts\nimport { seaRoute } from 'searoute-ts';\n\nconst route = seaRoute([-74.04, 40.69], [-0.13, 51.5]); // NYC → London\n// route.properties.length  // ≈ 3 362 nm\n// route.properties.units   // 'nauticalmiles'\n```\n\n### With ETA and units\n\n```ts\nseaRoute(shanghai, rotterdam, {\n  units: 'kilometers',\n  speedKnots: 22,\n});\n// → 19 753 km, properties.durationHours ≈ 485 h (≈ 20 days)\n```\n\n### Red Sea / Suez disruption — force Cape of Good Hope\n\n```ts\nseaRoute(shanghai, rotterdam, {\n  restrictions: ['suez', 'babelmandeb'],\n});\n// → routes via Cape of Good Hope, ~25 800 km\n```\n\n### Vessel-aware — Ultra Large Container Ship\n\n```ts\nseaRoute(shanghai, newYork, {\n  vesselDraftMeters: 16,  // exceeds Panama's 15.2 m TFW\n});\n// → Panama auto-blocked, route goes via Suez\n```\n\n### Port codes (UN/LOCODE)\n\n```ts\nimport 'searoute-ts/ports'; // enables UN/LOCODE strings on the core API\nimport { seaRoute } from 'searoute-ts';\n\nseaRoute('CNSHA', 'NLRTM'); // Shanghai → Rotterdam\nseaRoute('CNSHA', [4.4, 51.9]); // mixing a code and coordinates is fine too\n```\n\nThe ~1 600-port dataset lives behind the `searoute-ts/ports` subpath so the core\nstays lean — importing it registers the resolver. You can also resolve codes\nyourself:\n\n```ts\nimport { lookupPort, resolvePort } from 'searoute-ts/ports';\n\nlookupPort('SGSIN'); // → { code, name: 'Singapore', country, coordinates: [lon, lat] }\nresolvePort('SGSIN'); // → [103.85, 1.28]\n```\n\nUnknown codes throw `UnknownPortError`. See [Port codes](#port-codes-unlocode-1) below for provenance.\n\n#### Load the port dataset from a CDN instead of bundling it\n\nDon't want to bundle the ~135 KB dataset? Fetch it at runtime with `loadPorts` —\nthe analog of [`loadNetwork`](#fetch-the-network-from-a-url-instead-of-bundling-it-optional).\nThe dataset also ships as a raw `dist/ports.json`, so **jsDelivr/unpkg serve it\nversioned for free**:\n\n```ts\nimport { seaRoute, loadPorts } from 'searoute-ts';\n\n// Pin a version for reproducibility, or use @latest to always get the newest.\nawait loadPorts('https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json');\n\nseaRoute('CNSHA', 'NLRTM'); // works — the fetched dataset is now registered\n```\n\n```\nhttps://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json      # newest\nhttps://cdn.jsdelivr.net/npm/searoute-ts@<version>/dist/ports.json   # frozen/immutable\n```\n\n(`dist/ports.json` ships from the release that adds port codes onward — pin any\nversion at or after it for reproducibility.)\n\n`loadPorts` registers the fetched dataset (so code strings resolve) and returns\nit. It uses the global `fetch` (Node ≥18 / browsers); pass `{ fetch }` to override.\n\n### Multi-leg / port rotation\n\n```ts\nimport { seaRouteMulti } from 'searoute-ts';\n\nseaRouteMulti(\n  [shanghai, singapore, mumbai, rotterdam],\n  { units: 'kilometers', returnPassages: true },\n);\n// → one concatenated LineString, total length, union of passages\n```\n\n### Alternative routes (Yen-style canal permutation)\n\n```ts\nimport { seaRouteAlternatives } from 'searoute-ts';\n\nconst alts = seaRouteAlternatives(shanghai, rotterdam, { k: 4 });\n//  baseline           19 753 km via Suez\n//  no-malacca         20 759 km\n//  no-suez            25 315 km (via Panama)\n//  no-suez-no-panama  25 845 km (via Cape of Good Hope)\n```\n\n### Fetch the network from a URL instead of bundling it (optional)\n\nThe network is bundled by default, so `seaRoute` works offline with zero setup.\nIf you'd rather **not** ship the ~1 MB network (e.g. to trim a browser bundle,\nor to use an updated network without upgrading the package), fetch it at\nruntime and pass it via the existing `network` option:\n\n```ts\nimport { seaRoute, loadNetwork } from 'searoute-ts';\n\n// CORS-enabled, served from GitHub Pages (or point at your own host / a CDN).\nconst network = await loadNetwork('https://mayurrawte.github.io/searoute-ts/marnet.json');\n\nconst route = seaRoute(shanghai, rotterdam, { network });\n```\n\nOnly the fetch is async — `seaRoute` itself stays synchronous. `loadNetwork`\nuses the global `fetch` (Node ≥18 and all browsers); pass `{ fetch }` to supply\nyour own. This is purely opt-in; nothing changes if you don't use it.\n\n#### Which option should I use?\n\n| Approach | How | Data version | Works offline | Best for |\n|----------|-----|--------------|---------------|----------|\n| **Bundled** (default) | `seaRoute(a, b)` — no `network` | pinned to your installed package | ✅ | Most users; zero config, deterministic |\n| **Latest via URL** | `loadNetwork('…/marnet.json')` | always the newest hosted | ❌ needs network | Always-current data without upgrading |\n| **Pinned via CDN** | `loadNetwork('https://cdn.jsdelivr.net/npm/searoute-ts@2.0.1/…')` | frozen (immutable) | ❌ needs network | Reproducible builds |\n\n#### Versioning the hosted network\n\nYou choose the version by choosing the **URL**:\n\n- **`@latest` / rolling** — the GitHub Pages URL above always serves the current\n  network. Convenient, but it can change under you.\n- **Pinned & immutable** — because the package is on npm, **jsDelivr** and\n  **unpkg** serve every published version automatically, with immutable\n  per-version URLs:\n\n  ```\n  https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/marnet.json   # newest\n  https://cdn.jsdelivr.net/npm/searoute-ts@2/dist/marnet.json        # newest 2.x\n  https://cdn.jsdelivr.net/npm/searoute-ts@2.0.1/dist/marnet.json    # frozen\n  ```\n\n  A pinned URL never changes, so your routes stay reproducible. (These\n  standalone-JSON CDN paths land with the package once the network ships as a\n  separate asset — see issue #10; until then, use the GitHub Pages URL.)\n\nFor production, prefer a **pinned** URL (or just the bundled default) so your\ndistances don't shift when the network is updated.\n\n### Higher-resolution networks (optional)\n\nThe bundled network is Eurostat's **100 km** `marnet_plus`. Eurostat also\npublishes finer resolutions, which give more accurate coastal routing and\nshorter-hop fidelity at the cost of a larger download and slightly slower\nfirst-route graph construction. Two moderate resolutions ship as **subpath\nexports** so you only pay for them if you import them:\n\n```ts\nimport { DEFAULT_MARNET } from 'searoute-ts/marnet-20km'; // or 'searoute-ts/marnet-50km'\nimport { seaRoute } from 'searoute-ts';\n\nseaRoute(origin, destination, { network: DEFAULT_MARNET });\n```\n\nLike the bundled default, each variant ships once as a shared\n`dist/data/marnet-<res>.cjs` asset that both the CJS and ESM builds load at\nruntime, so importing a variant doesn't duplicate the network across builds.\n\n| Import | Resolution | Segments | JSON size | gzipped | Coastal accuracy |\n| --- | --- | --- | --- | --- | --- |\n| `searoute-ts` (bundled default) | 100 km | 9,847 | ~1.3 MB | ~0.18 MB | Baseline — good for global routing |\n| `searoute-ts/marnet-50km` | 50 km | 15,498 | ~1.9 MB | ~0.27 MB | Modest step up |\n| `searoute-ts/marnet-20km` | 20 km | 29,581 | ~3.6 MB | ~0.51 MB | Noticeably finer coastal hops |\n| via `loadNetwork` (see below) | 10 km | 48,301 | ~5.9 MB | ~0.84 MB | High — larger download |\n| via `loadNetwork` (see below) | 5 km | 72,478 | ~9.0 MB | ~1.24 MB | Highest — largest download |\n\nThe 10 km and 5 km networks are large enough that bundling them would dominate\nthe install, so they are **not** shipped in the package. Generate them from the\nEurostat source with `scripts/build-marnet.cjs` (the script header documents the\nGDAL conversion), host the resulting JSON, and load it with\n[`loadNetwork`](#fetch-the-network-from-a-url-instead-of-bundling-it-optional)\n— or pass any `FeatureCollection<LineString>` to the `network` option directly.\n\n## Output shape\n\n```ts\n{\n  type: 'Feature',\n  geometry: { type: 'LineString', coordinates: [[lon, lat], ...] },\n  properties: {\n    length: number,                    // in `units`, in-water only\n    units: 'nauticalmiles' | 'kilometers' | 'miles' | ...,\n    bbox: [minLon, minLat, maxLon, maxLat],\n    greatCircleLength: number,         // haversine between inputs, same units\n    detourRatio: number,               // routeKm / greatCircleKm\n    originSnapKm: number,              // input → network distance\n    destinationSnapKm: number,\n    durationHours?: number,            // if `speedKnots` set\n    passages?: ('suez' | 'panama' | ...)[],  // if `returnPassages: true`\n    ecaKm?: number,                    // if `emissions` + `searoute-ts/eca` imported\n    ecaFraction?: number,              // ecaKm / length (0–1)\n    co2eTonnes?: number,               // if `emissions` + `vesselClass`/factor\n  }\n}\n```\n\n## Full options\n\n```ts\nseaRoute(origin, destination, {\n  units:                   'nauticalmiles',          // any Turf unit\n  restrictions:            ['suez', 'babelmandeb'],  // block passages (see table below)\n  via:                     ['panama'],               // require passages (inverse of restrictions)\n  allowArctic:             false,                    // default — blocks NWP & NEP\n  vesselDraftMeters:       15,                       // auto-restrict canals\n  speedKnots:              22,                       // → properties.durationHours\n  appendOriginDestination: false,                    // prepend/append raw inputs\n  returnPassages:          true,                     // populate properties.passages\n  maxSnapDistanceKm:       50,                       // SnapFailedError if exceeded\n  network:                 customMarnet,             // BYO FeatureCollection\n  antimeridian:            'split',                  // 'unwrap' | 'split' dateline handling\n  emissions:               true,                     // → properties.ecaKm / co2eTonnes\n  vesselClass:             'panamax',                // CO₂e estimate class\n  co2eFactorKgPerKm:       225,                      // override the class factor\n  glecInflation:           0.15,                     // +15% distance for CO₂e (GLEC)\n});\n```\n\nInputs can be `[lon, lat]` arrays, GeoJSON `Feature<Point>`, bare `Point` objects,\nor a UN/LOCODE string (e.g. `'CNSHA'`) once `searoute-ts/ports` is imported.\n\n### Antimeridian (dateline) handling\n\nRoutes that cross the ±180° meridian (e.g. Yokohama → LA) come back wrapped to\n`[-180, 180]` by default, which many map renderers draw as a straight streak\nacross the whole map. Pass `antimeridian` to get map-ready geometry:\n\n```ts\nseaRoute(yokohama, la, { antimeridian: 'unwrap' }); // continuous LineString (may exceed ±180)\nseaRoute(yokohama, la, { antimeridian: 'split' });  // MultiLineString cut at ±180 (RFC 7946)\n```\n\n`'unwrap'` shifts longitudes by multiples of 360° so the line never jumps the\ndateline (ideal for MapLibre/Leaflet/Deck.gl). `'split'` cuts the route into a\n`MultiLineString` at ±180°, keeping every coordinate in range. Both apply to\n`seaRoute` and `seaRouteMulti`; `properties.length` is unchanged either way.\n\n### Forcing routes through a passage (`via`)\n\n`restrictions` **blocks** a passage; `via` **requires** one — the inverse. Use\nit to compare explicit routings, e.g. \"via Suez\" against \"via Cape of Good Hope\",\nor to force a Pacific + Panama routing between Asia and Europe:\n\n```ts\nseaRoute('CNSHA', 'NLRTM', { via: ['suez'] });   // through Suez (the default)\nseaRoute('CNSHA', 'NLRTM', { via: ['panama'] });  // across the Pacific + Panama instead\n```\n\n`via` accepts the same passage names as `restrictions` and visits multiple\npassages in the order given. It routes `origin → passage → destination` through\neach passage's location using the multi-leg machinery, so it composes with the\nother options. A passage named in `via` is never blocked out from under the\nrequirement (`via: ['northeast']` reaches the Northeast Passage without also\nneeding `allowArctic`). Naming the same passage in both `via` and `restrictions`\nis a contradiction and throws `NoRouteError`.\n\n### Emissions & ECA/SECA reporting\n\nOpt in with `emissions: true` for two rough estimates on `properties`:\n\n```ts\nimport 'searoute-ts/eca';                 // load the ECA/SECA zones (enables ecaKm)\nimport { seaRoute } from 'searoute-ts';\n\nconst r = seaRoute('CNSHA', 'NLRTM', {\n  emissions: true,\n  vesselClass: 'panamax',                 // → co2eTonnes\n});\nr.properties.ecaKm;        // km of the route inside emission-control zones\nr.properties.ecaFraction;  // that as a fraction of route length (0–1)\nr.properties.co2eTonnes;   // rough CO₂e estimate for the voyage\n```\n\n- **`ecaKm`** — how much of the route lies inside ECA/SECA emission-control\n  areas (Baltic, North Sea, Mediterranean, North American and US Caribbean),\n  which drives fuel-type/cost. The zones ship behind the `searoute-ts/eca`\n  subpath export (to keep the core lean); importing it registers them. They are\n  **bounding-box approximations** of the IMO MARPOL Annex VI areas — good for\n  estimates, not compliance. Swap in higher-fidelity polygons with\n  `registerEcaZones`.\n- **`co2eTonnes`** — a deliberately simple `distance × vessel-class factor`\n  estimate, **not a certified figure**. Factors are derived transparently from a\n  representative fuel burn and the IMO HFO CO₂ conversion (see `VESSEL_CLASSES`);\n  override with `co2eFactorKgPerKm`. GLEC recommends inflating shortest-path\n  distance by ~15 % for real-world deviations — pass `glecInflation: 0.15`.\n\n## Restrictable passages\n\nThe first twelve are **natively labelled** in the Eurostat marnet (exact match\non the feature's `pass` attribute). The remaining four are detected via\nbounding boxes.\n\n| Name           | Type     | Notes                              |\n|----------------|----------|------------------------------------|\n| `suez`         | native   | Suez Canal                         |\n| `panama`       | native   | Panama Canal                       |\n| `gibraltar`    | native   | Strait of Gibraltar                |\n| `babelmandeb`  | native   | Bab-el-Mandeb (`babalmandab` alias) |\n| `malacca`      | native   | Malacca Strait                     |\n| `dover`        | native   | Dover Strait                       |\n| `kiel`         | native   | Kiel Canal                         |\n| `corinth`      | native   | Corinth Canal                      |\n| `bering`       | native   | Bering Strait                      |\n| `magellan`     | native   | Strait of Magellan                 |\n| `northwest`    | native   | Northwest Passage (blocked by default) |\n| `northeast`    | native   | Northeast Passage (blocked by default) |\n| `bosporus`     | bbox     | Bosphorus                          |\n| `ormuz`        | bbox     | Strait of Hormuz                   |\n| `sunda`        | bbox     | Sunda Strait                       |\n| `cape_horn`    | bbox     | Cape Horn region                   |\n\nThe Northwest and Northeast Passages are mathematically the shortest path for\nmany Asia ↔ Europe routes but are ice-blocked most of the year, so they are\n**blocked by default**. Opt in with `allowArctic: true`.\n\n## Validated against industry distances\n\n12 real-world lanes within ±10% of published Searoutes / Sea-Distances figures.\n\n| Lane                              | searoute-ts | Industry ref. |\n|-----------------------------------|-------------|---------------|\n| Shanghai → Rotterdam (Suez)       | 19 753 km   | ~19 300 km    |\n| Singapore → Rotterdam (Suez)      | 15 630 km   | ~15 500 km    |\n| Mumbai → Rotterdam (Suez)         | 11 918 km   | ~11 800 km    |\n| NY → Rotterdam                    |  6 227 km   | ~6 200 km     |\n| NY → LA (Panama)                  |  9 154 km   | ~9 100 km     |\n| Yokohama → LA                     |  9 145 km   | ~8 800 km     |\n| Singapore → LA (trans-Pacific)    | 14 364 km   | ~14 300 km    |\n| Caldera (CL) → Bahía Blanca (AR)  |  4 810 km   | ~5 180 km     |\n\nAll checks pass in the [test suite](https://github.com/mayurrawte/searoute-ts/blob/main/src/index.spec.ts).\n\n## Errors\n\n- **`SnapFailedError`** — input cannot be projected onto the network within\n  `maxSnapDistanceKm`. Carries `.side: 'origin' | 'destination'` and\n  `.distanceKm: number`.\n- **`NoRouteError`** — no path exists between the snapped origin and destination\n  (e.g. all viable canals blocked).\n\n## API reference\n\n```ts\nimport {\n  seaRoute,                  // single shortest route\n  seaRouteMulti,             // ordered waypoints (multi-leg)\n  seaRouteAlternatives,      // K-shortest alternatives\n  loadNetwork,               // optional: fetch a network from a URL/CDN\n  CANAL_MAX_DRAFT_M,         // { panama: 15.2, suez: 20.1, kiel: 7, corinth: 7.3 }\n  DEFAULT_MARNET,            // bundled FeatureCollection<LineString>\n  PASSAGE_BBOXES,            // passage bbox lookup\n  clearFinderCache,          // drop the PathFinder cache (tests / hot reload)\n  SnapFailedError,\n  NoRouteError,\n  UnknownPortError,          // thrown for unresolved UN/LOCODE strings\n  registerPortResolver,      // plug in a custom port dataset\n  // types\n  type Passage,\n  type Antimeridian,\n  type SeaRouteOptions,\n  type SeaRouteFeature,\n  type SeaRouteMultiFeature,\n  type SeaRouteProperties,\n  type LoadNetworkOptions,\n  type MarnetNetwork,\n  type MarnetProperties,\n} from 'searoute-ts';\n\nimport {\n  lookupPort,                // UN/LOCODE → { code, name, country, coordinates }\n  resolvePort,               // UN/LOCODE → [lon, lat]\n  PORTS,                     // the raw dataset (Record<code, PortRecord>)\n  PORT_COUNT,\n  type Port,\n  type PortRecord,\n} from 'searoute-ts/ports';\n```\n\n## Port codes (UN/LOCODE)\n\nOrigins and destinations may be given as UN/LOCODE strings (e.g. `'CNSHA'`)\ninstead of coordinates. The port dataset ships behind the `searoute-ts/ports`\nsubpath export, so consumers only pay for it if they use it — importing the\nsubpath (for any of its exports, or purely for its side effect) registers a\nresolver into the core so `seaRoute('CNSHA', 'NLRTM')` works.\n\n- **~1 600 seaports**, keyed by UN/LOCODE (primary codes and aliases).\n- **Source:** [marchah/sea-ports](https://github.com/marchah/sea-ports) (MIT),\n  itself derived from **UN/LOCODE**. Regenerate with `scripts/build-ports.cjs`.\n- **Coordinates are approximate** (port-city granularity) — the routing engine\n  snaps them onto the network anyway, so this is fine for distance/visualisation.\n- Unknown or malformed codes throw `UnknownPortError`.\n\n## Use from an AI agent (MCP)\n\nA companion [Model Context Protocol](https://modelcontextprotocol.io) server,\n[`@searoute-ts/mcp`](https://www.npmjs.com/package/@searoute-ts/mcp) ([source](https://github.com/mayurrawte/searoute-ts/tree/main/examples/mcp-server)),\nlets AI agents (Claude Desktop, the `claude` CLI, etc.) compute real sea routes\ninstead of guessing — asking \"how far is Shanghai to Rotterdam by sea, avoiding\nSuez?\" calls the library directly. It exposes two tools, `sea_route` and\n`sea_route_alternatives`, and accepts port codes (`'CNSHA'`) or coordinates.\n\n```bash\nclaude mcp add searoute -- npx -y @searoute-ts/mcp\n```\n\nOr add it to any MCP client config:\n\n```json\n{\n  \"mcpServers\": {\n    \"searoute\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@searoute-ts/mcp\"]\n    }\n  }\n}\n```\n\nSee the [server's README](https://github.com/mayurrawte/searoute-ts/tree/main/examples/mcp-server)\nfor the full tool reference. For the rail leg, add\n[`@railroute-ts/mcp`](https://www.npmjs.com/package/@railroute-ts/mcp) alongside it\n(`claude mcp add railroute -- npx -y @railroute-ts/mcp`).\n\n## Multimodal: add the rail leg (railroute-ts)\n\nSea distance is rarely the whole shipment. The sibling library\n[`railroute-ts`](https://github.com/mayurrawte/railroutes) routes over the\nOpenStreetMap rail network (Europe bundled, same API shape, same GeoJSON\noutput), so a port-to-inland quote or a GLEC/CountEmissions-style report is one\nextra call:\n\n```ts\nimport 'searoute-ts/ports';\nimport { seaRoute } from 'searoute-ts';\nimport { railRoute } from 'railroute-ts';\nimport { EUROPE_NETWORK } from 'railroute-ts/networks/europe';\n\nconst sea  = seaRoute('CNSHA', 'NLRTM', { units: 'kilometers', emissions: true, vesselClass: 'panamax' });\nconst rail = railRoute([4.47, 51.92], [8.92, 44.41], { network: EUROPE_NETWORK, speedKmh: 60 }); // Rotterdam → Genoa\n\nsea.properties.length;        // ≈ 19,753 km  Shanghai → Rotterdam via Suez\nsea.properties.co2eTonnes;    // ≈ 4448 t CO₂e (rough, see Emissions above)\nrail.properties.length;       // ≈ 1,180 km  Rotterdam → Genoa via the Gotthard base tunnel\nrail.properties.gaugeChanges; // 0 — standard gauge all the way\n```\n\n`npm install railroute-ts` — [docs & interactive demo](https://mayurrawte.is-a.dev/railroutes/).\nBoth libraries also ship MCP servers, so an AI agent can chain `sea_route` →\n`rail_route` for door-to-door distance (see below).\n\n## How it works\n\nA two-page deep-dive (graph data, snapping, Dijkstra, restrictions,\nantimeridian fix, draft logic, alternatives) is in [DOCS.md](https://github.com/mayurrawte/searoute-ts/blob/main/DOCS.md).\n\n## FAQ\n\n**Is this for navigation?** No. The routes are network paths suitable for\nvisualisation and rough distance/duration estimates, not for piloting ships.\n\n**Does it support weather routing?** No. For weather-aware routing see\n[VISIR-2](https://gmd.copernicus.org/articles/17/4355/2024/).\n\n**Why are my Asia→Europe routes going through Bering Strait?** They aren't,\nby default — the Northwest and Northeast Passages are blocked. Pass\n`allowArctic: true` to enable them.\n\n**Can I use my own network?** Yes — `seaRoute(origin, destination, { network })`.\nUseful for inland waterways or AIS-derived custom graphs. For higher-resolution\nEurostat data (5/10/20/50 km), see\n[Higher-resolution networks](#higher-resolution-networks-optional) — 20 km and\n50 km ship as subpath exports.\n\n**Does it handle the Red Sea / Suez crisis?** Yes — pass\n`restrictions: ['suez', 'babelmandeb']` to force Cape of Good Hope routing.\n\n**Is the great-circle distance correct across the antimeridian?** Yes — the\nmarnet has been normalised so the Pacific is a connected graph, and all\ndistances use haversine internally.\n\n**What's the bundle size?** What you import at runtime is small: the core plus\nthe bundled 100 km marnet (~1.1 MB JSON, shipped once as a shared\n`dist/data/marnet.cjs` asset both builds load, rather than inlined into each).\nTree-shakeable, so the optional `searoute-ts/marnet-20km` / `marnet-50km`\nnetworks only load if you import them. They do add to the npm tarball, though —\nincluding them the package is ~1.1 MB packed / ~7 MB unpacked (each variant is a\nsingle shared asset, not duplicated per build). If you need the finer networks\nwithout the install cost, generate and host them and use `loadNetwork` instead.\n\n## Credits\n\n- Maritime network — [Eurostat searoute v3.5](https://github.com/eurostat/searoute)\n  (EUPL-1.2). Oak Ridge National Labs Global Shipping Lane Network enriched\n  with European AIS data.\n- Dijkstra — [`geojson-path-finder@2`](https://github.com/perliedman/geojson-path-finder) by Per Liedman.\n- Inspired by [`searoute-py`](https://github.com/genthalili/searoute-py) (Apache-2.0).\n- Original JS port — [@johnx25bd](https://github.com/johnx25bd/searoute).\n- Geospatial primitives — [Turf.js](https://turfjs.org/).\n\n## License\n\nMIT © Mayur Rawte\n",
  "bytes": 25554,
  "sha": "a963df6f68b487748d8fa2e6ec4f2cf1ffaa6722eb1bcc3e8e5f8b661ecd5059",
  "repo_slug": "mayurrawte/searoute-ts",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_mayurrawte_searoute_06fa0632/readme"
}