Metadata-Version: 2.4
Name: nord-sahara-wgs84
Version: 0.1.0
Summary: Nord Sahara 1959 <-> WGS 84 datum transformations (EPSG Helmert sets) and UTM zones 29-32N, as a small library and CLI.
Keywords: geodesy,algeria,datum,helmert,utm,nord sahara 1959,epsg,coordinate transformation
Author: Abdeldjalil Chougui
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Typing :: Typed
Requires-Python: >=3.13
Project-URL: Homepage, https://github.com/AbdeldjalilChougui/nord-sahara-wgs84
Project-URL: Repository, https://github.com/AbdeldjalilChougui/nord-sahara-wgs84
Project-URL: Issues, https://github.com/AbdeldjalilChougui/nord-sahara-wgs84/issues
Description-Content-Type: text/markdown

# nord-sahara-wgs84

Converts coordinates between **Nord Sahara 1959** (EPSG:4307), the classic
Algerian datum on the Clarke 1880 (RGS) ellipsoid, and **WGS 84**, and
to and from the **UTM zones 29N–32N** on both datums. It is a small
pure-Python library and CLI with no runtime dependencies. Every number is
checked against PROJ.

## Install

```sh
pip install nord-sahara-wgs84     # or: uv tool install nord-sahara-wgs84
uvx --from nord-sahara-wgs84 ns59 convert --from ns59 --to wgs84 3.0588 36.7538   # run without installing
```

This installs the `nord_sahara_wgs84` module and the `ns59` command (Python 3.13+).

## Quick start (from a checkout)

```sh
uv sync
uv run ns59 convert --from ns59 --to wgs84 3.0588 36.7538            # lon lat
# 3.058054140 36.753905322
uv run ns59 convert --from ns59-utm31 --to wgs84-utm31 -v 500000 4070000
uv run ns59 convert --from 30732 --to 4326 --op auto 712000 3530000  # best local operation
printf "x,y,name\n500000,4000000,well-1\n" | uv run ns59 convert --from 30731 --to 32631 --csv
uv run ns59 ops        # published transformations
uv run ns59 crs        # supported CRSs
uv run pytest          # tests (needs the dev dependency pyproj)
```

```python
from nord_sahara_wgs84 import convert, ns59_to_wgs84

r = convert(500000, 4070000, "EPSG:30731", "EPSG:32631")      # easting/northing in, out
print(r.x, r.y, r.operation.name)
lat, lon, h = ns59_to_wgs84(36.7538, 3.0588, op=1253)          # pick an EPSG operation
```

Axis order is always x/y, so (lon, lat) or (easting, northing), like PROJ's
`always_xy=True`. The lower-level `ns59_to_wgs84` / `wgs84_to_ns59` take
(lat, lon).

Supported CRSs: EPSG:4307, 4326, 30729–30732 (Nord Sahara 1959 / UTM 29–32N)
and 32629–32632 (WGS 84 / UTM 29–32N). Aliases such as `ns59`, `wgs84`,
`ns59-utm31` and `wgs84-utm32` also work.

## The transformations

The EPSG dataset publishes **no grid** (NTv2 or similar) for Nord Sahara
1959, only Helmert parameter sets. All of them are included, with the
values exactly as published:

| EPSG | name | method | accuracy | area of use |
|---|---|---|---|---|
| **10296** (default) | Nord Sahara 1959 to WGS 84 (11) | Position Vector, 7 param. | 1 m | Algeria |
| 5660 | … (9) | Position Vector, 7 param. | 1 m | Algeria |
| 1815 | … (4) | Position Vector, 7 param. | 5 m | District 3 |
| 5630 | … (8) | Geocentric translations | 5 m | Ahnet licence area |
| 15874 | … (7) | Geocentric translations | 5 m | Hassi Mouina licence area |
| 1253 | … (1) | Geocentric translations | 44 m | Algeria onshore |
| 1255 | … (2) | Geocentric translations | 44 m | Algeria north of 32°N |
| 1816 | … (5) | Geocentric translations | 100 m | In Amenas |
| 1817 | … (6) | Geocentric translations | 100 m | Hassi Bir Reikaz |
| 7442 | … (10) | Geocentric translations | 100 m | Ain Tsila |

The default (10296) is also what PROJ ranks first for this pair. `--op auto`
picks the most accurate operation whose area of use contains the point. The
published accuracy is how well the parameters reproduce the real
relationship between the datums. It is not a property of this code.

## How it works

1. **UTM → geographic** on the source ellipsoid, if needed. Transverse
   Mercator uses Krüger's series to order n⁶ in Karney's (2011) formulation.
   That is nanometre-accurate in the zone and sub-millimetre several degrees
   outside it.
2. **Geographic → geocentric (ECEF)** on Clarke 1880 (RGS):
   a = 6 378 249.145 m, 1/f = 293.465.
3. **Helmert** in ECEF. EPSG 9606 (position vector) uses the linearised
   rotation matrix, rotations in arc-seconds and scale in ppm. EPSG 9603 uses
   translations only. The reverse direction applies the transposed matrix,
   as PROJ does.
4. **Geocentric → geographic** on WGS 84 (Bowring start plus fixed-point
   iteration), then **→ UTM** if needed.

As in EPSG and PROJ, these operations work in the *geog2D domain*. The input
ellipsoidal height feeds the geocentric step but comes back unchanged, so
heights are not transformed.

## Tests

`uv run pytest` runs 52 tests, including one doctest:

- every operation, forward and reverse, against PROJ at 200 random points
  across Algeria each (tolerance 1e-9° ≈ 0.1 mm)
- geocentric conversions on both ellipsoids against `+proj=cart` (1 µm)
- UTM 29–32N on both datums against PROJ, inside and up to 3° outside each zone
  (1 mm), plus inverse round trips
- projected → projected across datums against a PROJ pipeline
- the default operation against PROJ's own choice
- the CLI (single point, CSV batch with pass-through columns, errors)

Results agree with PROJ to about 1e-12°. The round trip NS59 → WGS 84 →
NS59 is only good to a few millimetres, because the height is not carried
through the shift and the linearised rotation is not exactly orthogonal.
PROJ behaves the same way.

## Status and limitations

- Only the CRSs listed above are supported. The Lambert projections *Nord
  Algérie* / *Sud Algérie* (EPSG:30791/30792) and Voirol 1875/1879 are not
  included yet.
- There is no vertical datum handling: heights are ellipsoidal and passed
  through.
- UTM zone exceptions (Norway/Svalbard) are irrelevant here and not handled.
- Pure Python and slow for millions of points. Use PROJ/pyproj for bulk work.
  This project exists to show the maths plainly.

## Data sources and license

Code: MIT, see [LICENSE](LICENSE).

The transformation parameters, ellipsoid constants, CRS definitions and areas
of use come from the **EPSG Geodetic Parameter Dataset** (IOGP), read from
the copy distributed with PROJ 9.8. Under the EPSG terms of use the data may
be used and redistributed, but the dataset is © IOGP and must not be
modified and presented as EPSG data. See <https://epsg.org/terms-of-use.html>.
pyproj and PROJ (MIT) are used only as test oracles.
