parcelform 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.
- parcelform-0.1.0/CHANGELOG.md +30 -0
- parcelform-0.1.0/CITATION.cff +23 -0
- parcelform-0.1.0/LICENSE +21 -0
- parcelform-0.1.0/MANIFEST.in +9 -0
- parcelform-0.1.0/PKG-INFO +273 -0
- parcelform-0.1.0/README.md +238 -0
- parcelform-0.1.0/examples/quickstart.py +65 -0
- parcelform-0.1.0/pyproject.toml +113 -0
- parcelform-0.1.0/setup.cfg +4 -0
- parcelform-0.1.0/src/parcelform/__init__.py +130 -0
- parcelform-0.1.0/src/parcelform/__main__.py +7 -0
- parcelform-0.1.0/src/parcelform/_common.py +211 -0
- parcelform-0.1.0/src/parcelform/_version.py +1 -0
- parcelform-0.1.0/src/parcelform/analysis.py +217 -0
- parcelform-0.1.0/src/parcelform/cli.py +190 -0
- parcelform-0.1.0/src/parcelform/envelope.py +306 -0
- parcelform-0.1.0/src/parcelform/frontage.py +485 -0
- parcelform-0.1.0/src/parcelform/geometry.py +382 -0
- parcelform-0.1.0/src/parcelform/io.py +155 -0
- parcelform-0.1.0/src/parcelform/py.typed +0 -0
- parcelform-0.1.0/src/parcelform/shape.py +179 -0
- parcelform-0.1.0/src/parcelform/subdivision.py +286 -0
- parcelform-0.1.0/src/parcelform.egg-info/PKG-INFO +273 -0
- parcelform-0.1.0/src/parcelform.egg-info/SOURCES.txt +31 -0
- parcelform-0.1.0/src/parcelform.egg-info/dependency_links.txt +1 -0
- parcelform-0.1.0/src/parcelform.egg-info/entry_points.txt +2 -0
- parcelform-0.1.0/src/parcelform.egg-info/requires.txt +7 -0
- parcelform-0.1.0/src/parcelform.egg-info/top_level.txt +1 -0
- parcelform-0.1.0/tests/conftest.py +33 -0
- parcelform-0.1.0/tests/test_envelope_subdivision.py +183 -0
- parcelform-0.1.0/tests/test_frontage.py +156 -0
- parcelform-0.1.0/tests/test_geometry_shape.py +174 -0
- parcelform-0.1.0/tests/test_io_cli.py +212 -0
|
@@ -0,0 +1,30 @@
|
|
|
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-15
|
|
8
|
+
### Added
|
|
9
|
+
- Planar geometry: area, perimeter (with holes), centroid, convex hull (monotone chain),
|
|
10
|
+
minimum-area rectangle by rotating calipers, minimum enclosing circle (Welzl),
|
|
11
|
+
point-in-polygon, half-plane clipping.
|
|
12
|
+
- Shape indices: Polsby-Popper, Schwartzberg (and its original ratio), Reock,
|
|
13
|
+
rectangularity, elongation, aspect ratio, area and perimeter convexity, FRAGSTATS
|
|
14
|
+
fractal dimension; MBR orientation as angle and azimuth.
|
|
15
|
+
- Frontage against street centrelines or block outlines (exact capsule intervals with an
|
|
16
|
+
angle filter), per-street lengths, corner / through / interior / landlocked lot types.
|
|
17
|
+
- Lot depth, width, mean depth and width-to-depth ratio relative to the primary front.
|
|
18
|
+
- Buildable envelope by uniform, per-edge or role-based setbacks (exact for convex
|
|
19
|
+
parcels; verified mitred offset with edge-collapse handling for non-convex parcels).
|
|
20
|
+
- Lot rules (minimum area, frontage, depth, width) and subdivision feasibility by
|
|
21
|
+
equal-frontage or equal-area perpendicular cuts.
|
|
22
|
+
- FeatureCollection batch analysis, envelope and subdivision layers; GeoJSON and CSV I/O.
|
|
23
|
+
- Command line: `parcelform metrics | envelope | subdivide`.
|
|
24
|
+
|
|
25
|
+
### Fixed (pre-release review)
|
|
26
|
+
- Frontage capsule intervals: an edge that passes a street segment's end inside the end
|
|
27
|
+
disc, but crosses the perpendicular band only beyond the end, no longer gets an
|
|
28
|
+
over-long frontage interval; checked against dense sampling in the tests.
|
|
29
|
+
- Shape-index docs: the two convexity ratios are labelled as the classical measures
|
|
30
|
+
discussed by Zunic & Rosin (2004); reference details completed.
|
|
@@ -0,0 +1,23 @@
|
|
|
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: "parcelform"
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
date-released: "2026-11-15"
|
|
11
|
+
license: "MIT"
|
|
12
|
+
repository-code: "https://gitlab.com/geophilo1/parcelform_sdk"
|
|
13
|
+
url: "https://pypi.org/project/parcelform/"
|
|
14
|
+
abstract: "Parcel and lot geometry metrics from GeoJSON polygons in projected coordinates: area, perimeter, convex hull, minimum-area rectangle and orientation; Polsby-Popper, Schwartzberg, Reock, rectangularity, elongation, convexity and fractal dimension; frontage against street centrelines or blocks with corner, through and interior lot classification; lot depth and width; buildable envelopes by setback; lot-rule checks and subdivision feasibility."
|
|
15
|
+
keywords:
|
|
16
|
+
- parcel
|
|
17
|
+
- lot
|
|
18
|
+
- cadastre
|
|
19
|
+
- frontage
|
|
20
|
+
- setback
|
|
21
|
+
- shape index
|
|
22
|
+
- urban planning
|
|
23
|
+
- gis
|
parcelform-0.1.0/LICENSE
ADDED
|
@@ -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,273 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parcelform
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Parcel and lot geometry metrics from GeoJSON polygons: shape indices, minimum-area rectangle, frontage and lot type, depth and width, setback envelopes and subdivision feasibility checks.
|
|
5
|
+
Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/geophilo1/parcelform_sdk
|
|
8
|
+
Project-URL: Repository, https://gitlab.com/geophilo1/parcelform_sdk
|
|
9
|
+
Project-URL: Issues, https://gitlab.com/geophilo1/parcelform_sdk/-/work_items
|
|
10
|
+
Project-URL: Changelog, https://gitlab.com/geophilo1/parcelform_sdk/-/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: parcel,lot,cadastre,land-subdivision,frontage,setback,zoning,shape-index,polsby-popper,minimum-bounding-rectangle,geojson,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: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# parcelform
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/parcelform/)
|
|
39
|
+
[](https://pypi.org/project/parcelform/)
|
|
40
|
+
[](https://gitlab.com/geophilo1/parcelform_sdk/-/pipelines)
|
|
41
|
+
[](https://gitlab.com/geophilo1/parcelform_sdk/-/blob/main/LICENSE)
|
|
42
|
+
|
|
43
|
+
Parcel and lot geometry metrics from GeoJSON polygons in projected coordinates.
|
|
44
|
+
|
|
45
|
+
`parcelform` measures cadastral parcels and building lots the way planners and land-use
|
|
46
|
+
analysts usually need them: size and shape, how much of the lot faces a street, whether it
|
|
47
|
+
is a corner, through or interior lot, how deep and wide it is, what is left after
|
|
48
|
+
setbacks, and whether it can be split into lots that meet minimum standards. It needs only
|
|
49
|
+
NumPy and reads and writes plain GeoJSON.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install parcelform
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Requires Python 3.9 or newer and NumPy. Coordinates must be projected (metres or feet);
|
|
58
|
+
the package does not reproject and refuses data that looks like longitude/latitude.
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import parcelform as pf
|
|
64
|
+
|
|
65
|
+
lot = {"type": "Polygon",
|
|
66
|
+
"coordinates": [[[0, 0], [20, 0], [20, 40], [0, 40], [0, 0]]]}
|
|
67
|
+
streets = [{"type": "LineString", "coordinates": [[-50, -4], [200, -4]]}, # main street
|
|
68
|
+
{"type": "LineString", "coordinates": [[-4, -50], [-4, 100]]}] # side street
|
|
69
|
+
|
|
70
|
+
pf.shape_indices(lot).polsby_popper # 0.698
|
|
71
|
+
pf.min_area_rectangle(lot).to_dict() # length 40, width 20, angle 90
|
|
72
|
+
|
|
73
|
+
fr = pf.frontage(lot, streets, tolerance=5, primary_rule="shortest")
|
|
74
|
+
fr.total, fr.lot_type # 60.0, 'corner'
|
|
75
|
+
pf.lot_dimensions(lot, fr) # depth 40, width 20, width/depth 0.5
|
|
76
|
+
|
|
77
|
+
env = pf.buildable_envelope(lot, {"front": 5, "side": 3, "rear": 4,
|
|
78
|
+
"secondary_front": 3}, fr)
|
|
79
|
+
env.area, env.share # 434.0, 0.5425
|
|
80
|
+
|
|
81
|
+
rules = pf.LotRules(min_area=300, min_frontage=12, min_depth=20) # from your zoning code
|
|
82
|
+
pf.check_lot(lot, rules, streets, tolerance=5).passed
|
|
83
|
+
|
|
84
|
+
wide = {"type": "Polygon",
|
|
85
|
+
"coordinates": [[[0, 0], [60, 0], [60, 30], [0, 30], [0, 0]]]}
|
|
86
|
+
res = pf.subdivision_feasibility(wide, rules, streets[:1], tolerance=4)
|
|
87
|
+
res.n_lots # 5 lots of 12 m x 30 m
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Whole layers in one call:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
fc = pf.analyze("parcels.geojson", streets="streets.geojson", tolerance=8,
|
|
94
|
+
setbacks={"front": 5, "side": 3, "rear": 3},
|
|
95
|
+
rules=pf.LotRules(min_area=400, min_frontage=12), subdivide=True,
|
|
96
|
+
on_error="record")
|
|
97
|
+
pf.write_geojson(fc, "parcels_measured.geojson")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Features
|
|
101
|
+
|
|
102
|
+
| Area | Functions | Notes |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| Geometry | `area`, `perimeter`, `centroid`, `convex_hull`, `min_area_rectangle`, `min_enclosing_circle`, `points_in_polygon` | holes and MultiPolygons supported |
|
|
105
|
+
| Orientation | `Rectangle.angle`, `Rectangle.azimuth`, `orientation_difference` | long axis of the minimum-area rectangle |
|
|
106
|
+
| Shape indices | `shape_indices`, `polsby_popper`, `schwartzberg`, `fractal_dimension` | Polsby-Popper, Schwartzberg, Reock, rectangularity, elongation, aspect ratio, area and perimeter convexity, fractal dimension |
|
|
107
|
+
| Frontage | `frontage -> Frontage`, `StreetSegments`, `as_streets` | street centrelines or block outlines; per-street lengths; corner / through / interior / landlocked |
|
|
108
|
+
| Lot dimensions | `lot_dimensions -> LotDimensions` | depth, width, mean depth, width-to-depth ratio |
|
|
109
|
+
| Setbacks | `buildable_envelope -> Envelope`, `edge_roles`, `resolve_setbacks` | uniform, per edge, or by role (front, side, rear, secondary front) |
|
|
110
|
+
| Lot rules | `LotRules`, `check_lot`, `evaluate_rules` | minimum area, frontage, depth, width (all user-supplied) |
|
|
111
|
+
| Subdivision | `subdivision_feasibility`, `split_parcel`, `lot_count_bound` | equal-frontage or equal-area perpendicular cuts |
|
|
112
|
+
| Layers | `analyze`, `envelopes`, `subdivide_collection` | FeatureCollection in, FeatureCollection out |
|
|
113
|
+
| I/O | `read_geojson`, `write_geojson`, `write_csv`, `read_rules` | standard library only |
|
|
114
|
+
|
|
115
|
+
## Methods
|
|
116
|
+
|
|
117
|
+
### Geometry
|
|
118
|
+
|
|
119
|
+
- Areas by the shoelace formula; perimeters include holes unless `include_holes=False`.
|
|
120
|
+
- Convex hull by Andrew's (1979) monotone chain.
|
|
121
|
+
- **Minimum-area rectangle** by rotating calipers: one side of the optimal rectangle is
|
|
122
|
+
collinear with a convex-hull edge (Freeman & Shapira 1975; Toussaint 1983), so every
|
|
123
|
+
hull-edge direction is tried, vectorised. `length` is the long side, `width` the short
|
|
124
|
+
side, `angle` the long axis counter-clockwise from +x in `[0, 180)`, `azimuth` the same
|
|
125
|
+
axis as a bearing from grid north.
|
|
126
|
+
- Minimum enclosing circle by Welzl's (1991) algorithm with a fixed shuffle seed.
|
|
127
|
+
|
|
128
|
+
### Shape indices
|
|
129
|
+
|
|
130
|
+
`A` area, `P` perimeter, `A_h`/`P_h` convex hull area and perimeter, `L`/`W` long and short
|
|
131
|
+
side of the minimum-area rectangle (MBR).
|
|
132
|
+
|
|
133
|
+
| Index | Formula | Range | Source |
|
|
134
|
+
| --- | --- | --- | --- |
|
|
135
|
+
| Polsby-Popper | `4 pi A / P^2` | (0, 1], circle = 1 | Polsby & Popper (1991) |
|
|
136
|
+
| Schwartzberg | `2 sqrt(pi A) / P` | (0, 1], circle = 1 | Schwartzberg (1966) |
|
|
137
|
+
| Reock | `A / A_circle` (minimum enclosing circle) | (0, 1] | Reock (1961) |
|
|
138
|
+
| Rectangularity | `A / (L W)` | (0, 1] | Rosin (1999) |
|
|
139
|
+
| Elongation | `1 - W / L` | [0, 1), square MBR = 0 | MBR (Freeman & Shapira 1975) |
|
|
140
|
+
| Aspect ratio | `L / W` | >= 1 | MBR (Freeman & Shapira 1975) |
|
|
141
|
+
| Convexity (area, "solidity") | `A / A_h` | (0, 1] | classical measure, see Zunic & Rosin (2004) |
|
|
142
|
+
| Convexity (perimeter) | `P_h / P` | (0, 1] | classical measure, see Zunic & Rosin (2004) |
|
|
143
|
+
| Fractal dimension | `2 ln(0.25 P) / ln A` | about 1-2 | McGarigal & Marks (1995), FRAGSTATS FRAC |
|
|
144
|
+
|
|
145
|
+
Schwartzberg (1966) defined `P / (2 sqrt(pi A))` (>= 1); `schwartzberg` reports its
|
|
146
|
+
reciprocal so that all compactness indices point the same way, and `schwartzberg_ratio`
|
|
147
|
+
gives the original. The fractal dimension uses the FRAGSTATS correction (a square scores
|
|
148
|
+
1); it depends on the length unit and is NaN for `A <= 1`.
|
|
149
|
+
|
|
150
|
+
### Frontage and lot type
|
|
151
|
+
|
|
152
|
+
For every parcel edge and street segment, the part of the edge within `tolerance` of the
|
|
153
|
+
segment is the intersection of the edge with the segment's capsule (a convex set, so the
|
|
154
|
+
answer is one interval, computed in closed form). Only parts where the edge runs within
|
|
155
|
+
`max_angle` (default 20 degrees) of the street direction count, so side lot lines that end
|
|
156
|
+
at the street do not. Intervals are merged per edge, so overlapping street lines are not
|
|
157
|
+
double-counted; `by_street` gives the length per street id.
|
|
158
|
+
|
|
159
|
+
With street **centrelines**, set `tolerance` to about half the right-of-way width plus a
|
|
160
|
+
digitising allowance. With **block outlines** (or lines on the street side of the lots),
|
|
161
|
+
use a small tolerance.
|
|
162
|
+
|
|
163
|
+
- `corner`: two frontage pieces at `corner_angle` (default 45 degrees) or more to each
|
|
164
|
+
other;
|
|
165
|
+
- `through`: two roughly parallel frontage pieces facing away from each other;
|
|
166
|
+
- `interior`: frontage on one side; `landlocked`: none.
|
|
167
|
+
|
|
168
|
+
The **primary front** is the longest frontage piece by default; `primary_rule="shortest"`
|
|
169
|
+
uses the shortest piece, which matches codes that put the front of a corner lot on the
|
|
170
|
+
narrower street side. `depth` is the extent of the lot perpendicular to the primary front,
|
|
171
|
+
`width` the extent along it, `mean_depth = area / width`. Without frontage the MBR is used
|
|
172
|
+
(`basis = "mbr"`). Zoning codes define lot depth and width in several ways; check that
|
|
173
|
+
these definitions fit the code you apply.
|
|
174
|
+
|
|
175
|
+
### Buildable envelope
|
|
176
|
+
|
|
177
|
+
Each lot line is moved inward, parallel to itself, by its setback, and the envelope is the
|
|
178
|
+
area inside all setback lines. Setbacks are uniform, per edge, or by role: an edge is a
|
|
179
|
+
`front` edge when at least half of it is frontage, `secondary_front` when it is a front
|
|
180
|
+
edge turned 45 degrees or more from the primary front, `rear` when it faces away from the
|
|
181
|
+
primary front, otherwise `side`.
|
|
182
|
+
|
|
183
|
+
- Convex parcels: exact half-plane intersection by Sutherland-Hodgman clipping
|
|
184
|
+
(Sutherland & Hodgman 1974).
|
|
185
|
+
- Non-convex parcels: mitred offset with removal of collapsing edges, as in the edge
|
|
186
|
+
events of a straight skeleton (Aichholzer et al. 1995), then verified (simple ring, every
|
|
187
|
+
vertex and edge midpoint keeps every setback). An envelope that would split into pieces
|
|
188
|
+
raises `ParcelError`; one with no buildable point is returned empty.
|
|
189
|
+
|
|
190
|
+
### Subdivision feasibility
|
|
191
|
+
|
|
192
|
+
`subdivision_feasibility` first checks the parcel against the rules, bounds the lot count
|
|
193
|
+
by `floor(min(area / min_area, frontage / min_frontage))`, then tries that count and fewer,
|
|
194
|
+
splitting the parcel by straight cuts perpendicular to the primary front (equal frontage or
|
|
195
|
+
equal area) and re-measuring every new lot's frontage, depth and width against the
|
|
196
|
+
streets. It returns the largest conforming count (`0` if the parcel itself fails, `1` if it
|
|
197
|
+
cannot be split). It is a screening tool: flag lots, curved streets, easements and access
|
|
198
|
+
rules are out of scope.
|
|
199
|
+
|
|
200
|
+
No lot standards or setback distances are built in. They are parameters, because they come
|
|
201
|
+
from the zoning or subdivision code that applies.
|
|
202
|
+
|
|
203
|
+
## Command line
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
parcelform metrics parcels.geojson --streets streets.geojson --tolerance 8 \
|
|
207
|
+
--front 5 --side 3 --rear 3 --min-area 400 --min-frontage 12 --subdivide \
|
|
208
|
+
-o parcels_measured.geojson --csv parcels.csv
|
|
209
|
+
|
|
210
|
+
parcelform envelope parcels.geojson --setback 3 -o envelopes.geojson
|
|
211
|
+
parcelform envelope parcels.geojson --streets streets.geojson --tolerance 8 \
|
|
212
|
+
--front 5 --side 3 --rear 4 --secondary-front 3 -o envelopes.geojson
|
|
213
|
+
|
|
214
|
+
parcelform subdivide parcels.geojson --streets streets.geojson --tolerance 8 \
|
|
215
|
+
--rules rules.json --by area -o lots.geojson --csv lots.csv
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`--rules` takes a JSON object or a two-column `key,value` CSV with `min_area`,
|
|
219
|
+
`min_frontage`, `min_depth`, `min_width`. Without `-o` and `--csv`, GeoJSON goes to stdout.
|
|
220
|
+
|
|
221
|
+
## Limitations
|
|
222
|
+
|
|
223
|
+
- Planar geometry only; no reprojection, no curved segments.
|
|
224
|
+
- Non-convex envelopes that would split into several pieces are not computed; split the
|
|
225
|
+
parcel first.
|
|
226
|
+
- Lot classification uses straight frontage pieces; along strongly curved streets
|
|
227
|
+
(cul-de-sac bulbs) set `min_piece` or `corner_angle` to avoid false corner lots.
|
|
228
|
+
- Subdivision uses parallel straight cuts only.
|
|
229
|
+
|
|
230
|
+
## References
|
|
231
|
+
|
|
232
|
+
- Aichholzer, O., Aurenhammer, F., Alberts, D. & Gärtner, B. (1995). A novel type of
|
|
233
|
+
skeleton for polygons. *Journal of Universal Computer Science* 1(12), 752-761.
|
|
234
|
+
- Andrew, A. M. (1979). Another efficient algorithm for convex hulls in two dimensions.
|
|
235
|
+
*Information Processing Letters* 9(5), 216-219.
|
|
236
|
+
- Freeman, H. & Shapira, R. (1975). Determining the minimum-area encasing rectangle for an
|
|
237
|
+
arbitrary closed curve. *Communications of the ACM* 18(7), 409-413.
|
|
238
|
+
- McGarigal, K. & Marks, B. J. (1995). FRAGSTATS: spatial pattern analysis program for
|
|
239
|
+
quantifying landscape structure. USDA Forest Service, Gen. Tech. Rep. PNW-GTR-351.
|
|
240
|
+
- Polsby, D. D. & Popper, R. D. (1991). The third criterion: compactness as a procedural
|
|
241
|
+
safeguard against partisan gerrymandering. *Yale Law & Policy Review* 9(2), 301-353.
|
|
242
|
+
- Reock, E. C. (1961). A note: measuring compactness as a requirement of legislative
|
|
243
|
+
apportionment. *Midwest Journal of Political Science* 5(1), 70-74.
|
|
244
|
+
- Rosin, P. L. (1999). Measuring rectangularity. *Machine Vision and Applications* 11(4),
|
|
245
|
+
191-196.
|
|
246
|
+
- Schwartzberg, J. E. (1966). Reapportionment, gerrymanders, and the notion of
|
|
247
|
+
compactness. *Minnesota Law Review* 50, 443-452.
|
|
248
|
+
- Sutherland, I. E. & Hodgman, G. W. (1974). Reentrant polygon clipping. *Communications
|
|
249
|
+
of the ACM* 17(1), 32-42.
|
|
250
|
+
- Toussaint, G. T. (1983). Solving geometric problems with the rotating calipers.
|
|
251
|
+
*Proceedings of IEEE MELECON '83*, Athens.
|
|
252
|
+
- Welzl, E. (1991). Smallest enclosing disks (balls and ellipsoids). In *New Results and
|
|
253
|
+
New Trends in Computer Science*, LNCS 555, 359-370.
|
|
254
|
+
- Zunic, J. & Rosin, P. L. (2004). A new convexity measure for polygons. *IEEE
|
|
255
|
+
Transactions on Pattern Analysis and Machine Intelligence* 26(7), 923-934.
|
|
256
|
+
|
|
257
|
+
## Citation
|
|
258
|
+
|
|
259
|
+
```bibtex
|
|
260
|
+
@software{eminoglu_parcelform,
|
|
261
|
+
author = {Eminoğlu, Yusuf},
|
|
262
|
+
title = {parcelform},
|
|
263
|
+
year = {2026},
|
|
264
|
+
version = {0.1.0},
|
|
265
|
+
url = {https://gitlab.com/geophilo1/parcelform_sdk}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
See also [CITATION.cff](CITATION.cff).
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# parcelform
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/parcelform/)
|
|
4
|
+
[](https://pypi.org/project/parcelform/)
|
|
5
|
+
[](https://gitlab.com/geophilo1/parcelform_sdk/-/pipelines)
|
|
6
|
+
[](https://gitlab.com/geophilo1/parcelform_sdk/-/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Parcel and lot geometry metrics from GeoJSON polygons in projected coordinates.
|
|
9
|
+
|
|
10
|
+
`parcelform` measures cadastral parcels and building lots the way planners and land-use
|
|
11
|
+
analysts usually need them: size and shape, how much of the lot faces a street, whether it
|
|
12
|
+
is a corner, through or interior lot, how deep and wide it is, what is left after
|
|
13
|
+
setbacks, and whether it can be split into lots that meet minimum standards. It needs only
|
|
14
|
+
NumPy and reads and writes plain GeoJSON.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install parcelform
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Requires Python 3.9 or newer and NumPy. Coordinates must be projected (metres or feet);
|
|
23
|
+
the package does not reproject and refuses data that looks like longitude/latitude.
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import parcelform as pf
|
|
29
|
+
|
|
30
|
+
lot = {"type": "Polygon",
|
|
31
|
+
"coordinates": [[[0, 0], [20, 0], [20, 40], [0, 40], [0, 0]]]}
|
|
32
|
+
streets = [{"type": "LineString", "coordinates": [[-50, -4], [200, -4]]}, # main street
|
|
33
|
+
{"type": "LineString", "coordinates": [[-4, -50], [-4, 100]]}] # side street
|
|
34
|
+
|
|
35
|
+
pf.shape_indices(lot).polsby_popper # 0.698
|
|
36
|
+
pf.min_area_rectangle(lot).to_dict() # length 40, width 20, angle 90
|
|
37
|
+
|
|
38
|
+
fr = pf.frontage(lot, streets, tolerance=5, primary_rule="shortest")
|
|
39
|
+
fr.total, fr.lot_type # 60.0, 'corner'
|
|
40
|
+
pf.lot_dimensions(lot, fr) # depth 40, width 20, width/depth 0.5
|
|
41
|
+
|
|
42
|
+
env = pf.buildable_envelope(lot, {"front": 5, "side": 3, "rear": 4,
|
|
43
|
+
"secondary_front": 3}, fr)
|
|
44
|
+
env.area, env.share # 434.0, 0.5425
|
|
45
|
+
|
|
46
|
+
rules = pf.LotRules(min_area=300, min_frontage=12, min_depth=20) # from your zoning code
|
|
47
|
+
pf.check_lot(lot, rules, streets, tolerance=5).passed
|
|
48
|
+
|
|
49
|
+
wide = {"type": "Polygon",
|
|
50
|
+
"coordinates": [[[0, 0], [60, 0], [60, 30], [0, 30], [0, 0]]]}
|
|
51
|
+
res = pf.subdivision_feasibility(wide, rules, streets[:1], tolerance=4)
|
|
52
|
+
res.n_lots # 5 lots of 12 m x 30 m
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Whole layers in one call:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
fc = pf.analyze("parcels.geojson", streets="streets.geojson", tolerance=8,
|
|
59
|
+
setbacks={"front": 5, "side": 3, "rear": 3},
|
|
60
|
+
rules=pf.LotRules(min_area=400, min_frontage=12), subdivide=True,
|
|
61
|
+
on_error="record")
|
|
62
|
+
pf.write_geojson(fc, "parcels_measured.geojson")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
| Area | Functions | Notes |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| Geometry | `area`, `perimeter`, `centroid`, `convex_hull`, `min_area_rectangle`, `min_enclosing_circle`, `points_in_polygon` | holes and MultiPolygons supported |
|
|
70
|
+
| Orientation | `Rectangle.angle`, `Rectangle.azimuth`, `orientation_difference` | long axis of the minimum-area rectangle |
|
|
71
|
+
| Shape indices | `shape_indices`, `polsby_popper`, `schwartzberg`, `fractal_dimension` | Polsby-Popper, Schwartzberg, Reock, rectangularity, elongation, aspect ratio, area and perimeter convexity, fractal dimension |
|
|
72
|
+
| Frontage | `frontage -> Frontage`, `StreetSegments`, `as_streets` | street centrelines or block outlines; per-street lengths; corner / through / interior / landlocked |
|
|
73
|
+
| Lot dimensions | `lot_dimensions -> LotDimensions` | depth, width, mean depth, width-to-depth ratio |
|
|
74
|
+
| Setbacks | `buildable_envelope -> Envelope`, `edge_roles`, `resolve_setbacks` | uniform, per edge, or by role (front, side, rear, secondary front) |
|
|
75
|
+
| Lot rules | `LotRules`, `check_lot`, `evaluate_rules` | minimum area, frontage, depth, width (all user-supplied) |
|
|
76
|
+
| Subdivision | `subdivision_feasibility`, `split_parcel`, `lot_count_bound` | equal-frontage or equal-area perpendicular cuts |
|
|
77
|
+
| Layers | `analyze`, `envelopes`, `subdivide_collection` | FeatureCollection in, FeatureCollection out |
|
|
78
|
+
| I/O | `read_geojson`, `write_geojson`, `write_csv`, `read_rules` | standard library only |
|
|
79
|
+
|
|
80
|
+
## Methods
|
|
81
|
+
|
|
82
|
+
### Geometry
|
|
83
|
+
|
|
84
|
+
- Areas by the shoelace formula; perimeters include holes unless `include_holes=False`.
|
|
85
|
+
- Convex hull by Andrew's (1979) monotone chain.
|
|
86
|
+
- **Minimum-area rectangle** by rotating calipers: one side of the optimal rectangle is
|
|
87
|
+
collinear with a convex-hull edge (Freeman & Shapira 1975; Toussaint 1983), so every
|
|
88
|
+
hull-edge direction is tried, vectorised. `length` is the long side, `width` the short
|
|
89
|
+
side, `angle` the long axis counter-clockwise from +x in `[0, 180)`, `azimuth` the same
|
|
90
|
+
axis as a bearing from grid north.
|
|
91
|
+
- Minimum enclosing circle by Welzl's (1991) algorithm with a fixed shuffle seed.
|
|
92
|
+
|
|
93
|
+
### Shape indices
|
|
94
|
+
|
|
95
|
+
`A` area, `P` perimeter, `A_h`/`P_h` convex hull area and perimeter, `L`/`W` long and short
|
|
96
|
+
side of the minimum-area rectangle (MBR).
|
|
97
|
+
|
|
98
|
+
| Index | Formula | Range | Source |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
| Polsby-Popper | `4 pi A / P^2` | (0, 1], circle = 1 | Polsby & Popper (1991) |
|
|
101
|
+
| Schwartzberg | `2 sqrt(pi A) / P` | (0, 1], circle = 1 | Schwartzberg (1966) |
|
|
102
|
+
| Reock | `A / A_circle` (minimum enclosing circle) | (0, 1] | Reock (1961) |
|
|
103
|
+
| Rectangularity | `A / (L W)` | (0, 1] | Rosin (1999) |
|
|
104
|
+
| Elongation | `1 - W / L` | [0, 1), square MBR = 0 | MBR (Freeman & Shapira 1975) |
|
|
105
|
+
| Aspect ratio | `L / W` | >= 1 | MBR (Freeman & Shapira 1975) |
|
|
106
|
+
| Convexity (area, "solidity") | `A / A_h` | (0, 1] | classical measure, see Zunic & Rosin (2004) |
|
|
107
|
+
| Convexity (perimeter) | `P_h / P` | (0, 1] | classical measure, see Zunic & Rosin (2004) |
|
|
108
|
+
| Fractal dimension | `2 ln(0.25 P) / ln A` | about 1-2 | McGarigal & Marks (1995), FRAGSTATS FRAC |
|
|
109
|
+
|
|
110
|
+
Schwartzberg (1966) defined `P / (2 sqrt(pi A))` (>= 1); `schwartzberg` reports its
|
|
111
|
+
reciprocal so that all compactness indices point the same way, and `schwartzberg_ratio`
|
|
112
|
+
gives the original. The fractal dimension uses the FRAGSTATS correction (a square scores
|
|
113
|
+
1); it depends on the length unit and is NaN for `A <= 1`.
|
|
114
|
+
|
|
115
|
+
### Frontage and lot type
|
|
116
|
+
|
|
117
|
+
For every parcel edge and street segment, the part of the edge within `tolerance` of the
|
|
118
|
+
segment is the intersection of the edge with the segment's capsule (a convex set, so the
|
|
119
|
+
answer is one interval, computed in closed form). Only parts where the edge runs within
|
|
120
|
+
`max_angle` (default 20 degrees) of the street direction count, so side lot lines that end
|
|
121
|
+
at the street do not. Intervals are merged per edge, so overlapping street lines are not
|
|
122
|
+
double-counted; `by_street` gives the length per street id.
|
|
123
|
+
|
|
124
|
+
With street **centrelines**, set `tolerance` to about half the right-of-way width plus a
|
|
125
|
+
digitising allowance. With **block outlines** (or lines on the street side of the lots),
|
|
126
|
+
use a small tolerance.
|
|
127
|
+
|
|
128
|
+
- `corner`: two frontage pieces at `corner_angle` (default 45 degrees) or more to each
|
|
129
|
+
other;
|
|
130
|
+
- `through`: two roughly parallel frontage pieces facing away from each other;
|
|
131
|
+
- `interior`: frontage on one side; `landlocked`: none.
|
|
132
|
+
|
|
133
|
+
The **primary front** is the longest frontage piece by default; `primary_rule="shortest"`
|
|
134
|
+
uses the shortest piece, which matches codes that put the front of a corner lot on the
|
|
135
|
+
narrower street side. `depth` is the extent of the lot perpendicular to the primary front,
|
|
136
|
+
`width` the extent along it, `mean_depth = area / width`. Without frontage the MBR is used
|
|
137
|
+
(`basis = "mbr"`). Zoning codes define lot depth and width in several ways; check that
|
|
138
|
+
these definitions fit the code you apply.
|
|
139
|
+
|
|
140
|
+
### Buildable envelope
|
|
141
|
+
|
|
142
|
+
Each lot line is moved inward, parallel to itself, by its setback, and the envelope is the
|
|
143
|
+
area inside all setback lines. Setbacks are uniform, per edge, or by role: an edge is a
|
|
144
|
+
`front` edge when at least half of it is frontage, `secondary_front` when it is a front
|
|
145
|
+
edge turned 45 degrees or more from the primary front, `rear` when it faces away from the
|
|
146
|
+
primary front, otherwise `side`.
|
|
147
|
+
|
|
148
|
+
- Convex parcels: exact half-plane intersection by Sutherland-Hodgman clipping
|
|
149
|
+
(Sutherland & Hodgman 1974).
|
|
150
|
+
- Non-convex parcels: mitred offset with removal of collapsing edges, as in the edge
|
|
151
|
+
events of a straight skeleton (Aichholzer et al. 1995), then verified (simple ring, every
|
|
152
|
+
vertex and edge midpoint keeps every setback). An envelope that would split into pieces
|
|
153
|
+
raises `ParcelError`; one with no buildable point is returned empty.
|
|
154
|
+
|
|
155
|
+
### Subdivision feasibility
|
|
156
|
+
|
|
157
|
+
`subdivision_feasibility` first checks the parcel against the rules, bounds the lot count
|
|
158
|
+
by `floor(min(area / min_area, frontage / min_frontage))`, then tries that count and fewer,
|
|
159
|
+
splitting the parcel by straight cuts perpendicular to the primary front (equal frontage or
|
|
160
|
+
equal area) and re-measuring every new lot's frontage, depth and width against the
|
|
161
|
+
streets. It returns the largest conforming count (`0` if the parcel itself fails, `1` if it
|
|
162
|
+
cannot be split). It is a screening tool: flag lots, curved streets, easements and access
|
|
163
|
+
rules are out of scope.
|
|
164
|
+
|
|
165
|
+
No lot standards or setback distances are built in. They are parameters, because they come
|
|
166
|
+
from the zoning or subdivision code that applies.
|
|
167
|
+
|
|
168
|
+
## Command line
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
parcelform metrics parcels.geojson --streets streets.geojson --tolerance 8 \
|
|
172
|
+
--front 5 --side 3 --rear 3 --min-area 400 --min-frontage 12 --subdivide \
|
|
173
|
+
-o parcels_measured.geojson --csv parcels.csv
|
|
174
|
+
|
|
175
|
+
parcelform envelope parcels.geojson --setback 3 -o envelopes.geojson
|
|
176
|
+
parcelform envelope parcels.geojson --streets streets.geojson --tolerance 8 \
|
|
177
|
+
--front 5 --side 3 --rear 4 --secondary-front 3 -o envelopes.geojson
|
|
178
|
+
|
|
179
|
+
parcelform subdivide parcels.geojson --streets streets.geojson --tolerance 8 \
|
|
180
|
+
--rules rules.json --by area -o lots.geojson --csv lots.csv
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`--rules` takes a JSON object or a two-column `key,value` CSV with `min_area`,
|
|
184
|
+
`min_frontage`, `min_depth`, `min_width`. Without `-o` and `--csv`, GeoJSON goes to stdout.
|
|
185
|
+
|
|
186
|
+
## Limitations
|
|
187
|
+
|
|
188
|
+
- Planar geometry only; no reprojection, no curved segments.
|
|
189
|
+
- Non-convex envelopes that would split into several pieces are not computed; split the
|
|
190
|
+
parcel first.
|
|
191
|
+
- Lot classification uses straight frontage pieces; along strongly curved streets
|
|
192
|
+
(cul-de-sac bulbs) set `min_piece` or `corner_angle` to avoid false corner lots.
|
|
193
|
+
- Subdivision uses parallel straight cuts only.
|
|
194
|
+
|
|
195
|
+
## References
|
|
196
|
+
|
|
197
|
+
- Aichholzer, O., Aurenhammer, F., Alberts, D. & Gärtner, B. (1995). A novel type of
|
|
198
|
+
skeleton for polygons. *Journal of Universal Computer Science* 1(12), 752-761.
|
|
199
|
+
- Andrew, A. M. (1979). Another efficient algorithm for convex hulls in two dimensions.
|
|
200
|
+
*Information Processing Letters* 9(5), 216-219.
|
|
201
|
+
- Freeman, H. & Shapira, R. (1975). Determining the minimum-area encasing rectangle for an
|
|
202
|
+
arbitrary closed curve. *Communications of the ACM* 18(7), 409-413.
|
|
203
|
+
- McGarigal, K. & Marks, B. J. (1995). FRAGSTATS: spatial pattern analysis program for
|
|
204
|
+
quantifying landscape structure. USDA Forest Service, Gen. Tech. Rep. PNW-GTR-351.
|
|
205
|
+
- Polsby, D. D. & Popper, R. D. (1991). The third criterion: compactness as a procedural
|
|
206
|
+
safeguard against partisan gerrymandering. *Yale Law & Policy Review* 9(2), 301-353.
|
|
207
|
+
- Reock, E. C. (1961). A note: measuring compactness as a requirement of legislative
|
|
208
|
+
apportionment. *Midwest Journal of Political Science* 5(1), 70-74.
|
|
209
|
+
- Rosin, P. L. (1999). Measuring rectangularity. *Machine Vision and Applications* 11(4),
|
|
210
|
+
191-196.
|
|
211
|
+
- Schwartzberg, J. E. (1966). Reapportionment, gerrymanders, and the notion of
|
|
212
|
+
compactness. *Minnesota Law Review* 50, 443-452.
|
|
213
|
+
- Sutherland, I. E. & Hodgman, G. W. (1974). Reentrant polygon clipping. *Communications
|
|
214
|
+
of the ACM* 17(1), 32-42.
|
|
215
|
+
- Toussaint, G. T. (1983). Solving geometric problems with the rotating calipers.
|
|
216
|
+
*Proceedings of IEEE MELECON '83*, Athens.
|
|
217
|
+
- Welzl, E. (1991). Smallest enclosing disks (balls and ellipsoids). In *New Results and
|
|
218
|
+
New Trends in Computer Science*, LNCS 555, 359-370.
|
|
219
|
+
- Zunic, J. & Rosin, P. L. (2004). A new convexity measure for polygons. *IEEE
|
|
220
|
+
Transactions on Pattern Analysis and Machine Intelligence* 26(7), 923-934.
|
|
221
|
+
|
|
222
|
+
## Citation
|
|
223
|
+
|
|
224
|
+
```bibtex
|
|
225
|
+
@software{eminoglu_parcelform,
|
|
226
|
+
author = {Eminoğlu, Yusuf},
|
|
227
|
+
title = {parcelform},
|
|
228
|
+
year = {2026},
|
|
229
|
+
version = {0.1.0},
|
|
230
|
+
url = {https://gitlab.com/geophilo1/parcelform_sdk}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
See also [CITATION.cff](CITATION.cff).
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""parcelform quickstart on a small synthetic block (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 parcelform as pf
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def square(x0: float, y0: float, w: float, d: float) -> dict:
|
|
14
|
+
ring = [[x0, y0], [x0 + w, y0], [x0 + w, y0 + d], [x0, y0 + d], [x0, y0]]
|
|
15
|
+
return {"type": "Polygon", "coordinates": [ring]}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
# Three lots along a street whose centreline runs 6 m south of the lots; the
|
|
20
|
+
# westernmost lot also faces a side street.
|
|
21
|
+
parcels = {
|
|
22
|
+
"type": "FeatureCollection",
|
|
23
|
+
"features": [
|
|
24
|
+
{"type": "Feature", "id": "A", "properties": {}, "geometry": square(0, 0, 18, 35)},
|
|
25
|
+
{"type": "Feature", "id": "B", "properties": {}, "geometry": square(18, 0, 60, 35)},
|
|
26
|
+
{"type": "Feature", "id": "C", "properties": {}, "geometry": square(78, 10, 15, 25)},
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
streets = [
|
|
30
|
+
{"type": "LineString", "coordinates": [[-50, -6], [150, -6]]},
|
|
31
|
+
{"type": "LineString", "coordinates": [[-6, -50], [-6, 100]]},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
rep = pf.analyze_parcel(parcels["features"][0]["geometry"], streets=streets, tolerance=7)
|
|
35
|
+
print("lot A:", rep.frontage.lot_type, "frontage", rep.frontage.total)
|
|
36
|
+
print("shape:", {k: round(v, 3) for k, v in rep.shape.to_dict().items()})
|
|
37
|
+
|
|
38
|
+
# Zoning values below are placeholders; take them from the code that applies.
|
|
39
|
+
rules = pf.LotRules(min_area=400, min_frontage=12, min_depth=25)
|
|
40
|
+
out = pf.analyze(
|
|
41
|
+
parcels,
|
|
42
|
+
streets=streets,
|
|
43
|
+
tolerance=7,
|
|
44
|
+
setbacks={"front": 5, "side": 3, "rear": 3},
|
|
45
|
+
rules=rules,
|
|
46
|
+
subdivide=True,
|
|
47
|
+
primary_rule="shortest", # corner lots front on their narrower street side
|
|
48
|
+
)
|
|
49
|
+
for f in out["features"]:
|
|
50
|
+
p = f["properties"]
|
|
51
|
+
print(
|
|
52
|
+
f["id"],
|
|
53
|
+
p.get("lot_type"),
|
|
54
|
+
"frontage",
|
|
55
|
+
round(p.get("frontage", 0), 2),
|
|
56
|
+
"envelope",
|
|
57
|
+
round(p.get("envelope_area") or 0, 1),
|
|
58
|
+
"max lots",
|
|
59
|
+
p.get("max_lots"),
|
|
60
|
+
)
|
|
61
|
+
print(json.dumps(out["features"][1]["properties"], indent=1)[:400])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|