feat(maps): pinch, twist, two-finger tilt and tap zoom on touch - #42
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Multi-touch on the map: pinch to zoom, twist to turn, two fingers to pitch, and both tap zooms.
The gestures. A pinch scales 1:1 with the finger separation, anchored on their midpoint so the ground under them stays put. A twist turns the bearing 1:1. Two fingers dragging together pitch the camera at
GLYPH_MAP_TILT_DRAG_DEG_PER_PX— the same 0.5 °/px constant the mouse orient stroke already uses. Double-tap zooms in a level about the tapped point, two-finger tap zooms out, and double-tap-hold-drag zooms continuously. One-finger pan is untouched, and lifting one finger from a pinch continues the pan with no seam.Taps zoom outright rather than easing, for the reason
applyWheelalready gives about the wheel: an eased span makesgetView().spandisagree with the gesture and feeds tile LOD a span nobody asked for.Pan, pinch and twist compose — they are the three components of one similarity transform of the finger pair, and each has its own activation threshold, so a pure gesture moves only its own quantity. Those thresholds are a ratio (7.2% of separation) and an arc (25 px along the finger circle), not pixel counts, so a wide grip and a narrow one are the same gesture. Pitch is exclusive and latched once: recognising it needs the fingers side by side, both travelled, both more vertical than horizontal and both the same way, with a 100 ms grace for one finger leading — the clause without which a slightly uneven drag zooms before it pitches.
Sourcing. Google publishes the gesture vocabulary and not one threshold, rate or arbitration rule, so the feel comes from MapLibre — the same library this widget's 0.5 °/px pitch and 0.8 °/px bearing rates already came from — cross-checked against MapLibre Native. One documented disagreement: Google's Android docs say two fingers down increases tilt, while MapLibre GL JS, MapLibre Native and the Maps app all raise pitch on an upward drag. This follows the products, and the existing Ctrl+drag.
One thing that is ours rather than MapLibre's. MapLibre reads
TouchEvent.touches, so every event carries both fingers; a Pointer Event carries one, so a pair assembled mid-recognition holds one live position and one stale one — and the zoom then magnifies that error. Measured, a 5 px reference error grew to 5.38 / 6.15 / 6.92 px through 1.08× / 1.23× / 1.38×, exactly5 × magnification. Every event that arrives undecided now applies nothing and re-bases, the resolving one included, so each increment is a true similarity between two real configurations.No new
controlsflag: the three that exist are capabilities rather than devices, sowheelcovers pinch and both tap zooms,tiltcovers the two-finger pitch and the twist, anddragcovers pan. The host takestouch-action: none, restored ondestroy().Everything routes through the existing state setters and the single motion loop, so the cover clamp,
getMaxTilt(), bearing normalisation, the URL write andinteractiveDownscaleall still apply.Also fixed:
/mapsheld its drag-density press as a boolean, so lifting one finger from a pinch settled the map back to full detail mid-gesture and never went coarse again for the rest of the stroke.Verified in a Pixel 7 context with real multi-touch: double-tap halves the span exactly, two-finger tap doubles it exactly, drag-zoom lands on
144.08 / 2^0.9375, and a two-finger drag at Zurich takes the tilt to its livegetMaxTilt()ceiling with span and bearing bit-identical.