@superrouteca/smap-leaflet 0.0.0-stage → 1.1.1

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,166 @@
1
+ // sMap tool adapters — an sMap map (the Web SDK) drawn inside another tool. The tool owns the camera and the input;
2
+ // the embedded map draws the basemap and sMap's layers. Its own camera moves (Auto Zoom, fitLayer, flyTo, a popup
3
+ // panning into view) are handed to the tool, and the tool's clicks and pointer moves are handed to it, so popups,
4
+ // Auto Zoom, hover and onClick work as on an sMap map. Kept identical in leaflet/, openlayers/ and mapbox/
5
+ // (test/common.test.mjs checks).
6
+ import { injectCss, mergeAttributions } from './badge.js';
7
+
8
+ /** The engine's camera calls that are sent to the tool instead of moving the embedded map on its own. */
9
+ export const CAMERA_CALLS = ['jumpTo', 'easeTo', 'flyTo', 'fitBounds', 'panBy', 'panTo', 'setCenter', 'setZoom', 'zoomTo',
10
+ 'zoomIn', 'zoomOut', 'setBearing', 'rotateTo', 'setPitch'];
11
+ const INSTANT = ['jumpTo', 'setCenter', 'setZoom', 'setBearing', 'setPitch'];
12
+
13
+ /** The SDK options the embedding sets itself. */
14
+ const OWN_OPTIONS = { interactive: false, attributionControl: false, keyboard3D: false };
15
+
16
+ function lngLatOf(engineModule, v) {
17
+ if (v == null) { return null; }
18
+ if (engineModule && engineModule.LngLat && engineModule.LngLat.convert) {
19
+ const c = engineModule.LngLat.convert(v);
20
+ return { lng: c.lng, lat: c.lat };
21
+ }
22
+ if (Array.isArray(v)) { return { lng: +v[0], lat: +v[1] }; }
23
+ return { lng: +(v.lng !== undefined ? v.lng : v.lon), lat: +v.lat };
24
+ }
25
+
26
+ /**
27
+ * Where a camera call would take the engine: { center: {lng, lat}, zoom, bearing, pitch } and how to get there
28
+ * ({ animate, duration, fly }). Pure over the engine's read-only calls (getCenter, project, cameraForBounds, …).
29
+ */
30
+ export function cameraTarget(engine, engineModule, call, args) {
31
+ const c = engine.getCenter();
32
+ const t = { center: { lng: c.lng, lat: c.lat }, zoom: engine.getZoom(), bearing: engine.getBearing ? engine.getBearing() : 0, pitch: engine.getPitch ? engine.getPitch() : 0 };
33
+ let o = {};
34
+ const take = function (cam) {
35
+ if (!cam) { return; }
36
+ if (cam.center !== undefined) { t.center = lngLatOf(engineModule, cam.center); }
37
+ if (typeof cam.zoom === 'number') { t.zoom = cam.zoom; }
38
+ if (typeof cam.bearing === 'number') { t.bearing = cam.bearing; }
39
+ if (typeof cam.pitch === 'number') { t.pitch = cam.pitch; }
40
+ };
41
+ switch (call) {
42
+ case 'jumpTo': case 'easeTo': case 'flyTo': o = args[0] || {}; take(o); break;
43
+ case 'fitBounds': o = args[1] || {}; take(engine.cameraForBounds ? engine.cameraForBounds(args[0], o) : null); break;
44
+ case 'panBy': {
45
+ o = args[1] || {};
46
+ const off = Array.isArray(args[0]) ? args[0] : [args[0].x, args[0].y];
47
+ const p = engine.project([t.center.lng, t.center.lat]);
48
+ take({ center: engine.unproject([p.x + off[0], p.y + off[1]]) });
49
+ break;
50
+ }
51
+ case 'panTo': o = args[1] || {}; take({ center: args[0] }); break;
52
+ case 'setCenter': take({ center: args[0] }); break;
53
+ case 'setZoom': take({ zoom: args[0] }); break;
54
+ case 'zoomTo': o = args[1] || {}; take({ zoom: args[0] }); break;
55
+ case 'zoomIn': o = args[0] || {}; t.zoom += 1; break;
56
+ case 'zoomOut': o = args[0] || {}; t.zoom -= 1; break;
57
+ case 'setBearing': take({ bearing: args[0] }); break;
58
+ case 'rotateTo': o = args[1] || {}; take({ bearing: args[0] }); break;
59
+ case 'setPitch': take({ pitch: args[0] }); break;
60
+ default: break;
61
+ }
62
+ const animate = INSTANT.indexOf(call) < 0 && o.animate !== false && o.duration !== 0;
63
+ return { target: t, how: { animate: animate, duration: typeof o.duration === 'number' ? o.duration : undefined, fly: call === 'flyTo' } };
64
+ }
65
+
66
+ /**
67
+ * Creates the embedded sMap map.
68
+ *
69
+ * @param {Object} sdk the sMap Web SDK module (`Map`, `getEngine`)
70
+ * @param {HTMLElement} el the element it draws in (sized and placed by the caller)
71
+ * @param {Object} options sMap map options (style, language, apiKey, token, getToken, baseUrl, …)
72
+ * @param {Object} hooks move(target, how): move the tool's camera; attribution(list): the credits changed
73
+ */
74
+ export function createEmbedded(sdk, el, options, hooks) {
75
+ injectCss(el.ownerDocument);
76
+ el.classList.add('smap-embedded');
77
+ const map = new sdk.Map(Object.assign({ minZoom: -2, maxZoom: 24 }, options, OWN_OPTIONS, { container: el }));
78
+ const engineModule = typeof sdk.getEngine === 'function' ? sdk.getEngine() : null;
79
+ const state = { jumpTo: null, engine: null, credits: [], removed: false };
80
+
81
+ function credits() {
82
+ const engine = state.engine;
83
+ const list = [];
84
+ try {
85
+ const caches = engine.style && (engine.style.sourceCaches || engine.style._sourceCaches);
86
+ const ids = caches ? Object.keys(caches) : Object.keys((engine.getStyle() || {}).sources || {});
87
+ ids.forEach(function (id) {
88
+ const s = engine.getSource(id);
89
+ if (s && typeof s.attribution === 'string') { list.push(s.attribution); }
90
+ });
91
+ } catch (e) { /* no style yet */ }
92
+ const merged = mergeAttributions(list);
93
+ if (merged.join('\n') !== state.credits.join('\n')) {
94
+ state.credits = merged;
95
+ if (hooks.attribution) { hooks.attribution(merged.slice()); }
96
+ }
97
+ }
98
+
99
+ function adopt(engine) {
100
+ if (state.engine || !engine) { return; }
101
+ state.engine = engine;
102
+ state.jumpTo = engine.jumpTo.bind(engine);
103
+ CAMERA_CALLS.forEach(function (call) {
104
+ if (typeof engine[call] !== 'function') { return; }
105
+ engine[call] = function () {
106
+ const r = cameraTarget(engine, engineModule, call, Array.prototype.slice.call(arguments));
107
+ if (hooks.move) { hooks.move(r.target, r.how); }
108
+ return engine;
109
+ };
110
+ });
111
+ if (engine.on) {
112
+ engine.on('sourcedata', function (e) { if (!e || e.sourceDataType === 'metadata' || e.isSourceLoaded) { credits(); } });
113
+ engine.on('styledata', credits);
114
+ }
115
+ credits();
116
+ if (hooks.adopted) { hooks.adopted(engine); }
117
+ }
118
+
119
+ const ready = map.ready.then(function (m) { adopt(m.engine); return m; });
120
+
121
+ const api = {
122
+ map: map,
123
+ ready: ready,
124
+ /** The engine once created (null before). */
125
+ engine: function () { return state.engine || (map.engine && (adopt(map.engine), state.engine)) || null; },
126
+ /** Puts the embedded camera where the tool's is (zoom in the engine's 512 px scale). */
127
+ sync: function (camera) {
128
+ const engine = api.engine();
129
+ if (!engine || state.removed) { return; }
130
+ state.jumpTo({ center: [camera.center.lng, camera.center.lat], zoom: camera.zoom, bearing: camera.bearing || 0, pitch: camera.pitch || 0 });
131
+ if (camera.redraw && typeof engine.redraw === 'function') { engine.redraw(); }
132
+ },
133
+ resize: function () { const engine = api.engine(); if (engine && engine.resize) { engine.resize(); } },
134
+ /**
135
+ * Hands one of the tool's pointer events to the embedded map (click, dblclick, mousemove, mouseout, contextmenu):
136
+ * `x`, `y` in pixels of the embedded map's element, the position, and the browser event.
137
+ */
138
+ forward: function (type, x, y, lngLat, originalEvent) {
139
+ const engine = api.engine();
140
+ if (!engine || state.removed) { return; }
141
+ const point = engineModule && engineModule.Point ? new engineModule.Point(x, y) : { x: x, y: y };
142
+ const ll = engineModule && engineModule.LngLat ? new engineModule.LngLat(lngLat.lng, lngLat.lat) : lngLat;
143
+ engine.fire(type, { point: point, lngLat: ll, originalEvent: originalEvent, type: type });
144
+ // The tool's element shows the pointer the embedded map asks for (a hand over a clickable feature).
145
+ if (type === 'mousemove' && hooks.cursor && engine.getCanvas) { hooks.cursor(engine.getCanvas().style.cursor || ''); }
146
+ },
147
+ /** True when the browser event happened inside one of the embedded map's popups (the popup handles it). */
148
+ inPopup: function (originalEvent) {
149
+ const t = originalEvent && originalEvent.target;
150
+ return !!(t && typeof t.closest === 'function' && t.closest('.maplibregl-popup'));
151
+ },
152
+ credits: function () { return state.credits.slice(); },
153
+ remove: function () { state.removed = true; map.remove(); },
154
+ };
155
+ return api;
156
+ }
157
+
158
+ /** True where the browser can draw the vector map (WebGL 2). */
159
+ export function hasWebGL2(doc) {
160
+ try {
161
+ const d = doc || (typeof document !== 'undefined' ? document : null);
162
+ if (!d) { return false; }
163
+ const c = d.createElement('canvas');
164
+ return !!(c.getContext && c.getContext('webgl2'));
165
+ } catch (e) { return false; }
166
+ }