Metadata-Version: 2.4
Name: maritime-routing
Version: 0.1.0
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.
License: MIT
License-File: LICENSE
Author: Junior Dantas
Author-email: juniordante01@gmail.com
Requires-Python: >=3.10,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: geopandas (>=1.1.4)
Requires-Dist: kaleido (>=1.3.0)
Requires-Dist: numpy (>=2.5.1)
Requires-Dist: pandas (>=3.0.5)
Requires-Dist: plotly (>=6.9.0)
Requires-Dist: pyogrio (>=0.13.0)
Requires-Dist: pyproj (>=3.7.2)
Requires-Dist: rasterio (>=1.5.0)
Requires-Dist: scipy (>=1.18.0)
Requires-Dist: shapely (>=2.1.2)
Description-Content-Type: text/markdown

# 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:

```bash
cd maritime-routing
poetry install
poetry run maritime-routing-fetch      # downloads the coastline and copies the ports.csv seed
```

Or, without Poetry, with pip:

```bash
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)

```python
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:

```python
fetch_data()                 # downloads coastline + copies the ports.csv seed
# fetch_data(include_ports=True)  # also shows the WPI notice (optional)
```

### By port names

```python
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

```python
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:

```python
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

```bash
# 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 the `ports.csv` **seed** is embedded; the active file is
> external.

## Data

- The active `ports.csv` (with Santos, Shanghai, etc.) lives in
  `data/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 into `data/` (a folder
  external to the package; or `$MARITIME_ROUTING_DATA`) with a **single
  call**: `fetch_data()` from the API, or `maritime-routing-fetch` from 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 to `data/routes/` and the PNG map to
  `data/maps/`. Everything lives in the same user `data/` 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.9M` cells (`uint8`, ~26 MB);
  at `0.1°`: `1800 × 3600`; at `0.25°`: `720 × 1440`.
- Rasterization via `rasterio.features.rasterize` (vectorized) + per-resolution
  caching.
- A\* with `heapq` (lazy deletion) and flat NumPy arrays for
  `g_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:

```bash
# 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](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/


