Protocols¶
The five seams of the package, as typing.Protocol definitions. Implementations are structural
— nothing subclasses these — so the behavioural contract is stated once, here, and each
implementation's own docstring says what it does differently rather than restating the interface.
One implementation per protocol is the right number for now. The seam is the point, not the number of implementations.
lczkit.protocols
¶
Pluggable-source protocols for the lczkit pipeline.
Every stage after spatial-unit generation exchanges data as a GeoDataFrame indexed by a stable
string unit_id. These five protocols are the seams between the pipeline and its data sources.
There is one implementation of each; the seam is the point, not the number of implementations.
No implementations live here. Each protocol states the contract once, and implementations satisfy it structurally rather than by subclassing.
BBox
module-attribute
¶
A bounding box as (minx, miny, maxx, maxy) in EPSG:4326.
VectorSource
¶
Bases: Protocol
Supplies cleaned vector layers for a bounding box.
Implementations own their cache under settings.source_dir(<name>) and must pin any
upstream release/version explicitly rather than tracking "latest".
buildings
¶
buildings(bbox: BBox) -> GeoDataFrame
Return building footprints intersecting bbox.
Columns include at least geometry, height (nullable), num_floors (nullable),
subtype and class (usage type), and sources (upstream provenance metadata).
height and num_floors are nullable by design — upstream vector sources conflate
footprints from several datasets and only some of them carry height at all. A null
height is never an error at this layer; the height cascade owns it. subtype, class and
sources must survive cleaning: class is the only route to LCZ 10, and sources drives
the source-availability diagnostic.
Source code in src/lczkit/protocols.py
water
¶
water(bbox: BBox) -> tuple[GeoDataFrame, GeoDataFrame]
Return (waterlines, waterbodies) intersecting bbox.
Waterlines are LineStrings, waterbodies are Polygons.
rail
¶
rail(bbox: BBox) -> GeoDataFrame
Return rail-network segments intersecting bbox, as LineStrings.
EnclosureUnits needs rail as a barrier layer alongside streets and waterbodies. Nothing
else in the pipeline reads it.
Source code in src/lczkit/protocols.py
land_use
¶
land_use(bbox: BBox) -> GeoDataFrame
Return land-use polygons intersecting bbox, retaining subtype and class.
Functional semantics only. This layer supplies the industrial share of a unit's area —
industrial_fraction, which the LCZ 8/10 rule reads — and nothing else. It is not a
barrier for spatial-unit generation and not a
land-cover source — rasters own land cover.
Source code in src/lczkit/protocols.py
HeightSource
¶
Bases: Protocol
One tier of the building-height cascade.
A cascade runs a sequence of HeightSource tiers over the same buildings layer; each
tier fills height only for the rows it can resolve, tagging every row it touches with
height_source and height_confidence.
height_sources
property
¶
Every height_source tag this tier can write.
Usually one, matching name — but a tier resolving a row by more than one route
distinguishes them here, so the per-unit tier fractions can report a fixed set of
columns determined by the configured cascade rather than by which routes happened to
fire on a given city.
fill
¶
Populate height, height_source and height_confidence where this tier can.
Returns buildings with those three columns filled for every row it resolves.
Rows this tier cannot resolve are returned unchanged (still nullable) for the next tier in the cascade.
Source code in src/lczkit/protocols.py
RasterSource
¶
Bases: Protocol
Supplies zonal land-cover fractions keyed by unit_id — never raw pixels.
fractions
¶
Return a table indexed by unit_id with one fraction column per land-cover class.
Fractions must sum to ~1.0 per unit. The class-to-fraction mapping is a config value, never hardcoded.
Source code in src/lczkit/protocols.py
SpatialUnitStrategy
¶
Bases: Protocol
Partitions a city into the spatial units the rest of the pipeline joins on.
generate
¶
generate(bbox: BBox, barriers: GeoDataFrame | None = None) -> GeoDataFrame
Return unit polygons indexed by a stable string unit_id, covering bbox.
barriers (streets, rail, waterbodies, large vegetation patches) constrain
enclosure-based strategies; grid-based strategies may ignore it.
Source code in src/lczkit/protocols.py
Classifier
¶
Bases: Protocol
Classifies spatial units into Local Climate Zones by distance to LCZ prototypes.
classify
¶
Return a table indexed by unit_id carrying the full 17-way distance vector.
One distance per LCZ prototype, plus lcz_primary, lcz_secondary, and uniqueness.
Never collapse to a bare integer label here — that is a downstream convenience function, not part of the core classification output.
Source code in src/lczkit/protocols.py
Coordinate reference system¶
All internal computation happens in a projected CRS. This is checked on entry to each stage rather than left as a convention, because a geographic CRS makes every area statistic meaningless without raising anything.
lczkit.crs
¶
CRS enforcement for lczkit's internal computation.
All internal computation happens in a projected CRS obtained via gdf.estimate_utm_crs().
Lat/lon appears only at ingestion and export boundaries — this helper is the single place
that enforces it, rather than relying on a docstring convention.
local_utm_crs
¶
local_utm_crs(bbox: BBox) -> CRS
The single UTM CRS to use for all internal computation over bbox.
Computed from bbox itself (in EPSG:4326), not from any individual layer —
estimate_utm_crs() can differ between layers covering nearly the same area (e.g. near a
UTM zone boundary), which would silently break cross-layer operations if each layer picked
its own zone. Every stage working over the same bbox should call this once and share the
result rather than re-deriving it.
Source code in src/lczkit/crs.py
assert_projected_crs
¶
Raise ValueError unless gdf has a projected CRS.
Call this at the entry point of any function that performs internal geometric computation (areas, lengths, buffers, distances) — those are meaningless in a geographic CRS.