Metadata-Version: 2.4
Name: python-bluekenue
Version: 0.1.0
Summary: Read and write BlueKenue ASCII files (.i2s, .i3s, .t3s)
Project-URL: Homepage, https://gitlab.com/isl-ingenierie/modules-python/python-bluekenue
Project-URL: Source, https://gitlab.com/isl-ingenierie/modules-python/python-bluekenue
Project-URL: Issues, https://gitlab.com/isl-ingenierie/modules-python/python-bluekenue/-/issues
Author-email: Nicolas Godet <godet@isl.fr>
License: BSD-3-Clause
License-File: LICENSE
Keywords: bluekenue,ensim,hydraulics,i2s,i3s,mesh,t3s,telemac
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
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: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Scientific/Engineering :: Hydrology
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Provides-Extra: dev
Requires-Dist: pre-commit; extra == 'dev'
Requires-Dist: pyproj>=3.5; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Requires-Dist: shapely>=2.0; extra == 'dev'
Provides-Extra: geometry
Requires-Dist: shapely>=2.0; extra == 'geometry'
Provides-Extra: projection
Requires-Dist: pyproj>=3.5; extra == 'projection'
Description-Content-Type: text/markdown

# bluekenue

Read and write [BlueKenue](https://nrc.canada.ca/en/research-development/products-services/software-applications/blue-kenuetm-software-tool-hydraulic-modellers)
ASCII files (`.i2s`, `.i3s`, `.t3s`) from Python.

`bluekenue` ships a small, typed, dependency-light I/O layer for the EnSim 1.0
text format used by BlueKenue (and the wider TELEMAC ecosystem):

| Format | Purpose                          | Reader        | Writer         |
| ------ | -------------------------------- | ------------- | -------------- |
| `.i2s` | 2D line set (open or closed)     | `read_i2s`    | `write_i2s`    |
| `.i3s` | 3D line set (cross-sections, …)  | `read_i3s`    | `write_i3s`    |
| `.t3s` | 2D triangular scalar mesh        | `read_t3s`    | `write_t3s`    |

All three preserve the full header (projection block, multi-attribute specs,
ISO-8859-1 encoding, CRLF line endings) on read **and** write — round-tripping
a real-world BlueKenue file does not silently drop metadata.

## Installation

```sh
pip install python-bluekenue
```

The distribution is named `python-bluekenue` on PyPI; the import name is
`bluekenue` (a common pattern, like `python-dateutil` → `import dateutil`).

Optional extras:

```sh
pip install "python-bluekenue[geometry]"    # adds shapely
pip install "python-bluekenue[projection]"  # adds pyproj
pip install "python-bluekenue[geometry,projection]"  # both
```

## Quickstart

### Read a 2D line set (`.i2s`)

```python
from bluekenue import read_i2s

line_set = read_i2s("emprise.i2s")
print(line_set.header.name, [a.name for a in line_set.header.attributes])
for poly in line_set.polylines:
    print(poly.n_points, "points,", "closed" if poly.is_closed else "open")
    print(poly.coordinates)  # numpy array, shape (n, 2)
```

### Read a 3D line set (`.i3s`)

```python
from bluekenue import read_i3s

line_set = read_i3s("outline.i3s")
print(line_set.polylines[0].coordinates.shape)  # (n_points, 3)
```

### Read a triangular mesh (`.t3s`)

```python
from bluekenue import read_t3s

mesh = read_t3s("mesh.t3s")
print(mesh.n_nodes, "nodes,", mesh.n_triangles, "triangles")
print(mesh.nodes)            # (n_nodes, 2)  — X, Y
print(mesh.node_attributes)  # (n_nodes, n_attributes)
print(mesh.triangles)        # (n_triangles, 3) — 1-based BlueKenue indices
```

### Write a synthetic mesh

```python
import numpy as np
from bluekenue import Attribute, BlueKenueHeader, Mesh, write_t3s

header = BlueKenueHeader(
    file_type="t3s",
    name="demo",
    attributes=[Attribute(name="BOTTOM", type="double", units="m")],
)
nodes = np.array([[0, 0], [1, 0], [1, 1], [0, 1]], dtype=float)
triangles = np.array([[1, 2, 3], [1, 3, 4]], dtype=np.int32)  # 1-based
node_attrs = np.array([[0.0], [1.0], [2.0], [1.0]])

write_t3s("demo.t3s", Mesh(header, nodes, triangles, node_attrs))
```

### Convert polylines to shapely

`Polyline2D` / `Polyline3D` and `LineSet2D` / `LineSet3D` expose
`to_shapely()` / `from_shapely()` (extra `[geometry]`).

```python
from bluekenue import read_i2s

line_set = read_i2s("emprise.i2s")
geom = line_set.to_shapely()      # GeometryCollection of LineStrings / LinearRings
ring = line_set.polylines[0].to_shapely()  # LinearRing if closed, LineString otherwise

from shapely.geometry import Polygon
from bluekenue import Polyline2D

polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
poly = Polyline2D.from_shapely(polygon)  # uses the exterior ring, returns Polyline2D
```

If `shapely` is not installed, methods raise a clear `ImportError` telling
you exactly which extra to install.

### Convert the BlueKenue projection to PROJ4 / pyproj

The `:Projection` block of a BlueKenue file is essentially a PROJ4 string
split into named keys. `Projection.to_proj4()` is pure Python;
`to_crs` / `from_crs` need `pyproj` (install with
`pip install bluekenue[projection]`).

```python
from bluekenue import read_t3s

mesh = read_t3s("mesh.t3s")
print(mesh.header.projection.to_proj4())
# +proj=lcc +lon_0=3 +lat_1=49 +lat_2=44 +lat_0=46.5 +x_0=700000 +y_0=6600000 +ellps=GRS80 +units=m +no_defs

crs = mesh.header.projection.to_crs()      # pyproj.CRS
```

`Projection.from_crs` is flexible — it accepts anything you can already feed
to pyproj plus QGIS' `QgsCoordinateReferenceSystem` (duck-typed, no QGIS
dependency required):

```python
from bluekenue import Projection

proj = Projection.from_crs(2154, name="LambertConformal")          # EPSG int
proj = Projection.from_crs("EPSG:2154", name="LambertConformal")    # EPSG string
proj = Projection.from_crs("+proj=lcc +lat_0=46.5 ...")             # PROJ4 string
proj = Projection.from_crs(pyproj.CRS.from_epsg(2154))              # pyproj.CRS
proj = Projection.from_crs(qgs_layer.crs(), name="LambertConformal")  # QGIS CRS
```

## Development

```sh
git clone https://gitlab.com/isl-ingenierie/modules-python/python-bluekenue.git
cd python-bluekenue
pip install -e ".[dev,geometry,projection]"
pre-commit install
pytest
```

### Releasing

See [`CHANGELOG.md`](CHANGELOG.md) for the version history (format: [Keep a
Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/), versions follow
[Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html)).

The release flow:

1. Move entries from `## [Unreleased]` to a new `## [VERSION] - YYYY-MM-DD`
   section in `CHANGELOG.md`.
2. Commit and tag (`git tag VERSION && git push origin VERSION`).
3. GitLab CI uploads to PyPI / TestPyPI via Trusted Publisher (OIDC, no API
   token stored as a CI variable) and creates a GitLab Release whose body is
   the matching CHANGELOG section.

The leading `v` is optional (SemVer 2.0 does not include it; it is purely a
Git tag convention). Both `0.1.0` and `v0.1.0` work; `hatch-vcs` strips the
`v` when computing the package version.

| Tag pattern                          | Target                     |
| ------------------------------------ | -------------------------- |
| `0.1.0a1`, `v0.1.0rc2`, `0.1.0.dev3` | TestPyPI (`pypi-test` job) |
| `0.1.0`, `v1.2.3`                    | PyPI prod (`pypi` job)     |

```sh
git tag v0.1.0
git push origin v0.1.0
```

## License

[BSD 3-Clause](LICENSE).

The library was inspired by the BlueKenue I/O code in
[pyteltools](https://github.com/CNR-Engineering/PyTelTools); credits to its
authors for documenting the format.
