How an action is verified
What geowright checks before it sends real input to a feature, and again when the press arrives.
A click() on a feature is not “compute a pixel and send a mouse event”. geowright first checks what a person would check by looking, sends real input only when all of it holds, and checks again when the press arrives.
Before the press
Each of these must hold, and the whole check runs again on every retry.
- Exactly one feature, and it is painted.
The query is strict, like a Locator: two matches are an error that names them, never a guess. A feature the style paints at opacity 0 does not count.
- The map is still.
isMoving()answers false, and the chosen pixel is the same on two animation frames running. An in-flightflyTois a wait, not a race. - The pixel is on the feature.
Candidate pixels come from the geometry and are kept only if the map's own hit test confirms them. For a polygon: interior points ranked by distance from the nearest edge, never the centroid. For a line: from its middle outwards. For a point buried in a dense cluster: a ring of pixels around it.
- Nothing a person would see is on top.
Not another feature, and not a label unless it is the feature's own. A feature the style paints invisibly is not cover. No popup, control or other element may cover the pixel either.
- The pixel can be reached.
A map further down the page is scrolled into view first, as a Locator would be. A pixel outside the browser window is refused.
The search is bounded: after 600 hit tests the action reports the feature as covered and names what is on top, rather than hunting for a sliver no person would find.
Layers the hit test cannot see
The map's hit test returns features only. When a raster, image, video, canvas, heatmap, hillshade, color-relief, background or custom layer (deck.gl's, for example) is drawn above the target at the chosen pixel, geowright looks at pixels instead: it renders the map with and without the target's own layer and compares that spot. If hiding the target changes nothing there, the pixel is covered and the next candidate is tried. A translucent overlay, or a transparent gap in an opaque one, lets the action through. A map without such a layer above its features pays nothing for this.
At the press
Between choosing a pixel and the input arriving, the app can move the map. So a listener is armed before the press and runs the hit test again at the exact pixel and moment of the real pointerdown. If the feature the query resolved to is no longer there (a look-alike that slid under the pointer does not count), the whole click is swallowed before the app sees it, a tap's touch events included, and the action starts over. A press that reached the app is never sent again.
Limit. For a target that never stops moving (moving: 'allow'), the check is at the press, while MapLibre decides which layer was clicked at click, after the release. A fast target can move off in between. All limits
Where the pixel really is
- A location on the far side of the globe, behind the camera or hidden behind a mountain is an error, never a click on whatever is drawn at that pixel. geowright projects the location to a pixel and back: if the round trip does not return the same place, something else is drawn there.
- A pixel is checked as the app will see it. Candidates are snapped to whole pixels before the hit test, because the browser truncates a mouse event's coordinates.
- A 3D building is aimed at where it is drawn: roof first, then its walls, then its footprint. Every candidate is still confirmed by the hit test.
Which feature it was
MapLibre's rendered feature id cannot be trusted on its own: it keeps an id only if it is a non-negative integer, and a feature comes back once for every tile it is sliced into, each time with a different piece of its geometry. So a feature's identity is its layer, id and properties, and geometry from the renderer is treated as a piece; where the full shape is needed, it is read from the source's data. Features with no id and identical properties are told apart by whether their pieces touch on screen. Two such shapes that do touch are still counted as one.
What it costs
On a dense test page with about 3,240 points and 415 polygons in view, resolving one action point takes about 33 ms (median, software rendering), including the two-frame wait. The pixel check for a covering layer adds a few renders, and only when such a layer is drawn above the target.
The same checks as a list, with the options that change them, are in the What an action waits for guide.
Read more
- What an action waits forThe same checks as a list, with the options that relax them.
- Action options
force,ignoreLayers,autoPan,moving. - LimitsWhat an action cannot check, stated plainly.