sprawlmetrics 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.
- sprawlmetrics-0.1.0/CHANGELOG.md +24 -0
- sprawlmetrics-0.1.0/CITATION.cff +21 -0
- sprawlmetrics-0.1.0/LICENSE +21 -0
- sprawlmetrics-0.1.0/MANIFEST.in +9 -0
- sprawlmetrics-0.1.0/PKG-INFO +253 -0
- sprawlmetrics-0.1.0/README.md +214 -0
- sprawlmetrics-0.1.0/examples/quickstart.py +70 -0
- sprawlmetrics-0.1.0/pyproject.toml +120 -0
- sprawlmetrics-0.1.0/setup.cfg +4 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/__init__.py +107 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/__main__.py +7 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/_common.py +215 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/_version.py +1 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/analysis.py +197 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/cli.py +211 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/compactness.py +143 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/entropy.py +118 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/gradient.py +209 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/growth.py +107 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/io.py +146 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/lei.py +254 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/patches.py +236 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/py.typed +0 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/sdg.py +94 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics/zones.py +186 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics.egg-info/PKG-INFO +253 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics.egg-info/SOURCES.txt +38 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics.egg-info/dependency_links.txt +1 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics.egg-info/entry_points.txt +2 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics.egg-info/requires.txt +13 -0
- sprawlmetrics-0.1.0/src/sprawlmetrics.egg-info/top_level.txt +1 -0
- sprawlmetrics-0.1.0/tests/conftest.py +47 -0
- sprawlmetrics-0.1.0/tests/test_compactness.py +61 -0
- sprawlmetrics-0.1.0/tests/test_entropy_gradient.py +143 -0
- sprawlmetrics-0.1.0/tests/test_growth_sdg.py +86 -0
- sprawlmetrics-0.1.0/tests/test_io_cli.py +141 -0
- sprawlmetrics-0.1.0/tests/test_labels.py +89 -0
- sprawlmetrics-0.1.0/tests/test_lei.py +115 -0
- sprawlmetrics-0.1.0/tests/test_patches.py +103 -0
- sprawlmetrics-0.1.0/tests/test_zones_analysis.py +104 -0
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
- Growth metrics: built-up area and share, total growth rate, annual growth rate (linear,
|
|
10
|
+
compound, exponential), annual expansion area, urban expansion intensity index (UEII).
|
|
11
|
+
- Landscape Expansion Index (Liu et al. 2010) per new patch with raster buffers (square or
|
|
12
|
+
disc, any width), infilling / edge-expansion / outlying classification, MEI, AWMEI,
|
|
13
|
+
growth-type raster and per-patch records.
|
|
14
|
+
- FRAGSTATS-style class metrics: NP, total area, mean patch area, LPI, total edge, ED, PD,
|
|
15
|
+
mean and area-weighted shape index, patch compactness; per-patch table.
|
|
16
|
+
- Footprint compactness: normalised moment of inertia, exchange index, proximity index.
|
|
17
|
+
- Shannon entropy across zones (Yeh & Li 2001), ring and sector zone builders.
|
|
18
|
+
- Concentric-ring density profile with Clark (1951) negative exponential fit.
|
|
19
|
+
- SDG 11.3.1 land consumption rate, population growth rate and LCRPGR.
|
|
20
|
+
- Per-zone summaries with LEI growth-type areas; CSV writer.
|
|
21
|
+
- `compare()` one-call report with JSON-safe `to_dict()`.
|
|
22
|
+
- Pure-NumPy run-length connected-component labelling (SciPy used when installed).
|
|
23
|
+
- Raster I/O: `.npy` with NumPy, GeoTIFF via the optional `raster` extra (rasterio).
|
|
24
|
+
- Command line: `sprawlmetrics compare | patches | gradient | lcrpgr`.
|
|
@@ -0,0 +1,21 @@
|
|
|
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: "sprawlmetrics"
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
date-released: "2026-11-09"
|
|
11
|
+
license: "MIT"
|
|
12
|
+
repository-code: "https://gitlab.com/geophilo1/sprawlmetrics_sdk"
|
|
13
|
+
url: "https://pypi.org/project/sprawlmetrics/"
|
|
14
|
+
abstract: "Urban expansion and sprawl metrics from multi-date binary built-up rasters: growth rates and expansion intensity, the Landscape Expansion Index (infilling, edge-expansion, outlying), FRAGSTATS-style patch metrics, footprint compactness, Shannon entropy across zones, concentric-ring density gradients with Clark's negative exponential fit, the SDG 11.3.1 land consumption to population growth ratio and per-zone summaries."
|
|
15
|
+
keywords:
|
|
16
|
+
- urban expansion
|
|
17
|
+
- urban sprawl
|
|
18
|
+
- landscape expansion index
|
|
19
|
+
- landscape metrics
|
|
20
|
+
- SDG 11.3.1
|
|
21
|
+
- gis
|
|
@@ -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,253 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sprawlmetrics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Urban expansion and sprawl metrics from multi-date binary built-up rasters: growth rates, Landscape Expansion Index, patch metrics, compactness, entropy, density gradients and SDG 11.3.1.
|
|
5
|
+
Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/geophilo1/sprawlmetrics_sdk
|
|
8
|
+
Project-URL: Repository, https://gitlab.com/geophilo1/sprawlmetrics_sdk
|
|
9
|
+
Project-URL: Issues, https://gitlab.com/geophilo1/sprawlmetrics_sdk/-/work_items
|
|
10
|
+
Project-URL: Changelog, https://gitlab.com/geophilo1/sprawlmetrics_sdk/-/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: urban-expansion,urban-sprawl,landscape-expansion-index,lei,landscape-metrics,fragstats,shannon-entropy,density-gradient,compactness,built-up,sdg-11,remote-sensing,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
|
+
Requires-Dist: numpy>=1.21
|
|
29
|
+
Provides-Extra: fast
|
|
30
|
+
Requires-Dist: scipy>=1.7; extra == "fast"
|
|
31
|
+
Provides-Extra: raster
|
|
32
|
+
Requires-Dist: rasterio>=1.2; extra == "raster"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# sprawlmetrics
|
|
41
|
+
|
|
42
|
+
[](https://pypi.org/project/sprawlmetrics/)
|
|
43
|
+
[](https://pypi.org/project/sprawlmetrics/)
|
|
44
|
+
[](https://gitlab.com/geophilo1/sprawlmetrics_sdk/-/pipelines)
|
|
45
|
+
[](https://gitlab.com/geophilo1/sprawlmetrics_sdk/-/blob/main/LICENSE)
|
|
46
|
+
|
|
47
|
+
**Urban expansion and sprawl metrics from multi-date binary built-up rasters.**
|
|
48
|
+
|
|
49
|
+
`sprawlmetrics` takes two (or more) built-up / non-built-up rasters of the same grid, for
|
|
50
|
+
example classified Landsat or Sentinel-2 scenes or GHSL built-up layers, and computes the
|
|
51
|
+
standard indicators used in urban expansion studies:
|
|
52
|
+
|
|
53
|
+
- **Growth**: built-up area, total and annual growth rate (linear, compound, exponential),
|
|
54
|
+
annual expansion area, and the **urban expansion intensity index** (UEII).
|
|
55
|
+
- **Landscape Expansion Index** (LEI; Liu et al. 2010): sorts every new patch into
|
|
56
|
+
*infilling*, *edge-expansion* or *outlying* growth, and reports MEI and AWMEI.
|
|
57
|
+
- **Patch metrics** (FRAGSTATS definitions): number of patches, mean patch size, largest
|
|
58
|
+
patch index, edge density, patch density, mean and area-weighted shape index, and patch
|
|
59
|
+
compactness.
|
|
60
|
+
- **Footprint compactness**: normalised moment of inertia, exchange index and proximity
|
|
61
|
+
index.
|
|
62
|
+
- **Shannon entropy** of built-up land across rings, sectors or any zones
|
|
63
|
+
(Yeh & Li 2001).
|
|
64
|
+
- **Density gradient**: built-up density in concentric rings around a centre, with
|
|
65
|
+
Clark's (1951) negative exponential fit `D(r) = D0 exp(-b r)`.
|
|
66
|
+
- **SDG 11.3.1**: land consumption rate, population growth rate and their ratio (LCRPGR).
|
|
67
|
+
- **Per-zone summaries** (districts, rings, grid cells), written as CSV.
|
|
68
|
+
|
|
69
|
+
It needs only NumPy. SciPy (optional) speeds up patch labelling, and rasterio (optional)
|
|
70
|
+
reads and writes GeoTIFFs. A command-line tool is included.
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install sprawlmetrics # NumPy only
|
|
76
|
+
pip install "sprawlmetrics[fast]" # + SciPy (faster connected-component labelling)
|
|
77
|
+
pip install "sprawlmetrics[raster]" # + rasterio (GeoTIFF and other GDAL rasters)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Requires Python 3.9 or newer.
|
|
81
|
+
|
|
82
|
+
## Quickstart
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import numpy as np
|
|
86
|
+
import sprawlmetrics as sm
|
|
87
|
+
|
|
88
|
+
# 0/1 rasters on the same 30 m grid (here: a synthetic city)
|
|
89
|
+
t1 = np.zeros((200, 200), dtype=np.uint8)
|
|
90
|
+
t1[80:120, 80:120] = 1
|
|
91
|
+
t2 = t1.copy()
|
|
92
|
+
t2[75:125, 75:125] = 1 # edge expansion around the core
|
|
93
|
+
t2[20:24, 150:154] = 1 # an outlying settlement
|
|
94
|
+
|
|
95
|
+
report = sm.compare(t1, t2, years=10, cell_size=30, ring_width=300,
|
|
96
|
+
population=(250_000, 310_000))
|
|
97
|
+
print(report.growth["annual_growth_rate_compound_pct"])
|
|
98
|
+
print(report.lei.summary()) # patch counts and areas by growth type, MEI, AWMEI
|
|
99
|
+
print(report.gradient_t2.fit) # Clark fit: D0, b, R²
|
|
100
|
+
print(report.sdg_11_3_1.ratio) # LCRPGR
|
|
101
|
+
|
|
102
|
+
import json
|
|
103
|
+
json.dumps(report.to_dict()) # JSON-safe (NaN -> null)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Single metrics are plain functions:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
lei = sm.landscape_expansion_index(t1, t2, buffer_cells=1, cell_size=30)
|
|
110
|
+
lei.records() # one dict per new patch: area, A_o, A_v, LEI, type
|
|
111
|
+
lei.type_raster # 0 none, 1 infilling, 2 edge-expansion, 3 outlying
|
|
112
|
+
|
|
113
|
+
sm.patch_metrics(t2, cell_size=30).to_dict()
|
|
114
|
+
sm.compactness(t2, cell_size=30)
|
|
115
|
+
sm.zonal_entropy(t2, sm.ring_zones(t2.shape, (100, 100), 300, cell_size=30))
|
|
116
|
+
sm.density_gradient(t2, 300, centre=(100, 100), cell_size=30)
|
|
117
|
+
sm.expansion_intensity(area_t1=144.0, area_t2=225.0, total_area=3600.0, years=10)
|
|
118
|
+
sm.lcrpgr(area_t1=144.0, area_t2=225.0, pop_t1=250_000, pop_t2=310_000, years=10)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Reading rasters (`.npy` always works; GeoTIFF needs the `raster` extra):
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
built, valid, info = sm.load_built("built_2000.tif", built_values=[1]) # class code
|
|
125
|
+
built, valid, info = sm.load_built("ghsl_2020.tif", threshold=0.2) # share >= 20 %
|
|
126
|
+
info.cell_size # (xres, yres) from the GeoTIFF transform
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Command line
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
sprawlmetrics compare built_2000.tif built_2020.tif --years 20 \
|
|
133
|
+
--built-values 1 --ring-width 1000 --centre 512,498 \
|
|
134
|
+
--zones districts.tif --population 1200000,1650000 \
|
|
135
|
+
--lei-raster lei_types.tif --lei-csv lei.csv --zones-csv zones.csv -o report.json
|
|
136
|
+
|
|
137
|
+
sprawlmetrics patches built_2020.tif --threshold 0.2
|
|
138
|
+
sprawlmetrics gradient built_2020.tif --ring-width 500 --csv rings.csv
|
|
139
|
+
sprawlmetrics lcrpgr --area1 144 --area2 225 --pop1 250000 --pop2 310000 --years 10
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
All commands print JSON (or write it with `-o`). `.npy` inputs work without rasterio; set
|
|
143
|
+
`--cell-size` for them.
|
|
144
|
+
|
|
145
|
+
## API overview
|
|
146
|
+
|
|
147
|
+
| Area | Functions / classes |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| One call | `compare(t1, t2, years, ...) -> SprawlReport` (`.to_dict()`) |
|
|
150
|
+
| Growth | `built_area`, `built_share`, `growth_rate`, `annual_growth_rate(method=...)`, `annual_expansion_area`, `expansion_intensity` |
|
|
151
|
+
| LEI | `landscape_expansion_index -> LEIResult` (`mei`, `awmei`, `summary()`, `records()`, `type_raster`), `classify_lei` |
|
|
152
|
+
| Patches | `patch_metrics -> PatchMetrics`, `patch_table`, `shape_index`, `min_perimeter_cells`, `label` |
|
|
153
|
+
| Compactness | `compactness`, `normalized_moment_of_inertia`, `exchange_index`, `proximity_index`, `built_centroid` |
|
|
154
|
+
| Entropy | `shannon_entropy`, `zonal_entropy -> EntropyResult` |
|
|
155
|
+
| Gradient | `density_gradient -> GradientResult`, `fit_negative_exponential -> ExponentialFit` |
|
|
156
|
+
| Zones | `ring_zones`, `sector_zones`, `distance_grid`, `zonal_summary`, `write_csv` |
|
|
157
|
+
| SDG 11.3.1 | `land_consumption_rate`, `population_growth_rate`, `lcrpgr -> LCRPGRResult` |
|
|
158
|
+
| I/O | `read_raster`, `load_built`, `binarize`, `write_raster`, `RasterInfo` |
|
|
159
|
+
|
|
160
|
+
Conventions: rasters are 2-D arrays, non-zero = built-up. `cell_size` is a cell side or an
|
|
161
|
+
`(xres, yres)` pair in map units, so areas come out in map units squared. Centres are
|
|
162
|
+
`(row, col)` indices (fractional values allowed). An optional `valid` mask marks the study
|
|
163
|
+
area; cells outside it count neither as land nor as built-up.
|
|
164
|
+
|
|
165
|
+
## Methods and sources
|
|
166
|
+
|
|
167
|
+
| Metric | Definition | Source |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| Growth rate | `100 (A2 - A1) / A1` | standard |
|
|
170
|
+
| Annual growth rate | linear `100 (A2-A1)/(A1 t)`; compound `100((A2/A1)^(1/t) - 1)`; exponential `100 ln(A2/A1)/t` | standard |
|
|
171
|
+
| UEII | `100 (A2 - A1) / (TA t)`, `TA` = land area of the unit | Liu, Zhan & Deng (2005) |
|
|
172
|
+
| LEI | `100 A_o / (A_o + A_v)` in a buffer ring around each new patch; infilling `> 50`, edge-expansion `(0, 50]`, outlying `= 0`; `MEI = mean(LEI)`, `AWMEI = sum(LEI_i a_i)/A` | Liu et al. (2010) |
|
|
173
|
+
| Patch metrics | NP, CA, AREA_MN, LPI `= 100 max(a)/A`, ED `= E/A * 10 000`, PD `= NP/A * 10^6`, SHAPE `= p / min p` (raster form), SHAPE_MN, SHAPE_AM | McGarigal et al. (FRAGSTATS) |
|
|
174
|
+
| Patch compactness | `2 sqrt(pi a) / p` | isoperimetric ratio |
|
|
175
|
+
| NMI | `A^2 / (2 pi I)`, `I` = polar moment of inertia about the centroid | Li et al. (2014) |
|
|
176
|
+
| Exchange, proximity | share of the footprint inside the equal-area circle; `(2r/3) / mean distance` | Angel, Parent & Civco (2010) |
|
|
177
|
+
| Entropy | `H = -sum p_i ln p_i`, `p_i = x_i / sum x`, `x_i` = built-up density of zone `i`; relative `H / ln n` | Yeh & Li (2001) |
|
|
178
|
+
| Density gradient | OLS of `ln D` on ring mid-distance: `D(r) = D0 exp(-b r)` | Clark (1951) |
|
|
179
|
+
| SDG 11.3.1 | `LCR = ln(Urb2/Urb1)/y`, `PGR = ln(Pop2/Pop1)/y`, ratio `LCR/PGR` | UN-Habitat SDG 11.3.1 metadata |
|
|
180
|
+
|
|
181
|
+
Raster details:
|
|
182
|
+
|
|
183
|
+
- Patches use the 8-cell rule by default (FRAGSTATS default); `connectivity=4` is
|
|
184
|
+
available.
|
|
185
|
+
- **LEI buffer** = morphological dilation of the patch by `buffer_cells` cells (square or
|
|
186
|
+
disc) minus the patch itself; cells outside the raster or the study area are left out.
|
|
187
|
+
`A_v` counts every buffer cell not built at t1, including cells of other new patches.
|
|
188
|
+
- **Edge**: the class edge for ED counts cell sides between built-up and valid non-built
|
|
189
|
+
cells; `count_boundary=True` also counts sides on the raster border or against invalid
|
|
190
|
+
cells. A patch's own perimeter (for SHAPE and compactness) always includes them.
|
|
191
|
+
- **ED** is in map units of edge per hectare when cells are in metres (FRAGSTATS m/ha);
|
|
192
|
+
**PD** is patches per 100 ha.
|
|
193
|
+
- **NMI** treats each cell as a uniform square, adding its own polar moment
|
|
194
|
+
`c (xres² + yres²) / 12`, so a square footprint scores exactly `3/π ≈ 0.955` and a disc
|
|
195
|
+
approaches 1.
|
|
196
|
+
- **Ring k** holds cells whose centre lies at `(k-1) w <= d < k w`.
|
|
197
|
+
|
|
198
|
+
### Relation to `sdg11`
|
|
199
|
+
|
|
200
|
+
The companion package [`sdg11`](https://pypi.org/project/sdg11/) implements the full SDG
|
|
201
|
+
11.3.1 reporting workflow (degree-of-urbanisation delineation, interpretation and report
|
|
202
|
+
bundles). `sprawlmetrics.lcrpgr` uses the same formula for quick use inside sprawl
|
|
203
|
+
analyses; use `sdg11` for official-style reporting.
|
|
204
|
+
|
|
205
|
+
## Limitations
|
|
206
|
+
|
|
207
|
+
- The LEI buffer is raster-based; Liu et al. (2010) describe a vector buffer. With one-cell
|
|
208
|
+
buffers the results match the usual raster implementations, but values for very small
|
|
209
|
+
patches depend on the buffer shape and width.
|
|
210
|
+
- No reprojection or resampling: both dates must already be on the same grid.
|
|
211
|
+
- Entropy and gradient results depend on the chosen centre and ring width; report them.
|
|
212
|
+
- The negative exponential fit is OLS on log density and drops rings with zero density;
|
|
213
|
+
it is not a maximum-likelihood fit.
|
|
214
|
+
- UEII class thresholds (e.g. "high-speed", "slow") vary between studies and are not built
|
|
215
|
+
in.
|
|
216
|
+
- Not an official UN product; check SDG values against the current UN-Habitat metadata.
|
|
217
|
+
|
|
218
|
+
## References
|
|
219
|
+
|
|
220
|
+
- Angel, S., Parent, J. & Civco, D. L. (2010). Ten compactness properties of circles:
|
|
221
|
+
measuring shape in geography. *The Canadian Geographer* 54(4), 441-461.
|
|
222
|
+
- Clark, C. (1951). Urban population densities. *Journal of the Royal Statistical Society,
|
|
223
|
+
Series A* 114(4), 490-496.
|
|
224
|
+
- Li, W., Chen, T., Wentz, E. A. & Fan, C. (2014). NMMI: A mass compactness measure for
|
|
225
|
+
spatial pattern analysis of areal features. *Annals of the AAG* 104(6), 1116-1133.
|
|
226
|
+
- Liu, J., Zhan, J. & Deng, X. (2005). Spatio-temporal patterns and driving forces of urban
|
|
227
|
+
land expansion in China during the economic reform era. *Ambio* 34(6), 450-455.
|
|
228
|
+
- Liu, X., Li, X., Chen, Y., Tan, Z., Li, S. & Ai, B. (2010). A new landscape index for
|
|
229
|
+
quantifying urban expansion using multi-temporal remotely sensed data. *Landscape
|
|
230
|
+
Ecology* 25, 671-682. https://doi.org/10.1007/s10980-010-9454-5
|
|
231
|
+
- McGarigal, K., Cushman, S. A. & Ene, E. FRAGSTATS v4: Spatial Pattern Analysis Program
|
|
232
|
+
for Categorical Maps. University of Massachusetts, Amherst.
|
|
233
|
+
- UN-Habitat. SDG indicator metadata 11.3.1: Ratio of land consumption rate to population
|
|
234
|
+
growth rate.
|
|
235
|
+
- Yeh, A. G.-O. & Li, X. (2001). Measurement and monitoring of urban sprawl in a rapidly
|
|
236
|
+
growing region using entropy. *Photogrammetric Engineering & Remote Sensing* 67(1),
|
|
237
|
+
83-90.
|
|
238
|
+
|
|
239
|
+
## Academic citation
|
|
240
|
+
|
|
241
|
+
```bibtex
|
|
242
|
+
@software{eminoglu_sprawlmetrics,
|
|
243
|
+
author = {Eminoğlu, Yusuf},
|
|
244
|
+
title = {sprawlmetrics},
|
|
245
|
+
year = {2026},
|
|
246
|
+
version = {0.1.0},
|
|
247
|
+
url = {https://gitlab.com/geophilo1/sprawlmetrics_sdk}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# sprawlmetrics
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/sprawlmetrics/)
|
|
4
|
+
[](https://pypi.org/project/sprawlmetrics/)
|
|
5
|
+
[](https://gitlab.com/geophilo1/sprawlmetrics_sdk/-/pipelines)
|
|
6
|
+
[](https://gitlab.com/geophilo1/sprawlmetrics_sdk/-/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
**Urban expansion and sprawl metrics from multi-date binary built-up rasters.**
|
|
9
|
+
|
|
10
|
+
`sprawlmetrics` takes two (or more) built-up / non-built-up rasters of the same grid, for
|
|
11
|
+
example classified Landsat or Sentinel-2 scenes or GHSL built-up layers, and computes the
|
|
12
|
+
standard indicators used in urban expansion studies:
|
|
13
|
+
|
|
14
|
+
- **Growth**: built-up area, total and annual growth rate (linear, compound, exponential),
|
|
15
|
+
annual expansion area, and the **urban expansion intensity index** (UEII).
|
|
16
|
+
- **Landscape Expansion Index** (LEI; Liu et al. 2010): sorts every new patch into
|
|
17
|
+
*infilling*, *edge-expansion* or *outlying* growth, and reports MEI and AWMEI.
|
|
18
|
+
- **Patch metrics** (FRAGSTATS definitions): number of patches, mean patch size, largest
|
|
19
|
+
patch index, edge density, patch density, mean and area-weighted shape index, and patch
|
|
20
|
+
compactness.
|
|
21
|
+
- **Footprint compactness**: normalised moment of inertia, exchange index and proximity
|
|
22
|
+
index.
|
|
23
|
+
- **Shannon entropy** of built-up land across rings, sectors or any zones
|
|
24
|
+
(Yeh & Li 2001).
|
|
25
|
+
- **Density gradient**: built-up density in concentric rings around a centre, with
|
|
26
|
+
Clark's (1951) negative exponential fit `D(r) = D0 exp(-b r)`.
|
|
27
|
+
- **SDG 11.3.1**: land consumption rate, population growth rate and their ratio (LCRPGR).
|
|
28
|
+
- **Per-zone summaries** (districts, rings, grid cells), written as CSV.
|
|
29
|
+
|
|
30
|
+
It needs only NumPy. SciPy (optional) speeds up patch labelling, and rasterio (optional)
|
|
31
|
+
reads and writes GeoTIFFs. A command-line tool is included.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install sprawlmetrics # NumPy only
|
|
37
|
+
pip install "sprawlmetrics[fast]" # + SciPy (faster connected-component labelling)
|
|
38
|
+
pip install "sprawlmetrics[raster]" # + rasterio (GeoTIFF and other GDAL rasters)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requires Python 3.9 or newer.
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import numpy as np
|
|
47
|
+
import sprawlmetrics as sm
|
|
48
|
+
|
|
49
|
+
# 0/1 rasters on the same 30 m grid (here: a synthetic city)
|
|
50
|
+
t1 = np.zeros((200, 200), dtype=np.uint8)
|
|
51
|
+
t1[80:120, 80:120] = 1
|
|
52
|
+
t2 = t1.copy()
|
|
53
|
+
t2[75:125, 75:125] = 1 # edge expansion around the core
|
|
54
|
+
t2[20:24, 150:154] = 1 # an outlying settlement
|
|
55
|
+
|
|
56
|
+
report = sm.compare(t1, t2, years=10, cell_size=30, ring_width=300,
|
|
57
|
+
population=(250_000, 310_000))
|
|
58
|
+
print(report.growth["annual_growth_rate_compound_pct"])
|
|
59
|
+
print(report.lei.summary()) # patch counts and areas by growth type, MEI, AWMEI
|
|
60
|
+
print(report.gradient_t2.fit) # Clark fit: D0, b, R²
|
|
61
|
+
print(report.sdg_11_3_1.ratio) # LCRPGR
|
|
62
|
+
|
|
63
|
+
import json
|
|
64
|
+
json.dumps(report.to_dict()) # JSON-safe (NaN -> null)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Single metrics are plain functions:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
lei = sm.landscape_expansion_index(t1, t2, buffer_cells=1, cell_size=30)
|
|
71
|
+
lei.records() # one dict per new patch: area, A_o, A_v, LEI, type
|
|
72
|
+
lei.type_raster # 0 none, 1 infilling, 2 edge-expansion, 3 outlying
|
|
73
|
+
|
|
74
|
+
sm.patch_metrics(t2, cell_size=30).to_dict()
|
|
75
|
+
sm.compactness(t2, cell_size=30)
|
|
76
|
+
sm.zonal_entropy(t2, sm.ring_zones(t2.shape, (100, 100), 300, cell_size=30))
|
|
77
|
+
sm.density_gradient(t2, 300, centre=(100, 100), cell_size=30)
|
|
78
|
+
sm.expansion_intensity(area_t1=144.0, area_t2=225.0, total_area=3600.0, years=10)
|
|
79
|
+
sm.lcrpgr(area_t1=144.0, area_t2=225.0, pop_t1=250_000, pop_t2=310_000, years=10)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Reading rasters (`.npy` always works; GeoTIFF needs the `raster` extra):
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
built, valid, info = sm.load_built("built_2000.tif", built_values=[1]) # class code
|
|
86
|
+
built, valid, info = sm.load_built("ghsl_2020.tif", threshold=0.2) # share >= 20 %
|
|
87
|
+
info.cell_size # (xres, yres) from the GeoTIFF transform
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Command line
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
sprawlmetrics compare built_2000.tif built_2020.tif --years 20 \
|
|
94
|
+
--built-values 1 --ring-width 1000 --centre 512,498 \
|
|
95
|
+
--zones districts.tif --population 1200000,1650000 \
|
|
96
|
+
--lei-raster lei_types.tif --lei-csv lei.csv --zones-csv zones.csv -o report.json
|
|
97
|
+
|
|
98
|
+
sprawlmetrics patches built_2020.tif --threshold 0.2
|
|
99
|
+
sprawlmetrics gradient built_2020.tif --ring-width 500 --csv rings.csv
|
|
100
|
+
sprawlmetrics lcrpgr --area1 144 --area2 225 --pop1 250000 --pop2 310000 --years 10
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
All commands print JSON (or write it with `-o`). `.npy` inputs work without rasterio; set
|
|
104
|
+
`--cell-size` for them.
|
|
105
|
+
|
|
106
|
+
## API overview
|
|
107
|
+
|
|
108
|
+
| Area | Functions / classes |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| One call | `compare(t1, t2, years, ...) -> SprawlReport` (`.to_dict()`) |
|
|
111
|
+
| Growth | `built_area`, `built_share`, `growth_rate`, `annual_growth_rate(method=...)`, `annual_expansion_area`, `expansion_intensity` |
|
|
112
|
+
| LEI | `landscape_expansion_index -> LEIResult` (`mei`, `awmei`, `summary()`, `records()`, `type_raster`), `classify_lei` |
|
|
113
|
+
| Patches | `patch_metrics -> PatchMetrics`, `patch_table`, `shape_index`, `min_perimeter_cells`, `label` |
|
|
114
|
+
| Compactness | `compactness`, `normalized_moment_of_inertia`, `exchange_index`, `proximity_index`, `built_centroid` |
|
|
115
|
+
| Entropy | `shannon_entropy`, `zonal_entropy -> EntropyResult` |
|
|
116
|
+
| Gradient | `density_gradient -> GradientResult`, `fit_negative_exponential -> ExponentialFit` |
|
|
117
|
+
| Zones | `ring_zones`, `sector_zones`, `distance_grid`, `zonal_summary`, `write_csv` |
|
|
118
|
+
| SDG 11.3.1 | `land_consumption_rate`, `population_growth_rate`, `lcrpgr -> LCRPGRResult` |
|
|
119
|
+
| I/O | `read_raster`, `load_built`, `binarize`, `write_raster`, `RasterInfo` |
|
|
120
|
+
|
|
121
|
+
Conventions: rasters are 2-D arrays, non-zero = built-up. `cell_size` is a cell side or an
|
|
122
|
+
`(xres, yres)` pair in map units, so areas come out in map units squared. Centres are
|
|
123
|
+
`(row, col)` indices (fractional values allowed). An optional `valid` mask marks the study
|
|
124
|
+
area; cells outside it count neither as land nor as built-up.
|
|
125
|
+
|
|
126
|
+
## Methods and sources
|
|
127
|
+
|
|
128
|
+
| Metric | Definition | Source |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| Growth rate | `100 (A2 - A1) / A1` | standard |
|
|
131
|
+
| Annual growth rate | linear `100 (A2-A1)/(A1 t)`; compound `100((A2/A1)^(1/t) - 1)`; exponential `100 ln(A2/A1)/t` | standard |
|
|
132
|
+
| UEII | `100 (A2 - A1) / (TA t)`, `TA` = land area of the unit | Liu, Zhan & Deng (2005) |
|
|
133
|
+
| LEI | `100 A_o / (A_o + A_v)` in a buffer ring around each new patch; infilling `> 50`, edge-expansion `(0, 50]`, outlying `= 0`; `MEI = mean(LEI)`, `AWMEI = sum(LEI_i a_i)/A` | Liu et al. (2010) |
|
|
134
|
+
| Patch metrics | NP, CA, AREA_MN, LPI `= 100 max(a)/A`, ED `= E/A * 10 000`, PD `= NP/A * 10^6`, SHAPE `= p / min p` (raster form), SHAPE_MN, SHAPE_AM | McGarigal et al. (FRAGSTATS) |
|
|
135
|
+
| Patch compactness | `2 sqrt(pi a) / p` | isoperimetric ratio |
|
|
136
|
+
| NMI | `A^2 / (2 pi I)`, `I` = polar moment of inertia about the centroid | Li et al. (2014) |
|
|
137
|
+
| Exchange, proximity | share of the footprint inside the equal-area circle; `(2r/3) / mean distance` | Angel, Parent & Civco (2010) |
|
|
138
|
+
| Entropy | `H = -sum p_i ln p_i`, `p_i = x_i / sum x`, `x_i` = built-up density of zone `i`; relative `H / ln n` | Yeh & Li (2001) |
|
|
139
|
+
| Density gradient | OLS of `ln D` on ring mid-distance: `D(r) = D0 exp(-b r)` | Clark (1951) |
|
|
140
|
+
| SDG 11.3.1 | `LCR = ln(Urb2/Urb1)/y`, `PGR = ln(Pop2/Pop1)/y`, ratio `LCR/PGR` | UN-Habitat SDG 11.3.1 metadata |
|
|
141
|
+
|
|
142
|
+
Raster details:
|
|
143
|
+
|
|
144
|
+
- Patches use the 8-cell rule by default (FRAGSTATS default); `connectivity=4` is
|
|
145
|
+
available.
|
|
146
|
+
- **LEI buffer** = morphological dilation of the patch by `buffer_cells` cells (square or
|
|
147
|
+
disc) minus the patch itself; cells outside the raster or the study area are left out.
|
|
148
|
+
`A_v` counts every buffer cell not built at t1, including cells of other new patches.
|
|
149
|
+
- **Edge**: the class edge for ED counts cell sides between built-up and valid non-built
|
|
150
|
+
cells; `count_boundary=True` also counts sides on the raster border or against invalid
|
|
151
|
+
cells. A patch's own perimeter (for SHAPE and compactness) always includes them.
|
|
152
|
+
- **ED** is in map units of edge per hectare when cells are in metres (FRAGSTATS m/ha);
|
|
153
|
+
**PD** is patches per 100 ha.
|
|
154
|
+
- **NMI** treats each cell as a uniform square, adding its own polar moment
|
|
155
|
+
`c (xres² + yres²) / 12`, so a square footprint scores exactly `3/π ≈ 0.955` and a disc
|
|
156
|
+
approaches 1.
|
|
157
|
+
- **Ring k** holds cells whose centre lies at `(k-1) w <= d < k w`.
|
|
158
|
+
|
|
159
|
+
### Relation to `sdg11`
|
|
160
|
+
|
|
161
|
+
The companion package [`sdg11`](https://pypi.org/project/sdg11/) implements the full SDG
|
|
162
|
+
11.3.1 reporting workflow (degree-of-urbanisation delineation, interpretation and report
|
|
163
|
+
bundles). `sprawlmetrics.lcrpgr` uses the same formula for quick use inside sprawl
|
|
164
|
+
analyses; use `sdg11` for official-style reporting.
|
|
165
|
+
|
|
166
|
+
## Limitations
|
|
167
|
+
|
|
168
|
+
- The LEI buffer is raster-based; Liu et al. (2010) describe a vector buffer. With one-cell
|
|
169
|
+
buffers the results match the usual raster implementations, but values for very small
|
|
170
|
+
patches depend on the buffer shape and width.
|
|
171
|
+
- No reprojection or resampling: both dates must already be on the same grid.
|
|
172
|
+
- Entropy and gradient results depend on the chosen centre and ring width; report them.
|
|
173
|
+
- The negative exponential fit is OLS on log density and drops rings with zero density;
|
|
174
|
+
it is not a maximum-likelihood fit.
|
|
175
|
+
- UEII class thresholds (e.g. "high-speed", "slow") vary between studies and are not built
|
|
176
|
+
in.
|
|
177
|
+
- Not an official UN product; check SDG values against the current UN-Habitat metadata.
|
|
178
|
+
|
|
179
|
+
## References
|
|
180
|
+
|
|
181
|
+
- Angel, S., Parent, J. & Civco, D. L. (2010). Ten compactness properties of circles:
|
|
182
|
+
measuring shape in geography. *The Canadian Geographer* 54(4), 441-461.
|
|
183
|
+
- Clark, C. (1951). Urban population densities. *Journal of the Royal Statistical Society,
|
|
184
|
+
Series A* 114(4), 490-496.
|
|
185
|
+
- Li, W., Chen, T., Wentz, E. A. & Fan, C. (2014). NMMI: A mass compactness measure for
|
|
186
|
+
spatial pattern analysis of areal features. *Annals of the AAG* 104(6), 1116-1133.
|
|
187
|
+
- Liu, J., Zhan, J. & Deng, X. (2005). Spatio-temporal patterns and driving forces of urban
|
|
188
|
+
land expansion in China during the economic reform era. *Ambio* 34(6), 450-455.
|
|
189
|
+
- Liu, X., Li, X., Chen, Y., Tan, Z., Li, S. & Ai, B. (2010). A new landscape index for
|
|
190
|
+
quantifying urban expansion using multi-temporal remotely sensed data. *Landscape
|
|
191
|
+
Ecology* 25, 671-682. https://doi.org/10.1007/s10980-010-9454-5
|
|
192
|
+
- McGarigal, K., Cushman, S. A. & Ene, E. FRAGSTATS v4: Spatial Pattern Analysis Program
|
|
193
|
+
for Categorical Maps. University of Massachusetts, Amherst.
|
|
194
|
+
- UN-Habitat. SDG indicator metadata 11.3.1: Ratio of land consumption rate to population
|
|
195
|
+
growth rate.
|
|
196
|
+
- Yeh, A. G.-O. & Li, X. (2001). Measurement and monitoring of urban sprawl in a rapidly
|
|
197
|
+
growing region using entropy. *Photogrammetric Engineering & Remote Sensing* 67(1),
|
|
198
|
+
83-90.
|
|
199
|
+
|
|
200
|
+
## Academic citation
|
|
201
|
+
|
|
202
|
+
```bibtex
|
|
203
|
+
@software{eminoglu_sprawlmetrics,
|
|
204
|
+
author = {Eminoğlu, Yusuf},
|
|
205
|
+
title = {sprawlmetrics},
|
|
206
|
+
year = {2026},
|
|
207
|
+
version = {0.1.0},
|
|
208
|
+
url = {https://gitlab.com/geophilo1/sprawlmetrics_sdk}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""sprawlmetrics quickstart on a synthetic two-date city (no input files needed).
|
|
2
|
+
|
|
3
|
+
Run: python examples/quickstart.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
import sprawlmetrics as sm
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def synthetic_city() -> tuple:
|
|
16
|
+
"""A compact core at t1; at t2 it has grown at its edge, filled a gap and leapfrogged."""
|
|
17
|
+
t1 = np.zeros((200, 200), dtype=np.uint8)
|
|
18
|
+
t1[80:120, 80:120] = 1
|
|
19
|
+
t1[95:105, 95:105] = 0 # an unbuilt gap in the core
|
|
20
|
+
t1[60:70, 140:150] = 1 # a satellite town
|
|
21
|
+
t2 = t1.copy()
|
|
22
|
+
t2[75:125, 75:125] = 1 # edge expansion and infilling of the gap
|
|
23
|
+
t2[20:24, 20:24] = 1 # outlying (leapfrog) development
|
|
24
|
+
t2[60:70, 150:156] = 1 # satellite grows at its edge
|
|
25
|
+
return t1, t2
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
t1, t2 = synthetic_city()
|
|
30
|
+
report = sm.compare(
|
|
31
|
+
t1,
|
|
32
|
+
t2,
|
|
33
|
+
years=10,
|
|
34
|
+
cell_size=30, # metres
|
|
35
|
+
centre=(100, 100),
|
|
36
|
+
ring_width=300,
|
|
37
|
+
population=(250_000, 310_000),
|
|
38
|
+
)
|
|
39
|
+
print(f"Built-up t1: {report.built_t1 / 10_000:.1f} ha, t2: {report.built_t2 / 10_000:.1f} ha")
|
|
40
|
+
print(f"Annual growth (compound): {report.growth['annual_growth_rate_compound_pct']:.2f} %/yr")
|
|
41
|
+
print(f"UEII: {report.growth['ueii']:.3f} % of the study area per year")
|
|
42
|
+
|
|
43
|
+
lei = report.lei.summary()
|
|
44
|
+
for key in ("infilling", "edge_expansion", "outlying"):
|
|
45
|
+
print(f" {key:15s} {lei[key + '_patches']:3d} patches {lei[key + '_area_pct']:5.1f} %")
|
|
46
|
+
print(f"MEI {lei['mei']:.1f} AWMEI {lei['awmei']:.1f}")
|
|
47
|
+
|
|
48
|
+
assert report.gradient_t1 is not None and report.gradient_t2 is not None
|
|
49
|
+
for name, grad in (("t1", report.gradient_t1), ("t2", report.gradient_t2)):
|
|
50
|
+
if grad.fit is not None:
|
|
51
|
+
print(f"Clark fit {name}: D0={grad.fit.d0:.2f} b={grad.fit.b * 1000:.3f}/km")
|
|
52
|
+
assert report.entropy_t1 is not None and report.entropy_t2 is not None
|
|
53
|
+
print(
|
|
54
|
+
f"Relative entropy: {report.entropy_t1.relative_entropy:.3f} -> "
|
|
55
|
+
f"{report.entropy_t2.relative_entropy:.3f}"
|
|
56
|
+
)
|
|
57
|
+
print(
|
|
58
|
+
f"NMI: {report.compactness_t1.normalized_moment_of_inertia:.3f} -> "
|
|
59
|
+
f"{report.compactness_t2.normalized_moment_of_inertia:.3f}"
|
|
60
|
+
)
|
|
61
|
+
assert report.sdg_11_3_1 is not None
|
|
62
|
+
print(f"SDG 11.3.1 LCRPGR: {report.sdg_11_3_1.ratio:.2f} ({report.sdg_11_3_1.interpretation})")
|
|
63
|
+
|
|
64
|
+
# Everything as JSON
|
|
65
|
+
text = json.dumps(report.to_dict(), indent=2)
|
|
66
|
+
print(f"JSON report: {len(text)} characters")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
main()
|