Loading repository data…
Loading repository data…
sanderdesnaijer / repository
Control OpenLayers, Google Maps, and Leaflet with hand gestures via webcam. Uses MediaPipe for real time hand tracking to pan, zoom, and navigate maps hands free in the browser. No backend required.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
Control web maps with hand gestures using your webcam. No mouse, no touch, no backend.
Using MediaPipe hand-tracking WASM running entirely in the browser, users can pan a map with the left hand, zoom with the right hand, and rotate with both hands. Each action can be triggered with either a fist or a pinch -- whichever feels more natural. This makes maps accessible in kiosk and exhibit environments, enables hands-free interaction for users with limited mobility, and opens up novel touchless UI experiences. Camera data never leaves the device.
Supports OpenLayers | Google Maps | Leaflet
GestureController opens the user's camera and feeds each frame to MediaPipe Hand Landmarker, which returns 21 3-D landmarks per detected hand.GestureStateMachine classifies each frame using classifyGesture(): left fist or pinch = pan; right fist or pinch = zoom (vertical movement); both hands active = rotate; anything else = idle. A configurable dwell timer (actionDwellMs, default 80 ms) prevents flickering, and a grace period (releaseGraceMs, default 150 ms) smooths gesture releases.OpenLayersGestureInteraction translates frame-over-frame hand deltas into ol/Map pan pixel offsets and zoom-level adjustments, applying dead-zone filtering and exponential smoothing before every update.| Package | npm | Description |
|---|---|---|
@map-gesture-controls/core | Gesture detection engine, map-agnostic. Exports GestureController, GestureStateMachine, WebcamOverlay, classifyGesture, all types, constants, and utility functions. | |
@map-gesture-controls/ol | OpenLayers integration. Re-exports the full core API and adds GestureMapController and OpenLayersGestureInteraction. | |
@map-gesture-controls/google-maps | Google Maps integration. Re-exports the full core API and adds GestureMapController and GoogleMapsGestureInteraction. | |
@map-gesture-controls/leaflet | Leaflet integration. Re-exports the full core API and adds GestureMapController and LeafletGestureInteraction. |
Most users only need the
ol,google-maps, orleafletpackage. Each re-exports everything from core.
getUserMedia (webcam permission).OpenLayers:
npm install @map-gesture-controls/ol ol
Google Maps:
npm install @map-gesture-controls/google-maps @googlemaps/js-api-loader
npm install -D @types/google.maps
Leaflet:
npm install @map-gesture-controls/leaflet leaflet
npm install -D @types/leaflet
Publish flow (maintainers): run
npm run buildso thedist/folder exists beforenpm publish(this repo does not commitdist/).
You need a container element in your HTML (e.g. <div id="map"></div>) and an OpenLayers Map instance:
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import OSM from 'ol/source/OSM.js';
import { fromLonLat } from 'ol/proj.js';
import { GestureMapController } from '@map-gesture-controls/ol';
import '@map-gesture-controls/ol/style.css';
const map = new Map({
target: 'map',
layers: [new TileLayer({ source: new OSM() })],
view: new View({ center: fromLonLat([0, 0]), zoom: 2 }),
});
const controller = new GestureMapController({ map });
// Must be called from a user gesture (e.g. button click) for webcam permission
await controller.start();
controller.stop(); // tear down webcam and overlay
import { Loader } from '@googlemaps/js-api-loader';
import { GestureMapController } from '@map-gesture-controls/google-maps';
import '@map-gesture-controls/google-maps/style.css';
const loader = new Loader({ apiKey: 'YOUR_API_KEY', version: 'weekly' });
const { Map } = await loader.importLibrary('maps');
const map = new Map(document.getElementById('map')!, {
center: { lat: 0, lng: 0 },
zoom: 2,
mapId: 'YOUR_MAP_ID', // enables vector maps (required for rotation)
});
const controller = new GestureMapController({ map });
await controller.start();
controller.stop();
You need a Google Maps API key and a Map ID from the Google Cloud Console. Create the Map ID with the Vector map type to enable rotation support. For local development, add both to a .env file:
VITE_GOOGLE_MAPS_API_KEY=your_api_key
VITE_GOOGLE_MAPS_MAP_ID=your_map_id
See the Google Maps Getting Started guide for full setup details.
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { GestureMapController } from '@map-gesture-controls/leaflet';
import '@map-gesture-controls/leaflet/style.css';
const map = L.map('map').setView([52.37, 4.9], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
const controller = new GestureMapController({ map });
await controller.start();
controller.stop();
No API key needed. Leaflet uses OpenStreetMap tiles by default. Rotation is supported via CSS transforms on the map pane.
See the Leaflet Getting Started guide for full setup details.
Optional config: webcam, tuning, and debug. See the Configuration section below.
All options are optional. Pass only the keys you want to override; the rest use sensible defaults.
// `map` is your ol/Map or google.maps.Map instance (see Usage above)
const controller = new GestureMapController({
map,
webcam: {
position: 'top-left', // move overlay to top-left corner
width: 240, // narrower overlay
height: 180,
margin: 24, // 24 px from viewport edges
opacity: 0.7,
},
tuning: {
actionDwellMs: 40, // faster gesture confirmation
releaseGraceMs: 80, // shorter grace period
panDeadzonePx: 0, // direct panning for slow movement
},
debug: true, // log gesture mode to console
});
webcam options (WebcamConfig)| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Show/hide the overlay entirely. |
mode | 'corner' | 'full' | 'hidden' | 'corner' | Display mode. |
position | 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'bottom-right' | Corner when mode === 'corner'. |
width | number | 320 | Overlay width in px (corner mode). |
height | number | 240 | Overlay height in px (corner mode). |
margin | number | 16 | Distance in px from the nearest edge(s). |
opacity | number | 0.85 | CSS opacity (0 to 1). |
tuning options (TuningConfig)| Key | Type | Default | Description |
|---|---|---|---|
actionDwellMs | number | 80 | Hold time (ms) before a gesture is confirmed. |
releaseGraceMs | number | 150 | Grace period (ms) before returning to idle after gesture ends. |
panDeadzonePx | number | 0 | Minimum pixel movement to register a pan. |
zoomDeadzoneRatio | number | 0.005 | Minimum distance-ratio change to register a zoom. |