Custom Gestures
Placeholder
This page is a placeholder for future documentation. The API described here reflects the current internal structure and may change as custom gesture support is formalised in an upcoming release.
Overview
The gesture pipeline has two well-defined extension points: gesture classification and gesture application. You can hook into either layer depending on how much of the built-in behaviour you want to keep.
Extension point 1: classifyGesture
classifyGesture(landmarks: HandLandmark[]): GestureType is the function that maps 21 MediaPipe landmarks to a gesture label. The current implementation returns 'fist', 'openPalm', or 'none'.
To support additional gesture types, you can:
- Define a new
GestureTypeunion that extends the existing one. - Write your own classification function with the same signature.
- Pass it to a custom
GestureStateMachinesubclass that calls your function instead of (or in addition to) the built-in one.
// Example: recognise a pointing gesture
function classifyExtended(landmarks: HandLandmark[]): ExtendedGestureType {
const base = classifyGesture(landmarks);
if (base !== 'none') return base;
// Custom: index finger extended, others curled
const indexExtended = isExtended(landmarks, LANDMARKS.INDEX_TIP, LANDMARKS.INDEX_MCP);
const othersDown = isCurled(landmarks, LANDMARKS.MIDDLE_TIP, LANDMARKS.MIDDLE_MCP)
&& isCurled(landmarks, LANDMARKS.RING_TIP, LANDMARKS.RING_MCP);
if (indexExtended && othersDown) return 'pointing';
return 'none';
}Extension point 2: OpenLayersGestureInteraction.apply()
OpenLayersGestureInteraction.apply(output: StateMachineOutput) is called on every frame with the state machine's output. You can subclass OpenLayersGestureInteraction and override apply() to intercept output before it reaches the map:
class MyCustomInteraction extends OpenLayersGestureInteraction {
apply(output: StateMachineOutput): void {
if (output.mode === 'panning') {
// Custom pan behaviour
console.log('Custom pan delta:', output.panDelta);
}
// Call super to continue with default behaviour
super.apply(output);
}
}Extension point 3: replacing GestureStateMachine
For full control, bypass GestureMapController entirely and wire up the pipeline manually using GestureController from core:
import {
GestureController,
GestureStateMachine,
DEFAULT_TUNING_CONFIG,
} from '@map-gesture-controls/core';
import { OpenLayersGestureInteraction } from '@map-gesture-controls/ol';
const tuning = { ...DEFAULT_TUNING_CONFIG, actionDwellMs: 50 };
const stateMachine = new GestureStateMachine(tuning);
const interaction = new OpenLayersGestureInteraction(map);
const controller = new GestureController(tuning, (frame) => {
const output = stateMachine.update(frame);
interaction.apply(output);
});
await controller.init();
controller.start();This gives you full control over every step. You can replace GestureStateMachine with your own implementation, add logging, or fan out to multiple interactions.