Skip to content

#749 add feature keyboard nav addon - #752

Open
PavelOlkhovoi wants to merge 20 commits into
devfrom
feature/00749-create-features-selections-with-arrows-keys-addon
Open

#749 add feature keyboard nav addon#752
PavelOlkhovoi wants to merge 20 commits into
devfrom
feature/00749-create-features-selections-with-arrows-keys-addon

Conversation

@PavelOlkhovoi

Copy link
Copy Markdown
Contributor

deploy: ["geoportal"]

@nx-cloud

nx-cloud Bot commented Aug 11, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 2a2ef49

Command Status Duration Result
nx run kita-finder:build --configuration=produc... ✅ Succeeded 10s View ↗
nx run lagis-desktop:build --configuration=prod... ✅ Succeeded 9s View ↗
nx run klimaorte:build --configuration=producti... ✅ Succeeded 4s View ↗
nx run hitzeinderstadt:build --configuration=pr... ✅ Succeeded 11s View ↗
nx run rainhazard-meschede:build --configuratio... ✅ Succeeded 9s View ↗
nx run ng-topicmap-playground:build --configura... ✅ Succeeded 16s View ↗
nx run rainhazardmap-huerth:build --configurati... ✅ Succeeded 4s View ↗
nx run rainhazardmap-tholey:build --configurati... ✅ Succeeded 10s View ↗
Additional runs (45) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-14 16:49:42 UTC

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/752/geoportal/

Based on commit 548eb4e

@helllth
helllth force-pushed the feature/00749-create-features-selections-with-arrows-keys-addon branch from 806b309 to 7bfbec7 Compare August 13, 2026 08:35
@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/752/geoportal/

Based on commit d2095eb

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/752/geoportal/

Based on commit 16f061e

PavelOlkhovoi and others added 19 commits August 14, 2026 18:04
The origin dots were drawn for the whole candidate set, up to 300 of them,
re-projected on every map frame under a CSS blur. On a viewport of ALKIS
parcels that stalled the main thread while panning, and it also cost a
polylabel run per visible feature on every idle (measured: 54ms per 300
real parcels).

Now one dot, for the feature that is actually selected, from a click as
well as from an arrow key, in the same blue as the selection outline and
without the blur that softened a "possible" origin into an approximation.
The per-candidate origin plumbing is gone with it: NavCandidate.origin,
the originsUpTo bound and DEFAULT_MAX_ORIGIN_DOTS had no other user.

Note that interiorPointOf itself was not at fault. Checked against the
tiles the geoportal renders, z14/8517/5466: 12544 real polygons across
landparcel, building, buildingpart, buildingstructure and landuse, every
interior point strictly inside under booleanPointInPolygon with
ignoreBoundary, no fallback to pointOnFeature.
… trail back

Two things the mode got wrong once the user took over from the arrow keys.

A selection the addon did not publish now erases the explanation at once.
The picture describes one step, and after a click it describes a decision
that no longer led to what is selected; under explain: "hold" it stayed on
screen next to the wrong feature until the next keypress. Steps mark the
selection they are about to cause, so only the ones that arrive unclaimed
count as hand-made.

Up and then down now lands on the feature it started from. Picking could
not guarantee that: the reverse step measures from a different origin
through a different cone, so a neighbour that came second on the way out
can win on the way back. Every step records the feature it came from, and
the opposite key pops that trail instead of asking the picker, which is
also cheaper, since it projects no candidates at all. A click ends the
walk and drops the trail with it, as does leaving the mode.
Measured in the geoportal: single steps cost up to 1461ms, all of it in
interiorPointOf. The culprit was a 40m parcel with five holes and 252
vertices, not a big one: polylabel quarters cells until one falls below the
precision, and size/10000 asked for ~6mm there while the holes kept its
queue from pruning anything.

min(width, height)/50 is a fixed number of refinement rounds whatever the
shape, and stays below the clearance a thin polygon can offer, so the
distance > 0 guard still passes and no origin falls back to a boundary
vertex. Same steps after the change: 19ms worst, 1-2ms typical.
idle fires after tile loads, after fades and after every keepInView pan, so
a held arrow re-queried the whole viewport between steps. The set now
carries the view it was built for — scope, centre to about a metre, zoom,
bearing, pitch — and an idle on an unchanged view is ignored.

The tiles-loaded flag is part of that signature, so a view that gains
features while its tiles arrive is still rebuilt once, and a failed query
clears it so the next idle retries.
Three reasons the origin could sit on a boundary and send the next step
into the wrong neighbour.

queryRenderedFeatures returns a feature once per tile it touches, and the
origin was taken from whichever piece arrived first, so on anything larger
than a tile it was the interior point of a fragment. Pieces are now merged
per candidate and the origin comes from the merge. Measured in the app:
every selection arrived in 3 to 12 pieces, one of eight moved 48m once
merged.

Most of those pieces were not tile fragments but the same geometry returned
once per style layer drawing the feature, multiplying the work; identical
pieces are now recognised and merged once.

And a feature walked into now measures from the middle of the stretch the
step's ray spent inside it, not from its pole. Crossings along a ray
alternate outside/inside, so that midpoint is strictly inside, holes
included, and it lies where the user arrived rather than where the shape
happens to be widest — entering a long street from the south, the pole can
be hundreds of metres down the road. Drawn red to tell it from the pole,
which still serves a feature picked by click.

Also: a held arrow started a step per OS key repeat, each measuring from a
selection its predecessor had already replaced. Steps are dropped while one
runs rather than queued, so releasing the key ends the walk.
Development convenience while the mode is what this Fachzwilling is being
built around: switch `startActive` back off in boden.ts before this merges.

A fallback for the mode's value rather than a write on mount, because the
addon state provider drops every channel when its scope identity changes
and anything written once is lost with it. The control keeps working:
switching off stores isOn: false, and a stored value beats the fallback.
The pole says nothing about the middle of a corridor: every point is equally
far from the two long sides, so the widest spot is wherever a junction
bulges, usually near an end. Two alternatives, chosen by hand, no automatic
pick yet.

originStrategy:
- "pole" (default), unchanged, the only one guaranteed to lie inside
- "spine", the middle along the shape. The vertex furthest from the centroid
  and the vertex furthest from that one are the ends; they cut the outer ring
  into two chains, and the average of their arc-length midpoints is half way
  down a street. O(n)
- "centroid", the area centroid

Neither of the two has a containment guarantee, so each is tested against
the rings and falls back to the pole where it lands outside, which a bent
shape does.

originMode: "dynamic" keeps the arrival origin, the red point where a walk
entered the feature; "static" always uses the computed one.

All origins are drawn at once while the choice is open, one colour each and
the one in force larger, a hollow dot marking a point that fell outside and
is therefore unused. originDotColor is gone, since colour now identifies the
strategy.

The walk itself is a path with a cursor rather than a stack. Stepping back
used to pop the entry and throw the way forward away, so up-up-up-down-down-
up asked the picker again for that last up although the answer was known.
Back replays while the key undoes the step that led here, forward while it
repeats the step taken from here, and only a step leaving the path is
picked; stepping off the middle drops what lay ahead, as an edit after an
undo does.

Tests: a corridor with a bulge near one end (pole in the bulge, spine near
the middle), the C-shape falling back from centroid to pole, and a square
using its centroid.
The fan exists so a neighbour meeting the origin at a corner stays
reachable, but all three rays were ranked by raw distance, so a parcel the
outer ray merely clips beat the one lying straight ahead. Observed in the
geoportal: a step left took the parcel diagonally below, clipped 132px out,
over the one ahead at 141px.

centerRayBonus multiplies the cost of a centre-ray hit, the way
currentLayerBonus does for the current layer. Default 0.85, so the fan has
to be about 15% nearer to win; 1 ranks all three alike and is the previous
behaviour; 0 lets anything the centre ray touches win outright.

This does not rescue every case: a ray that misses a feature cannot select
it, so a neighbour the centre ray passes by is still only reachable through
the fan, far out. Rejecting grazing hits by chord length is the next lever
there.

Tests use the observed geometry, one polygon ahead at 141px and one beside
the axis the fan enters at ~126px: at 1 the clipped one wins, at 0.85 the
one ahead does. The two spec fixtures gained the field; note that spec files
are outside tsconfig.lib.json, so a missing PickInput member is not a type
error there, and the code defaults it rather than computing NaN costs.
…rking yet)

A selection published into the map selection context — arrow-key navigation
publishes one per step — only got drawn as selected. The store kept the
feature that was clicked last, so the infobox never followed the keyboard,
and clicking that old feature again counted as a re-click and zoomed to it.

Geoportal's selection flows one way, store -> context (the effect right
above this one), because clicking used to be the only way to select. This
adds the missing direction: a context selection the store did not make is
built into an infobox feature and dispatched with setSelectedFeature, which
is the route the main developer confirmed.

Not working yet. createVectorFeature returns undefined for a published
feature. Measured in the running app: the infobox mappings read properties
that SelectionManager.enrichHits attaches to real click hits and that a
feature from queryRenderedFeatures does not carry — the clicked feature has
carmaInfo, the published one does not, and neither carries
targetProperties. enrichSelectedFeature reproduces that enrichment, but the
mapping still returns null, so something else is missing.

Two console.info lines under [SELECTION_SYNC] are left in on purpose: one
prints the resolved layer, the published feature's property keys and the
layer's full infoboxMapping text, the other whether a feature was built.
Reading the mapping text should name the missing property.

Note firing a synthetic map click at the feature does make it work end to
end — that is how gazetteer selections reach the infobox
(GeoportalMap.onComplete) — but simulating clicks is not wanted here, so it
is deliberately not in this commit.
The config value only seeds the picture mode now; the readout carries a switch that steps it through brief, hold and off while the mode runs. explain: "off" still keeps the readout and its switch away entirely.
@helllth
helllth force-pushed the feature/00749-create-features-selections-with-arrows-keys-addon branch from e16d7f0 to b4cad6e Compare August 14, 2026 16:05
@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/752/geoportal/

Based on commit fdd6b26

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/752/geoportal/

Based on commit 0f387e2

Two parcels sharing a long diagonal border each lie in the pressed direction from the other, so repeating a key ping-ponged between them with no way out. A repeated direction now excludes the feature one step back; the edge pan runs under the same rule and only then is the exclusion dropped, so a real dead end still selects.
@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/752/geoportal/

Based on commit 2aac553

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants