Skip to content
Merged
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
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI

on:
push:
pull_request:

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: actions/setup-python@v6
with:
python-version: "3.11"

- uses: astral-sh/setup-uv@v8.0.0

- run: uv sync --all-extras --dev

- name: Lint
run: uv run ruff check .

- name: Type check
run: uv run mypy buildingregulariser/ tests/

- name: Test
run: uv run pytest tests/ -x -q
43 changes: 43 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Publish to PyPI

on:
push:
tags:
- "v*"

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true

- uses: actions/setup-python@v6
with:
python-version: "3.11"

- uses: astral-sh/setup-uv@v8.0.0

- name: Build sdist and wheel
run: uv build

- uses: actions/upload-artifact@v6
with:
name: dist
path: dist/

publish:
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v8
with:
name: dist
path: dist/

- uses: pypa/gh-action-pypi-publish@release/v1
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ ipython_config.py
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
uv.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
Expand Down Expand Up @@ -179,4 +179,4 @@ private test data/*
buildingregulariser.egg-info/*
.DS_Store
test data/input/old/*
test data/output/*
tests/output/
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.8
hooks:
- id: ruff-check
- id: ruff-format
- repo: local
hooks:
- id: pytest
name: pytest
entry: uv run pytest tests/ -x -q
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"geodataframe",
"linalg",
"ndarray",
"pyproj",
"neighbour",
"neighbourhood",
"neighbouring",
Expand Down
142 changes: 142 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Changelog

All notable changes to Building-Regulariser are documented here.

## [Unreleased]

## [0.2.5] - 2026-05-04

### Added
- Pre-commit configuration (`ruff-check`, `ruff-format`, and a pre-push
`pytest` hook).
- GitHub Actions CI workflow running ruff lint, mypy, and pytest on
every push and pull request.
- GitHub Actions publish workflow that builds the package and pushes to
PyPI via OIDC trusted publishing on `v*` tag pushes.
- `py.typed` marker so downstream type checkers pick up the package's
type hints.
- Multipolygon regression tests covering self-intersecting and
vertex-touching inputs.

### Changed
- Versioning is now derived from git tags via `setuptools-scm`. The
hardcoded `__version__.py` has been removed; `__version__` is now
read at runtime from package metadata via `importlib.metadata`.
- `regularize_single_polygon` signature widened to
`Polygon | MultiPolygon` to match its actual runtime behaviour.
- `uv.lock` is no longer tracked in version control.

### Fixed
- Type errors in `geometry_utils.rotate_edge` where the no-rotation
branch returned `ndarray` instead of the expected coordinate tuple.

## [0.2.4] - 2025-07-24

### Added
- Project metadata: license, keywords, project URL.
- Ruff to the dev dependency group.

### Fixed
- Improved robustness when input geometries are invalid
(self-intersecting or otherwise malformed polygons).
- Handling of invalid inputs reported in issue #5.

### Changed
- Layer order in the regularization pipeline.
- Updated example parameters and dataset.

## [0.2.2] - 2025-05-22

### Added
- Neighbour alignment: edges of nearby buildings can be aligned to a
shared direction.
- End-to-end tests for geometry quality and parameterized regularization.
- Example notebook and data.

### Changed
- Throughput optimisations.
- Improved spatial-index handling and data preparation in neighbour
alignment.
- Internal consolidation and refactoring; `rotate_edge` and other line
operations consolidated into `geometry_utils`.
- Type-hint and argument-name cleanup across the codebase.

## [0.1.12] - 2025-04-12

### Added
- `include_metadata` option to return per-feature regularization
metadata alongside the output geometries.
- Conda-forge install instructions.

### Changed
- Improved main-edge finding with mirroring and smoothing.
- Relaxed dependency version requirements.

## [0.1.11] - 2025-04-10

### Changed
- Reordered filter operations in the regularization pipeline.

## [0.1.10] - 2025-04-09

### Added
- Coarse and fine bins when finding the main direction of a polygon.

## [0.1.9] - 2025-04-09

### Added
- Per-feature metadata output.
- Inputs are now exploded so multipart geometries are handled.
- Increased histogram bin size for direction estimation.

## [0.1.8] - 2025-04-04

### Added
- Additional histogram bins for finer direction estimation.

## [0.1.7] - 2025-04-03

### Added
- Exposed the diagonal threshold reduction parameter.

## [0.1.6] - 2025-04-03

### Fixed
- Handling of unusually large buildings.

## [0.1.4] - 2025-04-02

### Added
- Optional circle output for round-shaped buildings.

### Changed
- Throughput improvements.

## [0.1.3] - 2025-04-01

### Added
- MIT license file and project README.

### Fixed
- Cleaning logic refinements following the initial release.

## [0.1.0] - 2025-04-01

### Added
- Initial public release with the core `regularize_geodataframe` API
for aligning building polygon edges to principal directions.

[Unreleased]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.2.5...HEAD
[0.2.5]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.2.4...v0.2.5
[0.2.4]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.2.2...v0.2.4
[0.2.2]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.12...v0.2.2
[0.1.12]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.11...v0.1.12
[0.1.11]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.10...v0.1.11
[0.1.10]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.9...v0.1.10
[0.1.9]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.8...v0.1.9
[0.1.8]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.7...v0.1.8
[0.1.7]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.6...v0.1.7
[0.1.6]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.4...v0.1.6
[0.1.4]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/DPIRD-DMA/Building-Regulariser/compare/v0.1.0...v0.1.3
[0.1.0]: https://github.com/DPIRD-DMA/Building-Regulariser/releases/tag/v0.1.0
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

A Python library for regularizing building footprints in geospatial data. This library helps clean up and standardize building polygon geometries by aligning edges to principal directions. Built as an open source alternative to the [ArcGIS Regularize Building Footprint (3D Analyst) tool](https://pro.arcgis.com/en/pro-app/latest/tool-reference/3d-analyst/regularize-building-footprint.htm).

[![Python](https://img.shields.io/badge/Python-3.9%2B-blue)]()
[![License](https://img.shields.io/badge/License-MIT-green)]()
[![PyPI](https://img.shields.io/pypi/v/buildingregulariser)](https://pypi.org/project/buildingregulariser/)
[![conda-forge](https://img.shields.io/conda/vn/conda-forge/buildingregulariser)](https://anaconda.org/conda-forge/buildingregulariser)
[![Downloads](https://static.pepy.tech/badge/buildingregulariser)](https://pepy.tech/project/buildingregulariser)
[![Python](https://img.shields.io/pypi/pyversions/buildingregulariser)](https://pypi.org/project/buildingregulariser/)
[![CI](https://github.com/DPIRD-DMA/Building-Regulariser/actions/workflows/ci.yml/badge.svg)](https://github.com/DPIRD-DMA/Building-Regulariser/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/License-MIT-green)](LICENSE)

## Example Results

Expand Down Expand Up @@ -93,31 +97,32 @@ regularized = regularize_geodataframe(buildings)
```python
regularized = regularize_geodataframe(
buildings,
parallel_threshold=2.0, # Higher values allow less edge alignment
simplify_tolerance=0.5, # Controls simplification level, should be 2-3 x the raster pixel size
allow_45_degree=True, # Enable 45-degree angles
allow_circles=True, # Enable circle detection
circle_threshold=0.9 # IOU threshold for circle detection
neighbor_alignment=True, # After regularization try to align each building with neighboring buildings
neighbor_search_distance: float = 100.0, # The search distance around each building to find neighbors
neighbor_max_rotation: float = 10, # The maximum rotation allowed to align with neighbors
parallel_threshold=2.0, # Higher values allow less edge alignment
simplify_tolerance=0.5, # Controls simplification level, should be 2-3 x the raster pixel size
allow_45_degree=True, # Enable 45-degree angles
allow_circles=True, # Enable circle detection
circle_threshold=0.9, # IOU threshold for circle detection
neighbor_alignment=True, # After regularization try to align each building with neighboring buildings
neighbor_search_distance=100, # Search distance around each building when looking for neighbors
neighbor_max_rotation=10, # Maximum rotation allowed to align with neighbors
)
```

## Parameters

- **geodataframe**: Input GeoDataFrame with polygon geometries
- **parallel_threshold**: Distance threshold for handling parallel lines (default: 1.0)
- **target_crs**: Optional CRS (string or `pyproj.CRS`) to reproject the input to before regularization (default: None)
- **simplify**: If True, applies simplification to the geometry (default: True)
- **simplify_tolerance**: Tolerance for simplification (default: 0.5)
- **allow_45_degree**: If True, allows edges to be oriented at 45-degree angles (default: True)
- **diagonal_threshold_reduction**: Used to reduce the chance of diagonal edges being generated, can be from 0 to 22.5 (default: 15.0)
- **allow_circles**: If True, detects and converts near-circular shapes to perfect circles (default: True)
- **circle_threshold**: Intersection over Union (IoU) threshold for circle detection (default: 0.9)
- **num_cores**: Number of CPU cores to use for parallel processing (default: 1)
- **include_metadata**: Include the main direction, IOU, perimeter and aligned_direction (if used) in output gdf
- **num_cores**: Number of CPU cores to use for parallel processing; 0 uses all available cores (default: 0)
- **include_metadata**: Include the main direction, IOU, perimeter and aligned_direction (if used) in output gdf (default: False)
- **neighbor_alignment**: If True, try to align each building with neighboring buildings (default: False)
- **neighbor_search_distance**: The distance to find neighboring buildings (default: 350.0)
- **neighbor_search_distance**: The distance to find neighboring buildings (default: 100.0)
- **neighbor_max_rotation**: The maximum allowable rotation to align with neighbors (default: 10)


Expand Down
50 changes: 50 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Releasing Building-Regulariser

How to cut a new release to PyPI. The whole flow is driven from a single
`v*` git tag — `setuptools-scm` reads the version from the tag and
`pypa/gh-action-pypi-publish` ships the wheel.

## Cutting a release

1. **Update [`CHANGELOG.md`](CHANGELOG.md).**
Promote the `[Unreleased]` section to `[X.Y.Z] - YYYY-MM-DD` and add
a fresh empty `[Unreleased]` heading on top. Update the comparison
links at the bottom of the file to add the new version.

2. **Merge to `main`.**
Make sure the merge commit is the one you intend to release —
the tag will be cut from it.

3. **Tag and push.**
```bash
git checkout main
git pull
git tag vX.Y.Z # e.g. v0.3.0
git push origin vX.Y.Z
```

4. **Approve the deployment.**
The push triggers the [`Publish to PyPI`](.github/workflows/publish.yml)
workflow. Open *Actions* on GitHub and click *Review pending
deployments → Approve and deploy* on the `pypi` environment when
prompted.

5. **Verify on PyPI.**
Within a minute or two `pip install buildingregulariser==X.Y.Z` should
work and the project page on
<https://pypi.org/project/buildingregulariser/> should show the new
version.

## Notes

- **Versions come from tags.** `buildingregulariser.__version__` and the
wheel filename both come from `setuptools-scm` reading the latest `v*`
tag. Don't hand-edit a version anywhere — bumping a tag is the entire
bump.
- **Pre-releases** (e.g. `v0.3.0rc1`) work the same way; PEP 440 markers
in the tag carry through.
- **Yanking a bad release** is done from the PyPI web UI, not from this
repo. The tag and CHANGELOG entry stay.
- **Hotfix on an older line** (e.g. `v0.2.5` while `main` is on `0.3.x`):
branch from the older tag, fix, tag `v0.2.5` on that branch, push the
tag. The same workflow handles it.
8 changes: 7 additions & 1 deletion buildingregulariser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@
A package for regularizing polygons by aligning edges to principal directions.
"""

from .__version__ import __version__
from importlib.metadata import PackageNotFoundError, version

from .coordinator import regularize_geodataframe

try:
__version__ = version("buildingregulariser")
except PackageNotFoundError:
__version__ = "0.0.0+unknown"

# Package-wide exports
__all__ = [
"regularize_geodataframe",
Expand Down
1 change: 0 additions & 1 deletion buildingregulariser/__version__.py

This file was deleted.

Loading