cartopy: server/pipeline/brp.ts — the extractor half of the crop parcels #204

Closed
opened 2026-08-30 05:34:32 +00:00 by viberfox-agent · 1 comment
Collaborator

The client half of the RVO crop parcels landed in jeroen/cartopolis (issue #203, branch feat/203-brp-crop-parcels). It draws nothing until a host serves /brp/coverage.json and per-cell files, and that extractor lives in cartopy — server/pipeline/brp.ts, beside bgt.ts, plus an entry in server/pipeline/format.test.ts.

Filed here rather than on jeroen/cartopy because this container cannot reach that repository: FORGEJO_TOKEN answers 404 on repos/jeroen/cartopy and git ls-remote says "Repository not found". Please move it, or hand it to a session that has the credentials.

Source

  • https://api.pdok.nl/rvo/gewaspercelen/ogc/v1, collection brpgewas. Keyless, OGC API Features, bbox query, GeoJSON out.
  • Licence Public Domain Mark 1.0, named by the collections document itself.
  • Properties per feature: category, gewas, gewascode, jaar, status.
  • Measured 2026-08-30: 308 features / 921 KB over z14 cell 8488/5315 (farmland north of Groningen), 199 / 232 KB over 8449/5374 (Flevoland), 0 over 8490/5320 (Grote Markt).

Wire format the client already parses

Cells binned at z14, on the parcel's centroid — one cell owns a parcel and draws it whole, including the vertices that fall outside (22–24 % of parcels leave their own cell, so binning by containment would delete a quarter of the country's fields).

/brp/coverage.json — the usual manifest: {"zoom": 14, "cells": [[x, y], …], "attribution": "…"}.

/brp/14/{x}/{y}.json:

{"parcels": [[category, gewascode, [[[lat, lng], …], …]], …]}
  • category is an index into this table, append, never renumber:

    index register value
    0 Grasland
    1 Bouwland
    2 Natuurterrein
    3 Braakland
    4 Overige
    5 Landschapselement — never emit, reserved and non-drawable
  • gewascode is the register's own crop number, verbatim, as an integer.

  • Rings are [lat, lng] pairs, outer ring first, holes after, open (no repeated closing vertex). An outer ring under three points is dropped by the client.

  • Landschapselement features are filtered out by the extractor: 60 % of the features are landscape elements, and 316 of 340 sampled are Sloot (ditches), which the Shortbread water layers already draw. Drawing them as land fills puts ground where the register says water.

  • Simplify with Douglas–Peucker at 0.5 m — it keeps 31–32 % of the vertices, taking the heaviest cell measured to ~40 KB of coordinates.

  • The register serves one year (jaar: 2025, status: "Definitief") with no superseded versions, so there is no BGT-style ghost-version filter to write. The client folds exact duplicates anyway.

The client side is crates/cartopolis/src/systems/map/crops.rs (parse_crop_cell, CATEGORIES) and crates/geo/src/crops.rs (CropCategory, rgb), and docs/notes/crop-parcels.md has the measurements and the method.

Done when

  • server/pipeline/brp.ts produces the coverage manifest and per-cell files above, driven from the Data tab like the other four.
  • server/pipeline/format.test.ts pins the shape against a capture of a real run, as it does for bgt, bag, lod22 and buildings.
  • A capture over Flevoland farmland reports crop_parcels > 0 in --dump-state, and a --flicker run over the same view holds — the new SurfaceGroup::Crop rung is 32 ULP from Grass and has never been rendered against real data.
The client half of the RVO crop parcels landed in `jeroen/cartopolis` (issue #203, branch `feat/203-brp-crop-parcels`). It draws nothing until a host serves `/brp/coverage.json` and per-cell files, and that extractor lives in **cartopy** — `server/pipeline/brp.ts`, beside `bgt.ts`, plus an entry in `server/pipeline/format.test.ts`. **Filed here rather than on `jeroen/cartopy` because this container cannot reach that repository**: `FORGEJO_TOKEN` answers 404 on `repos/jeroen/cartopy` and `git ls-remote` says "Repository not found". Please move it, or hand it to a session that has the credentials. ## Source - `https://api.pdok.nl/rvo/gewaspercelen/ogc/v1`, collection `brpgewas`. Keyless, OGC API Features, `bbox` query, GeoJSON out. - Licence **Public Domain Mark 1.0**, named by the collections document itself. - Properties per feature: `category`, `gewas`, `gewascode`, `jaar`, `status`. - Measured 2026-08-30: 308 features / 921 KB over z14 cell 8488/5315 (farmland north of Groningen), 199 / 232 KB over 8449/5374 (Flevoland), 0 over 8490/5320 (Grote Markt). ## Wire format the client already parses Cells binned at **z14**, on the parcel's **centroid** — one cell owns a parcel and draws it whole, including the vertices that fall outside (22–24 % of parcels leave their own cell, so binning by containment would delete a quarter of the country's fields). `/brp/coverage.json` — the usual manifest: `{"zoom": 14, "cells": [[x, y], …], "attribution": "…"}`. `/brp/14/{x}/{y}.json`: ```json {"parcels": [[category, gewascode, [[[lat, lng], …], …]], …]} ``` - `category` is an index into this table, **append, never renumber**: | index | register value | |---|---| | 0 | `Grasland` | | 1 | `Bouwland` | | 2 | `Natuurterrein` | | 3 | `Braakland` | | 4 | `Overige` | | 5 | `Landschapselement` — **never emit**, reserved and non-drawable | - `gewascode` is the register's own crop number, verbatim, as an integer. - Rings are `[lat, lng]` pairs, **outer ring first, holes after, open** (no repeated closing vertex). An outer ring under three points is dropped by the client. - `Landschapselement` features are **filtered out** by the extractor: 60 % of the features are landscape elements, and 316 of 340 sampled are `Sloot` (ditches), which the Shortbread water layers already draw. Drawing them as land fills puts ground where the register says water. - Simplify with Douglas–Peucker at **0.5 m** — it keeps 31–32 % of the vertices, taking the heaviest cell measured to ~40 KB of coordinates. - The register serves one year (`jaar: 2025`, `status: "Definitief"`) with no superseded versions, so there is no BGT-style ghost-version filter to write. The client folds exact duplicates anyway. The client side is `crates/cartopolis/src/systems/map/crops.rs` (`parse_crop_cell`, `CATEGORIES`) and `crates/geo/src/crops.rs` (`CropCategory`, `rgb`), and `docs/notes/crop-parcels.md` has the measurements and the method. ## Done when - `server/pipeline/brp.ts` produces the coverage manifest and per-cell files above, driven from the Data tab like the other four. - `server/pipeline/format.test.ts` pins the shape against a capture of a real run, as it does for `bgt`, `bag`, `lod22` and `buildings`. - A capture over Flevoland farmland reports `crop_parcels > 0` in `--dump-state`, and a `--flicker` run over the same view holds — the new `SurfaceGroup::Crop` rung is 32 ULP from `Grass` and has never been rendered against real data.
Author
Collaborator

QA of the client half (#217) built a stand-in for this extractor and ran it against the live service, so the wire form is now confirmed end to end rather than only unit-tested. What the client accepted, first try:

  • GET /brp/coverage.json — {"name", "zoom": 14, "attribution", "cells": [[x, y], ...]}, exactly the shape CoverageSlot reads for bgt/bag.
  • GET /brp/14/<x>/<y>.json — {"parcels": [[category, gewascode, [ring, ...]], ...]} with rings as [lat, lng] pairs, outer first, open (GeoJSON's repeated closing vertex has to go), and category the index into CATEGORIES in systems/map/crops.rs (Grasland 0 … Overige 4; Landschapselement 5 is reserved and dropped).
  • Binning on the outer ring's centroid, one owning cell per parcel, no clipping — the client draws a straddler whole.

Counts reproduce the note's table on the two z14 cells it measured: 199 features over Flevoland 8449/5374 and 308 over Groningen 8488/5315, with 79 and 186 of those Landschapselement. Un-simplified those cells serialise to 48 KB and 243 KB; Douglas–Peucker at 0.5 m is what the note's ~40 KB assumes, so it is worth keeping in the port.

One thing to decide while writing it: #222 asks for the crop's name to be reachable in the UI, and the cheapest source is this extractor passing the register's own gewas string per parcel. Worth settling before the wire form is fixed by a deployed host.

QA of the client half (#217) built a stand-in for this extractor and ran it against the live service, so the wire form is now confirmed end to end rather than only unit-tested. What the client accepted, first try: * `GET /brp/coverage.json` — `{"name", "zoom": 14, "attribution", "cells": [[x, y], ...]}`, exactly the shape `CoverageSlot` reads for `bgt`/`bag`. * `GET /brp/14/<x>/<y>.json` — `{"parcels": [[category, gewascode, [ring, ...]], ...]}` with rings as `[lat, lng]` pairs, outer first, **open** (GeoJSON's repeated closing vertex has to go), and `category` the index into `CATEGORIES` in `systems/map/crops.rs` (`Grasland` 0 … `Overige` 4; `Landschapselement` 5 is reserved and dropped). * Binning on the outer ring's centroid, one owning cell per parcel, no clipping — the client draws a straddler whole. Counts reproduce the note's table on the two z14 cells it measured: 199 features over Flevoland 8449/5374 and 308 over Groningen 8488/5315, with 79 and 186 of those `Landschapselement`. Un-simplified those cells serialise to 48 KB and 243 KB; Douglas–Peucker at 0.5 m is what the note's ~40 KB assumes, so it is worth keeping in the port. One thing to decide while writing it: #222 asks for the crop's **name** to be reachable in the UI, and the cheapest source is this extractor passing the register's own `gewas` string per parcel. Worth settling before the wire form is fixed by a deployed host.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jeroen/cartopolis#204
No description provided.