Metadata-Version: 2.4
Name: planx-3d-city
Version: 0.1.2
Summary: Turn GeoJSON, GeoDataFrames and DEMs into an interactive Three.js 3D city viewer: headless scene export, portable HTML packages, OpenStreetMap import and a local preview server.
Author-email: Yusuf Eminoğlu <yusufeminoglu@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://geophilo.com/
Project-URL: Repository, https://github.com/YusufEminoglu/planx_3d_city_sdk
Project-URL: Issues, https://github.com/YusufEminoglu/planx_3d_city_sdk/issues
Project-URL: Documentation, https://github.com/YusufEminoglu/planx_3d_city_sdk#readme
Project-URL: Changelog, https://github.com/YusufEminoglu/planx_3d_city_sdk/blob/main/CHANGELOG.md
Keywords: gis,3d-city,urban-planning,threejs,three.js,webgl,geojson,geotiff,dem,openstreetmap,overpass,buildings,massing,digital-twin,visualization,cityjson,3d-tiles
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: Programming Language :: JavaScript
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Scientific/Engineering :: Visualization
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
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_LICENSES.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: geopandas>=0.12.0; extra == "geo"
Requires-Dist: pyproj>=3.3.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">

# planx-3d-city

[![PyPI version](https://img.shields.io/pypi/v/planx-3d-city.svg?color=10b981)](https://pypi.org/project/planx-3d-city/)
[![Python version support](https://img.shields.io/pypi/pyversions/planx-3d-city.svg?color=3b82f6)](https://pypi.org/project/planx-3d-city/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/YusufEminoglu/planx_3d_city_sdk/blob/main/LICENSE)
[![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-D7FF64.svg)](https://docs.astral.sh/ruff/)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.24057133.svg)](https://doi.org/10.5281/zenodo.24057133)

**Turn GeoJSON, GeoDataFrames and DEMs into an interactive Three.js 3D city you can open in any browser.**

[📦 **PyPI Package**](https://pypi.org/project/planx-3d-city/) • [💻 **GitHub**](https://github.com/YusufEminoglu/planx_3d_city_sdk) • [🐛 **Issue Tracker**](https://github.com/YusufEminoglu/planx_3d_city_sdk/issues)

</div>

`planx-3d-city` is the headless, QGIS-free counterpart of the
[PlanX 3D City](https://github.com/YusufEminoglu/planx_3d_city) QGIS plugin. It takes
buildings (with heights or floor counts), roads, green areas, water, trees and an optional
DEM, writes the scene data the PlanX viewer reads, and packages the viewer itself
(three.js and every other library bundled, nothing loaded from a CDN) into a folder or
ZIP that works offline and can be published on any static host.

The viewer gives you massing typologies, facade and roof materials, sun and shadow
studies, sky view factor, viewsheds, wind screening, guided camera tours, street life,
and export to CityJSON, 3D Tiles, glTF/GLB, PNG and MP4.

## 📦 Installation

```bash
# Core: NumPy is the only dependency
pip install planx-3d-city

# With the geospatial stack (GeoPandas, Shapely, rasterio, pyproj)
pip install "planx-3d-city[geo]"

# Development toolchain (pytest, pytest-cov, ruff, mypy)
pip install "planx-3d-city[dev]"

# Editable install from a clone
git clone https://github.com/YusufEminoglu/planx_3d_city_sdk.git
cd planx_3d_city_sdk
pip install -e ".[geo,dev]"
```

**Requirements:** Python 3.9+ on Windows, macOS or Linux. WGS84, Web Mercator and every
UTM zone are handled in pure Python and plain GeoTIFFs are read and written with NumPy,
so the core works without GDAL. Install `[geo]` to pass GeoDataFrames, use other CRSs
(pyproj) or read other raster formats (rasterio).

## ⚡ Quickstart

```python
import planx_3d_city as px

# A synthetic 600 m x 600 m city with terrain, blocks, buildings, roads, trees and a stream.
scene = px.sample_scene()
result = px.export_viewer(scene, "sample_city")
print(result.path)            # sample_city/ : src/, assets/, data/, serve.py, index.html ...

# Preview it on http://127.0.0.1:<port>/src/ (Ctrl+C to stop)
px.serve("sample_city")
```

### Your own data

```python
import planx_3d_city as px

scene = px.CityScene(title="Konak waterfront", asset_theme="Mediterranean")

# GeoJSON paths, dicts, GeoDataFrames, shapely geometries or feature lists all work.
# WGS84 input is projected to the local UTM zone automatically.
scene.add_buildings("buildings.geojson", height_field="HEIGHT_M", floors_field="KAT")
scene.add_layer("roads", "roads.geojson")
scene.add_layer("green", "parks.geojson")        # added to blocks, function=GREEN
scene.add_layer("water", "sea.geojson")          # added to blocks, function=WATER
scene.add_layer("waterlines", "streams.geojson")
scene.add_layer("trees", "trees.geojson")
scene.set_dem("dem.tif")                          # reprojected and cropped to the scene

scene.export("konak_viewer", zip_path="konak_viewer.zip")
```

With GeoPandas:

```python
import geopandas as gpd
import planx_3d_city as px

gdf = gpd.read_file("buildings.gpkg")              # any CRS with an EPSG code
scene = px.CityScene(title="District").add_buildings(gdf, floors_field="floors")
px.export_viewer(scene, "district_viewer")
```

### OpenStreetMap

```python
import planx_3d_city as px

# (min_lon, min_lat, max_lon, max_lat); Overpass requests time out after timeout_s + 10 s.
osm = px.import_osm_bbox((27.125, 38.415, 27.140, 38.425), timeout_s=60)
print(osm.counts)                                  # {'buildings': ..., 'roads': ..., ...}
scene = osm.to_scene(title="Konak (OSM)")
px.export_viewer(scene, "konak_osm")
```

Areas are capped at 5 km on the longer side by default (`max_km=`) to keep requests to the
shared Overpass service polite. OSM data is © OpenStreetMap contributors (ODbL).

## 🖥️ Command line

```bash
planx-3d-city info                                    # layer roles, themes, terrain modes
planx-3d-city sample my_city --serve                  # sample city, then open it
planx-3d-city export site --buildings b.geojson --roads r.geojson \
    --green parks.geojson --dem dem.tif --theme Mediterranean --zip site.zip
planx-3d-city osm konak --bbox 27.125,38.415,27.140,38.425 --timeout 60
planx-3d-city serve site --port 8080
```

`export` accepts one flag per layer role (`--buildings`, `--roads`, `--blocks`,
`--parcels`, `--trees`, `--waterlines`, `--lights`, `--benches`, `--busstops`, ...), plus
`--green` and `--water`, `--dem`, `--plan-texture`, `--crs`, `--height-field`,
`--floors-field`, `--title`, `--theme`, `--atmosphere`, `--terrain-mode` and `--tour`.
`python -m planx_3d_city` works the same way.

## 🧱 What gets written

```
my_city/
├── index.html                 redirects to src/?portable=1 (static hosting entry point)
├── serve.py                   launcher: free local port, no caching, opens the browser
├── Start-PlanX-Viewer.bat/.ps1
├── .nojekyll, .gitlab-ci.yml  GitHub Pages / GitLab Pages
├── src/                       the viewer app (ES modules)
├── assets/                    three.js r160, three-mesh-bvh, geotiff.js, mp4-muxer, textures
└── data/
    ├── planx_manifest.json    schema planx-3d-city-manifest/v1 (same as the QGIS plugin)
    ├── vector/*.geojson       one file per layer role, in one metric CRS
    ├── dem/mydem.tif          optional DEM (float32, DEFLATE)
    └── texture/siteplan.tif   optional draped plan texture
```

Browsers block JavaScript modules and `fetch` on `file://` URLs, so serve the folder over
HTTP (`serve.py`, `planx-3d-city serve`, or any static host) instead of double-clicking
`src/index.html`. While served by `PreviewServer`, the viewer auto-saves your tuned settings
to `data/planx_scene_state.json`, so a later export keeps the look.

## 🧭 Layer roles and attributes

| Role | Geometry | Useful attributes |
|---|---|---|
| `buildings` | Polygon | `height` (m) or `floors` / `building:levels`; `function`; `planx_color`, `planx_facade`, `planx_massing`, `planx_roof_shape` |
| `roads` | LineString | `highway` / `road_type`, `lanes`, `oneway`, `width` |
| `blocks` | Polygon | `function` (PARK, GREEN, WATER, RESIDENTIAL, ...); aliases `green`, `parks`, `water` |
| `roi` | Polygon | study area; sets the scene extent |
| `trees` | Point | `height` |
| `waterlines` | LineString | `width` |
| `parcels`, `hardscape`, `sidewalks`, `pedestrian_paths`, `bike_lanes`, `fences` | lines / polygons | |
| `lights`, `benches`, `trashbins`, `busstops`, `mosques`, `tumulus` | Point | angle fields via options |

Viewer options go to `CityScene(**options)` or `scene.set_options(...)`: `asset_theme`,
`atmosphere_preset`, `topography_mode`, `day_of_year`, `latitude`, `dem_z_scale`,
`tree_render_mode`, `zoning_max_height`, `building_floors_field`, `road_access_field`, ...

## 🛠️ More examples

```python
import planx_3d_city as px

# Population estimate on buildings (metric CRS)
fc = px.sample_layers()["buildings"]
print(px.calculate_building_gfa_and_population(fc, default_floors=4))

# Style selected buildings
px.apply_style(fc, {"planx_massing": "Podium Tower", "planx_color": "#d9c3a5"},
               lambda f: f["properties"]["floors"] >= 7)

# A DEM from a NumPy array
import numpy as np
z = np.random.default_rng(0).normal(50, 2, (200, 200)).astype("float32")
dem = px.RasterArray.from_bounds(z, (500000, 4500000, 500600, 4500600), crs="EPSG:32635")
scene = px.CityScene(crs="EPSG:32635").set_dem(dem)

# Package an existing QGIS-plugin export (web/data) with the bundled viewer
px.export_viewer("path/to/plugin/web", "portable_copy")
```

See `examples/quickstart.py` for a runnable script.

## 🧪 Development & testing

```bash
pip install -e ".[geo,dev]"
python -m pytest --cov=planx_3d_city
python -m ruff check .
python -m ruff format --check src tests
python -m mypy src
python -m build && python -m twine check --strict dist/*
```

## 📜 License

MIT © Yusuf Eminoğlu. The bundled JavaScript libraries are MIT-licensed; see
[THIRD_PARTY_LICENSES.md](https://github.com/YusufEminoglu/planx_3d_city_sdk/blob/main/THIRD_PARTY_LICENSES.md).
If you use this software in research, please cite it using `CITATION.cff`.

## Academic citation

```bibtex
@software{eminoglu_planx_3d_city,
  author  = {Eminoğlu, Yusuf},
  title   = {planx-3d-city},
  year    = {2026},
  version = {0.1.2},
  doi     = {10.5281/zenodo.24057133},
  url     = {https://doi.org/10.5281/zenodo.24057133}
}
```

See also `CITATION.cff`.
