Metadata-Version: 2.4
Name: spacematrix
Version: 0.1.0
Summary: Spacematrix urban density indicators (FSI, GSI, OSR, L, network density N, tare T) at lot, island, fabric and district level after Berghauser Pont & Haupt, with building-type classification, the Spacemate diagram and TAKS/KAKS comparison.
Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/YusufEminoglu/spacematrix_sdk
Project-URL: Repository, https://github.com/YusufEminoglu/spacematrix_sdk
Project-URL: Issues, https://github.com/YusufEminoglu/spacematrix_sdk/issues
Project-URL: Changelog, https://github.com/YusufEminoglu/spacematrix_sdk/blob/main/CHANGELOG.md
Keywords: spacematrix,spacemate,urban-density,urban-morphology,floor-space-index,fsi,gsi,open-space-ratio,network-density,urban-form,taks,kaks,urban-planning,gis
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: Environment :: Console
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: plot
Requires-Dist: matplotlib>=3.5; extra == "plot"
Provides-Extra: shapely
Requires-Dist: shapely>=2.0; extra == "shapely"
Provides-Extra: all
Requires-Dist: matplotlib>=3.5; extra == "all"
Requires-Dist: shapely>=2.0; extra == "all"
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

# spacematrix

[![PyPI version](https://img.shields.io/pypi/v/spacematrix.svg)](https://pypi.org/project/spacematrix/)
[![Python versions](https://img.shields.io/pypi/pyversions/spacematrix.svg)](https://pypi.org/project/spacematrix/)
[![CI](https://github.com/YusufEminoglu/spacematrix_sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/YusufEminoglu/spacematrix_sdk/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

**Urban density indicators after Berghauser Pont & Haupt's *Spacematrix* (2010).**

`spacematrix` measures building intensity (FSI), coverage (GSI), spaciousness
(OSR), average storeys (L), network density (N) and tare (T) for lots, islands
(urban blocks), fabrics and districts from building footprints, plot/block
polygons and street centrelines. It classifies samples into Spacematrix building
types, draws the Spacemate diagram with iso-L and iso-OSR lines, and relates the
indicators to dwelling density and to the Turkish TAKS / KAKS (emsal) rules.

The core runs on the Python standard library alone (pure-Python polygon area,
exact polygon overlap and line clipping). `shapely` and `matplotlib` are optional.

```bash
pip install spacematrix                # stdlib only
pip install "spacematrix[plot]"        # + Spacemate diagram (matplotlib)
pip install "spacematrix[shapely]"     # + shapely overlap backend
pip install "spacematrix[all]"
```

Requires Python 3.9 or newer.

---

## Quick start

```python
import spacematrix as sm

lot = sm.Zone(sm.Polygon.rectangle(0, 0, 20, 30), id="lot-1")            # 600 m2
house = sm.Building(sm.Polygon.rectangle(5, 5, 15, 17), floors=3)          # 120 m2 x 3

r = sm.measure(lot, [house], level="lot")
r.fsi, r.gsi, r.osr, r.l      # (0.6, 0.2, 1.333, 3.0)
r.type                        # 'low-rise strip'  (approximate thresholds, see below)
```

A whole hierarchy, from GeoJSON layers in a projected (metric) CRS:

```python
buildings = sm.read_buildings("buildings.geojson", floors_field="floors")  # or height_field=
res = sm.analyze(
    buildings,
    lots=sm.read_zones("lots.geojson"),
    islands=sm.read_zones("blocks.geojson"),
    fabrics=sm.read_zones("fabrics.geojson"),     # bounded by street centrelines
    network=sm.read_network("streets.geojson"),
)
for f in res.fabrics:
    print(f.id, f.fsi, f.gsi, f.n, f.tare, f.mesh_size, f.profile_width, f.type)
sm.write_csv(res.records(), "density.csv")
```

The Spacemate:

```python
from spacematrix.plot import spacemate
ax = spacemate(res.islands, type_grid=sm.DEFAULT_THRESHOLDS)
ax.figure.savefig("spacemate.png", dpi=150)
```

### Command line

```bash
spacematrix metrics --floor-area 76800 --built-area 19200 --area 40000 \
    --interior-length 400 --edge-length 800 --lower-area 25600
spacematrix grid --w 100 --b 20                      # N and T of an orthogonal grid
spacematrix taks-kaks --taks 0.30 --kaks 1.50 --exempt-share 0.2 \
    --island-tare 0.1 --fabric-tare 0.3
spacematrix analyze --buildings b.geojson --lots lots.geojson --islands blocks.geojson \
    --fabrics fabrics.geojson --network streets.geojson --height-field height \
    --out density.csv --plot spacemate.png
spacematrix diagram density.csv -o islands.png --level island
```

`python examples/quickstart.py` reproduces the worked grid example below.

## API overview

| Area | Names |
|---|---|
| Inputs | `Polygon`, `Building` (`floors` or `Building.from_height`), `Zone`, `Network`, `floors_from_height` |
| Indicators | `fsi`, `gsi`, `osr`, `layers`, `network_density`, `tare`, `upscale`, `mesh_size`, `grid_network_density`, `grid_tare`, `profile_width`, `fsi_from_gsi_l`, `fsi_from_gsi_osr` |
| Measurement | `measure(zone, buildings, level=..., network=..., lower_level=...)`, `analyze(...)` → `SpacematrixResult`, `aggregate(records)`; records are `Density` objects with `as_dict()` |
| Types | `classify(fsi, gsi, thresholds)`, `TypeThresholds`, `DEFAULT_THRESHOLDS`, `THRESHOLDS_VERIFIED` (= `False`) |
| Other measures | `dwelling_density`, `coverage_percent`, `dwellings_from_fsi`, `fsi_from_dwelling_density`, `population_density`, `fsi_from_kaks`, `kaks_from_fsi`, `taks_kaks_equivalent`, `TAKS_KAKS_NOTE` |
| Geometry / IO | `shape_area`, `intersection_area` (`backend="python"` or `"shapely"`), `split_line_length`, `read_buildings`, `read_zones`, `read_network`, `write_csv`, `write_geojson` |
| Diagram | `spacematrix.plot.spacemate(samples, type_grid=..., iso_l=..., iso_osr=...)` |

## Method and sources

Definitions from Berghauser Pont, M. and Haupt, P. (2010), *Spacematrix: Space,
Density and Urban Form*, Rotterdam: NAi Publishers. `F` gross floor area, `B`
built-up (footprint) area, `A` plan area of level *x*:

| Indicator | Formula | Notes |
|---|---|---|
| FSI | `F / A` | floor space index (intensity) |
| GSI | `B / A` | ground space index (coverage) |
| OSR | `(1 − GSI) / FSI` | unbuilt ground per m² of floor area (spaciousness) |
| L | `FSI / GSI` | average number of storeys |
| N | `(l_i + l_e / 2) / A_f` | m of street per m² of fabric; edge streets (on the fabric boundary) count half |
| T | `(A_x − A_y) / A_x` | tare: share of level *x* not taken by the lower level *y* |
| w | `2 / N` | mesh size of the equivalent orthogonal grid |
| b | `2 (1 − √(1 − T)) / N` | profile width; inverse of `T = (2bw − b²) / w²` |

Levels: **lot** (plot) ⊂ **island** (block, island tare = internal paths and
communal space) ⊂ **fabric** (islands plus the streets around them, bounded by
street centrelines; fabric tare ≈ street space) ⊂ **district** (plus large parks,
water, infrastructure). Tare lowers FSI and GSI by the same factor
(`FSI_x = FSI_y (1 − T_x)`), so L is the same at every level.

Building floor area is `footprint × floors` (or a measured value). Floors can be
estimated from height with a configurable storey height (default 3.0 m, a
convention, not a standard). Buildings are allocated to zones by footprint
overlap (exact, buildings straddling a boundary are split) or by centroid.

### Worked example (tested)

Orthogonal 2 × 2 grid, street centrelines every 100 m (w = 100 m), 20 m
profiles (b = 20 m), 80 × 80 m islands each with a four-storey perimeter block
around a 40 × 40 m courtyard:

| Level | A (m²) | FSI | GSI | OSR | L | N (m/m²) | T |
|---|---|---|---|---|---|---|---|
| island | 6 400 | 3.00 | 0.75 | 0.083 | 4 | – | – |
| fabric | 40 000 | 1.92 | 0.48 | 0.271 | 4 | 0.02 (= 2/w) | 0.36 (= (2bw−b²)/w²) |

`profile_width(0.36, 0.02) = 20 m` recovers b.

### Building types

`classify` assigns one of nine types — low-/mid-/high-rise × point/strip/block —
from L (height class; bands between iso-L rays) and GSI (form class). The book
identifies these types as clusters in the Spacemate; **the numeric cut-offs used
here (L ≤ 3.5 low-rise, L ≤ 7.5 mid-rise; GSI < 0.20 point, GSI < 0.35 strip,
otherwise block) are approximations that were not verified against the book**.
They are fields of `TypeThresholds`; calibrate them to your own reference samples
or a cluster analysis (Berghauser Pont & Olsson, 2017, ISUF Valencia). Classify
island-level values: `analyze` classifies fabrics from their net values
`FSI/(1−T)`, `GSI/(1−T)` to avoid the tare shift.

### Other density measures and TAKS / KAKS

Dwelling density (dwellings/ha) depends on dwelling size and the residential
share of floor area, so `dwellings_from_fsi` / `fsi_from_dwelling_density` make
the conversion explicit. Coverage is `100 × GSI`.

The Turkish *Planlı Alanlar İmar Yönetmeliği* (RG 30113, 3 July 2017, as
amended; mevzuat.gov.tr no. 23722) defines in Article 4 **TAKS** = taban alanı /
imar parseli alanı (cccc) and **KAKS (emsal)** = sum of floor areas / imar
parseli alanı (tt), where floor area (*katlar alanı*, şş) excludes the areas of
Article 22. Article 20(5) applies the base area to the net parcel and Article 5(8)
caps all emsal-exempt areas at 30 % of the emsal area. Therefore:

* `TAKS ≈ GSI_lot` and `KAKS ≈ FSI_lot` — both are **lot-level** (net) ratios;
* gross `FSI_lot = KAKS × (1 + e)` with exempt share `0 ≤ e ≤ 0.30`;
* island/fabric values need the tares: `taks_kaks_equivalent(taks, kaks, exempt_share=e, island_tare=T_i, fabric_tare=T_f)`;
* plan values are permitted maxima, not the built stock.

## Limitations

* Coordinates must be projected and metric; the readers refuse data that looks
  like longitude/latitude (override with `allow_geographic=True`). No reprojection.
* Pure-Python overlaps are O(buildings × zones) with a bounding-box filter; fine
  for neighbourhoods and districts, use `backend="shapely"` for larger sets.
  Polygons must be valid (simple rings, holes inside the exterior, parts of a
  multipolygon not overlapping).
* Edge streets are detected as centreline pieces within `edge_tolerance` (1 cm by
  default) of the zone boundary; draw fabric boundaries on the centrelines.
* Network density at district level uses the same rule as at fabric level.
* Type thresholds are approximate (see above); `THRESHOLDS_VERIFIED` is `False`.
* Floors from height assume a uniform storey height; basements are not counted.

## Academic citation

```bibtex
@software{eminoglu_spacematrix,
  author  = {Eminoğlu, Yusuf},
  title   = {spacematrix},
  year    = {2026},
  version = {0.1.0},
  url     = {https://github.com/YusufEminoglu/spacematrix_sdk}
}
```

Please also cite the method: Berghauser Pont, M. and Haupt, P. (2010).
*Spacematrix: Space, Density and Urban Form*. Rotterdam: NAi Publishers.

## License

MIT © 2026 Yusuf Eminoğlu. Contact: geospacephilo@gmail.com
