Command line¶
lczkit cities, lczkit run, lczkit site build|serve, and lczkit export. Installed as the
lczkit console script.
The command line is deliberately thin. It configures a run through apply_preset rather than by
restating any setting of its own, so there is one definition of what a preset means and the command
line cannot drift from it.
lczkit cities cambridge # find an extent, and what it will cost
lczkit run --city cambridge --country GBR
lczkit run --city berlin --so2sat-window # the extent the recorded figures were measured over
lczkit run --bbox 13.29,52.45,13.52,52.59 # needs nothing on disk
lczkit run --city berlin --dry-run # resolve the config, create nothing
lczkit site build output/lczkit/<run_id>
lczkit site serve output/lczkit/<run_id>
lczkit export output/lczkit/<run_id> # add units.gpkg to a run already on disk
Three locators, and they mean different ground. --city names one of GUPPD's 5 558 urban
regions and covers it. --so2sat-window takes the densest 30 km window of that city's So2Sat
labels instead — the extent the published agreement figures were measured over — and works for 28
cities. The second is a flag rather than a fallback: a run that reached it by accident, or silently
failed to, would look comparable with a published figure while covering different ground. Whichever
was used is recorded in the run manifest's extent.
lczkit export reads only what a run already wrote, adds units.gpkg, and backfills the
manifest's crs, crs_wkt and extent — the last reconstructed from the units' own bounds and
tagged kind="recovered", never presented as the window the run was asked for. It edits the manifest as JSON rather than through RunManifest: revalidating an archived run
against today's model would fill in defaults for fields that run never had, and make it look like
it came from code that did not produce it. It is idempotent, and the archival GeoParquet is
byte-identical afterwards.
lczkit.cli
¶
The lczkit command line.
Everything here is a wrapper. run calls lczkit.pipeline.run_pipeline, site build calls
lczkit.viz.build_site, site serve calls lczkit.viz.serve, export calls
lczkit.output.export_gis. No command computes anything, and none of them is the only way to
reach what it wraps: the library API is unchanged and the experiment drivers in scripts/ still
call it directly.
main_callback
¶
main_callback(version: Annotated[bool, Option('--version', callback=_version, is_eager=True, help='Show the version.')] = False) -> None
Map cities into Local Climate Zones from open vector and raster data.
Every run records the Overture release, the height cascade, the classifier weights and the
resolved package versions in its manifest, so a result can be traced back to what produced
it. lczkit cities <name> finds an extent and lczkit run --city <name> maps it;
--dry-run resolves the config without acting.
Source code in src/lczkit/cli/__init__.py
Run¶
lczkit.cli.run
¶
lczkit run — a bbox or a city in, a run directory and a map site out.
Two city locators, and the distinction is load-bearing. --city names one of GUPPD's 5 558
urban regions and covers it; --city ... --so2sat-window takes the densest 30 km window of that
city's So2Sat labels instead, which is the extent the published agreement figures were measured
over. They are different ground, so a run says which one it used in its manifest rather than
leaving a reader to infer it from a bbox.
The default is the general one. Reproducing a recorded figure is the specialist case and asks for itself; getting a map of a city is what the command is for.
run
¶
run(bbox: Annotated[str | None, Option('--bbox', metavar='W,S,E,N', help='Extent in lon/lat degrees. Needs nothing on disk.')] = None, city: Annotated[str | None, Option('--city', metavar='NAME', help="Any of GUPPD's 5 558 urban regions. `lczkit cities` searches them.")] = None, country: Annotated[str | None, Option('--country', metavar='ISO', help='Disambiguate --city, e.g. GBR. 149 GUPPD names are shared.')] = None, so2sat_window: Annotated[bool, Option('--so2sat-window', help="Use the city's densest 30 km So2Sat window instead of its GUPPD extent.")] = False, extent_km: Annotated[float | None, Option('--extent-km', help='Shrink the extent to a concentric square of this side. Use it to try a run.')] = None, run_id: Annotated[str | None, Option('--run-id', help='Name the output directory. Defaults to a UTC timestamp.')] = None, preset: Annotated[str, Option('--preset', help=f"Run configuration. One of: {join(sorted(PRESETS))}.")] = DEFAULT_PRESET, config: Annotated[Path | None, Option('--config', exists=True, dir_okay=False, help='JSON overriding any settings section. A run manifest works here.')] = None, site: Annotated[bool, Option('--site/--no-site', help='Build the map site after the run.')] = True, buildings: Annotated[bool, Option('--buildings/--no-buildings', help='Tile building footprints for the 3D layer. Roughly triples the site.')] = False, basemap: Annotated[list[str] | None, Option('--basemap', metavar='KEY', help=BASEMAP_HELP)] = None, dry_run: Annotated[bool, Option('--dry-run', help='Resolve and print the configuration, then stop.')] = False, quiet: Annotated[bool, Option('--quiet', '-q', help='Suppress per-stage progress.')] = False) -> None
Run the whole pipeline over one extent.
Give it either an explicit window or a city:
lczkit run --bbox 13.29,52.45,13.52,52.59
lczkit run --city nairobi
lczkit run --city cambridge --country GBR --extent-km 3
Writes $DATA_DIR/output/lczkit/<run_id>/, plus the caches the Overture and height-product
sources own under input/. Nothing existing under input/ is modified.
Source code in src/lczkit/cli/run.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 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 | |
Site¶
lczkit.cli.site
¶
lczkit site — build a map site from a finished run, and serve it.
Both commands take a run directory, not a site directory. The run directory is what a run
produces and what gets archived; site/ is a thing inside it. Taking the run directory in both
places means a user never has to remember which level they are at.
build
¶
build(run_dir: Annotated[Path, Argument(exists=True, file_okay=False, help='A run directory, i.e. output/lczkit/<run_id>/.')], buildings: Annotated[bool | None, Option('--buildings/--no-buildings', help='Tile building footprints. Defaults to whatever the run recorded.')] = None, basemap: Annotated[list[str] | None, Option('--basemap', metavar='KEY', help=BASEMAP_HELP)] = None) -> None
Build <run_dir>/site/ from the run's own outputs.
A pure transform: everything drawn was decided by the run and written into its manifest and
its persisted layers. Rebuilding an archived run needs no access to input/.
Source code in src/lczkit/cli/site.py
serve_site
¶
serve_site(run_dir: Annotated[Path, Argument(exists=True, file_okay=False, help='A run directory with a built site/.')], port: Annotated[int, Option('--port', help='Port to bind.')] = 8000, bind: Annotated[str, Option('--bind', help='Address to bind.')] = '127.0.0.1') -> None
Serve a built site over loopback until interrupted.
A server is required rather than preferred: PMTiles reads byte ranges through fetch, and the
Fetch standard leaves file: URLs unhandled, so opening index.html fails in both Chrome and
Firefox. This reaches no network — it is the same standard-library server the site ships with.
Source code in src/lczkit/cli/site.py
Export¶
lczkit.cli.export
¶
lczkit export — make a finished run openable in a GIS.
Takes a run directory, like lczkit site build, for the same reason: that is the level a user
archives and the level everything else in the CLI already speaks.
A current run needs this only if it was written with output.gis_format = "none". It exists for
older runs, which carry a correct GeoParquet and no GeoPackage — and for which re-running a
ten-minute city to change how it is packaged would be the wrong trade.
export
¶
export(run_dir: Annotated[Path, Argument(exists=True, file_okay=False, help='A run directory, i.e. output/lczkit/<run_id>/.')]) -> None
Write units.gpkg beside a run's units.parquet, and record its CRS and extent.
The extent is recovered from the units' own bounds, not from the run's own record of what
it was asked for — an archived run has no such record, which is why the field exists. It is
tagged kind="recovered" so the two are never confused, and a run that already states its
extent keeps what it states.
Source code in src/lczkit/cli/export.py
lczkit cities¶
lczkit.cli.cities
¶
lczkit cities — find the urban region a run should cover, before spending a download.
The command that makes --city usable. GUPPD names 5 558 regions and 149 of those names are shared
by more than one, so a caller needs to see what they are about to ask for: which country, how much
ground, and therefore roughly what it will cost. Printing the area is the point — a 64 km² region
is a few minutes and a 17 661 km² one is not, and nothing else in the interface says so.
Reads one 564 KB table under input/NASA/. No pipeline, no network, no label archive.
cities
¶
cities(query: Annotated[str | None, Argument(help='Part of a city name. Omit to list the largest regions.')] = None, country: Annotated[str | None, Option('--country', metavar='ISO', help="ISO code or country name, e.g. GBR or 'united kingdom'.")] = None, limit: Annotated[int, Option('--limit', help='Most rows to print.')] = 20) -> None
Search the GUPPD urban regions --city resolves against.
lczkit cities cambridge
lczkit cities london --country gb
Prints each match's bounding box and its area, so the extent a run would cover is visible
before the run starts. Pass any of these names to lczkit run --city.