maritime-routing 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maritime_routing-0.1.0/LICENSE +21 -0
- maritime_routing-0.1.0/PKG-INFO +332 -0
- maritime_routing-0.1.0/README.md +303 -0
- maritime_routing-0.1.0/maritime_routing/__init__.py +99 -0
- maritime_routing-0.1.0/maritime_routing/__main__.py +5 -0
- maritime_routing-0.1.0/maritime_routing/astar.py +249 -0
- maritime_routing-0.1.0/maritime_routing/cli.py +260 -0
- maritime_routing-0.1.0/maritime_routing/coastline.py +161 -0
- maritime_routing-0.1.0/maritime_routing/config.py +114 -0
- maritime_routing-0.1.0/maritime_routing/data/ports.csv +69 -0
- maritime_routing-0.1.0/maritime_routing/distance.py +90 -0
- maritime_routing-0.1.0/maritime_routing/fetch.py +182 -0
- maritime_routing-0.1.0/maritime_routing/geojson.py +133 -0
- maritime_routing-0.1.0/maritime_routing/ports.py +194 -0
- maritime_routing-0.1.0/maritime_routing/raster.py +260 -0
- maritime_routing-0.1.0/maritime_routing/router.py +314 -0
- maritime_routing-0.1.0/maritime_routing/visualize.py +150 -0
- maritime_routing-0.1.0/pyproject.toml +33 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Junior Dantas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maritime-routing
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A computational maritime navigation system that transforms global geographic data into a navigable graph, enabling route calculation between ports using GIS, ocean rasterization, and optimized pathfinding algorithms.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Junior Dantas
|
|
8
|
+
Author-email: juniordante01@gmail.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: geopandas (>=1.1.4)
|
|
18
|
+
Requires-Dist: kaleido (>=1.3.0)
|
|
19
|
+
Requires-Dist: numpy (>=2.5.1)
|
|
20
|
+
Requires-Dist: pandas (>=3.0.5)
|
|
21
|
+
Requires-Dist: plotly (>=6.9.0)
|
|
22
|
+
Requires-Dist: pyogrio (>=0.13.0)
|
|
23
|
+
Requires-Dist: pyproj (>=3.7.2)
|
|
24
|
+
Requires-Dist: rasterio (>=1.5.0)
|
|
25
|
+
Requires-Dist: scipy (>=1.18.0)
|
|
26
|
+
Requires-Dist: shapely (>=2.1.2)
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# maritime-routing
|
|
30
|
+
|
|
31
|
+
Global maritime routing in Python. Computes the navigable route between any
|
|
32
|
+
two ports in the world — or between two arbitrary points given by
|
|
33
|
+
latitude/longitude — going around continents and islands.
|
|
34
|
+
|
|
35
|
+
## Motivation
|
|
36
|
+
|
|
37
|
+
Planning a sea crossing is, at its core, a shortest-path problem on a graph:
|
|
38
|
+
the ocean is the navigable space and the land is the set of obstacles. But
|
|
39
|
+
that space is a spherical surface covering the whole planet, with islands,
|
|
40
|
+
capes and straits that must be rounded — there are no predefined streets or
|
|
41
|
+
roads.
|
|
42
|
+
|
|
43
|
+
The goal of this package is to make that computation simple and programmable:
|
|
44
|
+
|
|
45
|
+
- based on **real data** (World Port Index for the ports and Natural Earth
|
|
46
|
+
10m Land for the coastline), not on manual approximations;
|
|
47
|
+
- with a classic, transparent algorithm (**A\***) over a rasterized
|
|
48
|
+
ocean/land grid, using Haversine distance as the heuristic;
|
|
49
|
+
- returning ready-to-use **objects** (the route, the GeoJSON, the CSV and the
|
|
50
|
+
map figure), instead of just writing files.
|
|
51
|
+
|
|
52
|
+
It is an educational and extensible foundation for maritime routing — not a
|
|
53
|
+
real commercial routing system (which involves currents, winds, draft, EEZ,
|
|
54
|
+
canals and economic factors), but designed to grow in that direction.
|
|
55
|
+
|
|
56
|
+
## What the package does
|
|
57
|
+
|
|
58
|
+
- Resolves ports by name (with country disambiguation) or accepts direct
|
|
59
|
+
coordinates for origin and destination.
|
|
60
|
+
- Builds a global navigable grid (1 = ocean, 0 = land) from the coastline,
|
|
61
|
+
with per-resolution caching.
|
|
62
|
+
- Runs A\* with 8 neighbors, real-distance step cost and a Haversine
|
|
63
|
+
heuristic (consistent, therefore optimal with weight 1.0).
|
|
64
|
+
- Returns the sequence of route coordinates, the total distance, and exports
|
|
65
|
+
GeoJSON/CSV and a map as in-memory **objects**.
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
With Poetry:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd maritime-routing
|
|
73
|
+
poetry install
|
|
74
|
+
poetry run maritime-routing-fetch # downloads the coastline and copies the ports.csv seed
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or, without Poetry, with pip:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python -m venv .venv && source .venv/bin/activate
|
|
81
|
+
pip install -e .
|
|
82
|
+
python -m maritime_routing.fetch
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> Dependencies and their minimum versions are declared in `pyproject.toml`
|
|
86
|
+
> — installation resolves everything automatically. Modern wheels already
|
|
87
|
+
> bundle GDAL/PROJ/GEOS, with no need for system libraries. Visualization
|
|
88
|
+
> uses plotly (with kaleido to export PNG), without cartopy.
|
|
89
|
+
|
|
90
|
+
## Library usage (returns objects)
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from maritime_routing import (
|
|
94
|
+
compute_route, compute_route_by_coords,
|
|
95
|
+
route_to_geojson, route_to_geojson_str, route_to_csv,
|
|
96
|
+
plot_route, save_geojson, save_csv, save_figure,
|
|
97
|
+
fetch_data,
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Before computing the first route, provision the data with a single call —
|
|
102
|
+
the coastline is downloaded and the `ports.csv` seed is copied into the
|
|
103
|
+
external `data/` folder:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
fetch_data() # downloads coastline + copies the ports.csv seed
|
|
107
|
+
# fetch_data(include_ports=True) # also shows the WPI notice (optional)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### By port names
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
res = compute_route("Santos", "Shanghai",
|
|
114
|
+
grid_resolution=0.1, heuristic_weight=1.15)
|
|
115
|
+
res.distance_km # float: total distance in km
|
|
116
|
+
res.path # list[(lat, lon), ...]
|
|
117
|
+
res.cells # number of points
|
|
118
|
+
res.stats # {'iterations', 'expanded', 'reason'}
|
|
119
|
+
|
|
120
|
+
geojson = route_to_geojson(res) # dict (FeatureCollection)
|
|
121
|
+
csv_text = route_to_csv(res) # str
|
|
122
|
+
fig = plot_route(res) # plotly.graph_objects.Figure
|
|
123
|
+
|
|
124
|
+
# Write to disk (optional):
|
|
125
|
+
# save_geojson(geojson, "route.geojson")
|
|
126
|
+
# save_csv(csv_text, "route.csv")
|
|
127
|
+
# save_figure(fig, "route.png")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### By origin and destination latitude/longitude
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
res = compute_route_by_coords(
|
|
134
|
+
-23.9608, -46.3331, # origin (lat, lon) — Santos
|
|
135
|
+
31.2304, 121.4737, # destination (lat, lon) — Shanghai
|
|
136
|
+
grid_resolution=0.1, heuristic_weight=1.15,
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Or directly through the `MaritimeRouter` class:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from maritime_routing import MaritimeRouter
|
|
144
|
+
router = MaritimeRouter(grid_resolution=0.1, heuristic_weight=1.15)
|
|
145
|
+
res = router.route("Santos", "Shanghai")
|
|
146
|
+
res2 = router.route_by_coords(-23.96, -46.33, 31.23, 121.47)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## CLI usage
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# By port names
|
|
153
|
+
maritime-routing --from "Santos" --to "Shanghai"
|
|
154
|
+
|
|
155
|
+
# Disambiguating the country
|
|
156
|
+
maritime-routing --from "New York" --from-country "United States" --to "Rotterdam"
|
|
157
|
+
|
|
158
|
+
# By coordinates (lat/lon of origin and destination)
|
|
159
|
+
maritime-routing --from-lat -23.96 --from-lon -46.33 \
|
|
160
|
+
--to-lat 31.23 --to-lon 121.47
|
|
161
|
+
|
|
162
|
+
# Coarser/faster, with a heuristic weight, writing to disk
|
|
163
|
+
maritime-routing --from "Santos" --to "Shanghai" \
|
|
164
|
+
--resolution 0.25 --heuristic-weight 1.15 --save
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Flags: `--resolution`, `--rebuild-grid`, `--max-iterations`,
|
|
168
|
+
`--heuristic-weight`, `--save` (writes GeoJSON+CSV to `data/routes/` and PNG
|
|
169
|
+
to `data/maps/`), `--no-map`, `--print-geojson`, `--print-csv`,
|
|
170
|
+
`--print-points`.
|
|
171
|
+
|
|
172
|
+
Also: `python -m maritime_routing ...` and `python -m maritime_routing.fetch`.
|
|
173
|
+
|
|
174
|
+
## Structure
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
maritime-routing/
|
|
178
|
+
├── pyproject.toml # Poetry + console scripts
|
|
179
|
+
├── README.md
|
|
180
|
+
├── maritime_routing/ # the package
|
|
181
|
+
│ ├── __init__.py # public API
|
|
182
|
+
│ ├── __main__.py # python -m maritime_routing
|
|
183
|
+
│ ├── cli.py # CLI
|
|
184
|
+
│ ├── fetch.py # data download
|
|
185
|
+
│ ├── config.py
|
|
186
|
+
│ ├── distance.py # haversine / route_distance
|
|
187
|
+
│ ├── ports.py # PortDatabase (WPI)
|
|
188
|
+
│ ├── coastline.py # CoastlineMap (shapefile)
|
|
189
|
+
│ ├── raster.py # OceanGrid (navigable grid)
|
|
190
|
+
│ ├── astar.py # AStarRouter (A*)
|
|
191
|
+
│ ├── router.py # MaritimeRouter, RouteResult, compute_route[_by_coords]
|
|
192
|
+
│ ├── geojson.py # returns dict/str (+ save_*)
|
|
193
|
+
│ ├── visualize.py # returns Figure (+ save_figure)
|
|
194
|
+
│ └── data/ports.csv # embedded seed (~70 ports, read-only)
|
|
195
|
+
└── data/ # EXTERNAL to the package — user folder (generated)
|
|
196
|
+
├── ne_10m_land.* # coastline (downloaded)
|
|
197
|
+
├── ocean_grid_<res>.npy # grid caches (one per resolution)
|
|
198
|
+
├── ports.csv # active (copied seed or user WPI)
|
|
199
|
+
├── routes/ # route GeoJSON + CSV (--save)
|
|
200
|
+
└── maps/ # map PNG (--save)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
> The `data/` folder is created in the current working directory (or in
|
|
204
|
+
> `$MARITIME_ROUTING_DATA`). Nothing is written inside the installed
|
|
205
|
+
> package — only the `ports.csv` **seed** is embedded; the active file is
|
|
206
|
+
> external.
|
|
207
|
+
|
|
208
|
+
## Data
|
|
209
|
+
|
|
210
|
+
- The active `ports.csv` (with Santos, Shanghai, etc.) lives in
|
|
211
|
+
`data/ports.csv` (external). `fetch_data()` copies the seed (~70 ports)
|
|
212
|
+
there on first run, **without overwriting** a CSV you may have placed
|
|
213
|
+
yourself. For the full database (WPI), replace the file with your own CSV.
|
|
214
|
+
- The coastline (`ne_10m_land.shp`) is downloaded into `data/` (a folder
|
|
215
|
+
external to the package; or `$MARITIME_ROUTING_DATA`) with a **single
|
|
216
|
+
call**: `fetch_data()` from the API, or `maritime-routing-fetch` from the
|
|
217
|
+
CLI.
|
|
218
|
+
- The grid is cached as `data/ocean_grid_<resolution>.npy` (one file per
|
|
219
|
+
resolution), so switching resolutions does not rebuild the grid.
|
|
220
|
+
- With `--save`, GeoJSON/CSV go to `data/routes/` and the PNG map to
|
|
221
|
+
`data/maps/`. Everything lives in the same user `data/` folder.
|
|
222
|
+
|
|
223
|
+
## Configuration (`config.py`)
|
|
224
|
+
|
|
225
|
+
| Parameter | Default | Description |
|
|
226
|
+
|-------------------------|------------|---------------------------------------------------|
|
|
227
|
+
| `GRID_RESOLUTION` | `0.05` | Cell size in degrees |
|
|
228
|
+
| `MAX_ITERATIONS` | `20000000` | A\* node expansion limit |
|
|
229
|
+
| `NEIGHBOR_MODE` | `8` | 4 (rook) or 8 (with diagonals) |
|
|
230
|
+
| `HEURISTIC_WEIGHT` | `1.0` | Heuristic weight (1.0=optimal; >1=faster) |
|
|
231
|
+
| `SNAP_SEARCH_RADIUS_DEG`| `2.0` | Radius to "snap" ports/points to the coastline |
|
|
232
|
+
|
|
233
|
+
The `MARITIME_ROUTING_DATA` environment variable overrides the writable
|
|
234
|
+
data directory (coastline, caches, routes and maps). The default is the
|
|
235
|
+
`data/` folder in the current working directory.
|
|
236
|
+
|
|
237
|
+
## Performance
|
|
238
|
+
|
|
239
|
+
- Global grid at `0.05°`: `3600 × 7200 ≈ 25.9M` cells (`uint8`, ~26 MB);
|
|
240
|
+
at `0.1°`: `1800 × 3600`; at `0.25°`: `720 × 1440`.
|
|
241
|
+
- Rasterization via `rasterio.features.rasterize` (vectorized) + per-resolution
|
|
242
|
+
caching.
|
|
243
|
+
- A\* with `heapq` (lazy deletion) and flat NumPy arrays for
|
|
244
|
+
`g_score`/`came_from`; longitudinal wrap-around (trans-Pacific routes).
|
|
245
|
+
- The Haversine heuristic is consistent (triangle inequality) → optimal A*
|
|
246
|
+
with weight 1.0.
|
|
247
|
+
|
|
248
|
+
### Practical tips (global routes)
|
|
249
|
+
|
|
250
|
+
Intercontinental routes require crossing entire ocean basins; at `0.05°`
|
|
251
|
+
A\* may need millions of expansions:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Fast (seconds): coarser resolution
|
|
255
|
+
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.25 --rebuild-grid
|
|
256
|
+
|
|
257
|
+
# Balanced
|
|
258
|
+
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.1 --heuristic-weight 1.15
|
|
259
|
+
|
|
260
|
+
# High resolution (several minutes): weight > 1 focuses the search
|
|
261
|
+
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.05 --heuristic-weight 1.15
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
A `--heuristic-weight` > 1 greatly reduces expansions at the cost of a
|
|
265
|
+
slightly suboptimal route (< 1 %). If you hit "ITERATION LIMIT reached",
|
|
266
|
+
increase `--max-iterations` or the weight, or use a coarser resolution.
|
|
267
|
+
|
|
268
|
+
## Limitations
|
|
269
|
+
|
|
270
|
+
- The route is the **shortest path on the grid** (optimizing Haversine
|
|
271
|
+
distance), not a real commercial route (currents, winds, draft, EEZ,
|
|
272
|
+
stopovers…).
|
|
273
|
+
- Suez/Panama canals are not modeled — A\* goes around Africa and South
|
|
274
|
+
America. (See the roadmap.)
|
|
275
|
+
- Points on land are automatically "snapped" to the nearest ocean cell.
|
|
276
|
+
- The lower the resolution, the faster the run and the coarser the route.
|
|
277
|
+
|
|
278
|
+
## Roadmap (architecture ready for extensions)
|
|
279
|
+
|
|
280
|
+
- **Suez/Panama canals:** mark the canal cells as navigable (and/or with a
|
|
281
|
+
lower weight) in `raster.py`/`config.py`.
|
|
282
|
+
- **Depth restriction:** integrate bathymetry (GEBCO) and make cells
|
|
283
|
+
shallower than the draft non-navigable in `OceanGrid.create_grid()`
|
|
284
|
+
(multiply masks).
|
|
285
|
+
- **Real commercial routes / AIS:** per-cell weights (cost ≠ distance)
|
|
286
|
+
derived from traffic density in `astar._step_cost()`.
|
|
287
|
+
- **Economic weights:** expose a weight array `W[row,col]` and multiply the
|
|
288
|
+
step cost by it.
|
|
289
|
+
|
|
290
|
+
## AI assistance
|
|
291
|
+
|
|
292
|
+
Parts of this project were developed with the assistance of an AI assistant (Claude).
|
|
293
|
+
|
|
294
|
+
The AI-assisted development process contributed to architectural improvements, code refinement,
|
|
295
|
+
documentation updates, usability enhancements, and implementation optimizations.
|
|
296
|
+
|
|
297
|
+
All generated suggestions and modifications were carefully reviewed, tested,
|
|
298
|
+
and verified to maintain the reliability and integrity of the project.
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
This project is licensed under the **MIT License** — see the
|
|
304
|
+
[LICENSE](LICENSE) file for details.
|
|
305
|
+
|
|
306
|
+
Copyright (c) 2026 Junior Dantas
|
|
307
|
+
|
|
308
|
+
## Data Sources
|
|
309
|
+
|
|
310
|
+
This project uses the following open datasets:
|
|
311
|
+
|
|
312
|
+
- **World Port Index (WPI)**
|
|
313
|
+
National Geospatial-Intelligence Agency (NGA)
|
|
314
|
+
License: Public Domain
|
|
315
|
+
Source: https://msi.nga.mil/Publications/WPI
|
|
316
|
+
|
|
317
|
+
- **Natural Earth Data**
|
|
318
|
+
Global geographic datasets for coastline and land boundaries
|
|
319
|
+
License: Public Domain
|
|
320
|
+
Source: https://www.naturalearthdata.com/
|
|
321
|
+
|
|
322
|
+
- **Global Self-consistent Hierarchical High-resolution Geography Database (GSHHG)**
|
|
323
|
+
High-resolution shoreline and coastline data
|
|
324
|
+
License: GNU Lesser General Public License (LGPL)
|
|
325
|
+
Source: https://www.soest.hawaii.edu/pwessel/gshhg/
|
|
326
|
+
|
|
327
|
+
- **OpenStreetMap (OSM) data (when applicable)**
|
|
328
|
+
Geographic information used for map-related features
|
|
329
|
+
License: Open Database License (ODbL)
|
|
330
|
+
Source: https://www.openstreetmap.org/
|
|
331
|
+
|
|
332
|
+
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# maritime-routing
|
|
2
|
+
|
|
3
|
+
Global maritime routing in Python. Computes the navigable route between any
|
|
4
|
+
two ports in the world — or between two arbitrary points given by
|
|
5
|
+
latitude/longitude — going around continents and islands.
|
|
6
|
+
|
|
7
|
+
## Motivation
|
|
8
|
+
|
|
9
|
+
Planning a sea crossing is, at its core, a shortest-path problem on a graph:
|
|
10
|
+
the ocean is the navigable space and the land is the set of obstacles. But
|
|
11
|
+
that space is a spherical surface covering the whole planet, with islands,
|
|
12
|
+
capes and straits that must be rounded — there are no predefined streets or
|
|
13
|
+
roads.
|
|
14
|
+
|
|
15
|
+
The goal of this package is to make that computation simple and programmable:
|
|
16
|
+
|
|
17
|
+
- based on **real data** (World Port Index for the ports and Natural Earth
|
|
18
|
+
10m Land for the coastline), not on manual approximations;
|
|
19
|
+
- with a classic, transparent algorithm (**A\***) over a rasterized
|
|
20
|
+
ocean/land grid, using Haversine distance as the heuristic;
|
|
21
|
+
- returning ready-to-use **objects** (the route, the GeoJSON, the CSV and the
|
|
22
|
+
map figure), instead of just writing files.
|
|
23
|
+
|
|
24
|
+
It is an educational and extensible foundation for maritime routing — not a
|
|
25
|
+
real commercial routing system (which involves currents, winds, draft, EEZ,
|
|
26
|
+
canals and economic factors), but designed to grow in that direction.
|
|
27
|
+
|
|
28
|
+
## What the package does
|
|
29
|
+
|
|
30
|
+
- Resolves ports by name (with country disambiguation) or accepts direct
|
|
31
|
+
coordinates for origin and destination.
|
|
32
|
+
- Builds a global navigable grid (1 = ocean, 0 = land) from the coastline,
|
|
33
|
+
with per-resolution caching.
|
|
34
|
+
- Runs A\* with 8 neighbors, real-distance step cost and a Haversine
|
|
35
|
+
heuristic (consistent, therefore optimal with weight 1.0).
|
|
36
|
+
- Returns the sequence of route coordinates, the total distance, and exports
|
|
37
|
+
GeoJSON/CSV and a map as in-memory **objects**.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
With Poetry:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd maritime-routing
|
|
45
|
+
poetry install
|
|
46
|
+
poetry run maritime-routing-fetch # downloads the coastline and copies the ports.csv seed
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or, without Poetry, with pip:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python -m venv .venv && source .venv/bin/activate
|
|
53
|
+
pip install -e .
|
|
54
|
+
python -m maritime_routing.fetch
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> Dependencies and their minimum versions are declared in `pyproject.toml`
|
|
58
|
+
> — installation resolves everything automatically. Modern wheels already
|
|
59
|
+
> bundle GDAL/PROJ/GEOS, with no need for system libraries. Visualization
|
|
60
|
+
> uses plotly (with kaleido to export PNG), without cartopy.
|
|
61
|
+
|
|
62
|
+
## Library usage (returns objects)
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from maritime_routing import (
|
|
66
|
+
compute_route, compute_route_by_coords,
|
|
67
|
+
route_to_geojson, route_to_geojson_str, route_to_csv,
|
|
68
|
+
plot_route, save_geojson, save_csv, save_figure,
|
|
69
|
+
fetch_data,
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Before computing the first route, provision the data with a single call —
|
|
74
|
+
the coastline is downloaded and the `ports.csv` seed is copied into the
|
|
75
|
+
external `data/` folder:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
fetch_data() # downloads coastline + copies the ports.csv seed
|
|
79
|
+
# fetch_data(include_ports=True) # also shows the WPI notice (optional)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### By port names
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
res = compute_route("Santos", "Shanghai",
|
|
86
|
+
grid_resolution=0.1, heuristic_weight=1.15)
|
|
87
|
+
res.distance_km # float: total distance in km
|
|
88
|
+
res.path # list[(lat, lon), ...]
|
|
89
|
+
res.cells # number of points
|
|
90
|
+
res.stats # {'iterations', 'expanded', 'reason'}
|
|
91
|
+
|
|
92
|
+
geojson = route_to_geojson(res) # dict (FeatureCollection)
|
|
93
|
+
csv_text = route_to_csv(res) # str
|
|
94
|
+
fig = plot_route(res) # plotly.graph_objects.Figure
|
|
95
|
+
|
|
96
|
+
# Write to disk (optional):
|
|
97
|
+
# save_geojson(geojson, "route.geojson")
|
|
98
|
+
# save_csv(csv_text, "route.csv")
|
|
99
|
+
# save_figure(fig, "route.png")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### By origin and destination latitude/longitude
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
res = compute_route_by_coords(
|
|
106
|
+
-23.9608, -46.3331, # origin (lat, lon) — Santos
|
|
107
|
+
31.2304, 121.4737, # destination (lat, lon) — Shanghai
|
|
108
|
+
grid_resolution=0.1, heuristic_weight=1.15,
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Or directly through the `MaritimeRouter` class:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from maritime_routing import MaritimeRouter
|
|
116
|
+
router = MaritimeRouter(grid_resolution=0.1, heuristic_weight=1.15)
|
|
117
|
+
res = router.route("Santos", "Shanghai")
|
|
118
|
+
res2 = router.route_by_coords(-23.96, -46.33, 31.23, 121.47)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## CLI usage
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# By port names
|
|
125
|
+
maritime-routing --from "Santos" --to "Shanghai"
|
|
126
|
+
|
|
127
|
+
# Disambiguating the country
|
|
128
|
+
maritime-routing --from "New York" --from-country "United States" --to "Rotterdam"
|
|
129
|
+
|
|
130
|
+
# By coordinates (lat/lon of origin and destination)
|
|
131
|
+
maritime-routing --from-lat -23.96 --from-lon -46.33 \
|
|
132
|
+
--to-lat 31.23 --to-lon 121.47
|
|
133
|
+
|
|
134
|
+
# Coarser/faster, with a heuristic weight, writing to disk
|
|
135
|
+
maritime-routing --from "Santos" --to "Shanghai" \
|
|
136
|
+
--resolution 0.25 --heuristic-weight 1.15 --save
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Flags: `--resolution`, `--rebuild-grid`, `--max-iterations`,
|
|
140
|
+
`--heuristic-weight`, `--save` (writes GeoJSON+CSV to `data/routes/` and PNG
|
|
141
|
+
to `data/maps/`), `--no-map`, `--print-geojson`, `--print-csv`,
|
|
142
|
+
`--print-points`.
|
|
143
|
+
|
|
144
|
+
Also: `python -m maritime_routing ...` and `python -m maritime_routing.fetch`.
|
|
145
|
+
|
|
146
|
+
## Structure
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
maritime-routing/
|
|
150
|
+
├── pyproject.toml # Poetry + console scripts
|
|
151
|
+
├── README.md
|
|
152
|
+
├── maritime_routing/ # the package
|
|
153
|
+
│ ├── __init__.py # public API
|
|
154
|
+
│ ├── __main__.py # python -m maritime_routing
|
|
155
|
+
│ ├── cli.py # CLI
|
|
156
|
+
│ ├── fetch.py # data download
|
|
157
|
+
│ ├── config.py
|
|
158
|
+
│ ├── distance.py # haversine / route_distance
|
|
159
|
+
│ ├── ports.py # PortDatabase (WPI)
|
|
160
|
+
│ ├── coastline.py # CoastlineMap (shapefile)
|
|
161
|
+
│ ├── raster.py # OceanGrid (navigable grid)
|
|
162
|
+
│ ├── astar.py # AStarRouter (A*)
|
|
163
|
+
│ ├── router.py # MaritimeRouter, RouteResult, compute_route[_by_coords]
|
|
164
|
+
│ ├── geojson.py # returns dict/str (+ save_*)
|
|
165
|
+
│ ├── visualize.py # returns Figure (+ save_figure)
|
|
166
|
+
│ └── data/ports.csv # embedded seed (~70 ports, read-only)
|
|
167
|
+
└── data/ # EXTERNAL to the package — user folder (generated)
|
|
168
|
+
├── ne_10m_land.* # coastline (downloaded)
|
|
169
|
+
├── ocean_grid_<res>.npy # grid caches (one per resolution)
|
|
170
|
+
├── ports.csv # active (copied seed or user WPI)
|
|
171
|
+
├── routes/ # route GeoJSON + CSV (--save)
|
|
172
|
+
└── maps/ # map PNG (--save)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
> The `data/` folder is created in the current working directory (or in
|
|
176
|
+
> `$MARITIME_ROUTING_DATA`). Nothing is written inside the installed
|
|
177
|
+
> package — only the `ports.csv` **seed** is embedded; the active file is
|
|
178
|
+
> external.
|
|
179
|
+
|
|
180
|
+
## Data
|
|
181
|
+
|
|
182
|
+
- The active `ports.csv` (with Santos, Shanghai, etc.) lives in
|
|
183
|
+
`data/ports.csv` (external). `fetch_data()` copies the seed (~70 ports)
|
|
184
|
+
there on first run, **without overwriting** a CSV you may have placed
|
|
185
|
+
yourself. For the full database (WPI), replace the file with your own CSV.
|
|
186
|
+
- The coastline (`ne_10m_land.shp`) is downloaded into `data/` (a folder
|
|
187
|
+
external to the package; or `$MARITIME_ROUTING_DATA`) with a **single
|
|
188
|
+
call**: `fetch_data()` from the API, or `maritime-routing-fetch` from the
|
|
189
|
+
CLI.
|
|
190
|
+
- The grid is cached as `data/ocean_grid_<resolution>.npy` (one file per
|
|
191
|
+
resolution), so switching resolutions does not rebuild the grid.
|
|
192
|
+
- With `--save`, GeoJSON/CSV go to `data/routes/` and the PNG map to
|
|
193
|
+
`data/maps/`. Everything lives in the same user `data/` folder.
|
|
194
|
+
|
|
195
|
+
## Configuration (`config.py`)
|
|
196
|
+
|
|
197
|
+
| Parameter | Default | Description |
|
|
198
|
+
|-------------------------|------------|---------------------------------------------------|
|
|
199
|
+
| `GRID_RESOLUTION` | `0.05` | Cell size in degrees |
|
|
200
|
+
| `MAX_ITERATIONS` | `20000000` | A\* node expansion limit |
|
|
201
|
+
| `NEIGHBOR_MODE` | `8` | 4 (rook) or 8 (with diagonals) |
|
|
202
|
+
| `HEURISTIC_WEIGHT` | `1.0` | Heuristic weight (1.0=optimal; >1=faster) |
|
|
203
|
+
| `SNAP_SEARCH_RADIUS_DEG`| `2.0` | Radius to "snap" ports/points to the coastline |
|
|
204
|
+
|
|
205
|
+
The `MARITIME_ROUTING_DATA` environment variable overrides the writable
|
|
206
|
+
data directory (coastline, caches, routes and maps). The default is the
|
|
207
|
+
`data/` folder in the current working directory.
|
|
208
|
+
|
|
209
|
+
## Performance
|
|
210
|
+
|
|
211
|
+
- Global grid at `0.05°`: `3600 × 7200 ≈ 25.9M` cells (`uint8`, ~26 MB);
|
|
212
|
+
at `0.1°`: `1800 × 3600`; at `0.25°`: `720 × 1440`.
|
|
213
|
+
- Rasterization via `rasterio.features.rasterize` (vectorized) + per-resolution
|
|
214
|
+
caching.
|
|
215
|
+
- A\* with `heapq` (lazy deletion) and flat NumPy arrays for
|
|
216
|
+
`g_score`/`came_from`; longitudinal wrap-around (trans-Pacific routes).
|
|
217
|
+
- The Haversine heuristic is consistent (triangle inequality) → optimal A*
|
|
218
|
+
with weight 1.0.
|
|
219
|
+
|
|
220
|
+
### Practical tips (global routes)
|
|
221
|
+
|
|
222
|
+
Intercontinental routes require crossing entire ocean basins; at `0.05°`
|
|
223
|
+
A\* may need millions of expansions:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Fast (seconds): coarser resolution
|
|
227
|
+
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.25 --rebuild-grid
|
|
228
|
+
|
|
229
|
+
# Balanced
|
|
230
|
+
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.1 --heuristic-weight 1.15
|
|
231
|
+
|
|
232
|
+
# High resolution (several minutes): weight > 1 focuses the search
|
|
233
|
+
maritime-routing --from "Santos" --to "Shanghai" --resolution 0.05 --heuristic-weight 1.15
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
A `--heuristic-weight` > 1 greatly reduces expansions at the cost of a
|
|
237
|
+
slightly suboptimal route (< 1 %). If you hit "ITERATION LIMIT reached",
|
|
238
|
+
increase `--max-iterations` or the weight, or use a coarser resolution.
|
|
239
|
+
|
|
240
|
+
## Limitations
|
|
241
|
+
|
|
242
|
+
- The route is the **shortest path on the grid** (optimizing Haversine
|
|
243
|
+
distance), not a real commercial route (currents, winds, draft, EEZ,
|
|
244
|
+
stopovers…).
|
|
245
|
+
- Suez/Panama canals are not modeled — A\* goes around Africa and South
|
|
246
|
+
America. (See the roadmap.)
|
|
247
|
+
- Points on land are automatically "snapped" to the nearest ocean cell.
|
|
248
|
+
- The lower the resolution, the faster the run and the coarser the route.
|
|
249
|
+
|
|
250
|
+
## Roadmap (architecture ready for extensions)
|
|
251
|
+
|
|
252
|
+
- **Suez/Panama canals:** mark the canal cells as navigable (and/or with a
|
|
253
|
+
lower weight) in `raster.py`/`config.py`.
|
|
254
|
+
- **Depth restriction:** integrate bathymetry (GEBCO) and make cells
|
|
255
|
+
shallower than the draft non-navigable in `OceanGrid.create_grid()`
|
|
256
|
+
(multiply masks).
|
|
257
|
+
- **Real commercial routes / AIS:** per-cell weights (cost ≠ distance)
|
|
258
|
+
derived from traffic density in `astar._step_cost()`.
|
|
259
|
+
- **Economic weights:** expose a weight array `W[row,col]` and multiply the
|
|
260
|
+
step cost by it.
|
|
261
|
+
|
|
262
|
+
## AI assistance
|
|
263
|
+
|
|
264
|
+
Parts of this project were developed with the assistance of an AI assistant (Claude).
|
|
265
|
+
|
|
266
|
+
The AI-assisted development process contributed to architectural improvements, code refinement,
|
|
267
|
+
documentation updates, usability enhancements, and implementation optimizations.
|
|
268
|
+
|
|
269
|
+
All generated suggestions and modifications were carefully reviewed, tested,
|
|
270
|
+
and verified to maintain the reliability and integrity of the project.
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
This project is licensed under the **MIT License** — see the
|
|
276
|
+
[LICENSE](LICENSE) file for details.
|
|
277
|
+
|
|
278
|
+
Copyright (c) 2026 Junior Dantas
|
|
279
|
+
|
|
280
|
+
## Data Sources
|
|
281
|
+
|
|
282
|
+
This project uses the following open datasets:
|
|
283
|
+
|
|
284
|
+
- **World Port Index (WPI)**
|
|
285
|
+
National Geospatial-Intelligence Agency (NGA)
|
|
286
|
+
License: Public Domain
|
|
287
|
+
Source: https://msi.nga.mil/Publications/WPI
|
|
288
|
+
|
|
289
|
+
- **Natural Earth Data**
|
|
290
|
+
Global geographic datasets for coastline and land boundaries
|
|
291
|
+
License: Public Domain
|
|
292
|
+
Source: https://www.naturalearthdata.com/
|
|
293
|
+
|
|
294
|
+
- **Global Self-consistent Hierarchical High-resolution Geography Database (GSHHG)**
|
|
295
|
+
High-resolution shoreline and coastline data
|
|
296
|
+
License: GNU Lesser General Public License (LGPL)
|
|
297
|
+
Source: https://www.soest.hawaii.edu/pwessel/gshhg/
|
|
298
|
+
|
|
299
|
+
- **OpenStreetMap (OSM) data (when applicable)**
|
|
300
|
+
Geographic information used for map-related features
|
|
301
|
+
License: Open Database License (ODbL)
|
|
302
|
+
Source: https://www.openstreetmap.org/
|
|
303
|
+
|