BGT, the remaining layers: is it worth wiring in? #176
Labels
No labels
agent
agent:ci
agent:done
agent:failed
agent:needs-input
agent:refined
agent:refining
agent:running
agent:shipped
agent:skip
autonomous
autopilot
driven
local
plan
proposal
qa
qa-gap
research
retro
ship
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jeroen/cartopolis#176
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
docs/direction.md:54lists BGT, the remaining layers as an unverified frontier candidate: "the surveyed surfaces not yet read." Nobody had confirmed the interface, the licence or the volume. That check is the first half of this ticket, and it is done below — but the row names a category, not a change: the BGT publishes 49 collections andsystems::surveyedreads seven of them.What is read today:
vegetatieobject_punt(trees),paal+straatmeubilair(lighting and furniture),scheiding_lijn(fences and walls),vegetatieobject_vlak(hedges),wegdeel+ondersteunendwegdeel(paving). The wire tables arecrates/cartopolis/src/systems/map/surveyed.rs:72(PROP_KINDS),:84(LINE_KINDS) and:88(SURFACE_KINDS), all marked append, never renumber.The reconnaissance (2026-08-29, this container, read-only HTTP)
Interface — confirmed unchanged:
https://api.pdok.nl/lv/bgt/ogc/v1, keyless, cursor-paged, nonumberMatched, every historical version returned. Exactly what the retired extractor documented (recovered atgit show 8c4b766^:tools/bgt_objects.py, header lines 10–20). Thetermination_datefilter is still mandatory and still the thing that silently doubles a layer.Licence — confirmed CC0 1.0, stated by the API itself: the
collectionsdocument carries{"rel":"license","title":"CC0 1.0","href":"…/publicdomain/zero/1.0/"}. Redistributing the cached cells is unambiguously allowed; the attribution line insurveyed.rs:65is courtesy, not obligation.Volume and content — one page (
limit=1000) per collection over a 1 km² box of central Groningen (bbox=6.5590,53.2149,6.5740,53.2239), and a second pass over ~10×9 km (bbox=6.4800,53.1800,6.6300,53.2600) for the collections the small box returned empty. Current-version counts only.A finding worth recording on its own: the 1 km² of central Groningen returns zero features for every point collection —
paal,straatmeubilair,bak,bord,put,kast,mast,sensor_punt,vegetatieobject_punt— while the same collections are well populated over the wider city. This is not a bbox bug: a 100 m box around a knownpaalreturns 10. It is the same municipality-level coverage holedocs/notes/surveyed-street-objects.mdalready records for trees, and it is wider than trees.A stale claim in our own note.
docs/notes/surveyed-street-objects.md:211saysscheiding_vlak's "typeis empty on every feature seen so far, so there is nothing to say whether one is a wall, a bank or a planter." That is wrong today, and probably describesplus_type. Measured over the 1 km² box: 223 current features,typefilled on all of them —muur140,kademuur71,transitie12;plus_typeisNoneon all 223.Approach
The answer to "is it worth doing" is: yes, for five collections, no for the other thirty-seven. The bar a collection has to clear, applied uniformly:
crates/geo/src/furniture.rsalready has an honest form for it (or one small form covers it),surveyed-street-objects.mdwas built on.Accept (five)
scheiding_vlakmuur140,kademuur71,transitie12)LineKind::Wall,crates/geo/src/furniture.rs:872vegetatieobject_lijnhaag32)LineKind::Hedge,furniture.rs:874bordverkeersbord629,wegwijzer80,informatiebord52,straatnaambord17)FurnitureKind::Board,furniture.rs:122kastelektrakast75,openbare verlichtingkast29,CAI-kast24)FurnitureKind::Cabinet,furniture.rs:120bakafvalbak15,container11)FurnitureKind::Bin/Recycling,furniture.rs:113,:115Three of the five need no new client type at all —
bord,kastandbakare new entries in the extractor's type→form table and, at most, appended entries inPROP_KINDS(surveyed.rs:72) if a form is not yet in the list.vegetatieobject_lijnis a hedge already surveyed as a line, so it skips thehedge_centrelinereduction the area version needs (bgt_objects.py:353) and reachesLineKind::Hedgedirectly.scheiding_vlaktakes the same centreline reduction hedges take, and dropskademuurfor the reasonscheiding_lijnalready dropswalbescherming/damwand/kademuur(note:214) — a metre of masonry along every canal on the side the map already draws as a bank.Reject, with the reason
pand,pand_nummeraanduiding(BAG / 3DBAG),spoor(OSM textured ribbons),waterdeel,ondersteunendwaterdeel,begroeidterreindeel,onbegroeidterreindeel(the tile's own water and land fills feed the shore-distance field, the grass mask and the globe's ocean mask — the standing reason insurveyed-street-objects.md),mast(1 current over the city) andkunstwerkdeel_punt(99hoogspanningsmast) —crates/cartopolis/src/systems/map/tall_structures.rs:1-17already streams masts, pylons and towers from Overpass at z11 over ±30 km, which is a wider ring than a z14 BGT cell can give them.buurt,wijk,stadsdeel,waterschap,openbareruimte,functioneelgebied.weginrichtingselement_vlak(0 current / km²),weginrichtingselement_punt(8 over the city, allniet-bgt),weginrichtingselement_lijn(molgootonly — a gutter channel, finer than the paving layer's own 10 cm simplification),ongeclassificeerdobjectandoverigescheiding(both Historisch).put(973 over the city, 787 of themkolk— a gully grate is a texture, not geometry),sensor_punt(71, mostly cameras),installatie(23, allzonnepaneel— rooftop),kunstwerkdeel_lijn(511, 434duiker— culverts, underground),overigbouwwerk(34, mostlylage trafo),gebouwinstallatie(16toegangstrap/bordes). This is the rule the note already states: an unstyled thing beats a wrongly styled one.overbruggingsdeel(20 / km²) andtunneldeel(45 / city) — bridge decks and tunnel bodies. This is the one rejected item with real picture value, because the paving extractor drops every surface atrelatieve_hoogteligging != 0(bgt_objects.py:485), so a bridge gets no surveyed paving today. Drawing it needs a deck height the BGT does not carry and a rung on the depth ladder the flat paving layer does not have. That is a layer, not a collection.openbareruimtelabel(217 / km², points carrying a rotation — surveyed street-name placements).systems::street_labelsplaces labels view-independently from world footprints on purpose; substituting surveyed anchors is a different argument and outside the shape this ticket names.begroeidterreindeel_kruinlijn,onbegroeidterreindeel_kruinlijn,ondersteunendwegdeel_kruinlijn,wegdeel_kruinlijn— crest lines, whose consumer would besystems::dikes, not this layer.Files it touches
cartopy (
jeroen/cartopy,server/pipeline/bgt.ts): five new collections in the walk, the type→form tables extended,scheiding_vlakreduced to centrelines by the same routine the hedges use,kademuur/transitiefiltered out.server/pipeline/format.test.tsgains the new shapes.cartopolis:
crates/cartopolis/src/systems/map/surveyed.rs— append toPROP_KINDS(:72) only if a form is missing; no renumbering, no new file, no new coverage slot. The parse functions (:206,:212) already skip an index past the end of the table.crates/geo/src/furniture.rs— nothing, if the five map ontoBin,Recycling,Board,Cabinet,Wall,Hedgeas the counts above suggest.docs/notes/surveyed-street-objects.md— correct thescheiding_vlakclaim at:211, record the point-collection coverage hole in central Groningen, and list what stays unread and why.docs/direction.md:54— replace the candidate row with the recorded verdict: interface confirmed, licence CC0, five collections wired, the rest listed as rejected with reasons,overbruggingsdeel/tunneldeelsplit off.Acceptance criteria
docs/direction.md:54's row records the verdict: the OGC interface, the CC0 licence, which collections were wired and which were rejected with the reason for each.docs/notes/surveyed-street-objects.md:211no longer claimsscheiding_vlak.typeis empty; it states what was measured (muur140,kademuur71,transitie12 over 1 km² of central Groningen, 2026-08-29) and thatplus_typeis the empty field.paal,straatmeubilair,bak,bord,put,kast,mast,sensor_puntandvegetatieobject_puntall return zero current features over that same 1 km², while the wider city has them.scheiding_vlak,vegetatieobject_lijn,bord,kastandbak, filters ontermination_date, and dropskademuur,transitieandniet-bgt.scheiding_vlakpolygons are reduced to a centreline, not laid as boxes round their outline — the same rule hedges follow, for the same reason.PROP_KINDS,LINE_KINDS,SURFACE_KINDS(surveyed.rs:72,:84,:88) is renumbered; any addition is appended, and cartopy's tables match position for position.surveyed.rs:206).bord/kast/bak/scheiding_vlakrecords renders exactly as it did.cargo fmt --checkis clean across the workspace.PROTOCOL_HISTORYand no migration is added — this is neither a wire nor a schema change.Verification
Reconnaissance is reproducible from any host with outbound HTTPS, no build:
Client side, on a machine that may build:
cartopy side:
bun installthen./node_modules/.bin/tsc --noEmitandbun test server/pipeline/format.test.ts.Only on a host that can render (not this container): a scene gate over a re-extracted Groningen box.
--dump-statealready carriessurveyed_propsandsurveyed_lines(crates/cartopolis/src/systems/dev/shot_harness.rs:558,:560), so the check is an--expect 'surveyed_lines>…'against the counts the extractor reports, plus--expecton an existing layer's counts to prove nothing moved. Whether the picture reads right — a signboard beside a kerb, a wall along a garden — is a look, and perdocs/direction.mdis not a machine gate.Not measured here, and it should be before the extractor is run at scale: the per-cell byte cost of the five collections. Paving is already the second-heaviest thing the client streams at 3.8 MB for the densest cell; these five go into the objects file, which is 60 kB for that same cell (
surveyed.rs:243). The command is a full extractor run over the 1 km² box anddu -bon the producedbgt/14/*/*.json.Out of scope
wegdeel'srijbaan *,spoorbaanandOV-baanstay with OpenStreetMap for the reasoncrates/geo/src/paving.rs:7-34gives, which this ticket does not reopen.begroeidterreindeel,onbegroeidterreindeel,waterdeel,ondersteunendwaterdeel).overbruggingsdeel,tunneldeel) — worth doing, own ticket, needs a height source and a depth rung.openbareruimtelabel) — would replacesystems::street_labels' placement rule.surveyed-street-objects.md), and a signboard is not interactable —world_places::prop_interactableis the existing split.Open questions
This lane cannot read the repository the extractor lives in. The
FORGEJO_TOKENin this container readsjeroen/cartopolis(HTTP 200) but notjeroen/cartopy(HTTP 404 — not visible;/useranswers 403 for missingread:user).git cloneover bothcode.garage44.euand the internal172.17.0.1:3000fails with "Repository not found". Half of this change isserver/pipeline/bgt.ts, so the implementing agent needs either a token that can read and push cartopy, or the ticket has to be split so the cartopolis half lands here and the extractor half is done elsewhere. Which?An already-cached cell never sees the new content.
load_surveyed_cellreturns the cached copy whenever one exists (surveyed.rs:281) and the coverage manifest carries no data version —zoom,cells,attributionand nothing else (crates/cartopolis/src/systems/map/coverage.rs:167-193);CoverageSlot::revisioncan only ever change once per process (coverage.rs:133). So re-running the extractor reaches no client that has already visited the city. Three ways out, and the choice affects three trees, not one: (a) accept it and let eviction handle it; (b) add aversiontocoverage.jsonand mix it into the cache key, which is a change to the sharedCoverageSlot; (c) put the new content behind a new path so old keys simply miss. Which?Is the coverage hole worth reporting upstream, or just recorded? Central Groningen has no current
paal,straatmeubilair,bak,bord,putorkastrecords at all, which means the lampposts this layer was built for are absent from precisely the densest part of the city it was measured on. Recording it in the note is cheap; deciding whether the map should say something about it is not mine to make.Branch:
feat/176-bgt-remaining-collectionsOriginal request
From the frontier in
docs/direction.md: BGT, the remaining layers — the surveyed surfaces not yet read.The first half of this ticket is deciding whether it is worth doing at all:
Not worth it is a valid answer. Record it in
docs/direction.mdagainst this rowwith the reason, and close this ticket — that is the result, not a failure.
If it is worth doing, follow the shape the surveyed layers already use:
an extractor under
tools/, a coverage manifest, a streamer that adds to whatis drawn rather than replacing it, and no unbounded per-frame upload.
Filed by the autopilot.
🤖 Refined by the viberfox issue agent. Reply with @agent refine and what is wrong to have this rewritten.
🤖 Refining this before anything gets built — reading the code, no branch, no commits. If it turns out I'd have to guess at the requirements I'll stop and ask instead.
🤖 This needs a decision before I build anything. The open questions are in the description above.
Answer them and reply with @agent refine to have the spec rewritten, or @agent to make me implement it as it stands.
🤖 viberfox issue agent on the VPS — 36 turns · $3.30 · session
420e631c-a0ae-4653-9586-72f4a32c1de1. Reply with @agent to continue this session.🤖 Nobody is going to answer this, so decide it yourself.
The seven values at the top of
docs/direction.mdare there to settle exactly this: measured over plausible, official sources first, degrade rather than break, add rather than replace, keep the frame budget, ship the smallest thing a machine can judge, and write down what you decided.Pick the reading those support, record which one you applied in the commit body, and build it. Stop again only for something no one can derive from the repository — a credential, a licence somebody must accept, or a question about what the project is for.
🤖 Picked this up — working on branch
feat/176-bgt-remaining-collectionsin a fresh worktree on the VPS. I'll report back here when I'm done.Label an issue
agent:skipto keep me off it, ordrivento keep it in a session you are driving.Verdict recorded: five of the BGT's 49 collections are worth reading, the other thirty-seven are not. The client half of those five has landed on this branch; the extractor half has not, because this container cannot read the repository it lives in.
What changed
crates/cartopolis/src/systems/map/surveyed.rs— appended three forms to the prop table: position 7Board, 8Bin, 9Recycling. Nothing renumbered. The boundary table did not move at all —WallandHedgealready existed, so both new boundary collections reuse them. Two tests added: one pins every wire position so a future renumber trips, one checks a cell file from before this change and one from after both parse.docs/notes/surveyed-street-objects.md— new section recording the sweep, and a wrong claim removed (see below).docs/direction.md— "BGT, the remaining layers" is out of the unverified-candidates table and is now a recorded verdict.Nothing is drawn differently yet. Nothing emits positions 7–9.
What the sweep found that the ticket did not
I queried all 49 collections myself rather than trusting the ticket's table, and three things came out differently:
typeis not the classifier for optional content —plus_typeis. Four of the five accepted collections carrytype: "niet-bgt"on every feature. The ticket's acceptance criterion says to "dropniet-bgt"; done literally, that reads nothing at all. This is the most important thing in the branch.ongeclassificeerdobjectandoverigescheidingare not retired, as the ticket said — their survivors carry an ordinarystatus: bestaand. They are just almost never used (2 and 5 features over the whole city). Andweginrichtingselement_vlakis not empty: 101 gratings over the city, zero over the small box, which is the coverage hole rather than the truth.verkeersbordis 629 of the 843bordrecords.Boardis an information board on two posts; a traffic sign is a plate on one pole. I did not decide this — it is written up as an open question with the honest subset (about 160 signs) costed out.Confirmed as the ticket stated: the interface is unchanged, the licence is CC0 1.0 stated by the API itself,
scheiding_vlakhastypefilled on all 223 features (the note's old claim that it was empty is deleted), and every point register returns zero over central Groningen while the same box returns 816onbegroeidterreindeel.How I verified it
cargo test -p cartopolis— 773 passed, 0 failed.cargo test -p cartopolis_geo— 196 passed, 0 failed.cargo fmt --checkclean across the workspace. The reconnaissance is reproducible with the commands in the note.Two things to know about the run. The container has no ALSA sound library and no root, so the audio-enabled build failed;
--no-default-featuresdoes not work either, because it trips pre-existing dead-code errors unrelated to this change. I extracted the library into a private directory and pointedpkg-configat it, so the suite above is the real default-feature one CI runs.Separately, this worktree's git metadata was deleted mid-session by something else on the machine — the disk is at 97% full, 6 GB free. I saved the three edited files, re-registered the worktree, diffed to confirm only those three differed, and restored them. Worth a look at the disk before the next unattended run.
Not verified
No visual check. This container cannot render, and the layer draws nothing new anyway. When the extractor half lands, the check is a scene gate over a re-extracted box using the
surveyed_propsandsurveyed_linescounts — and not over central Groningen, which has none of these objects.The per-cell byte cost of the five collections is still unmeasured; it needs an extractor run.
For you to decide
git cloneofjeroen/cartopyreturns "Repository not found" with this container's token. Either issue a token that can read and push it, or file the extractor work as its own ticket for a lane that can.verkeersbord— all 843 boards, or the ~160 that are honestly boards?Branch
feat/176-bgt-remaining-collections(pushed)🤖 viberfox issue agent on the VPS — 91 turns · $8.68 · session
a83ac0c7-cc67-43ed-a7d6-0e54e5b33d85. Reply with @agent to continue this session.🤖 Marked
agent:donewithout a CI verdict ona051d6c8— no workflow reported on this commit. The branch is waiting for review; check the run yourself before landing it.🤖 Could not ship this. Forgejo never reported the pull request as mergeable
The branch is intact; nothing was merged.
🤖 Could not ship this. Forgejo never reported the pull request as mergeable
The branch is intact; nothing was merged.
🤖 Could not ship this. Forgejo never reported the pull request as mergeable
The branch is intact; nothing was merged.
🤖 Could not ship this. Forgejo never reported the pull request as mergeable
The branch is intact; nothing was merged.
🤖 Parked by hand: pull request #185 has a real merge conflict with
main(docs/direction.mdanddocs/notes/surveyed-street-objects.mdmoved under it). The autopilot cannot tell a conflict from Forgejo still computing mergeability, so it was retrying and posting this failure every 15 minutes. The branch needs a rebase before it can land;shipremoved until then.🤖 Merged into
mainas pull request #185 (be0122a0).🤖 Merged, but not deployed. The deploy step exited 1:
mainhas the change; the public services are still on the previous build.agent:donenever means "CI agreed" #220docs/qa/targets.md— 14 of 14 passes re-verified a shipped ticket, 9 of them documentation-only verdicts #280