Metadata-Version: 2.4
Name: zero2geospatial3d-sdk
Version: 0.1.0
Summary: Headless 3D urban and terrain analysis in Python: sun hours, shadows, sky view factor, solar PV, UTCI/PET comfort, noise, dispersion, flooding and cut/fill.
Author-email: Yusuf Eminoğlu <yusufeminoglu@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://geophilo.com/
Project-URL: Repository, https://github.com/YusufEminoglu/zero2geospatial3d_sdk
Project-URL: Issues, https://github.com/YusufEminoglu/zero2geospatial3d_sdk/issues
Project-URL: Documentation, https://github.com/YusufEminoglu/zero2geospatial3d_sdk#readme
Project-URL: Changelog, https://github.com/YusufEminoglu/zero2geospatial3d_sdk/blob/main/CHANGELOG.md
Project-URL: QGIS plugin, https://github.com/YusufEminoglu/zero2geospatial3d
Keywords: gis,3d,urban-planning,urban-climate,solar,sun-hours,shadow,sky-view-factor,photovoltaic,daylight,bre-209,utci,pet,thermal-comfort,microclimate,wind-comfort,noise,air-quality,viewshed,terrain,dem,flood,cut-and-fill,epw,openstreetmap,walkability
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
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: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: THIRD_PARTY_NOTICES.md
Requires-Dist: numpy>=1.20.0
Provides-Extra: geo
Requires-Dist: shapely>=2.0.0; extra == "geo"
Requires-Dist: rasterio>=1.3.0; extra == "geo"
Requires-Dist: pyproj>=3.3.0; extra == "geo"
Requires-Dist: matplotlib>=3.5.0; extra == "geo"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Dynamic: license-file

<div align="center">

# zero2geospatial3d-sdk

[![PyPI version](https://img.shields.io/pypi/v/zero2geospatial3d-sdk.svg?color=10b981)](https://pypi.org/project/zero2geospatial3d-sdk/)
[![Python version support](https://img.shields.io/pypi/pyversions/zero2geospatial3d-sdk.svg?color=3b82f6)](https://pypi.org/project/zero2geospatial3d-sdk/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/YusufEminoglu/zero2geospatial3d_sdk/blob/main/LICENSE)
[![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-D7FF64.svg)](https://docs.astral.sh/ruff/)
[![Test Coverage](https://img.shields.io/badge/coverage-92%25-brightgreen.svg)](#development-and-testing)

**Headless 3D terrain and building analysis in Python: sun hours, shadows, sky view factor, solar PV, UTCI/PET comfort, wind, noise and air-quality screening, daylight rules, terrain sections, flooding and cut/fill.**

[PyPI Package](https://pypi.org/project/zero2geospatial3d-sdk/) • [GitHub](https://github.com/YusufEminoglu/zero2geospatial3d_sdk) • [Issue Tracker](https://github.com/YusufEminoglu/zero2geospatial3d_sdk/issues) • [QGIS plugin](https://github.com/YusufEminoglu/zero2geospatial3d)

</div>

`zero2geospatial3d-sdk` is the scriptable, QGIS-free engine behind the
**02 Geo3D Analyzer** QGIS plugin (`zero2geospatial3d`). The plugin runs its
analyses on the GPU in a browser; this package ports the same mathematics to
NumPy so they run in notebooks, batch jobs, servers and CI pipelines. Scene
packages are interchangeable: a scene exported by the plugin loads in Python,
and a scene built in Python opens in the bundled browser viewer.

## Installation

```bash
# Core: NumPy only
pip install zero2geospatial3d-sdk

# Optional geospatial extras (GeoTIFF output with rasterio, pyproj, shapely, matplotlib)
pip install "zero2geospatial3d-sdk[geo]"
```

Python 3.9 to 3.13. The import name is `zero2geospatial3d`.

## Quickstart (runs offline)

```python
import numpy as np
from zero2geospatial3d import Building, Scene, shading, comfort, terrain
from zero2geospatial3d.results import aggregate_by_id

# A 200 m x 200 m level site in Izmir (UTC+3) with a 2 m grid and two buildings.
scene = Scene.flat(200, 200, cell=2.0, lat=38.42, lon=27.14, utc_offset=3.0)
for cx, h in ((-30, 24.0), (30, 12.0)):
    ring = [[cx - 15, -10], [cx + 15, -10], [cx + 15, 10], [cx - 15, 10], [cx - 15, -10]]
    scene.buildings.append(Building([[ring]], height=h))

surface = scene.surface()                     # terrain + buildings as a Grid
_, ids = scene.building_rasters()             # building id per cell (0 = ground)

# BRE 209: sun hours on 21 March
hours = shading.sun_hours(surface, scene.lat, scene.lon, scene.utc_offset, month=3, day=21)
open_ground = hours[ids == 0]
print(f"open ground with >= 2 h of sun: {100 * np.mean(open_ground >= 2):.0f} %")

# Sky view factor and per-building roof statistics
svf = shading.sky_view_factor(surface, n_azimuths=16)
print(aggregate_by_id(ids, svf, cell_area=4.0)[1]["mean"])

# Thermal comfort for one set of conditions
print(comfort.utci(32.0, 55.0, 1.5, 40.0), comfort.pet(32.0, 55.0, 1.5, 40.0))

# Terrain: slope, flooding and earthworks on the DEM
slope, aspect = terrain.slope_aspect(scene.terrain)
pad = [[-20, -20], [20, -20], [20, 20], [-20, 20], [-20, -20]]
print(terrain.cut_fill(scene.terrain, pad, design_level=0.5).fill)
```

## Command line

```bash
zero2geospatial3d sun --lat 38.42 --lon 27.14 --date 2026-06-21 --tz 3
zero2geospatial3d comfort --ta 32 --tr 55 --v 1.5 --rh 40
zero2geospatial3d stations --lat 41.01 --lon 28.97 -n 3          # nearest TMYx stations
zero2geospatial3d epw --lat 41.01 --lon 28.97 --out istanbul.epw   # download the nearest one

zero2geospatial3d demo --out demo_scene                            # synthetic scene package
zero2geospatial3d osm --lat 38.4237 --lon 27.1428 --half-size 250 --out izmir_scene
zero2geospatial3d analyze demo_scene sun-hours --date 2026-03-21 --out sun.asc
zero2geospatial3d analyze demo_scene svf --azimuths 36 --out svf.npy
zero2geospatial3d analyze demo_scene irradiation --epw istanbul.epw --out kwh.tif   # .tif needs [geo]
zero2geospatial3d analyze demo_scene flood --level 12 --out depth.asc
zero2geospatial3d view demo_scene --results results/                 # bundled 3D viewer
```

`analyze` supports `sun-hours`, `shadow`, `svf`, `viewshed`, `irradiation`,
`slope`, `aspect`, `hillshade` and `flood`, and prints summary statistics as
JSON. `view` serves the plugin's three.js viewer on `127.0.0.1` (your data
stays on your computer); results sent from the viewer are saved to `--results`.

## How the 3D analyses work headlessly

The plugin renders depth maps of the full 3D scene on the GPU. The SDK works on
a 2.5-D **surface**: the DEM plus the heights of buildings (and optionally tree
crowns) rasterised onto the same grid. Sun visibility is tested by marching
rays across this surface with bilinear sampling, the sky view factor is
`1 - mean(sin^2(horizon))` over azimuth sectors, and irradiation combines
direct sun (from EPW or clear-sky sun-direction bins), isotropic diffuse sky
and ground reflection, exactly as the plugin's `solarRadiation` runner does.
Overhangs and facade receivers are not represented in 2.5-D.

## Public API

| Module | Main contents |
|---|---|
| `grid` | `Grid` (values, cell, min_x, max_y; `sample`, `coords`, `index_of`), `make_grid` |
| `scene` | `Scene` (`flat`, `from_osm`, `surface`, `building_rasters`, `tree_raster`, `export`), `Building`, `Tree`, `Road`, `load_scene`, `rasterize_polygons` |
| `solar` | `solar_position`, `sunrise_sunset`, `sun_path`, `sky_hemisphere`, `sun_vector`, `day_of_year` |
| `shading` | `shadow_mask`, `sun_hours`, `shadow_study`, `horizon_angles`, `sky_view_factor`, `viewshed`, `directional_irradiation`, `solar_irradiation`, `pv_yield` |
| `comfort` | `utci`, `pet`, `mean_radiant_temperature`, `sky_temperature`, `projected_area_factor`, `utci_category`, `pet_category` |
| `weather` | `Weather`, `parse_epw`, `read_epw`, `monthly_summary`, `wind_rose`, `clear_sky_year`, `annual_sun_bins`, `conditions_at`, `typical_doy` |
| `epw` | `nearest_stations`, `load_index`, `download_station`, `extract_epw`, `read_header` |
| `microclimate` | `surface_temperature`, `canopy_transmittance`, `canopy_cooling_grid`, `diurnal_point_profile`, `SURFACE_MATERIALS` |
| `wind` | `wind_ratio`, `lawson_class`, `OPEN_PEDESTRIAN_FACTOR` |
| `morphology` | `distance_to_built`, `macdonald`, `classify_lcz`, `block_indicators` |
| `dispersion` | `ventilation_efficiency`, `pollutant_dispersion`, `ventilation_class`, `air_quality_class` |
| `acoustics` | `noise_propagation`, `building_facade_noise`, `maekawa_diffraction`, `noise_class` |
| `road_sources` | `sample_road_lines`, `street_centrelines`, `road_weight` |
| `daylight` | `prepare_rule`, `check_rule`, `vsc_long`, `allowed_angle`, `reach25`, `wall_samples` (BRE 209) |
| `urban_form` | `green_view_index`, `solar_envelope`, `gvi_class` |
| `walkability` | `pedestrian_walkability_index`, `find_cool_route`, subscores |
| `terrain` | `slope_aspect`, `hillshade`, `contours`, `profile`, `flood`, `cut_fill`, `balanced_level`, `curvature`, `tpi`, `ruggedness`, `aspect_classes`, `flow_accumulation`, `wetness`, `crop_grid` |
| `classify` | `equal_breaks`, `quantile_breaks`, `jenks_breaks`, `ramp_stops`, `ramp_color`, `RAMPS` |
| `nsga2` | `Problem`, `nsga2`, `dominates`, `non_dominated_sort`, `crowding_distance` |
| `osm`, `osm_tags` | `fetch` (Overpass, timeouts, cache), `payload_to_features`, `building_height`, `road_traffic` |
| `geo` | `lonlat_to_local`, `local_to_lonlat`, `bbox_around`, `utm_epsg_for`, `grid_shape` |
| `results` | `aggregate_by_id`, `grid_stats`, `write_ascii_grid`, `write_grid_npy`, `write_geotiff`, `save_result` |
| `server` | `serve_scene`, `ViewerServer`, `viewer_root` |

All grids are NumPy arrays of shape `(rows, cols)` with row 0 at the north
edge; coordinates are local metres (x east, y north). Angles from `solar` are
radians with azimuth clockwise from north.

## Validation

The test suite reproduces the numeric assertions of the plugin's JavaScript
tests (`tests/js/*.test.mjs`): the NREL SPA reference sun position, UTCI and
PET reference values, EPW parsing and wind roses, surface energy balance and
canopy cooling, Maekawa diffraction and traffic-noise scaling, ventilation and
dispersion, BRE 209 VSC closed forms, solar envelopes and green view, terrain
curvature, TPI, TRI, flow accumulation and wetness, flood and cut/fill
volumes, Jenks breaks, and the seeded NSGA-II run. The shading engines are
checked against closed forms (canyon sky view factor, wall shadow length).

## Development and testing

```bash
git clone https://github.com/YusufEminoglu/zero2geospatial3d_sdk.git
cd zero2geospatial3d_sdk
pip install -e ".[dev]"
python -m pytest --cov=zero2geospatial3d
python -m ruff check .
python -m mypy src
```

## Academic citation

```bibtex
@software{eminoglu_zero2geospatial3d_sdk,
  author  = {Eminoğlu, Yusuf},
  title   = {zero2geospatial3d-sdk: headless 3D terrain and building analysis in Python},
  year    = {2026},
  version = {0.1.0},
  url     = {https://github.com/YusufEminoglu/zero2geospatial3d_sdk}
}
```

See also `CITATION.cff`.

## License

MIT, see [LICENSE](LICENSE). The bundled viewer includes three.js (MIT) and the
UTCI polynomial follows pythermalcomfort (MIT); the station index credits
Climate.OneBuilding.Org. Details in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
