Pipeline¶
The end-to-end chain: clean vectors, generate units, fill heights, read land cover, compute
parameters, classify, write outputs. run_pipeline is the only place the stages are wired
together, and the command line calls it rather than restating any of it.
lczkit.pipeline
¶
The whole chain, from a bbox to a run directory and optionally a map site.
run_pipeline is the only place the stages are wired together. The command line calls it rather
than restating any of it, so there is one definition of what a run does.
What is deliberately absent. Validation. write_run takes a validation= report and the
manifest has a slot for it, but the chain never populates them: agreement is measured separately,
against reference datasets that are not always on disk. Wiring it in here would make every run
depend on those datasets being present. Call lczkit.validation yourself when you have them.
StageObserver is how a caller watches a long run without this module choosing a rendering. The
command line passes one backed by rich; any object with the same two methods will do.
STAGES
module-attribute
¶
STAGES = ('clean_vectors', 'heights', 'units', 'land_cover', 'provenance', 'parameters', 'classify', 'write_run', 'build_site')
Stage names, in order, so a caller can size a progress display before the run starts.
StageObserver
¶
Bases: Protocol
Something that watches each stage begin and end.
A typing.Protocol rather than a base class, following the same decision the five data-source
protocols were built under: the point is the seam, and a caller that already has a timer should
not have to inherit anything to use it.
PipelineResult
dataclass
¶
PipelineResult(outputs: RunOutputs, site: SiteReport | None, site_skipped: str | None = None, stages: dict[str, float] = dict(), height_products: dict[str, str | None] = dict())
What a run produced, and how long each stage took.
site
instance-attribute
¶
site: SiteReport | None
None when the run was asked not to build one, or when tippecanoe is absent.
site_skipped
class-attribute
instance-attribute
¶
Why no site was built, where one was asked for. None when one was built or not wanted.
The site is the last stage and everything else is already on disk by the time it runs, so
a missing tippecanoe must not cost a caller the run. It used to: the error propagated out of
run_pipeline, the command line turned it into an exit code, and the line naming the run
directory was never printed — a ten-minute city reported as a failure with no mention that its
output existed. lczkit site build <run_dir> completes it later.
stages
class-attribute
instance-attribute
¶
Wall seconds per stage, in the order they ran.
height_products
class-attribute
instance-attribute
¶
Which areal height product file each enabled tier resolved to, by tier name.
None where the product has no coverage for this extent — Open Buildings stops at Europe —
which is a different state from a tier that was disabled, and stays separable here.
build_strategy
¶
build_strategy(config: UnitsConfig, *, buildings: GeoDataFrame | None = None) -> SpatialUnitStrategy
The configured SpatialUnitStrategy.
buildings is only read by patch, and only when patch_merge_on_morphology is on. It is
passed at construction rather than to generate because the protocol's signature is
(bbox, barriers), and widening that for one strategy would put a building layer into an
interface the other two have no use for.
Source code in src/lczkit/pipeline.py
run_pipeline
¶
run_pipeline(settings: Settings, bbox: BBox, *, build_site_after: bool = True, observer: StageObserver | None = None, extent: ExtentRecord | None = None) -> PipelineResult
Clean, fill heights, classify, write a run directory and optionally build its map site.
settings must already carry a runnable configuration — CleaningConfig and HeightConfig
both have fields that default to None and raise at call time. lczkit.presets.apply_preset
is what fills them.
Every path comes from settings: the run directory, the tile cache, and the input/
subdirectories the Overture and height-product fetchers own. Nothing existing under input/
is modified or removed.
extent records how bbox was chosen — a named place, a So2Sat window, or four numbers —
and goes into the manifest. It defaults to the bbox alone, which is all a library caller who
computed their own window can honestly claim.
Source code in src/lczkit/pipeline.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 | |
Raster windows¶
Clipping a raster to a study window, and checking that what came back actually covers it. The check
matters because neither half fails loudly on its own: read(window=…) returns a smaller array
rather than raising when the window runs off the edge of a raster, and units the raster never
reached come back as all-NaN fractions by design. Together that is a partly missing map with
nothing raised.
lczkit.raster_window
¶
Raster windowing shared by the places in the package that read or clip a raster.
The height cascade reads a mean per building footprint; the land-cover sources read class
fractions per spatial unit. Those are different reductions over different libraries, but
both begin by finding the one window of a raster that covers a set of geometries, and getting the
edge padding wrong is a quiet off-by-one rather than a crash. It lives here, next to crs.py,
because it belongs to neither phase.
clip_raster and coverage_shortfall join them because a run has to materialise a window before
it can reduce over one — the global land-cover and reference products are read remotely and written
into the run directory. See lczkit.sources.worldcover.
covering_window
¶
covering_window(src: DatasetReader, bounds: ndarray | tuple[float, float, float, float]) -> Window | None
The raster window covering bounds, padded one cell and clipped to the raster.
bounds is (minx, miny, maxx, maxy) in the raster's own CRS. Returns None when it falls
entirely outside the raster, which callers report as "this product cannot answer" rather than
as an error.
The one-cell pad matters at the edges: a geometry whose boundary sits exactly on a cell boundary still touches the cell beyond it, and that cell has to be inside the window to be counted at all.
Source code in src/lczkit/raster_window.py
clip_raster
¶
clip_raster(source: str, destination: Path, bbox: BBox) -> Path
Window source to bbox and write it into the run directory, preserving nodata and CRS.
Source code in src/lczkit/raster_window.py
coverage_shortfall
¶
coverage_shortfall(bounds: tuple[float, float, float, float], bbox: BBox, res: float) -> dict[str, float]
How far a raster's bounds fall short of bbox on each side, in pixels.
Empty when the raster covers the window. A shortfall under one pixel is not reported: a clip lands on cell boundaries, so a fraction of a cell is rounding rather than missing ground.
Separated out because the failure this guards against is silent. clip_raster windows with
from_bounds and then read(window=...), which returns a smaller array rather than
raising when the window overruns the source, and LocalRasterSource.fractions turns units
with no coverage into all-NaN rather than an error. Both behaviours are correct on their
own; together they let a raster that covers a quarter of the requested window produce a map
with a quarter of its land cover missing and nothing anywhere saying so.