# Parcel Intelligence frontend delivery (delivery contract 2.1, tile schema v2.1)

Generated by germany-data-pipeline; do not edit by hand. Serve as static files
with HTTP byte-range support (PMTiles needs `Accept-Ranges: bytes`).

All map data is PMTiles; there are no GeoJSON or separate index files. Start
with `manifest.json`: it lists every file (path, bytes, SHA-256), the Länder
that have parcel data and where their files are. Read paths from the manifest;
do not build them.

## Layout

```
manifest.json            entry point
README.md                this file
CREDITS.md               source providers, licence/source links and modification notices
columns.json             data dictionary (tile fields, product columns, NULL meanings)
germany/admin.pmtiles    all 16 Land outlines + labels
lands/<code>_<name>/     one folder per Land with parcel data
  parcels.pmtiles        parcel tiles; metadata: parcel counts per unit
  admin.pmtiles          administrative boundaries + labels; metadata: unit index
  criteria.json          filter values of this Land
```

A new Land adds one folder under `lands/` and one entry in `manifest.lands`.

## Run identity

Each Land is built independently. Compare a Land's `parcels.pmtiles` and
`admin.pmtiles` run ids with `lands[].pipeline_run_id`.
`germany/admin.pmtiles` uses `germany.pipeline_run_id`.
The top-level `pipeline_run_id` is populated only for a single-Land delivery;
otherwise it is `null`. Each Land also records `release_id`, `source_version`
and `source_coverage.current_alkis_grz`.

## Tile schema 2.1 (2026-10-27)

Additive only: five optional fields (`plan_name`, `plan_gr_m2`,
`plan_gr_basis`, `additional_gr_m2`, `potential_class`); no v2 field changed.
`manifest.tile_schema` is `v2.1`; the value lists are in `columns.json`
(`tile.plan_gr_bases`, `tile.potential_classes`). Colour the potential map by
`potential_class`, not by `grz_headroom` thresholds: the same GRZ difference
means 30 m² on a 300 m² parcel and 500 m² on a 5,000 m² one.

## Loading flow

1. Load `manifest.json` and `germany/admin.pmtiles` (+ metadata). Mark the
   Länder in `manifest.lands` as available.
2. On Land selection: zoom to its `focusBbox` (Germany `admin_index`), open the
   Land's `admin.pmtiles`, `parcels.pmtiles` and `criteria.json`.
3. Administrative selection from `admin.pmtiles` metadata `admin_index`: levels
   in `meta.levels` order, skipping `hidden` levels; counts from
   `parcels.pmtiles` metadata `admin_parcel_counts[level][id]`.
4. Filter parcels by attribute (`adm_*` field named by the level's
   `parcelAttribute`), not by geometry.
5. Hide filters whose source does not exist in the Land
   (`manifest.lands[].source_coverage`) and say so.

## `parcels.pmtiles`

- `parcels_coverage`, zoom 0–9: simplified geometry only. Do not filter or count with it.
- `parcels`, zoom 10–14: every parcel, with the fields below.

| Field | Type | Meaning |
| --- | --- | --- |
| `parcel_key` | String | Flurstückskennzeichen (20 characters, starts with the Land code). Unique within a Land and across Länder. |
| `area_m2` | Number (integer) | ALKIS amtlicheFlaeche. |
| `land_use` | String, PIPE list | ALKIS land-use classes (`ax_…`). |
| `building_count` | Number (integer) | Buildings intersecting the parcel. |
| `planning_status` | String | Values in `criteria.json` → `planning_screening_statuses`. `no_source` = no planning source for this parcel's Gemeinde. |
| `plan_grz` | Number | Plan GRZ (GRZmax if no GRZ), area-weighted over the plan areas on the parcel; slivers <= 1 m² ignored. Screening value, not legally verified. |
| `plan_grz_share` | Number, 0.01 | Share of the parcel covered by the areas that gave `plan_grz` (where it applies). |
| `plan_grz_values` | String, PIPE list | Every GRZ value on the parcel (plan-area overlaps > 1 m²). |
| `grz_headroom` | Number, 0.01 | Permitted minus current ground area per m² of parcel: `plan_grz × plan_grz_share`, capped by the Baufenster on the parcel, minus current ALKIS utilisation (= `plan_grz` minus utilisation on a fully covered parcel without a limiting Baufenster). |
| `public_property` | String, PIPE list | Public-property classification codes. |
| `plan_name` | String | Name of the B-Plan when exactly one applicable plan lies on the parcel (XPlanung `xpPlanName`). |
| `plan_gr_m2` | Number (integer) | Zulässige Grundfläche GR (§ 19 Abs. 2 BauNVO), screening: `plan_grz` × the Baugebiet part of the parcel, capped by the überbaubare Grundstücksfläche (Baufenster) on the parcel (§ 23 BauNVO). Without the § 19 Abs. 4 allowance. |
| `plan_gr_basis` | String | How `plan_gr_m2` was derived: `baugebiet_grz` (GRZ of the Baugebiet), `baugebiet_grz_baufenster` (same, limited by the Baufenster), `baufenster_grz` (GRZ set on the überbaubare Grundstücksfläche). |
| `additional_gr_m2` | Number (integer) | Zusätzlich mögliche Grundfläche: `plan_gr_m2` minus current ALKIS ground area; negative = above the plan value. |
| `potential_class` | String | `high` (≥ 150 m² additional), `medium` (50 to < 150 m²), `low` (> 0 to < 50 m²), `none` (≤ 0), `review` (plan value, not calculable: several plans / unknown current use), `no_grz` (plan in force without GRZ), `no_plan` (no plan in force in the source; may be §§ 34/35 BauGB land — never show as "no potential"). Thresholds are product assumptions. Missing = no planning source. |
| `adm_*` | String | Administrative unit ids per level (`manifest.parcel_attributes`). |

Rules:

- **Types.** `area_m2`, `building_count`, `plan_grz`, `plan_grz_share`,
  `grz_headroom`, `plan_gr_m2`, `additional_gr_m2` are Numbers; every other field is a String. Compare `adm_*` as
  strings (`"02"`, never `2`).
- **A missing property means NULL.** A field no parcel of a Land has a value
  for is also missing from that file's `vector_layers` field list (Tippecanoe
  omits NULLs). Example: Berlin has no planning source, so `plan_grz`,
  `plan_grz_share`, `plan_grz_values`, `grz_headroom` and the five 2.1 fields are absent and every
  parcel has `planning_status = "no_source"`. Never treat absent as 0.
- **PIPE lists** have leading and trailing pipes (`|a|b|`); test with a
  substring match on `|value|`. An empty list is omitted.
- **Consistency.** `grz_headroom` set ⇒ `plan_grz` set. `plan_grz` set,
  `grz_headroom` missing and `building_count > 0` means current utilisation is
  unknown (only buildings that do not count toward GRZ, e.g. garages); show
  "unknown", not 0. Buildings that only touch the parcel count as 0.
- **Public property.** Missing never means "privately owned". Only Lands with
  `source_coverage.public_property = true` have a source (Hamburg: Landesgrundbesitzverzeichnis,
  state-owned land); the codes are explained in `criteria.json` (`public_property_catalog`,
  `public_property_groups`). A parcel without a code in such a Land is "not in the LBV".
- **Planning disclaimer.** Show `manifest.planning.disclaimer` with any plan value.

## `admin.pmtiles`

One polygon layer per visible level plus `labels` (`id`, `name`, `level`).
Metadata `admin_index.meta` (`land`, `gebietsstand`, `adminRunId`, `sources`,
`levels[]` with `level`, `rank`, `hidden`, `parcelAttribute`) and
`admin_index.units[]` (`id`, `level`, `name`, `parent`, `bbox`, `focusBbox`).

## `criteria.json`

`land_use_types` (present in the Land), `planning_screening_statuses` (static,
all values), `public_property_catalog` and `public_property_groups` (empty
without a public-property source).

## Attribution in derived exports

Preserve `manifest.attributions` in CSV/GeoJSON exports, including the provider,
source URL, licence URL, modification notice and release identity. Display source
credits in the interface; BKG also requires its provider and underlying-source
links. Each source keeps its own licence, including component licences in mixed
administrative inputs. These notices do not license unrelated basemap imagery.
