spacematrix 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spacematrix-0.1.0/CHANGELOG.md +21 -0
- spacematrix-0.1.0/CITATION.cff +31 -0
- spacematrix-0.1.0/LICENSE +21 -0
- spacematrix-0.1.0/MANIFEST.in +9 -0
- spacematrix-0.1.0/PKG-INFO +239 -0
- spacematrix-0.1.0/README.md +198 -0
- spacematrix-0.1.0/examples/quickstart.py +72 -0
- spacematrix-0.1.0/pyproject.toml +122 -0
- spacematrix-0.1.0/setup.cfg +4 -0
- spacematrix-0.1.0/src/spacematrix/__init__.py +107 -0
- spacematrix-0.1.0/src/spacematrix/__main__.py +7 -0
- spacematrix-0.1.0/src/spacematrix/_shapely.py +35 -0
- spacematrix-0.1.0/src/spacematrix/_version.py +1 -0
- spacematrix-0.1.0/src/spacematrix/analysis.py +255 -0
- spacematrix-0.1.0/src/spacematrix/classify.py +129 -0
- spacematrix-0.1.0/src/spacematrix/cli.py +254 -0
- spacematrix-0.1.0/src/spacematrix/compare.py +175 -0
- spacematrix-0.1.0/src/spacematrix/geometry.py +435 -0
- spacematrix-0.1.0/src/spacematrix/indicators.py +144 -0
- spacematrix-0.1.0/src/spacematrix/io.py +246 -0
- spacematrix-0.1.0/src/spacematrix/models.py +190 -0
- spacematrix-0.1.0/src/spacematrix/plot.py +124 -0
- spacematrix-0.1.0/src/spacematrix/py.typed +0 -0
- spacematrix-0.1.0/src/spacematrix.egg-info/PKG-INFO +239 -0
- spacematrix-0.1.0/src/spacematrix.egg-info/SOURCES.txt +33 -0
- spacematrix-0.1.0/src/spacematrix.egg-info/dependency_links.txt +1 -0
- spacematrix-0.1.0/src/spacematrix.egg-info/entry_points.txt +2 -0
- spacematrix-0.1.0/src/spacematrix.egg-info/requires.txt +16 -0
- spacematrix-0.1.0/src/spacematrix.egg-info/top_level.txt +1 -0
- spacematrix-0.1.0/tests/conftest.py +55 -0
- spacematrix-0.1.0/tests/test_analysis.py +189 -0
- spacematrix-0.1.0/tests/test_classify_compare.py +93 -0
- spacematrix-0.1.0/tests/test_geometry.py +171 -0
- spacematrix-0.1.0/tests/test_indicators.py +82 -0
- spacematrix-0.1.0/tests/test_io_cli.py +335 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-11-09
|
|
8
|
+
### Added
|
|
9
|
+
- Spacematrix indicators FSI, GSI, OSR, L, network density N and tare T, with the
|
|
10
|
+
orthogonal-grid relations (mesh size `w = 2/N`, tare `(2bw - b²)/w²`, profile width).
|
|
11
|
+
- Measurement at lot, island, fabric and district level (`measure`, `analyze`,
|
|
12
|
+
`aggregate`), with footprint-overlap or centroid allocation of buildings.
|
|
13
|
+
- Pure-Python geometry: polygon area and centroid with holes, exact polygon overlap
|
|
14
|
+
(ear clipping + Sutherland-Hodgman), interior/edge split of street centrelines;
|
|
15
|
+
optional shapely backend.
|
|
16
|
+
- Building-type classification (low/mid/high-rise x point/strip/block) with
|
|
17
|
+
configurable, explicitly approximate thresholds.
|
|
18
|
+
- Spacemate diagram with iso-L and iso-OSR lines (`spacematrix[plot]`).
|
|
19
|
+
- Dwelling density conversions and Turkish TAKS / KAKS (emsal) equivalence.
|
|
20
|
+
- GeoJSON readers, CSV / GeoJSON writers and the `spacematrix` CLI
|
|
21
|
+
(`metrics`, `grid`, `taks-kaks`, `analyze`, `diagram`).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
authors:
|
|
4
|
+
- family-names: "Eminoğlu"
|
|
5
|
+
given-names: "Yusuf"
|
|
6
|
+
email: "geospacephilo@gmail.com"
|
|
7
|
+
orcid: "https://orcid.org/0009-0005-6000-2934"
|
|
8
|
+
title: "spacematrix"
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
date-released: "2026-11-09"
|
|
11
|
+
license: "MIT"
|
|
12
|
+
repository-code: "https://github.com/YusufEminoglu/spacematrix_sdk"
|
|
13
|
+
url: "https://pypi.org/project/spacematrix/"
|
|
14
|
+
abstract: "Spacematrix urban density indicators (FSI, GSI, OSR, L, network density N, tare T) at lot, island, fabric and district level after Berghauser Pont and Haupt (2010), with building-type classification, the Spacemate diagram and comparison with dwelling density and Turkish TAKS/KAKS."
|
|
15
|
+
keywords:
|
|
16
|
+
- spacematrix
|
|
17
|
+
- urban density
|
|
18
|
+
- urban morphology
|
|
19
|
+
- floor space index
|
|
20
|
+
- gis
|
|
21
|
+
references:
|
|
22
|
+
- type: book
|
|
23
|
+
title: "Spacematrix: Space, Density and Urban Form"
|
|
24
|
+
authors:
|
|
25
|
+
- family-names: "Berghauser Pont"
|
|
26
|
+
given-names: "Meta"
|
|
27
|
+
- family-names: "Haupt"
|
|
28
|
+
given-names: "Per"
|
|
29
|
+
year: 2010
|
|
30
|
+
publisher:
|
|
31
|
+
name: "NAi Publishers"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yusuf Eminoğlu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spacematrix
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
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.
|
|
5
|
+
Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/YusufEminoglu/spacematrix_sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/YusufEminoglu/spacematrix_sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/YusufEminoglu/spacematrix_sdk/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/YusufEminoglu/spacematrix_sdk/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: spacematrix,spacemate,urban-density,urban-morphology,floor-space-index,fsi,gsi,open-space-ratio,network-density,urban-form,taks,kaks,urban-planning,gis
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Environment :: Console
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
22
|
+
Classifier: Intended Audience :: Science/Research
|
|
23
|
+
Classifier: Intended Audience :: Developers
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Provides-Extra: plot
|
|
29
|
+
Requires-Dist: matplotlib>=3.5; extra == "plot"
|
|
30
|
+
Provides-Extra: shapely
|
|
31
|
+
Requires-Dist: shapely>=2.0; extra == "shapely"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: matplotlib>=3.5; extra == "all"
|
|
34
|
+
Requires-Dist: shapely>=2.0; extra == "all"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# spacematrix
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/spacematrix/)
|
|
45
|
+
[](https://pypi.org/project/spacematrix/)
|
|
46
|
+
[](https://github.com/YusufEminoglu/spacematrix_sdk/actions/workflows/ci.yml)
|
|
47
|
+
[](LICENSE)
|
|
48
|
+
|
|
49
|
+
**Urban density indicators after Berghauser Pont & Haupt's *Spacematrix* (2010).**
|
|
50
|
+
|
|
51
|
+
`spacematrix` measures building intensity (FSI), coverage (GSI), spaciousness
|
|
52
|
+
(OSR), average storeys (L), network density (N) and tare (T) for lots, islands
|
|
53
|
+
(urban blocks), fabrics and districts from building footprints, plot/block
|
|
54
|
+
polygons and street centrelines. It classifies samples into Spacematrix building
|
|
55
|
+
types, draws the Spacemate diagram with iso-L and iso-OSR lines, and relates the
|
|
56
|
+
indicators to dwelling density and to the Turkish TAKS / KAKS (emsal) rules.
|
|
57
|
+
|
|
58
|
+
The core runs on the Python standard library alone (pure-Python polygon area,
|
|
59
|
+
exact polygon overlap and line clipping). `shapely` and `matplotlib` are optional.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install spacematrix # stdlib only
|
|
63
|
+
pip install "spacematrix[plot]" # + Spacemate diagram (matplotlib)
|
|
64
|
+
pip install "spacematrix[shapely]" # + shapely overlap backend
|
|
65
|
+
pip install "spacematrix[all]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Requires Python 3.9 or newer.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import spacematrix as sm
|
|
76
|
+
|
|
77
|
+
lot = sm.Zone(sm.Polygon.rectangle(0, 0, 20, 30), id="lot-1") # 600 m2
|
|
78
|
+
house = sm.Building(sm.Polygon.rectangle(5, 5, 15, 17), floors=3) # 120 m2 x 3
|
|
79
|
+
|
|
80
|
+
r = sm.measure(lot, [house], level="lot")
|
|
81
|
+
r.fsi, r.gsi, r.osr, r.l # (0.6, 0.2, 1.333, 3.0)
|
|
82
|
+
r.type # 'low-rise strip' (approximate thresholds, see below)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
A whole hierarchy, from GeoJSON layers in a projected (metric) CRS:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
buildings = sm.read_buildings("buildings.geojson", floors_field="floors") # or height_field=
|
|
89
|
+
res = sm.analyze(
|
|
90
|
+
buildings,
|
|
91
|
+
lots=sm.read_zones("lots.geojson"),
|
|
92
|
+
islands=sm.read_zones("blocks.geojson"),
|
|
93
|
+
fabrics=sm.read_zones("fabrics.geojson"), # bounded by street centrelines
|
|
94
|
+
network=sm.read_network("streets.geojson"),
|
|
95
|
+
)
|
|
96
|
+
for f in res.fabrics:
|
|
97
|
+
print(f.id, f.fsi, f.gsi, f.n, f.tare, f.mesh_size, f.profile_width, f.type)
|
|
98
|
+
sm.write_csv(res.records(), "density.csv")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The Spacemate:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from spacematrix.plot import spacemate
|
|
105
|
+
ax = spacemate(res.islands, type_grid=sm.DEFAULT_THRESHOLDS)
|
|
106
|
+
ax.figure.savefig("spacemate.png", dpi=150)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Command line
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
spacematrix metrics --floor-area 76800 --built-area 19200 --area 40000 \
|
|
113
|
+
--interior-length 400 --edge-length 800 --lower-area 25600
|
|
114
|
+
spacematrix grid --w 100 --b 20 # N and T of an orthogonal grid
|
|
115
|
+
spacematrix taks-kaks --taks 0.30 --kaks 1.50 --exempt-share 0.2 \
|
|
116
|
+
--island-tare 0.1 --fabric-tare 0.3
|
|
117
|
+
spacematrix analyze --buildings b.geojson --lots lots.geojson --islands blocks.geojson \
|
|
118
|
+
--fabrics fabrics.geojson --network streets.geojson --height-field height \
|
|
119
|
+
--out density.csv --plot spacemate.png
|
|
120
|
+
spacematrix diagram density.csv -o islands.png --level island
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`python examples/quickstart.py` reproduces the worked grid example below.
|
|
124
|
+
|
|
125
|
+
## API overview
|
|
126
|
+
|
|
127
|
+
| Area | Names |
|
|
128
|
+
|---|---|
|
|
129
|
+
| Inputs | `Polygon`, `Building` (`floors` or `Building.from_height`), `Zone`, `Network`, `floors_from_height` |
|
|
130
|
+
| 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` |
|
|
131
|
+
| Measurement | `measure(zone, buildings, level=..., network=..., lower_level=...)`, `analyze(...)` → `SpacematrixResult`, `aggregate(records)`; records are `Density` objects with `as_dict()` |
|
|
132
|
+
| Types | `classify(fsi, gsi, thresholds)`, `TypeThresholds`, `DEFAULT_THRESHOLDS`, `THRESHOLDS_VERIFIED` (= `False`) |
|
|
133
|
+
| 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` |
|
|
134
|
+
| Geometry / IO | `shape_area`, `intersection_area` (`backend="python"` or `"shapely"`), `split_line_length`, `read_buildings`, `read_zones`, `read_network`, `write_csv`, `write_geojson` |
|
|
135
|
+
| Diagram | `spacematrix.plot.spacemate(samples, type_grid=..., iso_l=..., iso_osr=...)` |
|
|
136
|
+
|
|
137
|
+
## Method and sources
|
|
138
|
+
|
|
139
|
+
Definitions from Berghauser Pont, M. and Haupt, P. (2010), *Spacematrix: Space,
|
|
140
|
+
Density and Urban Form*, Rotterdam: NAi Publishers. `F` gross floor area, `B`
|
|
141
|
+
built-up (footprint) area, `A` plan area of level *x*:
|
|
142
|
+
|
|
143
|
+
| Indicator | Formula | Notes |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| FSI | `F / A` | floor space index (intensity) |
|
|
146
|
+
| GSI | `B / A` | ground space index (coverage) |
|
|
147
|
+
| OSR | `(1 − GSI) / FSI` | unbuilt ground per m² of floor area (spaciousness) |
|
|
148
|
+
| L | `FSI / GSI` | average number of storeys |
|
|
149
|
+
| N | `(l_i + l_e / 2) / A_f` | m of street per m² of fabric; edge streets (on the fabric boundary) count half |
|
|
150
|
+
| T | `(A_x − A_y) / A_x` | tare: share of level *x* not taken by the lower level *y* |
|
|
151
|
+
| w | `2 / N` | mesh size of the equivalent orthogonal grid |
|
|
152
|
+
| b | `2 (1 − √(1 − T)) / N` | profile width; inverse of `T = (2bw − b²) / w²` |
|
|
153
|
+
|
|
154
|
+
Levels: **lot** (plot) ⊂ **island** (block, island tare = internal paths and
|
|
155
|
+
communal space) ⊂ **fabric** (islands plus the streets around them, bounded by
|
|
156
|
+
street centrelines; fabric tare ≈ street space) ⊂ **district** (plus large parks,
|
|
157
|
+
water, infrastructure). Tare lowers FSI and GSI by the same factor
|
|
158
|
+
(`FSI_x = FSI_y (1 − T_x)`), so L is the same at every level.
|
|
159
|
+
|
|
160
|
+
Building floor area is `footprint × floors` (or a measured value). Floors can be
|
|
161
|
+
estimated from height with a configurable storey height (default 3.0 m, a
|
|
162
|
+
convention, not a standard). Buildings are allocated to zones by footprint
|
|
163
|
+
overlap (exact, buildings straddling a boundary are split) or by centroid.
|
|
164
|
+
|
|
165
|
+
### Worked example (tested)
|
|
166
|
+
|
|
167
|
+
Orthogonal 2 × 2 grid, street centrelines every 100 m (w = 100 m), 20 m
|
|
168
|
+
profiles (b = 20 m), 80 × 80 m islands each with a four-storey perimeter block
|
|
169
|
+
around a 40 × 40 m courtyard:
|
|
170
|
+
|
|
171
|
+
| Level | A (m²) | FSI | GSI | OSR | L | N (m/m²) | T |
|
|
172
|
+
|---|---|---|---|---|---|---|---|
|
|
173
|
+
| island | 6 400 | 3.00 | 0.75 | 0.083 | 4 | – | – |
|
|
174
|
+
| fabric | 40 000 | 1.92 | 0.48 | 0.271 | 4 | 0.02 (= 2/w) | 0.36 (= (2bw−b²)/w²) |
|
|
175
|
+
|
|
176
|
+
`profile_width(0.36, 0.02) = 20 m` recovers b.
|
|
177
|
+
|
|
178
|
+
### Building types
|
|
179
|
+
|
|
180
|
+
`classify` assigns one of nine types — low-/mid-/high-rise × point/strip/block —
|
|
181
|
+
from L (height class; bands between iso-L rays) and GSI (form class). The book
|
|
182
|
+
identifies these types as clusters in the Spacemate; **the numeric cut-offs used
|
|
183
|
+
here (L ≤ 3.5 low-rise, L ≤ 7.5 mid-rise; GSI < 0.20 point, GSI < 0.35 strip,
|
|
184
|
+
otherwise block) are approximations that were not verified against the book**.
|
|
185
|
+
They are fields of `TypeThresholds`; calibrate them to your own reference samples
|
|
186
|
+
or a cluster analysis (Berghauser Pont & Olsson, 2017, ISUF Valencia). Classify
|
|
187
|
+
island-level values: `analyze` classifies fabrics from their net values
|
|
188
|
+
`FSI/(1−T)`, `GSI/(1−T)` to avoid the tare shift.
|
|
189
|
+
|
|
190
|
+
### Other density measures and TAKS / KAKS
|
|
191
|
+
|
|
192
|
+
Dwelling density (dwellings/ha) depends on dwelling size and the residential
|
|
193
|
+
share of floor area, so `dwellings_from_fsi` / `fsi_from_dwelling_density` make
|
|
194
|
+
the conversion explicit. Coverage is `100 × GSI`.
|
|
195
|
+
|
|
196
|
+
The Turkish *Planlı Alanlar İmar Yönetmeliği* (RG 30113, 3 July 2017, as
|
|
197
|
+
amended; mevzuat.gov.tr no. 23722) defines in Article 4 **TAKS** = taban alanı /
|
|
198
|
+
imar parseli alanı (cccc) and **KAKS (emsal)** = sum of floor areas / imar
|
|
199
|
+
parseli alanı (tt), where floor area (*katlar alanı*, şş) excludes the areas of
|
|
200
|
+
Article 22. Article 20(5) applies the base area to the net parcel and Article 5(8)
|
|
201
|
+
caps all emsal-exempt areas at 30 % of the emsal area. Therefore:
|
|
202
|
+
|
|
203
|
+
* `TAKS ≈ GSI_lot` and `KAKS ≈ FSI_lot` — both are **lot-level** (net) ratios;
|
|
204
|
+
* gross `FSI_lot = KAKS × (1 + e)` with exempt share `0 ≤ e ≤ 0.30`;
|
|
205
|
+
* island/fabric values need the tares: `taks_kaks_equivalent(taks, kaks, exempt_share=e, island_tare=T_i, fabric_tare=T_f)`;
|
|
206
|
+
* plan values are permitted maxima, not the built stock.
|
|
207
|
+
|
|
208
|
+
## Limitations
|
|
209
|
+
|
|
210
|
+
* Coordinates must be projected and metric; the readers refuse data that looks
|
|
211
|
+
like longitude/latitude (override with `allow_geographic=True`). No reprojection.
|
|
212
|
+
* Pure-Python overlaps are O(buildings × zones) with a bounding-box filter; fine
|
|
213
|
+
for neighbourhoods and districts, use `backend="shapely"` for larger sets.
|
|
214
|
+
Polygons must be valid (simple rings, holes inside the exterior, parts of a
|
|
215
|
+
multipolygon not overlapping).
|
|
216
|
+
* Edge streets are detected as centreline pieces within `edge_tolerance` (1 cm by
|
|
217
|
+
default) of the zone boundary; draw fabric boundaries on the centrelines.
|
|
218
|
+
* Network density at district level uses the same rule as at fabric level.
|
|
219
|
+
* Type thresholds are approximate (see above); `THRESHOLDS_VERIFIED` is `False`.
|
|
220
|
+
* Floors from height assume a uniform storey height; basements are not counted.
|
|
221
|
+
|
|
222
|
+
## Academic citation
|
|
223
|
+
|
|
224
|
+
```bibtex
|
|
225
|
+
@software{eminoglu_spacematrix,
|
|
226
|
+
author = {Eminoğlu, Yusuf},
|
|
227
|
+
title = {spacematrix},
|
|
228
|
+
year = {2026},
|
|
229
|
+
version = {0.1.0},
|
|
230
|
+
url = {https://github.com/YusufEminoglu/spacematrix_sdk}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Please also cite the method: Berghauser Pont, M. and Haupt, P. (2010).
|
|
235
|
+
*Spacematrix: Space, Density and Urban Form*. Rotterdam: NAi Publishers.
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT © 2026 Yusuf Eminoğlu. Contact: geospacephilo@gmail.com
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# spacematrix
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/spacematrix/)
|
|
4
|
+
[](https://pypi.org/project/spacematrix/)
|
|
5
|
+
[](https://github.com/YusufEminoglu/spacematrix_sdk/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**Urban density indicators after Berghauser Pont & Haupt's *Spacematrix* (2010).**
|
|
9
|
+
|
|
10
|
+
`spacematrix` measures building intensity (FSI), coverage (GSI), spaciousness
|
|
11
|
+
(OSR), average storeys (L), network density (N) and tare (T) for lots, islands
|
|
12
|
+
(urban blocks), fabrics and districts from building footprints, plot/block
|
|
13
|
+
polygons and street centrelines. It classifies samples into Spacematrix building
|
|
14
|
+
types, draws the Spacemate diagram with iso-L and iso-OSR lines, and relates the
|
|
15
|
+
indicators to dwelling density and to the Turkish TAKS / KAKS (emsal) rules.
|
|
16
|
+
|
|
17
|
+
The core runs on the Python standard library alone (pure-Python polygon area,
|
|
18
|
+
exact polygon overlap and line clipping). `shapely` and `matplotlib` are optional.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install spacematrix # stdlib only
|
|
22
|
+
pip install "spacematrix[plot]" # + Spacemate diagram (matplotlib)
|
|
23
|
+
pip install "spacematrix[shapely]" # + shapely overlap backend
|
|
24
|
+
pip install "spacematrix[all]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Python 3.9 or newer.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import spacematrix as sm
|
|
35
|
+
|
|
36
|
+
lot = sm.Zone(sm.Polygon.rectangle(0, 0, 20, 30), id="lot-1") # 600 m2
|
|
37
|
+
house = sm.Building(sm.Polygon.rectangle(5, 5, 15, 17), floors=3) # 120 m2 x 3
|
|
38
|
+
|
|
39
|
+
r = sm.measure(lot, [house], level="lot")
|
|
40
|
+
r.fsi, r.gsi, r.osr, r.l # (0.6, 0.2, 1.333, 3.0)
|
|
41
|
+
r.type # 'low-rise strip' (approximate thresholds, see below)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A whole hierarchy, from GeoJSON layers in a projected (metric) CRS:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
buildings = sm.read_buildings("buildings.geojson", floors_field="floors") # or height_field=
|
|
48
|
+
res = sm.analyze(
|
|
49
|
+
buildings,
|
|
50
|
+
lots=sm.read_zones("lots.geojson"),
|
|
51
|
+
islands=sm.read_zones("blocks.geojson"),
|
|
52
|
+
fabrics=sm.read_zones("fabrics.geojson"), # bounded by street centrelines
|
|
53
|
+
network=sm.read_network("streets.geojson"),
|
|
54
|
+
)
|
|
55
|
+
for f in res.fabrics:
|
|
56
|
+
print(f.id, f.fsi, f.gsi, f.n, f.tare, f.mesh_size, f.profile_width, f.type)
|
|
57
|
+
sm.write_csv(res.records(), "density.csv")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The Spacemate:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from spacematrix.plot import spacemate
|
|
64
|
+
ax = spacemate(res.islands, type_grid=sm.DEFAULT_THRESHOLDS)
|
|
65
|
+
ax.figure.savefig("spacemate.png", dpi=150)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Command line
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
spacematrix metrics --floor-area 76800 --built-area 19200 --area 40000 \
|
|
72
|
+
--interior-length 400 --edge-length 800 --lower-area 25600
|
|
73
|
+
spacematrix grid --w 100 --b 20 # N and T of an orthogonal grid
|
|
74
|
+
spacematrix taks-kaks --taks 0.30 --kaks 1.50 --exempt-share 0.2 \
|
|
75
|
+
--island-tare 0.1 --fabric-tare 0.3
|
|
76
|
+
spacematrix analyze --buildings b.geojson --lots lots.geojson --islands blocks.geojson \
|
|
77
|
+
--fabrics fabrics.geojson --network streets.geojson --height-field height \
|
|
78
|
+
--out density.csv --plot spacemate.png
|
|
79
|
+
spacematrix diagram density.csv -o islands.png --level island
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`python examples/quickstart.py` reproduces the worked grid example below.
|
|
83
|
+
|
|
84
|
+
## API overview
|
|
85
|
+
|
|
86
|
+
| Area | Names |
|
|
87
|
+
|---|---|
|
|
88
|
+
| Inputs | `Polygon`, `Building` (`floors` or `Building.from_height`), `Zone`, `Network`, `floors_from_height` |
|
|
89
|
+
| 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` |
|
|
90
|
+
| Measurement | `measure(zone, buildings, level=..., network=..., lower_level=...)`, `analyze(...)` → `SpacematrixResult`, `aggregate(records)`; records are `Density` objects with `as_dict()` |
|
|
91
|
+
| Types | `classify(fsi, gsi, thresholds)`, `TypeThresholds`, `DEFAULT_THRESHOLDS`, `THRESHOLDS_VERIFIED` (= `False`) |
|
|
92
|
+
| 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` |
|
|
93
|
+
| Geometry / IO | `shape_area`, `intersection_area` (`backend="python"` or `"shapely"`), `split_line_length`, `read_buildings`, `read_zones`, `read_network`, `write_csv`, `write_geojson` |
|
|
94
|
+
| Diagram | `spacematrix.plot.spacemate(samples, type_grid=..., iso_l=..., iso_osr=...)` |
|
|
95
|
+
|
|
96
|
+
## Method and sources
|
|
97
|
+
|
|
98
|
+
Definitions from Berghauser Pont, M. and Haupt, P. (2010), *Spacematrix: Space,
|
|
99
|
+
Density and Urban Form*, Rotterdam: NAi Publishers. `F` gross floor area, `B`
|
|
100
|
+
built-up (footprint) area, `A` plan area of level *x*:
|
|
101
|
+
|
|
102
|
+
| Indicator | Formula | Notes |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| FSI | `F / A` | floor space index (intensity) |
|
|
105
|
+
| GSI | `B / A` | ground space index (coverage) |
|
|
106
|
+
| OSR | `(1 − GSI) / FSI` | unbuilt ground per m² of floor area (spaciousness) |
|
|
107
|
+
| L | `FSI / GSI` | average number of storeys |
|
|
108
|
+
| N | `(l_i + l_e / 2) / A_f` | m of street per m² of fabric; edge streets (on the fabric boundary) count half |
|
|
109
|
+
| T | `(A_x − A_y) / A_x` | tare: share of level *x* not taken by the lower level *y* |
|
|
110
|
+
| w | `2 / N` | mesh size of the equivalent orthogonal grid |
|
|
111
|
+
| b | `2 (1 − √(1 − T)) / N` | profile width; inverse of `T = (2bw − b²) / w²` |
|
|
112
|
+
|
|
113
|
+
Levels: **lot** (plot) ⊂ **island** (block, island tare = internal paths and
|
|
114
|
+
communal space) ⊂ **fabric** (islands plus the streets around them, bounded by
|
|
115
|
+
street centrelines; fabric tare ≈ street space) ⊂ **district** (plus large parks,
|
|
116
|
+
water, infrastructure). Tare lowers FSI and GSI by the same factor
|
|
117
|
+
(`FSI_x = FSI_y (1 − T_x)`), so L is the same at every level.
|
|
118
|
+
|
|
119
|
+
Building floor area is `footprint × floors` (or a measured value). Floors can be
|
|
120
|
+
estimated from height with a configurable storey height (default 3.0 m, a
|
|
121
|
+
convention, not a standard). Buildings are allocated to zones by footprint
|
|
122
|
+
overlap (exact, buildings straddling a boundary are split) or by centroid.
|
|
123
|
+
|
|
124
|
+
### Worked example (tested)
|
|
125
|
+
|
|
126
|
+
Orthogonal 2 × 2 grid, street centrelines every 100 m (w = 100 m), 20 m
|
|
127
|
+
profiles (b = 20 m), 80 × 80 m islands each with a four-storey perimeter block
|
|
128
|
+
around a 40 × 40 m courtyard:
|
|
129
|
+
|
|
130
|
+
| Level | A (m²) | FSI | GSI | OSR | L | N (m/m²) | T |
|
|
131
|
+
|---|---|---|---|---|---|---|---|
|
|
132
|
+
| island | 6 400 | 3.00 | 0.75 | 0.083 | 4 | – | – |
|
|
133
|
+
| fabric | 40 000 | 1.92 | 0.48 | 0.271 | 4 | 0.02 (= 2/w) | 0.36 (= (2bw−b²)/w²) |
|
|
134
|
+
|
|
135
|
+
`profile_width(0.36, 0.02) = 20 m` recovers b.
|
|
136
|
+
|
|
137
|
+
### Building types
|
|
138
|
+
|
|
139
|
+
`classify` assigns one of nine types — low-/mid-/high-rise × point/strip/block —
|
|
140
|
+
from L (height class; bands between iso-L rays) and GSI (form class). The book
|
|
141
|
+
identifies these types as clusters in the Spacemate; **the numeric cut-offs used
|
|
142
|
+
here (L ≤ 3.5 low-rise, L ≤ 7.5 mid-rise; GSI < 0.20 point, GSI < 0.35 strip,
|
|
143
|
+
otherwise block) are approximations that were not verified against the book**.
|
|
144
|
+
They are fields of `TypeThresholds`; calibrate them to your own reference samples
|
|
145
|
+
or a cluster analysis (Berghauser Pont & Olsson, 2017, ISUF Valencia). Classify
|
|
146
|
+
island-level values: `analyze` classifies fabrics from their net values
|
|
147
|
+
`FSI/(1−T)`, `GSI/(1−T)` to avoid the tare shift.
|
|
148
|
+
|
|
149
|
+
### Other density measures and TAKS / KAKS
|
|
150
|
+
|
|
151
|
+
Dwelling density (dwellings/ha) depends on dwelling size and the residential
|
|
152
|
+
share of floor area, so `dwellings_from_fsi` / `fsi_from_dwelling_density` make
|
|
153
|
+
the conversion explicit. Coverage is `100 × GSI`.
|
|
154
|
+
|
|
155
|
+
The Turkish *Planlı Alanlar İmar Yönetmeliği* (RG 30113, 3 July 2017, as
|
|
156
|
+
amended; mevzuat.gov.tr no. 23722) defines in Article 4 **TAKS** = taban alanı /
|
|
157
|
+
imar parseli alanı (cccc) and **KAKS (emsal)** = sum of floor areas / imar
|
|
158
|
+
parseli alanı (tt), where floor area (*katlar alanı*, şş) excludes the areas of
|
|
159
|
+
Article 22. Article 20(5) applies the base area to the net parcel and Article 5(8)
|
|
160
|
+
caps all emsal-exempt areas at 30 % of the emsal area. Therefore:
|
|
161
|
+
|
|
162
|
+
* `TAKS ≈ GSI_lot` and `KAKS ≈ FSI_lot` — both are **lot-level** (net) ratios;
|
|
163
|
+
* gross `FSI_lot = KAKS × (1 + e)` with exempt share `0 ≤ e ≤ 0.30`;
|
|
164
|
+
* island/fabric values need the tares: `taks_kaks_equivalent(taks, kaks, exempt_share=e, island_tare=T_i, fabric_tare=T_f)`;
|
|
165
|
+
* plan values are permitted maxima, not the built stock.
|
|
166
|
+
|
|
167
|
+
## Limitations
|
|
168
|
+
|
|
169
|
+
* Coordinates must be projected and metric; the readers refuse data that looks
|
|
170
|
+
like longitude/latitude (override with `allow_geographic=True`). No reprojection.
|
|
171
|
+
* Pure-Python overlaps are O(buildings × zones) with a bounding-box filter; fine
|
|
172
|
+
for neighbourhoods and districts, use `backend="shapely"` for larger sets.
|
|
173
|
+
Polygons must be valid (simple rings, holes inside the exterior, parts of a
|
|
174
|
+
multipolygon not overlapping).
|
|
175
|
+
* Edge streets are detected as centreline pieces within `edge_tolerance` (1 cm by
|
|
176
|
+
default) of the zone boundary; draw fabric boundaries on the centrelines.
|
|
177
|
+
* Network density at district level uses the same rule as at fabric level.
|
|
178
|
+
* Type thresholds are approximate (see above); `THRESHOLDS_VERIFIED` is `False`.
|
|
179
|
+
* Floors from height assume a uniform storey height; basements are not counted.
|
|
180
|
+
|
|
181
|
+
## Academic citation
|
|
182
|
+
|
|
183
|
+
```bibtex
|
|
184
|
+
@software{eminoglu_spacematrix,
|
|
185
|
+
author = {Eminoğlu, Yusuf},
|
|
186
|
+
title = {spacematrix},
|
|
187
|
+
year = {2026},
|
|
188
|
+
version = {0.1.0},
|
|
189
|
+
url = {https://github.com/YusufEminoglu/spacematrix_sdk}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Please also cite the method: Berghauser Pont, M. and Haupt, P. (2010).
|
|
194
|
+
*Spacematrix: Space, Density and Urban Form*. Rotterdam: NAi Publishers.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT © 2026 Yusuf Eminoğlu. Contact: geospacephilo@gmail.com
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""spacematrix quick start: a hand-checkable 2 x 2 street grid.
|
|
2
|
+
|
|
3
|
+
Street centrelines every 100 m (mesh size w = 100 m), 20 m street profiles
|
|
4
|
+
(b = 20 m), so each island is 80 x 80 m. Every island holds a four-storey
|
|
5
|
+
perimeter block with a 40 x 40 m courtyard.
|
|
6
|
+
|
|
7
|
+
Expected (see the README): island FSI 3.0, GSI 0.75, L 4, OSR 0.083;
|
|
8
|
+
fabric T 0.36, FSI 1.92, GSI 0.48, N 0.02 m/m² (w = 100 m, b = 20 m).
|
|
9
|
+
|
|
10
|
+
python examples/quickstart.py # prints the table
|
|
11
|
+
python examples/quickstart.py plot.png # also draws the Spacemate (needs matplotlib)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
import spacematrix as sm
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_grid():
|
|
22
|
+
islands, buildings = [], []
|
|
23
|
+
for ox in (10.0, 110.0):
|
|
24
|
+
for oy in (10.0, 110.0):
|
|
25
|
+
islands.append(
|
|
26
|
+
sm.Zone(sm.Polygon.rectangle(ox, oy, ox + 80, oy + 80), id=f"{ox:g},{oy:g}")
|
|
27
|
+
)
|
|
28
|
+
outline = [(ox, oy), (ox + 80, oy), (ox + 80, oy + 80), (ox, oy + 80)]
|
|
29
|
+
court = [(ox + 20, oy + 20), (ox + 60, oy + 20), (ox + 60, oy + 60), (ox + 20, oy + 60)]
|
|
30
|
+
buildings.append(sm.Building(sm.Polygon.from_coords(outline, [court]), floors=4))
|
|
31
|
+
lines = []
|
|
32
|
+
for c in (0.0, 100.0, 200.0):
|
|
33
|
+
lines += [[(c, 0.0), (c, 200.0)], [(0.0, c), (200.0, c)]]
|
|
34
|
+
fabric = sm.Zone(sm.Polygon.rectangle(0, 0, 200, 200), id="fabric")
|
|
35
|
+
return buildings, islands, fabric, sm.Network.from_lines(lines)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main() -> None:
|
|
39
|
+
buildings, islands, fabric, network = build_grid()
|
|
40
|
+
res = sm.analyze(buildings, islands=islands, fabrics=[fabric], network=network)
|
|
41
|
+
print(
|
|
42
|
+
f"{'level':8} {'id':10} {'FSI':>6} {'GSI':>6} {'OSR':>6} {'L':>5} {'N':>7} {'T':>6} type"
|
|
43
|
+
)
|
|
44
|
+
for r in res.records():
|
|
45
|
+
n = f"{r.n:.4f}" if r.n is not None else "-"
|
|
46
|
+
t = f"{r.tare:.3f}" if r.tare is not None else "-"
|
|
47
|
+
print(
|
|
48
|
+
f"{r.level:8} {r.id!s:10} {r.fsi:6.3f} {r.gsi:6.3f} {r.osr:6.3f} {r.l:5.2f} "
|
|
49
|
+
f"{n:>7} {t:>6} {r.type}"
|
|
50
|
+
)
|
|
51
|
+
f = res.fabrics[0]
|
|
52
|
+
print(f"\nmesh size w = {f.mesh_size:.1f} m, profile width b = {f.profile_width:.1f} m")
|
|
53
|
+
|
|
54
|
+
eq = sm.taks_kaks_equivalent(0.30, 1.50, exempt_share=0.20, island_tare=0.0, fabric_tare=0.36)
|
|
55
|
+
print(
|
|
56
|
+
f"TAKS 0.30 / KAKS 1.50 (+20 % emsal-exempt): FSI_lot {eq.fsi_lot:.2f}, "
|
|
57
|
+
f"L {eq.l:.1f}, FSI_fabric {eq.fsi_fabric:.3f} -> {sm.classify(eq.fsi_lot, eq.gsi_lot).label}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if len(sys.argv) > 1:
|
|
61
|
+
import matplotlib
|
|
62
|
+
|
|
63
|
+
matplotlib.use("Agg")
|
|
64
|
+
from spacematrix.plot import spacemate
|
|
65
|
+
|
|
66
|
+
ax = spacemate(res.islands + res.fabrics, type_grid=sm.DEFAULT_THRESHOLDS)
|
|
67
|
+
ax.figure.savefig(sys.argv[1], dpi=150, bbox_inches="tight")
|
|
68
|
+
print(f"wrote {sys.argv[1]}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
main()
|