Maritime Routing
Global maritime routing in Python. Computes the navigable route between any two ports in the world — or between two arbitrary points given by latitude/longitude — going around continents and islands.
Motivation
Planning a sea crossing is, at its core, a shortest-path problem on a graph: the ocean is the navigable space and the land is the set of obstacles. But that space is a spherical surface covering the whole planet, with islands, capes and straits that must be rounded — there are no predefined streets or roads.
The goal of this package is to make that computation simple and programmable:
- based on real data (World Port Index for the ports and Natural Earth 10m Land for the coastline), not on manual approximations;
- with a classic, transparent algorithm (A*) over a rasterized ocean/land grid, using Haversine distance as the heuristic;
- returning ready-to-use objects (the route, the GeoJSON, the CSV and the map figure), instead of just writing files.
It is an educational and extensible foundation for maritime routing — not a real commercial routing system (which involves currents, winds, draft, EEZ, canals and economic factors), but designed to grow in that direction.
What the package does
- Resolves ports by name (with country disambiguation) or accepts direct coordinates for origin and destination.
- Builds a global navigable grid (1 = ocean, 0 = land) from the coastline, with per-resolution caching.
- Runs A* with 8 neighbors, real-distance step cost and a Haversine heuristic (consistent, therefore optimal with weight 1.0).
- Returns the sequence of route coordinates, the total distance, and exports GeoJSON/CSV and a map as in-memory objects.
Installation
With Poetry:
cd maritime-routing
poetry install
poetry run maritime-routing-fetch # downloads the coastline and copies the ports.csv seed
Or, without Poetry, with pip:
python -m venv .venv && source .venv/bin/activate
pip install -e .
python -m maritime_routing.fetch
Dependencies and their minimum versions are declared in
pyproject.toml— installation resolves everything automatically. Modern wheels already bundle GDAL/PROJ/GEOS, with no need for system libraries. Visualization uses plotly (with kaleido to export PNG), without cartopy.
Library usage (returns objects)
from maritime_routing import (
compute_route, compute_route_by_coords,
route_to_geojson, route_to_geojson_str, route_to_csv,
plot_route, save_geojson, save_csv, save_figure,
fetch_data,
)
Before computing the first route, provision the data with a single call —
the coastline is downloaded and the ports.csv seed is copied into the
external data/ folder:
fetch_data() # downloads coastline + copies the ports.csv seed
# fetch_data(include_ports=True) # also shows the WPI notice (optional)
By port names
res = compute_route("Santos", "Shanghai",
grid_resolution=0.1, heuristic_weight=1.15)
res.distance_km # float: total distance in km
res.path # list[(lat, lon), ...]
res.cells # number of points
res.stats # {'iterations', 'expanded', 'reason'}
geojson = route_to_geojson(res) # dict (FeatureCollection)
csv_text = route_to_csv(res) # str
fig = plot_route(res) # plotly.graph_objects.Figure
# Write to disk (optional):
# save_geojson(geojson, "route.geojson")
# save_csv(csv_text, "route.csv")
# save_figure(fig, "route.png")
By origin and destination latitude/longitude
res = compute_route_by_coords(
-23.9608, -46.3331, # origin (lat, lon) — Santos
31.2304, 121.4737, # destination (lat, lon) — Shanghai
grid_resolution=0.1, heuristic_weight=1.15,
)
Or directly through the MaritimeRouter class:
from maritime_routing import MaritimeRouter
router = MaritimeRouter(grid_resolution=0.1, heuristic_weight=1.15)
res = router.route("Santos", "Shanghai")
res2 = router.route_by_coords(-23.96, -46.33, 31.23, 121.47)
CLI usage
# By port names
maritime-routing --from "Santos" --to "Shanghai"
# Disambiguating the country
maritime-routing --from "New York" --from-country "United States" --to "Rotterdam"
# By coordinates (lat/lon of origin and destination)
maritime-routing --from-lat -23.96 --from-lon -46.33 \
--to-lat 31.23 --to-lon 121.47
# Coarser/faster, with a heuristic weight, writing to disk
maritime-routing --from "Santos" --to "Shanghai" \
--resolution 0.25 --heuristic-weight 1.15 --save
Flags: --resolution, --rebuild-grid, --max-iterations,
--heuristic-weight, --save (writes GeoJSON+CSV to data/routes/ and PNG
to data/maps/), --no-map, --print-geojson, --print-csv,
--print-points.
Also: python -m maritime_routing ... and python -m maritime_routing.fetch.
Structure
maritime-routing/
├── pyproject.toml # Poetry + console scripts
├── README.md
├── maritime_routing/ # the package
│ ├── __init__.py # public API
│ ├── __main__.py # python -m maritime_routing
│ ├── cli.py # CLI
│ ├── fetch.py # data download
│ ├── config.py
│ ├── distance.py # haversine / route_distance
│ ├── ports.py # PortDatabase (WPI)
│ ├── coastline.py # CoastlineMap (shapefile)
│ ├── raster.py # OceanGrid (navigable grid)
│ ├── astar.py # AStarRouter (A*)
│ ├── router.py # MaritimeRouter, RouteResult, compute_route[_by_coords]
│ ├── geojson.py # returns dict/str (+ save_*)
│ ├── visualize.py # returns Figure (+ save_figure)
│ └── data/ports.csv # embedded seed (~70 ports, read-only)
└── data/ # EXTERNAL to the package — user folder (generated)
├── ne_10m_land.* # coastline (downloaded)
├── ocean_grid_<res>.npy # grid caches (one per resolution)
├── ports.csv # active (copied seed or user WPI)
├── routes/ # route GeoJSON + CSV (--save)
└── maps/ # map PNG (--save)
The
data/folder is created in the current working directory (or in$MARITIME_ROUTING_DATA). Nothing is written inside the installed package — only theports.csvseed is embedded; the active file is external.
Data
- The active
ports.csv(with Santos, Shanghai, etc.) lives indata/ports.csv(external).fetch_data()copies the seed (~70 ports) there on first run, without overwriting a CSV you may have placed yourself. For the full database (WPI), replace the file with your own CSV. - The coastline (
ne_10m_land.shp) is downloaded intodata/(a folder external to the package; or$MARITIME_ROUTING_DATA) with a single call:fetch_data()from the API, ormaritime-routing-fetchfrom the CLI. - The grid is cached as
data/ocean_grid_<resolution>.npy(one file per resolution), so switching resolutions does not rebuild the grid. - With
--save, GeoJSON/CSV go todata/routes/and the PNG map todata/maps/. Everything lives in the same userdata/folder.
Configuration (config.py)
| Parameter | Default | Description |
|---|---|---|
GRID_RESOLUTION |
0.05 |
Cell size in degrees |
MAX_ITERATIONS |
20000000 |
A* node expansion limit |
NEIGHBOR_MODE |
8 |
4 (rook) or 8 (with diagonals) |
HEURISTIC_WEIGHT |
1.0 |
Heuristic weight (1.0=optimal; >1=faster) |
SNAP_SEARCH_RADIUS_DEG |
2.0 |
Radius to "snap" ports/points to the coastline |
The MARITIME_ROUTING_DATA environment variable overrides the writable
data directory (coastline, caches, routes and maps). The default is the
data/ folder in the current working directory.
Performance
- Global grid at
0.05°:3600 × 7200 ≈ 25.9Mcells (uint8, ~26 MB); at0.1°:1800 × 3600; at0.25°:720 × 1440. - Rasterization via
rasterio.features.rasterize(vectorized) + per-resolution caching. - A* with
heapq(lazy deletion) and flat NumPy arrays forg_score/came_from; longitudinal wrap-around (trans-Pacific routes). - The Haversine heuristic is consistent (triangle inequality) → optimal A* with weight 1.0.
Practical tips (global routes)
Intercontinental routes require crossing entire ocean basins; at 0.05°
A* may need millions of expansions:
# Fast (seconds): coarser resolution
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.25 --rebuild-grid
# Balanced
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.1 --heuristic-weight 1.15
# High resolution (several minutes): weight > 1 focuses the search
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.05 --heuristic-weight 1.15
A --heuristic-weight > 1 greatly reduces expansions at the cost of a
slightly suboptimal route (< 1 %). If you hit "ITERATION LIMIT reached",
increase --max-iterations or the weight, or use a coarser resolution.
Limitations
- The route is the shortest path on the grid (optimizing Haversine distance), not a real commercial route (currents, winds, draft, EEZ, stopovers…).
- Suez/Panama canals are not modeled — A* goes around Africa and South America. (See the roadmap.)
- Points on land are automatically "snapped" to the nearest ocean cell.
- The lower the resolution, the faster the run and the coarser the route.
Roadmap (architecture ready for extensions)
- Suez/Panama canals: mark the canal cells as navigable (and/or with a
lower weight) in
raster.py/config.py. - Depth restriction: integrate bathymetry (GEBCO) and make cells
shallower than the draft non-navigable in
OceanGrid.create_grid()(multiply masks). - Real commercial routes / AIS: per-cell weights (cost ≠ distance)
derived from traffic density in
astar._step_cost(). - Economic weights: expose a weight array
W[row,col]and multiply the step cost by it.
AI assistance
Parts of this project were developed with the assistance of an AI assistant (Claude).
The AI-assisted development process contributed to architectural improvements, code refinement, documentation updates, usability enhancements, and implementation optimizations.
All generated suggestions and modifications were carefully reviewed, tested, and verified to maintain the reliability and integrity of the project.
License
This project is licensed under the MIT License — see the LICENSE file for details.
Copyright (c) 2026 Junior Dantas
Data Sources
This project uses the following open datasets:
-
World Port Index (WPI) National Geospatial-Intelligence Agency (NGA) License: Public Domain Source: https://msi.nga.mil/Publications/WPI
-
Natural Earth Data Global geographic datasets for coastline and land boundaries License: Public Domain Source: https://www.naturalearthdata.com/
-
Global Self-consistent Hierarchical High-resolution Geography Database (GSHHG) High-resolution shoreline and coastline data License: GNU Lesser General Public License (LGPL) Source: https://www.soest.hawaii.edu/pwessel/gshhg/
-
OpenStreetMap (OSM) data (when applicable) Geographic information used for map-related features License: Open Database License (ODbL) Source: https://www.openstreetmap.org/
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file maritime_routing-0.1.1.tar.gz.
File metadata
- Download URL: maritime_routing-0.1.1.tar.gz
- Upload date:
- Size: 31.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d867b8f3fb137d0e8739e9d6c95d50ad862c1aa879400e7cb97592f5260f890
|
|
| MD5 |
355abc86f607b53ba495e59007cf62be
|
|
| BLAKE2b-256 |
0f269fffca802f9703e9d38bfad69a082a5c2a6ea5b911e9f1a68a1e7c26db08
|
File details
Details for the file maritime_routing-0.1.1-py3-none-any.whl.
File metadata
- Download URL: maritime_routing-0.1.1-py3-none-any.whl
- Upload date:
- Size: 35.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c495a4ff5a44b0b9cad9355d2c7acc86272428bc34bb3dbc540161785fcaeb93
|
|
| MD5 |
a2bb274b713c51a0c18dd43b34b31256
|
|
| BLAKE2b-256 |
cf0056b844c07dbab9034733e05cf75ced7fd5137abab5806f0d9eddc9412e8a
|