Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,14 @@ dmypy.json

# Pyre type checker
.pyre/

# others
gpt-input.txt
src/app_testing.py
.vscode/settings.json
test_gdal.py

#started grabbing some unit tests from o1-mini
tests/

src/app_testing.py
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Agent Guidelines for Pyromb

Welcome! When updating this repository, please review the explainer documents before making large changes:

* `documentation/explainers/overview.md` – architecture and data flow summary.
* `documentation/explainers/qgis_integration_plan.md` – QGIS Processing integration scope and dependency expectations.
* `documentation/explainers/ai_playbook.md` – contribution workflow and testing guardrails for AI assistants.

These explainers provide the context expected by the maintainers. Update them whenever the architecture or integration plan evolves.

When adding new explainer-style documentation, keep it in `documentation/explainers/` unless maintainers request another location.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Include all JSON files in the resources directory and its subdirectories
recursive-include src/pyromb/resources *.json
61 changes: 61 additions & 0 deletions build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
@echo off
setlocal

REM Verify Python and Pip paths
echo Using Python from:
where python
echo Using pip from:
where pip

REM Check setuptools version
python -m pip show setuptools

REM Upgrade setuptools and wheel
echo Upgrading setuptools and wheel...
python -m pip install --upgrade setuptools wheel

REM Define the directory where the package will be stored
set "PACKAGE_DIR=%~dp0dist"
echo Package directory: %PACKAGE_DIR%

REM Navigate to the directory containing the setup.py script
cd /d "%~dp0"

REM Clean previous builds
if exist "%PACKAGE_DIR%" (
echo Cleaning previous builds...
rmdir /s /q "%PACKAGE_DIR%"
)

REM Build the source distribution and wheel using setuptools
echo Building the package using setuptools...
python setup.py sdist bdist_wheel

REM Check if the build was successful
if %ERRORLEVEL% neq 0 (
echo Build failed. Please check the setup.py and pyproject.toml for errors.
endlocal
pause
goto :EOF
)

REM Create the dist directory if it doesn't exist
if not exist "%PACKAGE_DIR%" mkdir "%PACKAGE_DIR%"

REM Move the generated .tar.gz and .whl files to the desired folder
echo Moving built packages to %PACKAGE_DIR%...
move /Y "dist\*.tar.gz" "%PACKAGE_DIR%" >nul
move /Y "dist\*.whl" "%PACKAGE_DIR%" >nul

REM Check if the move was successful
if %ERRORLEVEL% neq 0 (
echo Failed to move the package to the destination folder.
endlocal
pause
goto :EOF
)

echo Package created and moved to %PACKAGE_DIR% successfully.
endlocal
pause
goto :EOF
Binary file modified data/reaches.dbf
Binary file not shown.
Binary file modified data/reaches.shp
Binary file not shown.
28 changes: 28 additions & 0 deletions documentation/explainers/ai_playbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# AI Contributor Playbook

Use this playbook when applying AI-assisted development to Pyromb. It distils the key conventions, guardrails, and exploratory steps that have helped previous iterations succeed.

## Before writing code

1. **Understand the target module** – Read the relevant files under `src/pyromb` (especially the `core` package) before proposing changes. The architecture overview in this folder explains the data flow and should be revisited when planning large updates.
2. **Confirm geometry expectations** – All geometry work happens through the abstractions in `core/geometry`. Avoid introducing new geometry libraries; reuse the existing point/line/polygon classes or extend them cautiously.
3. **Check dependency rules** – QGIS deployments expect only standard library, NumPy, and GDAL/OGR dependencies. If a feature appears to require Shapely or other heavy GIS libraries, reconsider the approach or design a fallback that uses native QGIS geometry APIs.
4. **Plan for serialisation** – Any new attributes that affect the output vector files must propagate through `Builder`, `Catchment`, and the relevant `model` writer. Sketch the end-to-end data path before coding to avoid partial integrations.

## During implementation

* **Reuse builders and validators** – Keep shapefile parsing inside the builder. If you need additional validation rules, add them to `core/geometry/shapefile_validation.py` so they apply consistently across entry points.
* **Maintain tolerance handling** – When matching coordinates, continue to use tolerance-based comparisons. If more precision is necessary, introduce configurable parameters rather than hard-coded thresholds.
* **Instrument with logging** – Use the `logging` module for standalone scripts and prepare to swap in QGIS `feedback` hooks when integrating into Processing. Avoid print statements.

## Testing strategy

1. **Unit coverage** – Prioritise pure Python units (geometry, maths, model writers) because they run reliably in CI and do not require QGIS.
2. **Integration checks** – For geometry or builder changes, run the helper script (`python -m src.app`) against the sample data in the `data` directory to confirm end-to-end output remains valid.
3. **Regression artefacts** – Use the serialisation helpers in `serialise.py` to capture JSON or CSV snapshots of intermediate structures when diagnosing behaviour differences between QGIS and standalone runs.

## Follow-up questions to resolve

* Do we need a dedicated fixture generation script to refresh sample shapefiles for new test cases?
* Should tolerance thresholds become part of a configuration file so they can be tuned per project?
* Would a lightweight CLI wrapper around the builder be useful for non-QGIS environments once the Processing integration is complete?
22 changes: 22 additions & 0 deletions documentation/explainers/change_review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Review: docs commit vs previous fork state

This note compares commit `8725355` (current `work` branch head) against the prior fork state at `c547d9e`.

## Summary of differences

* Adds three explainer documents (`overview.md`, `qgis_integration_plan.md`, `ai_playbook.md`) under `documentation/explainers/`.
* No Python package code, tests, or packaging files changed between the two commits.

## Accuracy and completeness observations

* The architecture overview correctly names the core entry points (`src/app.py`, `pyromb.Builder`, `Catchment`, `Traveller`) present in the repository.
* The QGIS integration plan notes the absence of Shapely and aligns with the dependency list declared in `pyproject.toml`.
* The AI playbook references testing helpers and pending questions consistent with repository structure.

## Suggested follow-ups

1. Keep the new explainers updated alongside future code changes so they remain trustworthy context for contributors.
2. Ensure future Processing-algorithm tasks reconcile with the dependency guidance—avoid adding non-native libraries unless QGIS bundles them.
3. Consider expanding the AI playbook with explicit guidelines for geometry tolerance changes once the QGIS integration amendments begin.

No regressions were detected in this comparison because only documentation files were added.
25 changes: 25 additions & 0 deletions documentation/explainers/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Pyromb Architecture Overview

This explainer summarises how the Pyromb library organises its logic so that AI-assisted contributors can quickly locate the right components.

## High-level package structure

Pyromb exposes a builder-style API for turning GIS vector layers into hydrologic model control files. The key entry point used by the command-line helper script is `src/app.py`, which orchestrates shapefile ingestion, catchment assembly, and file serialisation. It instantiates vector layer wrappers, builds model components with `pyromb.Builder`, connects them into a `Catchment`, then produces the RORB or WBNM text output via a `Traveller`. The helper also supports optional plotting and serialisation for regression testing. `src/app_testing.py` mirrors this workflow with different defaults for test automation, while `serialise.py` and `plot_catchment.py` contain supporting utilities for debugging and visualisation.

Inside the Python package (`src/pyromb`), functionality is grouped into four domains:

* `core`: domain models and algorithms for catchment construction, traversal, and validation. This includes attribute classes (basins, reaches, confluences), geometry abstractions that wrap raw coordinates, and the `Catchment` and `Traveller` classes that connect everything into incidence matrices and model-specific exports.
* `math`: hydrologic calculations (e.g. loss models) that are used during serialisation of the output control files.
* `model`: format-specific builders for RORB and WBNM control vectors, and serialisation helpers that convert the in-memory catchment into strings written by the traveller.
* `resources`: default templates and static assets used by the model writers.

Supporting modules such as `sf_vector_layer.py` expose a minimal wrapper around OGR vector layers so the builder can consume shapefile geometry consistently.

## Data flow from GIS layers to control files

1. **Load vector data** – Shapefiles for reaches, basins, centroids, and confluences are opened via `SFVectorLayer`, which exposes iterable records with geometry and attributes.
2. **Build domain objects** – `pyromb.Builder` reads the vector layers, instantiates geometry primitives (`core.geometry`) and attribute classes, and returns lists of reaches, basins, and confluences.
3. **Assemble the catchment** – A `Catchment` is created from those components. It deduplicates vertices, computes distance-based incidence matrices, and determines upstream/downstream relationships used later by the traveller.
4. **Generate model output** – A `Traveller` walks the connected catchment to populate model-specific writers (`model` package). For RORB this produces a `vector.catg`; for WBNM a `runfile.wbnm`. Optional plotting uses the connected structure to render diagnostic figures.

Understanding this flow helps target AI-driven changes: geometry or validation fixes generally belong in `core.geometry` or the builder; file-format changes live in `model`; and QGIS integration work happens in the app wrapper and layer adapters.
42 changes: 42 additions & 0 deletions documentation/explainers/qgis_integration_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# QGIS Integration and Dependency Plan

This note captures the changes required to embed Pyromb as a native QGIS Processing algorithm without introducing non-standard dependencies.

## Dependency expectations inside QGIS

* **Python environment** – Recent QGIS releases ship with Python 3.9+ and include GDAL/OGR, PyQt, NumPy, and QGIS core bindings. Third-party packages such as Shapely are not guaranteed to be present and should be avoided unless bundled manually.
* **Current state** – The repository already limits itself to standard library modules, NumPy, and GDAL/OGR via `osgeo`. A quick audit (`rg "shapely"`) shows no references to Shapely in the current codebase, so future contributions should preserve this dependency footprint.

## Python package installation footprint

Pyromb distributes as a pure Python package with a minimal dependency tree:

* **`gdal`** – Declared in `pyproject.toml` so that the GDAL/OGR Python bindings are available for shapefile access when running outside QGIS. Inside QGIS, the bundled GDAL satisfies this requirement.
* **Standard library / QGIS built-ins** – The remaining imports come from Python's standard library, NumPy, and QGIS' own modules that ship with the application. No additional PyPI packages are installed by default.

When packaging for QGIS Processing, avoid adding new third-party dependencies unless they are guaranteed to be shipped with QGIS or vendored with the plugin.

## Embedding Pyromb as a Processing provider

1. **Processing entry point** – Wrap the logic in `src/app.py` inside a `QgsProcessingAlgorithm` subclass. The `processAlgorithm` method should:
* Accept parameter definitions for the required vector layers (reaches, basins, centroids, confluences) and optional toggles (plotting, output path).
* Use `QgsProcessingParameterFeatureSource` to read vector layers directly from the QGIS context, avoiding temporary files.
* Instantiate `pyromb.Builder`, `Catchment`, and `Traveller` the same way the helper script does.
2. **Vector layer abstraction** – Replace `SFVectorLayer` usage with adapters that can consume `QgsFeatureSource` instances. The goal is to reuse existing validation and geometry code, so consider creating an interface that both the shapefile wrapper and a new QGIS wrapper implement.
3. **Output handling** – The algorithm should write to a processing output (`QgsProcessingOutputFile`) and optionally return the generated vector file path to the model catalog.
4. **Logging and feedback** – Use the `feedback` object provided by QGIS Processing for progress messages instead of the standard `logging` module when running inside QGIS. Retain standard logging for standalone runs by introducing a thin abstraction or conditional helper.

## Geometry logic amendments

The original plugin required adjustments to geometry handling to work reliably inside QGIS. Further work should focus on:

* **Tolerance-aware matching** – `Catchment._find_vertex_by_coordinates` currently uses a numeric tolerance when matching nodes. Confirm that the tolerance is appropriate for the coordinate precision in QGIS projects and expose it as a configurable parameter if needed.
* **Projected vs geographic CRS** – Ensure builders operate on projected coordinates (metres) when computing lengths, slopes, or areas. Incorporate CRS validation in the Processing algorithm to warn users when they run the tool in a geographic CRS.
* **Geometry extraction** – When moving from OGR-based layers to `QgsFeature`, use native geometry accessors (`feature.geometry().asPolyline()` etc.) to avoid relying on Shapely conversion helpers.

## Next steps and open questions

* Define a minimal adapter interface for vector layers so we can plug in both OGR and QGIS data sources without duplicating builder logic.
* Decide whether plotting should remain part of the Processing algorithm or become a standalone diagnostic tool. Plotting libraries may not be available in all QGIS deployments.
* Review existing serialisation helpers (`serialise.py`) to ensure they are optional in production builds; they are mainly for testing and may not suit a Processing context.
* Document user-facing parameter descriptions and default values for the QGIS tool so plugin development can begin immediately after the geometry updates.
41 changes: 41 additions & 0 deletions installer_osgeo4w.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@echo off
setlocal

REM THIS DIDN'T SEEM TO WORK, LIKELY USER ERROR.

REM Activate OSGeo4W environment
call "C:\OSGEO4W\bin\o4w_env.bat"

REM Define the directory where the built package is stored
set "PACKAGE_DIR=%~dp0dist"
echo Package directory: %PACKAGE_DIR%

REM Find the latest version of the package
for /f "delims=" %%i in ('dir /b /o-n "%PACKAGE_DIR%\pyromb-*.whl"') do (
set "LATEST_PACKAGE=%%i"
goto found
)

:found
if "%LATEST_PACKAGE%"=="" (
echo No package found in the directory.
pause
endlocal
goto :EOF
)

echo Installing or updating "%LATEST_PACKAGE%"

REM Install or update the package using pip from the OSGeo4W environment
pip install --upgrade "%PACKAGE_DIR%\%LATEST_PACKAGE%"

REM Check if the installation was successful
if %ERRORLEVEL% equ 0 (
echo Installation completed successfully.
) else (
echo Installation failed. Please check the path and try again.
)

endlocal
pause
goto :EOF
24 changes: 10 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/pyromb"]

[tool.hatch.build]
only-packages = true
exclude = [
".conda",
]
requires = ["setuptools>=61.0.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "pyromb"
version = "0.2.0"
version = "0.2.1"
authors = [
{ name="Tom Norman", email="tom@normcosystems.com" }
{ name = "Tom Norman", email = "tom@normcosystems.com" }
]
description = "Runoff Model Builder (Pyromb) is a package used for building RORB and WBNM control files from catchment diagrams built from ESRI shapefiles. Its primary use is in the QGIS plugin Runoff Model: RORB and Runoff Model: WBNM"
readme = "README.md"
requires-python = ">=3.9"

dependencies = [
"gdal",
# Add any additional dependencies here
]

classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
Expand All @@ -29,4 +25,4 @@ classifiers = [

[project.urls]
"Homepage" = "https://github.com/norman-tom/pyromb"
"Bug Tracker" = "https://github.com/norman-tom/pyromb/issues"
"Bug Tracker" = "https://github.com/norman-tom/pyromb/issues"
27 changes: 27 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from setuptools import setup, find_packages # type:ignore
import os

# Read the long description from README.md
current_dir = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(current_dir, "README.md"), "r", encoding="utf-8") as fh:
long_description = fh.read()

setup(
name="pyromb",
version="0.2.1",
packages=find_packages(where="src", exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
package_dir={"": "src"},
author="Tom Norman",
author_email="tom@normcosystems.com",
description="Runoff Model Builder (Pyromb) is a package used for building RORB and WBNM control files from catchment diagrams built from ESRI shapefiles. Its primary use is in the QGIS plugin Runoff Model: RORB and Runoff Model: WBNM",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/norman-tom/pyromb",
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires=">=3.9",
include_package_data=True, # Ensures inclusion of files specified in MANIFEST.in
)
Loading