Skip to main content

Rust transpilation of pymdu (Python Urban Data Model) - Python bindings

Project description

pymdurs

PyPI Python

Python bindings for the Rust library rsmdu — a high-performance reimplementation of pymdu (Python Urban Data Model). Geospatial data processing for urban analysis, with integration with IGN (Institut Géographique National) APIs and UMEP toolchains.

Shadow Mean radiant temperature (Tmrt) Thermal comfort (UTCI)
Shadow preview Tmrt preview UTCI preview

📋 Table of Contents


Installation

Install uv

uv is the Python package manager used by this project (see uv.lock). Recommended installation via the standalone Astral installer:

macOS / Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell):

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Other methods: Homebrew (brew install uv), pipx (pipx install uv), or PyPI.

After installation, restart your terminal. Update: uv self update (standalone installer only).

Create a virtual environment:

uv venv .venv --python 3.13

From PyPI (recommended)

The package is published on PyPI. To install the latest version:

uv pip install pymdurs

On Windows, the PyPI wheel bundles GDAL, GEOS, and PROJ via auditwheel repair — no system GDAL installation is required.

For a specific version (see the PyPI release history):

uv pip install "pymdurs==<version>"

From source

GDAL prerequisites (build only — not required for pip install pymdurs on Windows):

Platform Command
macOS brew install gdal or ARCHFLAGS="-arch arm64" uv pip install --no-cache-dir gdal
Linux sudo apt-get update && sudo apt-get install -y libgdal-dev gdal-bin libclang-dev
Windows OSGeo4W (GDAL, GEOS, PROJ, SQLite3) + choco install llvm pkgconfiglite sqlite -y + set GDAL_HOME, PKG_CONFIG_PATH, PATH

Prerequisites: Rust (required to build):

Windows:

# Download and run rustup-init.exe from https://rustup.rs/
# Or use PowerShell:
Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe
.\rustup-init.exe

macOS:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Linux:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

After installation, restart your terminal or run:

source $HOME/.cargo/env

Build (maturin develop)

Platform Target Command
macOS (Apple Silicon) aarch64-apple-darwin maturin develop --target aarch64-apple-darwin
macOS (Intel) x86_64-apple-darwin maturin develop --target x86_64-apple-darwin
Linux (x86_64) x86_64-unknown-linux-gnu maturin develop --target x86_64-unknown-linux-gnu or maturin develop
Linux (ARM64) aarch64-unknown-linux-gnu maturin develop --target aarch64-unknown-linux-gnu
Windows x86_64-pc-windows-msvc maturin develop --target x86_64-pc-windows-msvc

With uv: uv run maturin develop --target <target>

macOS: linker can't find GDAL / "library 'gdal' not found"
If you upgraded GDAL or PROJ with Homebrew, the linker may still use old paths. Clean and point pkg-config at the current install, then rebuild:

cd pymdurs && cargo clean && cd ..
export PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig"
maturin develop --target aarch64-apple-darwin

Build and install:

# Clone the repository
git clone https://github.com/rupeelab17/pymdurs.git
cd pymdurs

# Install maturin (Python-Rust build tool)
uv pip install maturin

# Build and install pymdurs
cd pymdurs

# For Apple Silicon (ARM64) - use native target
maturin develop --target aarch64-apple-darwin

# For Intel Mac (x86_64) - use default or specify target
maturin develop --target x86_64-apple-darwin

# Or let maturin auto-detect (may require rustup for cross-compilation)
maturin develop

Note: On Apple Silicon, if you get an error about missing x86_64-apple-darwin target, use --target aarch64-apple-darwin explicitly.


Quick Start

import pymdurs

# Create a BuildingCollection
buildings = pymdurs.geometric.Building(
    output_path="./output",
    defaultStoreyHeight=3.0
)

# Set bounding box (WGS84 coordinates)
buildings.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional, defaults to EPSG:2154 for France)
buildings.set_crs(2154)

# Download and process buildings from IGN API
buildings = buildings.run()

# Convert to pandas DataFrame
df = buildings.to_pandas()
print(df.head())

Core Classes

PyBoundingBox

Represents a geographic bounding box with min/max coordinates.

bbox = pymdurs.PyBoundingBox(min_x=-1.15, min_y=46.18, max_x=-1.14, max_y=46.19)

GeoCore / PyGeoCore

Base class providing common geospatial functionality (CRS, output paths, etc.).

# Access GeoCore from any geometric module
buildings = pymdurs.geometric.Building(output_path="./output")
geo = buildings.geo_core
print(f"CRS: EPSG:{geo.epsg}")
print(f"Output path: {geo.output_path}")

Geometric Data Modules

All geometric modules follow a similar API pattern:

  1. Create an instance with output_path
  2. Set bounding box with set_bbox(min_x, min_y, max_x, max_y) (WGS84)
  3. Optionally set CRS with set_crs(epsg_code)
  4. Run processing with run() or module-specific methods
  5. Access results via get_geojson(), to_pandas(), or file paths

🏢 Building / BuildingCollection

Load and process building data from Shapefiles, GeoJSON, or IGN API.

import pymdurs

# Create BuildingCollection
buildings = pymdurs.geometric.Building(
    output_path="./output",
    defaultStoreyHeight=3.0
)

# Set bounding box (WGS84)
buildings.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
buildings.set_crs(2154)

# Download from IGN API and process
buildings = buildings.run()

# Convert to pandas DataFrame
df = buildings.to_pandas()

# Access GeoCore
geo = buildings.geo_core
print(f"CRS: EPSG:{geo.epsg}")

Features:

  • Automatic height processing (storeys × default height or alternative height field)
  • Mean district height calculation (weighted by area)
  • Integration with pandas for tabular operations
  • Support for multiple input formats (Shapefile, GeoJSON, IGN API)

🗻 DEM (Digital Elevation Model)

Download and process DEM data from IGN API via WMS-R.

import pymdurs

# Create Dem instance
dem = pymdurs.geometric.Dem(output_path="./output")

# Set bounding box (WGS84)
dem.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
dem.set_crs(2154)

# Run DEM processing (downloads from IGN WMS-R)
dem = dem.run()

# Get output paths
tiff_path = dem.get_path_save_tiff()
mask_path = dem.get_path_save_mask()

print(f"DEM saved to: {tiff_path}")
print(f"Mask saved to: {mask_path}")

Features:

  • Automatic download from IGN WMS-R service
  • GeoTIFF generation with proper CRS
  • Mask generation for DEM boundaries
  • Optional shape parameter for resampling

📋 Cadastre

Download cadastral parcel data from IGN API via WFS.

import pymdurs

# Create Cadastre instance
cadastre = pymdurs.geometric.Cadastre(output_path="./output")

# Set bounding box (WGS84)
cadastre.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
cadastre.set_crs(2154)

# Download from IGN API
cadastre = cadastre.run()

# Get GeoJSON data
geojson = cadastre.get_geojson()

# Save to GeoJSON file
cadastre.to_geojson(name="cadastre")

📊 IRIS (Statistical Units)

Download IRIS statistical units from IGN API via WFS.

import pymdurs

# Create Iris instance
iris = pymdurs.geometric.Iris(output_path="./output")

# Set bounding box (WGS84)
iris.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
iris.set_crs(2154)

# Download from IGN API
iris = iris.run()

# Get GeoJSON data
geojson = iris.get_geojson()

# Save to GeoJSON file
iris.to_geojson(name="iris")

🌳 COSIA (Land Cover)

Download COSIA land-cover data from the IGN API.

import pymdurs

# Create Cosia instance
cosia = pymdurs.geometric.Cosia(output_path="./output")

# Set bounding box (WGS84)
cosia.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
cosia.set_crs(2154)

# Download from IGN API
cosia = cosia.run_ign()

# Get output path
tiff_path = cosia.get_path_save_tiff()
print(f"COSIA raster saved to: {tiff_path}")

Note: COSIA data is downloaded as a raster TIFF. See examples/cosia_from_ign.py for a complete workflow including vectorization and conversion to UMEP format.


🛰️ LiDAR

Download and process LiDAR point cloud data from IGN WFS service.

import pymdurs

# Create Lidar instance
lidar = pymdurs.geometric.Lidar(output_path="./output")

# Set bounding box (WGS84)
lidar.set_bbox(-1.154894, 46.182639, -1.148361, 46.186820)

# Set CRS (optional)
lidar.set_crs(2154)

# Generate CDSM from vegetation and water classes
classification_list = [3, 4, 5, 9]  # Vegetation and water
lidar.run(file_name="CDSM.tif", classification_list=classification_list)

# Generate DSM from ground and buildings classes
classification_list = [2, 6]  # Ground and buildings
output_path = lidar.run(file_name="DSM.tif", classification_list=classification_list)

print(f"DSM saved to: {output_path}")
# Output contains 3 bands: DSM, DTM, CHM

Features:

  • Downloads LAZ files from IGN WFS service
  • Processes point clouds to create DSM, DTM, and CHM rasters
  • Filters by LiDAR classification classes
  • Outputs multi-band GeoTIFF files

LiDAR Classification Classes:

  • 2 = Ground
  • 3 = Low Vegetation
  • 4 = Medium Vegetation
  • 5 = High Vegetation
  • 6 = Buildings
  • 9 = Water

🏢 RNB (French National Building Registry)

Download building data from RNB API.

import pymdurs

# Create Rnb instance
rnb = pymdurs.geometric.Rnb(output_path="./output")

# Set bounding box (WGS84)
rnb.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
rnb.set_crs(2154)

# Download from RNB API
rnb = rnb.run()

# Get GeoJSON data
geojson = rnb.get_geojson()

# Save to GPKG file
rnb.to_geojson(name="rnb")

🛣️ Road

Download road segment data from IGN API.

import pymdurs

# Create Road instance
road = pymdurs.geometric.Road(output_path="./output")

# Set bounding box (WGS84)
road.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
road.set_crs(2154)

# Download from IGN API
road = road.run()

# Get GeoJSON data
geojson = road.get_geojson()

# Save to GeoJSON file
road.to_geojson(name="road")

🌳 Vegetation

Calculate vegetation from IGN IRC images using NDVI (Normalized Difference Vegetation Index).

import pymdurs

# Create Vegetation instance
vegetation = pymdurs.geometric.Vegetation(
    output_path="./output",
    write_file=False,
    min_area=0.0
)

# Set bounding box (WGS84)
vegetation.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
vegetation.set_crs(2154)

# Process vegetation (downloads IRC, calculates NDVI, filters)
vegetation = vegetation.run()

# Get GeoJSON data
geojson = vegetation.get_geojson()

# Save to GeoJSON file
vegetation.to_geojson(name="vegetation")

Features:

  • Downloads IRC (Infrared Color) images from IGN API
  • Calculates NDVI = (NIR - Red) / (NIR + Red)
  • Filters pixels with NDVI < 0.2
  • Polygonizes raster and filters by minimum area

💧 Water

Download water body data from IGN API.

import pymdurs

# Create Water instance
water = pymdurs.geometric.Water(output_path="./output")

# Set bounding box (WGS84)
water.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Set CRS (optional)
water.set_crs(2154)

# Download from IGN API
water = water.run()

# Get GeoJSON data
geojson = water.get_geojson()

# Save to GeoJSON file
water.to_geojson(name="water")

🌡️ LCZ (Local Climate Zone)

Load Local Climate Zone data from external sources.

import pymdurs

# Create Lcz instance
lcz = pymdurs.geometric.Lcz(output_path="./output")

# Set bounding box (WGS84)
lcz.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

# Load from URL (zip file containing shapefiles)
lcz = lcz.run()

# Get GeoJSON data
geojson = lcz.get_geojson()

# Get LCZ color table
table_color = lcz.get_table_color()

# Save to GeoJSON file
lcz.to_geojson(name="lcz")

Features:

  • Loads LCZ data from zip URLs
  • Built-in LCZ color table (17 LCZ types)
  • Spatial filtering by bounding box
  • Shapefile support (requires GDAL)

Requirements

Python

  • Python >= 3.8
  • pandas >= 1.0.0
  • numpy < 2.0.0 (for compatibility with numexpr and other dependencies)

Note: If you encounter NumPy 2.x compatibility issues, install NumPy 1.x:

uv pip install 'numpy<2.0.0'

Optional Dependencies

For advanced workflows and examples:

# Geospatial operations
uv pip install geopandas rasterio pyproj shapely

# For UMEP integration
uv pip install "solweig @ git+https://github.com/UMEP-dev/solweig.git"
uv pip install umep  # Optional

UMEP Integration

The full UMEP workflow runs in two steps under examples/, using the same output directory (./output/umep_workflow) and the same study area (La Rochelle bbox by default).

Step 1 — Land cover (cosia_from_ign.py)

Downloads the COSIA orthophoto from the IGN API, vectorizes polygons by RGB color, reclassifies them to UMEP format (buildings, vegetation, water, etc.), and produces:

  • landcover.tif — land-cover raster compatible with SOLWEIG
  • terrain.shp, terrain.geojson — vector subset (bare soil and impervious surfaces)
python examples/cosia_from_ign.py

Step 2 — Thermal analysis (umep_workflow_new.py)

Builds on step 1 outputs and collects the remaining urban data via pymdurs:

  1. DEM from the IGN API
  2. DSM / CDSM from IGN LiDAR (WFS)
  3. Clip rasters to the mask (DEM_clip.tif, DSM_clip.tif, CDSM_clip.tif, landcover_clip.tif)
  4. SOLWEIG (solweig): sky view factor (SVF), Tmrt, thermal comfort (UTCI)

umep_workflow_new.py expects landcover.tif in ./output/umep_workflow/. Without that file, the SOLWEIG step is skipped (a warning is printed).

# On Apple Silicon (ARM64), add the x86_64 Rust target first:
rustup target add x86_64-apple-darwin

# Install solweig:
uv pip install "solweig @ git+https://github.com/UMEP-dev/solweig.git"

# Run the workflow (after cosia_from_ign.py):
python examples/umep_workflow_new.py

Note: solweig currently requires the x86_64-apple-darwin Rust target even on Apple Silicon — a limitation of the solweig package itself. Place a weather EPW file (e.g. la_rochelle_2025.epw) in examples/ for the SOLWEIG step.

Main outputs: clipped rasters, PNG/GIF previews, Tmrt/UTCI time series in ./output/umep_workflow/.


Examples

Comprehensive examples are available in the examples/ directory:

  • Basic usage: building_basic.py
  • IGN API integration: building_from_ign.py, dem_from_ign.py, cadastre_from_ign.py, etc.
  • LiDAR processing: lidar_from_wfs.py
  • COSIA workflow: cosia_from_ign.py
  • UMEP workflow: umep_workflow_new.py (complete urban analysis workflow)

See examples/README.md for detailed documentation of all examples.


API Reference

Common Methods

All geometric modules share these common methods:

set_bbox(min_x: float, min_y: float, max_x: float, max_y: float)

Set the bounding box in WGS84 (EPSG:4326) coordinates.

module.set_bbox(-1.152704, 46.181627, -1.139893, 46.18699)

set_crs(epsg: int)

Set the coordinate reference system (CRS) using EPSG code.

module.set_crs(2154)  # Lambert 93 (France)

geo_core: GeoCore

Access the GeoCore instance for CRS and path information.

geo = module.geo_core
print(f"CRS: EPSG:{geo.epsg}")
print(f"Output path: {geo.output_path}")

Module-Specific Methods

Building

  • run() -> Building - Download and process buildings
  • to_pandas() -> pandas.DataFrame - Convert to pandas DataFrame

Dem

  • run(shape: Optional[Tuple[int, int]] = None) -> Dem - Download and process DEM
  • get_path_save_tiff() -> str - Get DEM GeoTIFF path
  • get_path_save_mask() -> str - Get mask shapefile path

Cadastre, Iris, Road, Rnb, Water, Vegetation

  • run() -> Self - Download and process data
  • get_geojson() -> dict - Get GeoJSON data
  • to_geojson(name: str) -> None - Save to GeoJSON file

Cosia

  • run_ign() -> Cosia - Download COSIA from IGN API
  • get_path_save_tiff() -> str - Get COSIA raster path

Lidar

  • run(file_name: str, classification_list: List[int]) -> str - Process LiDAR data
  • Returns path to output GeoTIFF file

Lcz

  • run() -> Lcz - Load LCZ data
  • get_table_color() -> dict - Get LCZ color table

Notes

API Aliases

Both Pythonic aliases and original class names are available:

  • Building / PyBuilding
  • Dem / PyDem
  • Cadastre / PyCadastre
  • Iris / PyIris
  • Lcz / PyLcz
  • PyBoundingBox
  • GeoCore / PyGeoCore
  • etc.

Coordinate Systems

  • Input coordinates: Must be in WGS84 (EPSG:4326) for IGN API
  • Default CRS: EPSG:2154 (Lambert 93) for French data
  • Output CRS: Can be customized with set_crs()

IGN API Limitations

  • Rate limiting: The IGN API may have rate limits
  • Data availability: Some data may not be available for all areas
  • Internet connection: Required for all IGN API operations

Versions and releases

The version is synchronized across pyproject.toml, pymdurs/Cargo.toml, and rsmdu/Cargo.toml.

  • Set a version: ./scripts/set-version.sh 0.1.2
  • Bump (patch/minor/major): ./scripts/bump-version.sh patch
  • Release (bump + commit + tag): ./scripts/bump-version.sh patch --tag then git push && git push origin py-X.Y.Z

See docs/VERSIONING.md for details.


Support

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymdurs-0.1.5.tar.gz (197.0 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pymdurs-0.1.5-cp38-abi3-win_amd64.whl (50.0 MB view details)

Uploaded CPython 3.8+Windows x86-64

pymdurs-0.1.5-cp38-abi3-manylinux_2_39_x86_64.whl (69.2 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.39+ x86-64

pymdurs-0.1.5-cp38-abi3-macosx_11_0_arm64.whl (7.8 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

File details

Details for the file pymdurs-0.1.5.tar.gz.

File metadata

  • Download URL: pymdurs-0.1.5.tar.gz
  • Upload date:
  • Size: 197.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pymdurs-0.1.5.tar.gz
Algorithm Hash digest
SHA256 9d53d8ecaab74fac7d14bdddd313235bd6c36ce7c0743a6ef5c8830cccd10b5d
MD5 46e41da0ce3552894f4203e1a2c67d02
BLAKE2b-256 903b4a6824275cddedc557838ea2ccf98d98be24e7f3063a7c0fdc8635b3e3fc

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymdurs-0.1.5.tar.gz:

Publisher: release-python-windows.yml on rupeelab17/pymdurs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymdurs-0.1.5-cp38-abi3-win_amd64.whl.

File metadata

  • Download URL: pymdurs-0.1.5-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 50.0 MB
  • Tags: CPython 3.8+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pymdurs-0.1.5-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 e0262a537ae64cc54273d8dc9f23a5870c0f7ddfd111d06afdfce29d65e34209
MD5 f7c91f4152357634bcf720e8925ae1dd
BLAKE2b-256 bb96bb6542b4f3c84c3c5b5eb2290f9ea705de42f991963496ceac4019e00765

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymdurs-0.1.5-cp38-abi3-win_amd64.whl:

Publisher: release-python-windows.yml on rupeelab17/pymdurs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymdurs-0.1.5-cp38-abi3-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for pymdurs-0.1.5-cp38-abi3-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 3d74db8fe3c4063cdb4781da9fb8f5ba0989d0f8e95a2609bfdd496cd3295899
MD5 fafd9f757e847c00f674aa81bfc994df
BLAKE2b-256 053786f092a9d374450dbf3dfa2041958ad16c005be4dd64d646d456efb34f6d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymdurs-0.1.5-cp38-abi3-manylinux_2_39_x86_64.whl:

Publisher: release-python-unix.yml on rupeelab17/pymdurs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymdurs-0.1.5-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pymdurs-0.1.5-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5078f84c68436cdda6031173f4a60206d6d7b09edcaf8d0617c8c720e98d0c93
MD5 04029ea3f4a6055ee5027bd29587b7d2
BLAKE2b-256 aacf3ade432cdb033c35ed98af212715b884140e14d5d2d686838f480f49a98b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymdurs-0.1.5-cp38-abi3-macosx_11_0_arm64.whl:

Publisher: release-python-unix.yml on rupeelab17/pymdurs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page