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.
Files changed (35) hide show
  1. spacematrix-0.1.0/CHANGELOG.md +21 -0
  2. spacematrix-0.1.0/CITATION.cff +31 -0
  3. spacematrix-0.1.0/LICENSE +21 -0
  4. spacematrix-0.1.0/MANIFEST.in +9 -0
  5. spacematrix-0.1.0/PKG-INFO +239 -0
  6. spacematrix-0.1.0/README.md +198 -0
  7. spacematrix-0.1.0/examples/quickstart.py +72 -0
  8. spacematrix-0.1.0/pyproject.toml +122 -0
  9. spacematrix-0.1.0/setup.cfg +4 -0
  10. spacematrix-0.1.0/src/spacematrix/__init__.py +107 -0
  11. spacematrix-0.1.0/src/spacematrix/__main__.py +7 -0
  12. spacematrix-0.1.0/src/spacematrix/_shapely.py +35 -0
  13. spacematrix-0.1.0/src/spacematrix/_version.py +1 -0
  14. spacematrix-0.1.0/src/spacematrix/analysis.py +255 -0
  15. spacematrix-0.1.0/src/spacematrix/classify.py +129 -0
  16. spacematrix-0.1.0/src/spacematrix/cli.py +254 -0
  17. spacematrix-0.1.0/src/spacematrix/compare.py +175 -0
  18. spacematrix-0.1.0/src/spacematrix/geometry.py +435 -0
  19. spacematrix-0.1.0/src/spacematrix/indicators.py +144 -0
  20. spacematrix-0.1.0/src/spacematrix/io.py +246 -0
  21. spacematrix-0.1.0/src/spacematrix/models.py +190 -0
  22. spacematrix-0.1.0/src/spacematrix/plot.py +124 -0
  23. spacematrix-0.1.0/src/spacematrix/py.typed +0 -0
  24. spacematrix-0.1.0/src/spacematrix.egg-info/PKG-INFO +239 -0
  25. spacematrix-0.1.0/src/spacematrix.egg-info/SOURCES.txt +33 -0
  26. spacematrix-0.1.0/src/spacematrix.egg-info/dependency_links.txt +1 -0
  27. spacematrix-0.1.0/src/spacematrix.egg-info/entry_points.txt +2 -0
  28. spacematrix-0.1.0/src/spacematrix.egg-info/requires.txt +16 -0
  29. spacematrix-0.1.0/src/spacematrix.egg-info/top_level.txt +1 -0
  30. spacematrix-0.1.0/tests/conftest.py +55 -0
  31. spacematrix-0.1.0/tests/test_analysis.py +189 -0
  32. spacematrix-0.1.0/tests/test_classify_compare.py +93 -0
  33. spacematrix-0.1.0/tests/test_geometry.py +171 -0
  34. spacematrix-0.1.0/tests/test_indicators.py +82 -0
  35. 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,9 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CITATION.cff
5
+ include pyproject.toml
6
+ include src/spacematrix/py.typed
7
+ recursive-include src/spacematrix *.py
8
+ recursive-include tests *.py
9
+ recursive-include examples *.py
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/spacematrix.svg)](https://pypi.org/project/spacematrix/)
45
+ [![Python versions](https://img.shields.io/pypi/pyversions/spacematrix.svg)](https://pypi.org/project/spacematrix/)
46
+ [![CI](https://github.com/YusufEminoglu/spacematrix_sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/YusufEminoglu/spacematrix_sdk/actions/workflows/ci.yml)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/spacematrix.svg)](https://pypi.org/project/spacematrix/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/spacematrix.svg)](https://pypi.org/project/spacematrix/)
5
+ [![CI](https://github.com/YusufEminoglu/spacematrix_sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/YusufEminoglu/spacematrix_sdk/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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()