Metadata-Version: 2.4
Name: georoutelib
Version: 0.1.1
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering :: GIS
Requires-Dist: pytest>=9.0 ; extra == 'dev'
Provides-Extra: dev
License-File: LICENSE
Summary: Maritime route geometry and ECA intersect distance (Rust core)
License: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# georoutelib

Maritime route calculation: waypoints → route geometry → along-route analysis and editing.

| PyPI | `georoutelib` **0.1.1** |
|------|-----------------|
| import | `georoutelib` |

Requires Python 3.12+.

> **Shared references:** `RouteLine` and `GeoRoute` do **not** copy the list you pass in.
> Edits (`merge_point`, `remove_point`, `deduplicate`, `simplify`, …) mutate that same list in place.
> To keep the original data, pass a copy: `RouteLine(points.copy())` or `copy.deepcopy(segments)`.

## Install

```bash
pip install georoutelib
```

Development:

```bash
uv sync --extra dev
env -u CONDA_PREFIX maturin develop
.venv/bin/pytest
```

## Package layout (Rust)

```
src/
├── lib.rs           # PyO3 module entry
├── route/           # GeoRoute, RouteLine, GeoCalc, …
└── eca/             # ECAProcessor — GeoJSON zones × route distance
```

## Units

| Quantity | Unit |
|----------|------|
| Distance | nautical miles (nm) |
| Speed | knots (kts) |
| Bearing | degrees (0–360°) |
| Time | hours (h) or timestamp (Unix s) |

## Quick start

### Create points and measure distance

```python
from georoutelib import RoutePoint, GeoCalc

a = RoutePoint(lon=-50.9, lat=12.6)
b = RoutePoint(lon=-52.3, lat=12.9)

rhumb_nm = GeoCalc.distance(a, b, rhumb=True)
gc_nm = GeoCalc.distance(a, b, rhumb=False)
bearing = GeoCalc.bearing(a, b, rhumb=True)
```

### Build a route from points

```python
from georoutelib import RoutePoint, GeoRoute

points = [
    RoutePoint(lon=103.8, lat=1.2, is_great_circle=True),
    RoutePoint(lon=104.5, lat=1.5, is_great_circle=True),
    RoutePoint(lon=105.0, lat=2.0),
]

route = GeoRoute.from_points(points, spacing=200.0)
print(route.segments)  # GeoRoute: List[List[List[float]]], split at ±180° if needed
```

### Flat route operations

```python
from georoutelib import RouteLine, RoutePoint

points = [RoutePoint(lon=0, lat=0), RoutePoint(lon=1, lat=1), RoutePoint(lon=2, lat=0)]
route = RouteLine(points).deduplicate().simplify()

total_nm = route.total_distance()
center = route.center()
bbox = route.bbox()  # [minLon, minLat, maxLon, maxLat]
segments = route.to_multi_line()
```

### Advance along a route

```python
from georoutelib import RoutePoint, GeoRoute

route = GeoRoute.from_points(points, spacing=200.0)
current = RoutePoint(lon=104.0, lat=1.3)
result = route.advance_along(current, dist=50.0)
next_pos = result["point"]
remaining = result["remaining"]   # segments ahead
traveled = result["traveled"]     # points already passed
```

### Minimum distance to route

```python
result = route.min_distance_to(RoutePoint(lon=104.2, lat=1.4))
# {"min_dist": float, "seg_index": int, "min_index": int}
```

## Architecture

```
RoutePoint
    ↓
GeoCalc          ← bearing / distance / interpolate / dateline split
    ↓
RouteLine     ← flat List[RoutePoint], chainable edits, DR
    ↕
GeoRoute         ← List[List[List[float]]], dateline-safe storage, along-route ops
```

- **`RouteLine`**: flat, **undivided** `RoutePoint` list (not a `GeoRoute` sub-segment); `position_at` / `time_at`, point queries.
- **`GeoRoute`**: raw multi-segment coordinates split at the antimeridian; `from_points`, `advance_along`, `min_distance_to`, `remaining_route`, `fill_point_distances`.
- **`GeoCalc`**: geodesic math in Rust (`geo` / Turf-aligned great-circle), rhumb vs great-circle selectable.
- **`ECAProcessor`**: Emission Control Area intersect distance from GeoJSON (no Shapely).

### ECA (Emission Control Area)

```python
from georoutelib import ECAProcessor

processor = ECAProcessor("https://example.com/eca-zones.geojson")
# offline / tests: ECAProcessor.from_geojson(url, geojson_dict)

results = processor.calculate_eca_intersects([[120.0, 30.0], [122.0, 31.0]])
for item in results:
    print(item["eca"], item["distance"], "nm")
```

Supports `GeoRoute`, `list[RoutePoint]`, and coordinate segments. Optional `processor.geojson_loader` overrides HTTP fetch (e.g. tests).

## Native dependencies

| Crate | Role |
|-------|------|
| `geo` | Rhumb / haversine measures; ECA polygon predicates |
| `geojson` | Parse ECA FeatureCollection |
| `ureq` | Optional HTTP load for `ECAProcessor(url)` |

## Publish


```bash
uv sync --extra dev
maturin build --release -o dist
ls dist/
# georoutelib-0.1.1-cp312-cp312-*.whl
```

Upload to PyPI:

```bash
uv publish dist/*.whl
```

## License

MIT — see [LICENSE](LICENSE).

