@map-gesture-controls/ol 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ import type { GestureMapControllerConfig } from './types.js';
2
+ /**
3
+ * GestureMapController
4
+ *
5
+ * Top-level public API. Wires together all subsystems:
6
+ * GestureController → GestureStateMachine → OpenLayersGestureInteraction
7
+ * ↘ WebcamOverlay
8
+ *
9
+ * Usage:
10
+ * const ctrl = new GestureMapController({ map });
11
+ * await ctrl.start();
12
+ * // …
13
+ * ctrl.stop();
14
+ */
15
+ export declare class GestureMapController {
16
+ private config;
17
+ private gestureController;
18
+ private stateMachine;
19
+ private overlay;
20
+ private interaction;
21
+ private lastFrame;
22
+ private rafHandle;
23
+ private started;
24
+ private paused;
25
+ constructor(userConfig: GestureMapControllerConfig);
26
+ /**
27
+ * Initialise webcam + MediaPipe, mount overlay, begin detection loop.
28
+ * Must be called from a user-gesture event (e.g. button click) to allow
29
+ * webcam permission prompt.
30
+ */
31
+ start(): Promise<void>;
32
+ /** Stop detection and remove overlay. */
33
+ stop(): void;
34
+ /** Pause detection (overlay stays visible but inactive). */
35
+ pause(): void;
36
+ /** Resume after pause. */
37
+ resume(): void;
38
+ private renderLoop;
39
+ private handleVisibilityChange;
40
+ private logDebug;
41
+ }
@@ -0,0 +1,168 @@
1
+ import { DEFAULT_WEBCAM_CONFIG, DEFAULT_TUNING_CONFIG, GestureController, GestureStateMachine, WebcamOverlay, } from '@map-gesture-controls/core';
2
+ import { OpenLayersGestureInteraction } from './OpenLayersGestureInteraction.js';
3
+ /**
4
+ * GestureMapController
5
+ *
6
+ * Top-level public API. Wires together all subsystems:
7
+ * GestureController → GestureStateMachine → OpenLayersGestureInteraction
8
+ * ↘ WebcamOverlay
9
+ *
10
+ * Usage:
11
+ * const ctrl = new GestureMapController({ map });
12
+ * await ctrl.start();
13
+ * // …
14
+ * ctrl.stop();
15
+ */
16
+ export class GestureMapController {
17
+ constructor(userConfig) {
18
+ Object.defineProperty(this, "config", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: void 0
23
+ });
24
+ Object.defineProperty(this, "gestureController", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: void 0
29
+ });
30
+ Object.defineProperty(this, "stateMachine", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: void 0
35
+ });
36
+ Object.defineProperty(this, "overlay", {
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true,
40
+ value: void 0
41
+ });
42
+ Object.defineProperty(this, "interaction", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: void 0
47
+ });
48
+ Object.defineProperty(this, "lastFrame", {
49
+ enumerable: true,
50
+ configurable: true,
51
+ writable: true,
52
+ value: null
53
+ });
54
+ Object.defineProperty(this, "rafHandle", {
55
+ enumerable: true,
56
+ configurable: true,
57
+ writable: true,
58
+ value: null
59
+ });
60
+ Object.defineProperty(this, "started", {
61
+ enumerable: true,
62
+ configurable: true,
63
+ writable: true,
64
+ value: false
65
+ });
66
+ Object.defineProperty(this, "paused", {
67
+ enumerable: true,
68
+ configurable: true,
69
+ writable: true,
70
+ value: false
71
+ });
72
+ Object.defineProperty(this, "handleVisibilityChange", {
73
+ enumerable: true,
74
+ configurable: true,
75
+ writable: true,
76
+ value: () => {
77
+ if (document.hidden) {
78
+ this.pause();
79
+ }
80
+ else {
81
+ this.resume();
82
+ }
83
+ }
84
+ });
85
+ const webcamConfig = { ...DEFAULT_WEBCAM_CONFIG, ...userConfig.webcam };
86
+ const tuningConfig = { ...DEFAULT_TUNING_CONFIG, ...userConfig.tuning };
87
+ this.config = {
88
+ map: userConfig.map,
89
+ webcam: webcamConfig,
90
+ tuning: tuningConfig,
91
+ debug: userConfig.debug ?? false,
92
+ };
93
+ this.gestureController = new GestureController(tuningConfig, (frame) => {
94
+ this.lastFrame = frame;
95
+ });
96
+ this.stateMachine = new GestureStateMachine(tuningConfig);
97
+ this.overlay = new WebcamOverlay(webcamConfig);
98
+ this.interaction = new OpenLayersGestureInteraction(userConfig.map);
99
+ }
100
+ /**
101
+ * Initialise webcam + MediaPipe, mount overlay, begin detection loop.
102
+ * Must be called from a user-gesture event (e.g. button click) to allow
103
+ * webcam permission prompt.
104
+ */
105
+ async start() {
106
+ if (this.started)
107
+ return;
108
+ this.started = true;
109
+ const videoEl = await this.gestureController.init();
110
+ this.overlay.attachVideo(videoEl);
111
+ const mapTarget = this.config.map.getTargetElement();
112
+ this.overlay.mount(mapTarget ?? document.body);
113
+ this.gestureController.start();
114
+ this.renderLoop();
115
+ // Pause when tab is hidden to save resources
116
+ document.addEventListener('visibilitychange', this.handleVisibilityChange);
117
+ }
118
+ /** Stop detection and remove overlay. */
119
+ stop() {
120
+ this.gestureController.destroy();
121
+ this.overlay.unmount();
122
+ this.stateMachine.reset();
123
+ if (this.rafHandle !== null) {
124
+ cancelAnimationFrame(this.rafHandle);
125
+ this.rafHandle = null;
126
+ }
127
+ document.removeEventListener('visibilitychange', this.handleVisibilityChange);
128
+ this.started = false;
129
+ this.paused = false;
130
+ }
131
+ /** Pause detection (overlay stays visible but inactive). */
132
+ pause() {
133
+ this.paused = true;
134
+ this.gestureController.stop();
135
+ this.stateMachine.reset();
136
+ }
137
+ /** Resume after pause. */
138
+ resume() {
139
+ if (!this.paused)
140
+ return;
141
+ this.paused = false;
142
+ this.gestureController.start();
143
+ }
144
+ renderLoop() {
145
+ this.rafHandle = requestAnimationFrame(() => this.renderLoop());
146
+ if (this.paused) {
147
+ this.overlay.render(null, 'idle');
148
+ return;
149
+ }
150
+ const frame = this.lastFrame;
151
+ if (frame === null) {
152
+ this.overlay.render(null, 'idle');
153
+ return;
154
+ }
155
+ const output = this.stateMachine.update(frame);
156
+ this.interaction.apply(output);
157
+ this.overlay.render(frame, output.mode);
158
+ if (this.config.debug) {
159
+ this.logDebug(output.mode, frame);
160
+ }
161
+ }
162
+ logDebug(mode, frame) {
163
+ const hands = frame.hands
164
+ .map((h) => `${h.handedness}:${h.gesture}(${h.score.toFixed(2)})`)
165
+ .join(' ');
166
+ console.debug(`[ol-gestures] mode=${mode} ${hands}`);
167
+ }
168
+ }
@@ -0,0 +1,30 @@
1
+ import type Map from 'ol/Map.js';
2
+ import type { StateMachineOutput } from '@map-gesture-controls/core';
3
+ /**
4
+ * OpenLayersGestureInteraction
5
+ *
6
+ * Translates GestureStateMachine output into actual OL map movements.
7
+ *
8
+ * Pan: normalised hand delta → pixel offset → new map center
9
+ * Zoom: two-hand index-finger distance delta → zoom level change (hands apart = zoom in)
10
+ */
11
+ export declare class OpenLayersGestureInteraction {
12
+ private map;
13
+ private panScale;
14
+ private zoomScale;
15
+ constructor(map: Map);
16
+ /**
17
+ * Apply a state machine output frame to the map.
18
+ * Safe to call every animation frame.
19
+ */
20
+ apply(output: StateMachineOutput): void;
21
+ /**
22
+ * Pan map by a normalised delta (0–1 range from webcam space).
23
+ * dx/dy are hand movement as a fraction of frame width/height.
24
+ */
25
+ private pan;
26
+ /**
27
+ * Zoom map. delta > 0 = zoom in, delta < 0 = zoom out.
28
+ */
29
+ private zoom;
30
+ }
@@ -0,0 +1,87 @@
1
+ import { getCenter } from 'ol/extent.js';
2
+ /**
3
+ * OpenLayersGestureInteraction
4
+ *
5
+ * Translates GestureStateMachine output into actual OL map movements.
6
+ *
7
+ * Pan: normalised hand delta → pixel offset → new map center
8
+ * Zoom: two-hand index-finger distance delta → zoom level change (hands apart = zoom in)
9
+ */
10
+ export class OpenLayersGestureInteraction {
11
+ constructor(map) {
12
+ Object.defineProperty(this, "map", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: void 0
17
+ });
18
+ Object.defineProperty(this, "panScale", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: 2.0
23
+ });
24
+ // Two-hand distance delta is ~0.005–0.02 per frame at natural speed.
25
+ // zoomScale=4.0 ≈ 1.2 zoom levels/sec at 30fps. Adjust to taste.
26
+ Object.defineProperty(this, "zoomScale", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: 4.0
31
+ });
32
+ this.map = map;
33
+ }
34
+ /**
35
+ * Apply a state machine output frame to the map.
36
+ * Safe to call every animation frame.
37
+ */
38
+ apply(output) {
39
+ if (output.panDelta) {
40
+ this.pan(output.panDelta.x, output.panDelta.y);
41
+ }
42
+ if (output.zoomDelta !== null) {
43
+ this.zoom(output.zoomDelta);
44
+ }
45
+ }
46
+ /**
47
+ * Pan map by a normalised delta (0–1 range from webcam space).
48
+ * dx/dy are hand movement as a fraction of frame width/height.
49
+ */
50
+ pan(dx, dy) {
51
+ const view = this.map.getView();
52
+ const resolution = view.getResolution();
53
+ if (resolution === undefined)
54
+ return;
55
+ const size = this.map.getSize();
56
+ if (!size)
57
+ return;
58
+ const [mapW, mapH] = size;
59
+ // Convert normalised webcam delta to map pixels, then to map coords.
60
+ // Webcam is mirrored so negate dx.
61
+ const pixelDx = -dx * mapW * this.panScale;
62
+ const pixelDy = dy * mapH * this.panScale;
63
+ const center = view.getCenter();
64
+ if (!center)
65
+ return;
66
+ view.setCenter([
67
+ center[0] - pixelDx * resolution,
68
+ center[1] + pixelDy * resolution,
69
+ ]);
70
+ }
71
+ /**
72
+ * Zoom map. delta > 0 = zoom in, delta < 0 = zoom out.
73
+ */
74
+ zoom(delta) {
75
+ const view = this.map.getView();
76
+ const currentZoom = view.getZoom();
77
+ if (currentZoom === undefined)
78
+ return;
79
+ const extent = view.calculateExtent(this.map.getSize());
80
+ const zoomCenter = getCenter(extent);
81
+ view.animate({
82
+ zoom: currentZoom + delta * this.zoomScale,
83
+ center: zoomCenter,
84
+ duration: 0,
85
+ });
86
+ }
87
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { OpenLayersGestureInteraction } from './OpenLayersGestureInteraction.js';
3
+ // ─── Minimal OL map mock ──────────────────────────────────────────────────────
4
+ function makeMapMock(opts = {}) {
5
+ const center = opts.center ?? [0, 0];
6
+ const zoom = opts.zoom ?? 5;
7
+ const resolution = opts.resolution ?? 100;
8
+ const size = opts.size ?? [800, 600];
9
+ const setCenter = vi.fn();
10
+ const animate = vi.fn();
11
+ const view = {
12
+ getResolution: () => resolution,
13
+ getZoom: () => zoom,
14
+ getCenter: () => [...center],
15
+ setCenter,
16
+ animate,
17
+ calculateExtent: (_size) => [
18
+ center[0] - 400 * resolution,
19
+ center[1] - 300 * resolution,
20
+ center[0] + 400 * resolution,
21
+ center[1] + 300 * resolution,
22
+ ],
23
+ };
24
+ const map = {
25
+ getView: () => view,
26
+ getSize: () => size,
27
+ };
28
+ return { map, view, setCenter, animate };
29
+ }
30
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
31
+ function idle() {
32
+ return { mode: 'idle', panDelta: null, zoomDelta: null };
33
+ }
34
+ function panning(dx, dy) {
35
+ return { mode: 'panning', panDelta: { x: dx, y: dy }, zoomDelta: null };
36
+ }
37
+ function zooming(delta) {
38
+ return { mode: 'zooming', panDelta: null, zoomDelta: delta };
39
+ }
40
+ // ─── Tests ────────────────────────────────────────────────────────────────────
41
+ describe('OpenLayersGestureInteraction', () => {
42
+ let interaction;
43
+ let mocks;
44
+ beforeEach(() => {
45
+ mocks = makeMapMock({ center: [0, 0], zoom: 5, resolution: 100, size: [800, 600] });
46
+ // @ts-expect-error - we pass a minimal mock, not a full ol/Map instance
47
+ interaction = new OpenLayersGestureInteraction(mocks.map);
48
+ });
49
+ // ── idle output does nothing ───────────────────────────────────────────────
50
+ it('does not call setCenter or animate for idle output', () => {
51
+ interaction.apply(idle());
52
+ expect(mocks.setCenter).not.toHaveBeenCalled();
53
+ expect(mocks.animate).not.toHaveBeenCalled();
54
+ });
55
+ // ── pan ───────────────────────────────────────────────────────────────────
56
+ it('calls view.setCenter when panDelta is present', () => {
57
+ interaction.apply(panning(0.1, 0.0));
58
+ expect(mocks.setCenter).toHaveBeenCalledOnce();
59
+ });
60
+ it('negates dx (webcam mirror) and applies panScale', () => {
61
+ const dx = 0.1;
62
+ const dy = 0.0;
63
+ const resolution = 100;
64
+ const mapW = 800;
65
+ const panScale = 2.0;
66
+ interaction.apply(panning(dx, dy));
67
+ const [newCenter] = mocks.setCenter.mock.calls[0];
68
+ // pixelDx = -dx * mapW * panScale = -0.1 * 800 * 2 = -160
69
+ // newCenter[0] = 0 - (-160) * 100 = 16000
70
+ expect(newCenter[0]).toBeCloseTo(-(-dx * mapW * panScale) * resolution);
71
+ });
72
+ it('applies positive dy upward (map y increases with view y)', () => {
73
+ const dy = 0.05;
74
+ const resolution = 100;
75
+ const mapH = 600;
76
+ const panScale = 2.0;
77
+ interaction.apply(panning(0, dy));
78
+ const [newCenter] = mocks.setCenter.mock.calls[0];
79
+ // pixelDy = dy * mapH * panScale = 0.05 * 600 * 2 = 60
80
+ // newCenter[1] = 0 + 60 * 100 = 6000
81
+ expect(newCenter[1]).toBeCloseTo(dy * mapH * panScale * resolution);
82
+ });
83
+ it('does not call setCenter when panDelta is null', () => {
84
+ interaction.apply(idle());
85
+ expect(mocks.setCenter).not.toHaveBeenCalled();
86
+ });
87
+ // ── zoom ──────────────────────────────────────────────────────────────────
88
+ it('calls view.animate when zoomDelta is present', () => {
89
+ interaction.apply(zooming(0.01));
90
+ expect(mocks.animate).toHaveBeenCalledOnce();
91
+ });
92
+ it('applies zoomScale to zoomDelta', () => {
93
+ const delta = 0.01;
94
+ const zoomScale = 4.0;
95
+ const currentZoom = 5;
96
+ interaction.apply(zooming(delta));
97
+ const args = mocks.animate.mock.calls[0][0];
98
+ expect(args.zoom).toBeCloseTo(currentZoom + delta * zoomScale);
99
+ expect(args.duration).toBe(0);
100
+ });
101
+ it('zooms in (positive delta) increases zoom level', () => {
102
+ interaction.apply(zooming(0.02));
103
+ const args = mocks.animate.mock.calls[0][0];
104
+ expect(args.zoom).toBeGreaterThan(5);
105
+ });
106
+ it('zooms out (negative delta) decreases zoom level', () => {
107
+ interaction.apply(zooming(-0.02));
108
+ const args = mocks.animate.mock.calls[0][0];
109
+ expect(args.zoom).toBeLessThan(5);
110
+ });
111
+ it('does not call animate when zoomDelta is null', () => {
112
+ interaction.apply(idle());
113
+ expect(mocks.animate).not.toHaveBeenCalled();
114
+ });
115
+ // ── gracefully handles missing view data ──────────────────────────────────
116
+ it('does not throw when resolution is undefined', () => {
117
+ const { map } = makeMapMock();
118
+ map.getView().getResolution = () => undefined;
119
+ // @ts-expect-error
120
+ const i = new OpenLayersGestureInteraction(map);
121
+ expect(() => i.apply(panning(0.1, 0.1))).not.toThrow();
122
+ });
123
+ it('does not throw when map size is undefined', () => {
124
+ const { map } = makeMapMock();
125
+ map.getSize = () => undefined;
126
+ // @ts-expect-error
127
+ const i = new OpenLayersGestureInteraction(map);
128
+ expect(() => i.apply(panning(0.1, 0.1))).not.toThrow();
129
+ });
130
+ it('does not throw when zoom is undefined', () => {
131
+ const { map } = makeMapMock();
132
+ map.getView().getZoom = () => undefined;
133
+ // @ts-expect-error
134
+ const i = new OpenLayersGestureInteraction(map);
135
+ expect(() => i.apply(zooming(0.01))).not.toThrow();
136
+ });
137
+ });
@@ -0,0 +1,5 @@
1
+ export { GestureMapController } from './GestureMapController.js';
2
+ export { OpenLayersGestureInteraction } from './OpenLayersGestureInteraction.js';
3
+ export type { GestureMapControllerConfig } from './types.js';
4
+ export { GestureController, GestureStateMachine, WebcamOverlay, classifyGesture, getHandSize, getTwoHandDistance, DEFAULT_WEBCAM_CONFIG, DEFAULT_TUNING_CONFIG, LANDMARKS, COLORS, } from '@map-gesture-controls/core';
5
+ export type { GestureMode, GestureType, HandednessLabel, GestureFrame, DetectedHand, WebcamConfig, TuningConfig, StateMachineOutput, SmoothedPoint, Point2D, HandLandmark, } from '@map-gesture-controls/core';
package/dist/index.js ADDED
@@ -0,0 +1,134 @@
1
+ var d = Object.defineProperty;
2
+ var m = (a, e, t) => e in a ? d(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
3
+ var s = (a, e, t) => m(a, typeof e != "symbol" ? e + "" : e, t);
4
+ import { DEFAULT_WEBCAM_CONFIG as p, DEFAULT_TUNING_CONFIG as g, GestureController as f, GestureStateMachine as y, WebcamOverlay as C } from "@map-gesture-controls/core";
5
+ import { COLORS as L, DEFAULT_TUNING_CONFIG as S, DEFAULT_WEBCAM_CONFIG as E, GestureController as M, GestureStateMachine as A, LANDMARKS as N, WebcamOverlay as O, classifyGesture as T, getHandSize as H, getTwoHandDistance as _ } from "@map-gesture-controls/core";
6
+ function v(a) {
7
+ return [(a[0] + a[2]) / 2, (a[1] + a[3]) / 2];
8
+ }
9
+ class b {
10
+ constructor(e) {
11
+ s(this, "map");
12
+ s(this, "panScale", 2);
13
+ // Two-hand distance delta is ~0.005–0.02 per frame at natural speed.
14
+ // zoomScale=4.0 ≈ 1.2 zoom levels/sec at 30fps. Adjust to taste.
15
+ s(this, "zoomScale", 4);
16
+ this.map = e;
17
+ }
18
+ /**
19
+ * Apply a state machine output frame to the map.
20
+ * Safe to call every animation frame.
21
+ */
22
+ apply(e) {
23
+ e.panDelta && this.pan(e.panDelta.x, e.panDelta.y), e.zoomDelta !== null && this.zoom(e.zoomDelta);
24
+ }
25
+ /**
26
+ * Pan map by a normalised delta (0–1 range from webcam space).
27
+ * dx/dy are hand movement as a fraction of frame width/height.
28
+ */
29
+ pan(e, t) {
30
+ const n = this.map.getView(), i = n.getResolution();
31
+ if (i === void 0) return;
32
+ const r = this.map.getSize();
33
+ if (!r) return;
34
+ const [l, h] = r, c = -e * l * this.panScale, u = t * h * this.panScale, o = n.getCenter();
35
+ o && n.setCenter([
36
+ o[0] - c * i,
37
+ o[1] + u * i
38
+ ]);
39
+ }
40
+ /**
41
+ * Zoom map. delta > 0 = zoom in, delta < 0 = zoom out.
42
+ */
43
+ zoom(e) {
44
+ const t = this.map.getView(), n = t.getZoom();
45
+ if (n === void 0) return;
46
+ const i = t.calculateExtent(this.map.getSize()), r = v(i);
47
+ t.animate({
48
+ zoom: n + e * this.zoomScale,
49
+ center: r,
50
+ duration: 0
51
+ });
52
+ }
53
+ }
54
+ class w {
55
+ constructor(e) {
56
+ s(this, "config");
57
+ s(this, "gestureController");
58
+ s(this, "stateMachine");
59
+ s(this, "overlay");
60
+ s(this, "interaction");
61
+ s(this, "lastFrame", null);
62
+ s(this, "rafHandle", null);
63
+ s(this, "started", !1);
64
+ s(this, "paused", !1);
65
+ s(this, "handleVisibilityChange", () => {
66
+ document.hidden ? this.pause() : this.resume();
67
+ });
68
+ const t = { ...p, ...e.webcam }, n = { ...g, ...e.tuning };
69
+ this.config = {
70
+ map: e.map,
71
+ webcam: t,
72
+ tuning: n,
73
+ debug: e.debug ?? !1
74
+ }, this.gestureController = new f(n, (i) => {
75
+ this.lastFrame = i;
76
+ }), this.stateMachine = new y(n), this.overlay = new C(t), this.interaction = new b(e.map);
77
+ }
78
+ /**
79
+ * Initialise webcam + MediaPipe, mount overlay, begin detection loop.
80
+ * Must be called from a user-gesture event (e.g. button click) to allow
81
+ * webcam permission prompt.
82
+ */
83
+ async start() {
84
+ if (this.started) return;
85
+ this.started = !0;
86
+ const e = await this.gestureController.init();
87
+ this.overlay.attachVideo(e);
88
+ const t = this.config.map.getTargetElement();
89
+ this.overlay.mount(t ?? document.body), this.gestureController.start(), this.renderLoop(), document.addEventListener("visibilitychange", this.handleVisibilityChange);
90
+ }
91
+ /** Stop detection and remove overlay. */
92
+ stop() {
93
+ this.gestureController.destroy(), this.overlay.unmount(), this.stateMachine.reset(), this.rafHandle !== null && (cancelAnimationFrame(this.rafHandle), this.rafHandle = null), document.removeEventListener("visibilitychange", this.handleVisibilityChange), this.started = !1, this.paused = !1;
94
+ }
95
+ /** Pause detection (overlay stays visible but inactive). */
96
+ pause() {
97
+ this.paused = !0, this.gestureController.stop(), this.stateMachine.reset();
98
+ }
99
+ /** Resume after pause. */
100
+ resume() {
101
+ this.paused && (this.paused = !1, this.gestureController.start());
102
+ }
103
+ renderLoop() {
104
+ if (this.rafHandle = requestAnimationFrame(() => this.renderLoop()), this.paused) {
105
+ this.overlay.render(null, "idle");
106
+ return;
107
+ }
108
+ const e = this.lastFrame;
109
+ if (e === null) {
110
+ this.overlay.render(null, "idle");
111
+ return;
112
+ }
113
+ const t = this.stateMachine.update(e);
114
+ this.interaction.apply(t), this.overlay.render(e, t.mode), this.config.debug && this.logDebug(t.mode, e);
115
+ }
116
+ logDebug(e, t) {
117
+ const n = t.hands.map((i) => `${i.handedness}:${i.gesture}(${i.score.toFixed(2)})`).join(" ");
118
+ console.debug(`[ol-gestures] mode=${e} ${n}`);
119
+ }
120
+ }
121
+ export {
122
+ L as COLORS,
123
+ S as DEFAULT_TUNING_CONFIG,
124
+ E as DEFAULT_WEBCAM_CONFIG,
125
+ M as GestureController,
126
+ w as GestureMapController,
127
+ A as GestureStateMachine,
128
+ N as LANDMARKS,
129
+ b as OpenLayersGestureInteraction,
130
+ O as WebcamOverlay,
131
+ T as classifyGesture,
132
+ H as getHandSize,
133
+ _ as getTwoHandDistance
134
+ };
@@ -0,0 +1,7 @@
1
+ import type { WebcamConfig, TuningConfig } from '@map-gesture-controls/core';
2
+ export interface GestureMapControllerConfig {
3
+ map: import('ol').Map;
4
+ webcam?: Partial<WebcamConfig>;
5
+ tuning?: Partial<TuningConfig>;
6
+ debug?: boolean;
7
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@map-gesture-controls/ol",
3
+ "version": "0.1.2",
4
+ "description": "Control OpenLayers maps with hand gestures via MediaPipe",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./style.css": "./dist/style.css"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build:lib": "tsc -p tsconfig.lib.json && vite build --config vite.lib.config.ts",
21
+ "type-check": "tsc --noEmit"
22
+ },
23
+ "dependencies": {
24
+ "@map-gesture-controls/core": "*"
25
+ },
26
+ "peerDependencies": {
27
+ "ol": "^10.2.1"
28
+ },
29
+ "devDependencies": {
30
+ "ol": "^10.2.1",
31
+ "typescript": "^5.4.5",
32
+ "vite": "^5.2.11"
33
+ },
34
+ "keywords": [
35
+ "openlayers",
36
+ "mediapipe",
37
+ "hand-gestures",
38
+ "map",
39
+ "gesture-control"
40
+ ],
41
+ "overrides": {
42
+ "esbuild": "^0.25.0"
43
+ },
44
+ "license": "MIT"
45
+ }