#749 add feature keyboard nav addon - #752
Conversation
|
View your CI Pipeline Execution ↗ for commit 2a2ef49
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
🚀 Deployed
|
806b309 to
7bfbec7
Compare
🚀 Deployed
|
🚀 Deployed
|
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.
e16d7f0 to
b4cad6e
Compare
🚀 Deployed
|
🚀 Deployed
|
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.
🚀 Deployed
|
deploy: ["geoportal"]