@superrouteca/smap-leaflet 0.0.0-stage → 1.1.0
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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +158 -2
- package/examples/index.html +94 -0
- package/package.json +67 -4
- package/src/common/access.js +198 -0
- package/src/common/badge.js +56 -0
- package/src/common/embed.js +166 -0
- package/src/compat.js +847 -0
- package/src/index.js +304 -0
- package/types/compat.d.ts +226 -0
- package/types/index.d.ts +152 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// @superrouteca/smap-leaflet — sMap in Leaflet: the vector map (an sMap map drawn in a Leaflet pane, with sMap's
|
|
2
|
+
// layers, popups and Auto Zoom) or raster tiles, and the sMap badge and data credit in Leaflet's corner.
|
|
3
|
+
// Leaflet-style calls on an sMap map are in './compat.js' (`@superrouteca/smap-leaflet/compat`).
|
|
4
|
+
import * as LeafletModule from 'leaflet';
|
|
5
|
+
import * as sMapModule from '@superrouteca/smap';
|
|
6
|
+
import { SMapAccess, checkStyle, rasterPath, stylePath, FLAVORS, DEFAULT_BASE } from './common/access.js';
|
|
7
|
+
import { OSM_ATTRIBUTION, createBadge } from './common/badge.js';
|
|
8
|
+
import { createEmbedded, hasWebGL2 } from './common/embed.js';
|
|
9
|
+
|
|
10
|
+
// Leaflet's module build has named exports, its main build a default export: either works.
|
|
11
|
+
const L = LeafletModule.default && LeafletModule.default.Layer ? LeafletModule.default : LeafletModule;
|
|
12
|
+
const SDK = sMapModule.default && sMapModule.default.Map ? sMapModule.default : sMapModule;
|
|
13
|
+
|
|
14
|
+
export const version = '1.1.0';
|
|
15
|
+
export { FLAVORS as styles, checkStyle, rasterPath, stylePath, SMapAccess, DEFAULT_BASE, OSM_ATTRIBUTION };
|
|
16
|
+
|
|
17
|
+
/** Options that belong to Leaflet, not to the sMap map. */
|
|
18
|
+
const LEAFLET_OPTIONS = ['pane', 'interactive', 'sdk', 'attribution', 'zIndex', 'className'];
|
|
19
|
+
/** Options of the service access (key, token, nodes); the rest of a raster layer's options go to Leaflet. */
|
|
20
|
+
const ACCESS_OPTIONS = ['baseUrl', 'token', 'getToken', 'apiKey', 'nodes', 'nearest', 'fetch', 'timeoutMs', 'client'];
|
|
21
|
+
|
|
22
|
+
function pick(o, keys, inside) {
|
|
23
|
+
const out = {};
|
|
24
|
+
Object.keys(o || {}).forEach(function (k) { if ((keys.indexOf(k) >= 0) === inside) { out[k] = o[k]; } });
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ------------------------------------------------------------------ badge (one per map)
|
|
29
|
+
|
|
30
|
+
/** The sMap badge as a Leaflet control (bottom right, above the attribution). */
|
|
31
|
+
export const SMapBadge = L.Control.extend({
|
|
32
|
+
options: { position: 'bottomright' },
|
|
33
|
+
onAdd: function (map) { return createBadge(map.getContainer().ownerDocument); },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function holdBadge(map) {
|
|
37
|
+
const held = map._smapBadge || (map._smapBadge = { control: null, count: 0 });
|
|
38
|
+
held.count += 1;
|
|
39
|
+
if (!held.control) { held.control = new SMapBadge().addTo(map); }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function releaseBadge(map) {
|
|
43
|
+
const held = map._smapBadge;
|
|
44
|
+
if (!held) { return; }
|
|
45
|
+
held.count -= 1;
|
|
46
|
+
if (held.count <= 0 && held.control) { held.control.remove(); held.control = null; held.count = 0; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setCredits(map, layer, list) {
|
|
50
|
+
const ac = map && map.attributionControl;
|
|
51
|
+
const before = layer._credits || [];
|
|
52
|
+
layer._credits = list || [];
|
|
53
|
+
if (!ac) { return; }
|
|
54
|
+
before.forEach(function (a) { ac.removeAttribution(a); });
|
|
55
|
+
layer._credits.forEach(function (a) { ac.addAttribution(a); });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ------------------------------------------------------------------ vector: an sMap map in a pane
|
|
59
|
+
|
|
60
|
+
/** sMap map calls a layer forwards once its map is ready (each returns the layer). */
|
|
61
|
+
export const FORWARDED = ['setStyle', 'setLanguage', 'setBasemap', 'setAutoZoom', 'addPoints', 'addLines', 'addPolygons',
|
|
62
|
+
'addData', 'setData', 'setVisible', 'fitLayer', 'addSource', 'removeSource', 'addStyleLayer',
|
|
63
|
+
'setPaint', 'setLayout', 'setFilter', 'addRasterLayer', 'addWmsLayer', 'addWmtsLayer', 'addVectorTiles', 'addArcGisLayer',
|
|
64
|
+
'loadLayers'];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The sMap vector map as a Leaflet layer. Options: every sMap map option (style incl. `custom:<id>`, language, apiKey,
|
|
68
|
+
* token, getToken, baseUrl, nodes, nearest, popup, popupLoading, autoZoom) plus `pane` (default `tilePane`) and
|
|
69
|
+
* `interactive` (Leaflet's clicks and pointer moves reach sMap's layers; default true).
|
|
70
|
+
*/
|
|
71
|
+
export const SMapLayer = L.Layer.extend({
|
|
72
|
+
options: { pane: 'tilePane', interactive: true },
|
|
73
|
+
|
|
74
|
+
initialize: function (options) {
|
|
75
|
+
options = options || {};
|
|
76
|
+
L.setOptions(this, pick(options, ['pane', 'interactive'], true));
|
|
77
|
+
this._sdk = options.sdk || SDK;
|
|
78
|
+
this._smapOptions = pick(options, LEAFLET_OPTIONS, false);
|
|
79
|
+
if (this._smapOptions.style !== undefined && typeof this._smapOptions.style === 'string' && this._smapOptions.style.indexOf('://') < 0) {
|
|
80
|
+
checkStyle(this._smapOptions.style);
|
|
81
|
+
}
|
|
82
|
+
const self = this;
|
|
83
|
+
this.ready = new Promise(function (resolve) { self._resolveReady = resolve; });
|
|
84
|
+
this._embedded = null;
|
|
85
|
+
this._credits = [];
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
onAdd: function (map) {
|
|
89
|
+
if (!this._container) {
|
|
90
|
+
this._container = L.DomUtil.create('div', 'leaflet-smap-layer' + (map._zoomAnimated ? ' leaflet-zoom-animated' : ''));
|
|
91
|
+
// A click on an open popup is the popup's: Leaflet neither drags nor closes it.
|
|
92
|
+
L.DomEvent.on(this._container, 'mousedown touchstart pointerdown click dblclick wheel contextmenu', function (e) {
|
|
93
|
+
const t = e.target;
|
|
94
|
+
if (t && t.closest && t.closest('.maplibregl-popup')) { L.DomEvent.stopPropagation(e); }
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
this.getPane().appendChild(this._container);
|
|
98
|
+
this._size();
|
|
99
|
+
if (!this._embedded) {
|
|
100
|
+
const self = this;
|
|
101
|
+
const c = map.getCenter();
|
|
102
|
+
this._embedded = createEmbedded(this._sdk, this._container, Object.assign({}, this._smapOptions, {
|
|
103
|
+
center: [c.lng, c.lat],
|
|
104
|
+
zoom: map.getZoom() - 1,
|
|
105
|
+
}), {
|
|
106
|
+
move: function (t, how) { self._moveLeaflet(t, how); },
|
|
107
|
+
attribution: function (list) { setCredits(self._map, self, list); },
|
|
108
|
+
cursor: function (cursor) { if (self._map) { self._map.getContainer().style.cursor = cursor; } },
|
|
109
|
+
});
|
|
110
|
+
this._embedded.ready.then(function (m) { self._update(); self._resolveReady(m); });
|
|
111
|
+
} else {
|
|
112
|
+
this._embedded.resize();
|
|
113
|
+
}
|
|
114
|
+
holdBadge(map);
|
|
115
|
+
setCredits(map, this, this._embedded.credits().length ? this._embedded.credits() : [OSM_ATTRIBUTION]);
|
|
116
|
+
this._update();
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
onRemove: function (map) {
|
|
120
|
+
setCredits(map, this, []);
|
|
121
|
+
this._credits = [];
|
|
122
|
+
releaseBadge(map);
|
|
123
|
+
if (this._container && this._container.parentNode) { this._container.parentNode.removeChild(this._container); }
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
getEvents: function () {
|
|
127
|
+
const ev = { viewreset: this._update, move: this._update, moveend: this._update, zoom: this._update, zoomend: this._update, resize: this._resize };
|
|
128
|
+
if (this._map && this._map._zoomAnimated) { ev.zoomanim = this._animateZoom; }
|
|
129
|
+
if (this.options.interactive) {
|
|
130
|
+
ev.click = this._forward;
|
|
131
|
+
ev.dblclick = this._forward;
|
|
132
|
+
ev.contextmenu = this._forward;
|
|
133
|
+
ev.mousemove = this._forward;
|
|
134
|
+
ev.mouseout = this._forwardOut;
|
|
135
|
+
}
|
|
136
|
+
return ev;
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/** The sMap map (null until it is ready); `await layer.ready` gives it once it is. */
|
|
140
|
+
getSMap: function () { return this._embedded && this._embedded.engine() ? this._embedded.map : null; },
|
|
141
|
+
|
|
142
|
+
/** Removes the layer from its map and deletes the sMap map. */
|
|
143
|
+
destroy: function () {
|
|
144
|
+
if (this._map) { this.remove(); }
|
|
145
|
+
if (this._embedded) { this._embedded.remove(); this._embedded = null; }
|
|
146
|
+
return this;
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/** sMap map calls whose answer is a value: `layer.call('basemap')` resolves to it. */
|
|
150
|
+
call: function (name) {
|
|
151
|
+
const args = Array.prototype.slice.call(arguments, 1);
|
|
152
|
+
return this.ready.then(function (m) {
|
|
153
|
+
if (typeof m[name] !== 'function') { throw new Error('sMap: this version of @superrouteca/smap has no ' + name + '()'); }
|
|
154
|
+
return m[name].apply(m, args);
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
_size: function () {
|
|
159
|
+
const size = this._map.getSize();
|
|
160
|
+
this._container.style.width = size.x + 'px';
|
|
161
|
+
this._container.style.height = size.y + 'px';
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
_resize: function () {
|
|
165
|
+
this._size();
|
|
166
|
+
if (this._embedded) { this._embedded.resize(); }
|
|
167
|
+
this._update();
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
_update: function () {
|
|
171
|
+
const map = this._map;
|
|
172
|
+
if (!map || !this._embedded || map._animatingZoom) { return; }
|
|
173
|
+
L.DomUtil.setPosition(this._container, map.containerPointToLayerPoint([0, 0]));
|
|
174
|
+
const c = map.getCenter();
|
|
175
|
+
// Leaflet counts zoom in 256 px tiles, the sMap map in 512 px tiles: one level apart.
|
|
176
|
+
this._embedded.sync({ center: { lng: c.lng, lat: c.lat }, zoom: map.getZoom() - 1 });
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
_animateZoom: function (e) {
|
|
180
|
+
const map = this._map;
|
|
181
|
+
const scale = map.getZoomScale(e.zoom);
|
|
182
|
+
const offset = map._latLngBoundsToNewLayerBounds(map.getBounds(), e.zoom, e.center).min;
|
|
183
|
+
L.DomUtil.setTransform(this._container, offset, scale);
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
_forward: function (e) {
|
|
187
|
+
if (!this._embedded || !e.containerPoint || !e.latlng) { return; }
|
|
188
|
+
if (this._embedded.inPopup(e.originalEvent)) { return; }
|
|
189
|
+
this._embedded.forward(e.type, e.containerPoint.x, e.containerPoint.y, { lng: e.latlng.lng, lat: e.latlng.lat }, e.originalEvent);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
_forwardOut: function (e) {
|
|
193
|
+
if (!this._embedded) { return; }
|
|
194
|
+
const c = this._map.getCenter();
|
|
195
|
+
this._embedded.forward('mouseout', -1, -1, { lng: c.lng, lat: c.lat }, e.originalEvent);
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// The sMap map asked to move (Auto Zoom, fitLayer, flyTo, a popup panning into view): Leaflet moves, and the
|
|
199
|
+
// sMap map follows on Leaflet's move events.
|
|
200
|
+
_moveLeaflet: function (t, how) {
|
|
201
|
+
const map = this._map;
|
|
202
|
+
if (!map) { return; }
|
|
203
|
+
const latlng = L.latLng(t.center.lat, t.center.lng);
|
|
204
|
+
const zoom = t.zoom + 1;
|
|
205
|
+
const seconds = how.duration !== undefined ? how.duration / 1000 : undefined;
|
|
206
|
+
if (how.fly && how.animate) { map.flyTo(latlng, zoom, seconds !== undefined ? { duration: seconds } : {}); return; }
|
|
207
|
+
map.setView(latlng, zoom, how.animate ? { animate: true, duration: seconds } : { animate: false });
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
FORWARDED.forEach(function (name) {
|
|
212
|
+
SMapLayer.prototype[name] = function () {
|
|
213
|
+
const args = Array.prototype.slice.call(arguments);
|
|
214
|
+
if (name === 'setStyle' && typeof args[0] === 'string' && args[0].indexOf('://') < 0) { checkStyle(args[0]); }
|
|
215
|
+
this.call.apply(this, [name].concat(args)).catch(function (e) { if (typeof console !== 'undefined') { console.error(e); } });
|
|
216
|
+
return this;
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
// Leaflet's own openPopup / closePopup (a popup bound to the layer) stay Leaflet's; the sMap map's popup has its own names.
|
|
220
|
+
/** Opens the sMap map's popup at [lng, lat]. */
|
|
221
|
+
SMapLayer.prototype.openSMapPopup = function (lngLat, content, options) { this.call('openPopup', lngLat, content, options).catch(function () {}); return this; };
|
|
222
|
+
/** Closes the sMap map's popup. */
|
|
223
|
+
SMapLayer.prototype.closeSMapPopup = function () { this.call('closePopup').catch(function () {}); return this; };
|
|
224
|
+
/** Removes one of the sMap map's layers (the sMap map's removeLayer; Leaflet's removeLayer belongs to the map). */
|
|
225
|
+
SMapLayer.prototype.removeSMapLayer = function (id) { this.call('removeLayer', id).catch(function () {}); return this; };
|
|
226
|
+
|
|
227
|
+
/** The sMap vector map as a Leaflet layer. */
|
|
228
|
+
export function smapLayer(options) { return new SMapLayer(options); }
|
|
229
|
+
|
|
230
|
+
// ------------------------------------------------------------------ raster tiles
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* sMap raster tiles (256 px, or 512 px drawn for high-density screens) as a Leaflet grid layer: for browsers without
|
|
234
|
+
* WebGL, or wherever images are wanted. Tiles are read with the map key or token from the nearest node, moving to the
|
|
235
|
+
* next node when one fails. Options: style (a scheme or `custom:<id>`), language, retina (default: the screen's),
|
|
236
|
+
* the access options (apiKey, token, getToken, baseUrl, nodes, nearest) and Leaflet's grid layer options.
|
|
237
|
+
*/
|
|
238
|
+
export const SMapRasterLayer = L.GridLayer.extend({
|
|
239
|
+
initialize: function (options) {
|
|
240
|
+
options = options || {};
|
|
241
|
+
this._access = options.access || new SMapAccess(pick(options, ACCESS_OPTIONS, true));
|
|
242
|
+
this._style = checkStyle(options.style || 'light');
|
|
243
|
+
this._language = options.language || (typeof navigator !== 'undefined' && navigator.language) || 'en';
|
|
244
|
+
this._retina = options.retina !== undefined ? !!options.retina : (typeof window !== 'undefined' && window.devicePixelRatio > 1);
|
|
245
|
+
const rest = pick(options, ACCESS_OPTIONS.concat(['style', 'language', 'retina', 'access']), false);
|
|
246
|
+
if (rest.attribution === undefined) { rest.attribution = OSM_ATTRIBUTION; }
|
|
247
|
+
L.GridLayer.prototype.initialize.call(this, rest);
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
onAdd: function (map) {
|
|
251
|
+
L.GridLayer.prototype.onAdd.call(this, map);
|
|
252
|
+
holdBadge(map);
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
onRemove: function (map) {
|
|
256
|
+
releaseBadge(map);
|
|
257
|
+
L.GridLayer.prototype.onRemove.call(this, map);
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
createTile: function (coords, done) {
|
|
261
|
+
const tile = L.DomUtil.create('img', 'leaflet-tile');
|
|
262
|
+
tile.alt = '';
|
|
263
|
+
tile.setAttribute('role', 'presentation');
|
|
264
|
+
const access = this._access;
|
|
265
|
+
const style = this._style;
|
|
266
|
+
const o = { lang: this._language, retina: this._retina };
|
|
267
|
+
access.start()
|
|
268
|
+
.then(function () { return access.rasterTile(style, coords.z, coords.x, coords.y, o); })
|
|
269
|
+
.then(function (blob) {
|
|
270
|
+
const url = URL.createObjectURL(blob);
|
|
271
|
+
tile.onload = function () { URL.revokeObjectURL(url); done(null, tile); };
|
|
272
|
+
tile.onerror = function (e) { URL.revokeObjectURL(url); done(e || new Error('sMap: tile image failed'), tile); };
|
|
273
|
+
tile.src = url;
|
|
274
|
+
})
|
|
275
|
+
.catch(function (e) { done(e, tile); });
|
|
276
|
+
return tile;
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
/** Switches the scheme (or `custom:<id>`) and reloads the tiles. */
|
|
280
|
+
setStyle: function (style) { this._style = checkStyle(style); this.redraw(); return this; },
|
|
281
|
+
/** Switches the label language and reloads the tiles. */
|
|
282
|
+
setLanguage: function (language) { this._language = language; this.redraw(); return this; },
|
|
283
|
+
/** The access in use (token, nodes). */
|
|
284
|
+
getAccess: function () { return this._access; },
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
export function smapRasterLayer(options) { return new SMapRasterLayer(options); }
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The sMap basemap the browser can draw: the vector map where WebGL 2 exists, raster tiles elsewhere.
|
|
291
|
+
* `mode: 'vector' | 'raster' | 'auto'` (default auto).
|
|
292
|
+
*/
|
|
293
|
+
export function smapBasemap(options) {
|
|
294
|
+
const o = Object.assign({}, options);
|
|
295
|
+
const mode = o.mode || 'auto';
|
|
296
|
+
delete o.mode;
|
|
297
|
+
if (mode === 'raster' || (mode === 'auto' && !hasWebGL2())) { return new SMapRasterLayer(o); }
|
|
298
|
+
return new SMapLayer(o);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export { hasWebGL2 };
|
|
302
|
+
|
|
303
|
+
const SMapLeaflet = { version, styles: FLAVORS, SMapLayer, smapLayer, SMapRasterLayer, smapRasterLayer, smapBasemap, SMapBadge, SMapAccess, hasWebGL2, rasterPath, checkStyle };
|
|
304
|
+
export default SMapLeaflet;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Types of @superrouteca/smap-leaflet/compat: Leaflet-style calls on an sMap map.
|
|
2
|
+
import type { Map as SMapMap } from '@superrouteca/smap';
|
|
3
|
+
|
|
4
|
+
export type LatLngInput = [number, number] | [number, number, number] | { lat: number; lng: number } | { lat: number; lon: number };
|
|
5
|
+
export interface LatLng { lat: number; lng: number }
|
|
6
|
+
export type BoundsInput = LatLngBounds | [LatLngInput, LatLngInput] | LatLngInput[];
|
|
7
|
+
export type PopupContentInput = string | HTMLElement | ((layer: Layer) => string | HTMLElement);
|
|
8
|
+
|
|
9
|
+
export interface PopupOptions {
|
|
10
|
+
className?: string;
|
|
11
|
+
maxWidth?: number;
|
|
12
|
+
minWidth?: number;
|
|
13
|
+
closeButton?: boolean;
|
|
14
|
+
closeOnClick?: boolean;
|
|
15
|
+
autoPan?: boolean;
|
|
16
|
+
offset?: [number, number] | { x: number; y: number };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PathOptions {
|
|
20
|
+
stroke?: boolean;
|
|
21
|
+
color?: string;
|
|
22
|
+
weight?: number;
|
|
23
|
+
opacity?: number;
|
|
24
|
+
fill?: boolean;
|
|
25
|
+
fillColor?: string | null;
|
|
26
|
+
fillOpacity?: number;
|
|
27
|
+
/** Dash and gap lengths in pixels, `'8, 4'` or `[8, 4]`. */
|
|
28
|
+
dashArray?: string | number[] | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LeafletEvent {
|
|
32
|
+
type: string;
|
|
33
|
+
target: unknown;
|
|
34
|
+
sourceTarget?: unknown;
|
|
35
|
+
layer?: Layer;
|
|
36
|
+
latlng?: LatLng;
|
|
37
|
+
containerPoint?: { x: number; y: number };
|
|
38
|
+
originalEvent?: Event;
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export declare class LatLngBounds {
|
|
43
|
+
extend(p: LatLngInput | LatLngBounds): this;
|
|
44
|
+
isValid(): boolean;
|
|
45
|
+
getSouthWest(): LatLng;
|
|
46
|
+
getNorthEast(): LatLng;
|
|
47
|
+
getNorthWest(): LatLng;
|
|
48
|
+
getSouthEast(): LatLng;
|
|
49
|
+
getWest(): number;
|
|
50
|
+
getSouth(): number;
|
|
51
|
+
getEast(): number;
|
|
52
|
+
getNorth(): number;
|
|
53
|
+
getCenter(): LatLng;
|
|
54
|
+
contains(p: LatLngInput | LatLngBounds): boolean;
|
|
55
|
+
toLngLatArray(): [[number, number], [number, number]];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
declare class Evented {
|
|
59
|
+
on(type: string, fn: (e: LeafletEvent) => void, context?: unknown): this;
|
|
60
|
+
on(map: Record<string, (e: LeafletEvent) => void>, context?: unknown): this;
|
|
61
|
+
off(type?: string, fn?: (e: LeafletEvent) => void, context?: unknown): this;
|
|
62
|
+
once(type: string, fn: (e: LeafletEvent) => void, context?: unknown): this;
|
|
63
|
+
fire(type: string, data?: Record<string, unknown>, propagate?: boolean): this;
|
|
64
|
+
listens(type: string): boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export declare class Layer extends Evented {
|
|
68
|
+
options: Record<string, any>;
|
|
69
|
+
feature?: { type: 'Feature'; properties: Record<string, unknown> | null; geometry: unknown };
|
|
70
|
+
addTo(target: CompatMap | LayerGroup | SMapMap): this;
|
|
71
|
+
remove(): this;
|
|
72
|
+
removeFrom(target: CompatMap | LayerGroup | SMapMap): this;
|
|
73
|
+
bindPopup(content: PopupContentInput, options?: PopupOptions): this;
|
|
74
|
+
unbindPopup(): this;
|
|
75
|
+
openPopup(latlng?: LatLngInput): this;
|
|
76
|
+
closePopup(): this;
|
|
77
|
+
togglePopup(): this;
|
|
78
|
+
setPopupContent(content: PopupContentInput): this;
|
|
79
|
+
getBounds(): LatLngBounds;
|
|
80
|
+
toGeoJSON(): unknown;
|
|
81
|
+
setStyle(style: PathOptions): this;
|
|
82
|
+
bringToFront(): this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export declare class Marker extends Layer {
|
|
86
|
+
constructor(latlng: LatLngInput, options?: { icon?: Icon; opacity?: number; zIndexOffset?: number; title?: string });
|
|
87
|
+
getLatLng(): LatLng;
|
|
88
|
+
setLatLng(latlng: LatLngInput): this;
|
|
89
|
+
setIcon(icon: Icon): this;
|
|
90
|
+
setOpacity(opacity: number): this;
|
|
91
|
+
setZIndexOffset(z: number): this;
|
|
92
|
+
}
|
|
93
|
+
export declare class CircleMarker extends Layer {
|
|
94
|
+
constructor(latlng: LatLngInput, options?: PathOptions & { radius?: number });
|
|
95
|
+
getLatLng(): LatLng;
|
|
96
|
+
setLatLng(latlng: LatLngInput): this;
|
|
97
|
+
setRadius(radius: number): this;
|
|
98
|
+
getRadius(): number;
|
|
99
|
+
}
|
|
100
|
+
export declare class Circle extends CircleMarker {
|
|
101
|
+
constructor(latlng: LatLngInput, options?: (PathOptions & { radius?: number }) | number);
|
|
102
|
+
}
|
|
103
|
+
export declare class Polyline extends Layer {
|
|
104
|
+
constructor(latlngs: unknown[], options?: PathOptions);
|
|
105
|
+
getLatLngs(): unknown[];
|
|
106
|
+
setLatLngs(latlngs: unknown[]): this;
|
|
107
|
+
addLatLng(latlng: LatLngInput): this;
|
|
108
|
+
isEmpty(): boolean;
|
|
109
|
+
}
|
|
110
|
+
export declare class Polygon extends Polyline {}
|
|
111
|
+
export declare class Rectangle extends Polygon {
|
|
112
|
+
constructor(bounds: BoundsInput, options?: PathOptions);
|
|
113
|
+
setBounds(bounds: BoundsInput): this;
|
|
114
|
+
}
|
|
115
|
+
export declare class TileLayer extends Layer {
|
|
116
|
+
constructor(url: string, options?: { subdomains?: string | string[]; attribution?: string; opacity?: number; tileSize?: number; minZoom?: number; maxZoom?: number; maxNativeZoom?: number; tms?: boolean; detectRetina?: boolean; [placeholder: string]: unknown });
|
|
117
|
+
setUrl(url: string): this;
|
|
118
|
+
setOpacity(opacity: number): this;
|
|
119
|
+
getAttribution(): string | undefined;
|
|
120
|
+
tileUrls(): string[];
|
|
121
|
+
}
|
|
122
|
+
export declare class WmsLayer extends TileLayer {
|
|
123
|
+
setParams(params: Record<string, unknown>): this;
|
|
124
|
+
}
|
|
125
|
+
export declare class LayerGroup extends Layer {
|
|
126
|
+
constructor(layers?: Layer[], options?: Record<string, unknown>);
|
|
127
|
+
addLayer(layer: Layer): this;
|
|
128
|
+
removeLayer(layer: Layer | number): this;
|
|
129
|
+
hasLayer(layer: Layer): boolean;
|
|
130
|
+
clearLayers(): this;
|
|
131
|
+
eachLayer(fn: (layer: Layer) => void, context?: unknown): this;
|
|
132
|
+
getLayers(): Layer[];
|
|
133
|
+
getLayer(id: number): Layer | null;
|
|
134
|
+
getLayerId(layer: Layer): number;
|
|
135
|
+
invoke(name: string, ...args: unknown[]): this;
|
|
136
|
+
}
|
|
137
|
+
export declare class FeatureGroup extends LayerGroup {}
|
|
138
|
+
export interface GeoJSONOptions {
|
|
139
|
+
style?: PathOptions | ((feature: any) => PathOptions);
|
|
140
|
+
pointToLayer?: (feature: any, latlng: LatLng) => Layer;
|
|
141
|
+
onEachFeature?: (feature: any, layer: Layer) => void;
|
|
142
|
+
filter?: (feature: any) => boolean;
|
|
143
|
+
coordsToLatLng?: (coords: number[]) => LatLngInput;
|
|
144
|
+
}
|
|
145
|
+
export declare class GeoJSON extends FeatureGroup {
|
|
146
|
+
constructor(data?: unknown, options?: GeoJSONOptions);
|
|
147
|
+
addData(data: unknown): this;
|
|
148
|
+
resetStyle(layer?: Layer): this;
|
|
149
|
+
}
|
|
150
|
+
export declare class Popup {
|
|
151
|
+
constructor(options?: PopupOptions, source?: Layer);
|
|
152
|
+
setLatLng(latlng: LatLngInput): this;
|
|
153
|
+
getLatLng(): LatLng | null;
|
|
154
|
+
setContent(content: PopupContentInput): this;
|
|
155
|
+
getContent(): PopupContentInput | null;
|
|
156
|
+
openOn(map: CompatMap | SMapMap): this;
|
|
157
|
+
close(): this;
|
|
158
|
+
remove(): this;
|
|
159
|
+
}
|
|
160
|
+
export declare class Icon { constructor(options: { iconUrl?: string; iconSize?: [number, number]; iconAnchor?: [number, number]; [key: string]: unknown }); options: Record<string, unknown> }
|
|
161
|
+
export declare class DivIcon extends Icon {}
|
|
162
|
+
|
|
163
|
+
/** A Leaflet-style map over an sMap map: zoom levels as Leaflet counts them (one above the sMap map's). */
|
|
164
|
+
export declare class CompatMap extends Evented {
|
|
165
|
+
readonly smap: SMapMap;
|
|
166
|
+
readonly engine: any;
|
|
167
|
+
addLayer(layer: Layer): this;
|
|
168
|
+
removeLayer(layer: Layer): this;
|
|
169
|
+
hasLayer(layer: Layer): boolean;
|
|
170
|
+
eachLayer(fn: (layer: Layer) => void, context?: unknown): this;
|
|
171
|
+
getCenter(): LatLng;
|
|
172
|
+
getZoom(): number;
|
|
173
|
+
getBounds(): LatLngBounds;
|
|
174
|
+
getMinZoom(): number;
|
|
175
|
+
getMaxZoom(): number;
|
|
176
|
+
setView(center: LatLngInput, zoom?: number, options?: { animate?: boolean; duration?: number }): this;
|
|
177
|
+
setZoom(zoom: number, options?: { animate?: boolean }): this;
|
|
178
|
+
zoomIn(delta?: number, options?: { animate?: boolean }): this;
|
|
179
|
+
zoomOut(delta?: number, options?: { animate?: boolean }): this;
|
|
180
|
+
panTo(center: LatLngInput, options?: { animate?: boolean; duration?: number }): this;
|
|
181
|
+
panBy(offset: [number, number] | { x: number; y: number }, options?: { animate?: boolean }): this;
|
|
182
|
+
flyTo(center: LatLngInput, zoom?: number, options?: { duration?: number }): this;
|
|
183
|
+
fitBounds(bounds: BoundsInput, options?: { padding?: [number, number]; paddingTopLeft?: [number, number]; paddingBottomRight?: [number, number]; maxZoom?: number; animate?: boolean }): this;
|
|
184
|
+
flyToBounds(bounds: BoundsInput, options?: Record<string, unknown>): this;
|
|
185
|
+
invalidateSize(): this;
|
|
186
|
+
getContainer(): HTMLElement;
|
|
187
|
+
getSize(): { x: number; y: number };
|
|
188
|
+
project(latlng: LatLngInput): { x: number; y: number };
|
|
189
|
+
unproject(point: [number, number] | { x: number; y: number }): LatLng;
|
|
190
|
+
latLngToContainerPoint(latlng: LatLngInput): { x: number; y: number };
|
|
191
|
+
containerPointToLatLng(point: [number, number] | { x: number; y: number }): LatLng;
|
|
192
|
+
openPopup(content: PopupContentInput | Popup, latlng?: LatLngInput, options?: PopupOptions): this;
|
|
193
|
+
closePopup(): this;
|
|
194
|
+
remove(): this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface LeafletCompat {
|
|
198
|
+
version: string;
|
|
199
|
+
map: CompatMap;
|
|
200
|
+
marker(latlng: LatLngInput, options?: ConstructorParameters<typeof Marker>[1]): Marker;
|
|
201
|
+
circleMarker(latlng: LatLngInput, options?: ConstructorParameters<typeof CircleMarker>[1]): CircleMarker;
|
|
202
|
+
circle(latlng: LatLngInput, options?: ConstructorParameters<typeof Circle>[1]): Circle;
|
|
203
|
+
polyline(latlngs: unknown[], options?: PathOptions): Polyline;
|
|
204
|
+
polygon(latlngs: unknown[], options?: PathOptions): Polygon;
|
|
205
|
+
rectangle(bounds: BoundsInput, options?: PathOptions): Rectangle;
|
|
206
|
+
geoJSON(data?: unknown, options?: GeoJSONOptions): GeoJSON;
|
|
207
|
+
geoJson(data?: unknown, options?: GeoJSONOptions): GeoJSON;
|
|
208
|
+
tileLayer: ((url: string, options?: ConstructorParameters<typeof TileLayer>[1]) => TileLayer) & { wms(url: string, options?: Record<string, unknown>): WmsLayer };
|
|
209
|
+
layerGroup(layers?: Layer[], options?: Record<string, unknown>): LayerGroup;
|
|
210
|
+
featureGroup(layers?: Layer[], options?: Record<string, unknown>): FeatureGroup;
|
|
211
|
+
popup(options?: PopupOptions, source?: Layer): Popup;
|
|
212
|
+
icon(options: ConstructorParameters<typeof Icon>[0]): Icon;
|
|
213
|
+
divIcon(options?: Record<string, unknown>): DivIcon;
|
|
214
|
+
latLng(a: LatLngInput | number, b?: number): LatLng | null;
|
|
215
|
+
latLngBounds(a: BoundsInput | LatLngInput, b?: LatLngInput): LatLngBounds;
|
|
216
|
+
Marker: typeof Marker; CircleMarker: typeof CircleMarker; Circle: typeof Circle; Polyline: typeof Polyline; Polygon: typeof Polygon;
|
|
217
|
+
Rectangle: typeof Rectangle; GeoJSON: typeof GeoJSON; TileLayer: typeof TileLayer; LayerGroup: typeof LayerGroup;
|
|
218
|
+
FeatureGroup: typeof FeatureGroup; Popup: typeof Popup; Icon: typeof Icon; DivIcon: typeof DivIcon;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Leaflet-style calls on an sMap map (after `await map.ready`). */
|
|
222
|
+
export declare function createLeafletCompat(smap: SMapMap): LeafletCompat;
|
|
223
|
+
export declare function toLatLng(v: LatLngInput | null | undefined): LatLng | null;
|
|
224
|
+
export declare function latLngBounds(a: BoundsInput | LatLngInput, b?: LatLngInput): LatLngBounds;
|
|
225
|
+
export declare function withAlpha(color: string, alpha?: number): string;
|
|
226
|
+
export default createLeafletCompat;
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Types of @superrouteca/smap-leaflet.
|
|
2
|
+
import * as L from 'leaflet';
|
|
3
|
+
import type { Map as SMapMap, MapOptions as SMapMapOptions, StyleFlavor } from '@superrouteca/smap';
|
|
4
|
+
import type SMapClient from '@superrouteca/smap/client';
|
|
5
|
+
|
|
6
|
+
export type { StyleFlavor };
|
|
7
|
+
/** A colour scheme or a saved custom style (`custom:<id>`). */
|
|
8
|
+
export type SMapStyle = StyleFlavor | `custom:${string}`;
|
|
9
|
+
|
|
10
|
+
/** How the adapters reach the service: a map key or a token (renewed through getToken), the nearest node. */
|
|
11
|
+
export interface AccessOptions {
|
|
12
|
+
/** The map service, `https://tile.srsr.ca` by default. */
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/** A map key (`smk_…`) whose allowlist names this site. */
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
token?: string;
|
|
17
|
+
/** Called at start and again two minutes before each token expires. */
|
|
18
|
+
getToken?: () => string | Promise<string>;
|
|
19
|
+
/** Node URLs known in advance, used while `baseUrl` is down. */
|
|
20
|
+
nodes?: string[];
|
|
21
|
+
/** Read from the nearest node (default true). */
|
|
22
|
+
nearest?: boolean;
|
|
23
|
+
fetch?: typeof fetch;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
/** A JS client of your own instead of the options above. */
|
|
26
|
+
client?: SMapClient;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The service access the raster layer uses (token, node order, failover). */
|
|
30
|
+
export declare class SMapAccess {
|
|
31
|
+
constructor(options?: AccessOptions);
|
|
32
|
+
readonly client: SMapClient;
|
|
33
|
+
readonly baseUrl: string;
|
|
34
|
+
start(): Promise<this>;
|
|
35
|
+
refresh(): Promise<string | null>;
|
|
36
|
+
token(): string | null;
|
|
37
|
+
nodes(): string[];
|
|
38
|
+
head(): string;
|
|
39
|
+
isOurs(url: string): boolean;
|
|
40
|
+
headers(url: string): Record<string, string>;
|
|
41
|
+
rewrite(url: string): string;
|
|
42
|
+
failed(url: string): void;
|
|
43
|
+
withKey(path: string): string;
|
|
44
|
+
style(style: SMapStyle, language?: string): Promise<Record<string, unknown>>;
|
|
45
|
+
rasterTile(style: SMapStyle, z: number, x: number, y: number, options?: { lang?: string; retina?: boolean }): Promise<Blob>;
|
|
46
|
+
rasterTemplate(style: SMapStyle, options?: { lang?: string; retina?: boolean }): string;
|
|
47
|
+
on(listener: (type: 'token' | 'nodes', value: unknown) => void): this;
|
|
48
|
+
stop(): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SMapLayerOptions extends Omit<SMapMapOptions, 'container' | 'style'> {
|
|
52
|
+
/** A colour scheme, `custom:<id>`, or a style URL or object of your own. */
|
|
53
|
+
style?: SMapStyle | string | Record<string, unknown>;
|
|
54
|
+
/** The Leaflet pane the map is drawn in (default `tilePane`). */
|
|
55
|
+
pane?: string;
|
|
56
|
+
/** Leaflet's clicks and pointer moves reach sMap's layers (popups, Auto Zoom, onClick, hover); default true. */
|
|
57
|
+
interactive?: boolean;
|
|
58
|
+
/** The sMap Web SDK module to use (default the installed `@superrouteca/smap`). */
|
|
59
|
+
sdk?: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The sMap vector map in a Leaflet pane. The sMap map's calls are forwarded once it is ready. */
|
|
63
|
+
export declare class SMapLayer extends L.Layer {
|
|
64
|
+
constructor(options?: SMapLayerOptions);
|
|
65
|
+
/** Resolves to the sMap map once it is ready. */
|
|
66
|
+
readonly ready: Promise<SMapMap>;
|
|
67
|
+
/** The sMap map, or null before it is ready. */
|
|
68
|
+
getSMap(): SMapMap | null;
|
|
69
|
+
/** An sMap map call whose answer is a value (e.g. `call('basemap')`, `call('toJSON')`). */
|
|
70
|
+
call<T = unknown>(name: string, ...args: unknown[]): Promise<T>;
|
|
71
|
+
/** Removes the layer and deletes its sMap map. */
|
|
72
|
+
destroy(): this;
|
|
73
|
+
setStyle(style: SMapStyle | string | Record<string, unknown>): this;
|
|
74
|
+
setLanguage(language: string): this;
|
|
75
|
+
setBasemap(overrides: Record<string, Record<string, unknown>>): this;
|
|
76
|
+
setAutoZoom(on: boolean): this;
|
|
77
|
+
addPoints(id: string, data: unknown, options?: Record<string, unknown>): this;
|
|
78
|
+
addLines(id: string, data: unknown, options?: Record<string, unknown>): this;
|
|
79
|
+
addPolygons(id: string, data: unknown, options?: Record<string, unknown>): this;
|
|
80
|
+
addData(id: string, input: unknown, options?: Record<string, unknown>): this;
|
|
81
|
+
setData(id: string, data: unknown): this;
|
|
82
|
+
setVisible(id: string, visible: boolean): this;
|
|
83
|
+
fitLayer(id: string, options?: Record<string, unknown>): this;
|
|
84
|
+
/** Opens the sMap map's popup (Leaflet's openPopup stays Leaflet's own). */
|
|
85
|
+
openSMapPopup(lngLat: [number, number] | { lng: number; lat: number }, content: unknown, options?: Record<string, unknown>): this;
|
|
86
|
+
closeSMapPopup(): this;
|
|
87
|
+
addSource(id: string, source: Record<string, unknown>): this;
|
|
88
|
+
removeSource(id: string): this;
|
|
89
|
+
addStyleLayer(layer: Record<string, unknown>, options?: { before?: string; slot?: 'bottom' | 'roads' | 'labels' | 'top' }): this;
|
|
90
|
+
setPaint(layerId: string, name: string, value: unknown): this;
|
|
91
|
+
setLayout(layerId: string, name: string, value: unknown): this;
|
|
92
|
+
setFilter(layerId: string, filter: unknown): this;
|
|
93
|
+
addRasterLayer(id: string, options: Record<string, unknown>): this;
|
|
94
|
+
addWmsLayer(id: string, options: Record<string, unknown>): this;
|
|
95
|
+
addWmtsLayer(id: string, options: Record<string, unknown>): this;
|
|
96
|
+
addVectorTiles(id: string, options: Record<string, unknown>): this;
|
|
97
|
+
addArcGisLayer(id: string, options: Record<string, unknown>): this;
|
|
98
|
+
loadLayers(json: unknown): this;
|
|
99
|
+
/** Removes one of the sMap map's layers. */
|
|
100
|
+
removeSMapLayer(id: string): this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface SMapRasterLayerOptions extends AccessOptions, L.GridLayerOptions {
|
|
104
|
+
style?: SMapStyle;
|
|
105
|
+
language?: string;
|
|
106
|
+
/** 512 px tiles for high-density screens (default: the screen's pixel ratio above 1). */
|
|
107
|
+
retina?: boolean;
|
|
108
|
+
/** An access of your own, shared between layers. */
|
|
109
|
+
access?: SMapAccess;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** sMap raster tiles as a Leaflet grid layer. */
|
|
113
|
+
export declare class SMapRasterLayer extends L.GridLayer {
|
|
114
|
+
constructor(options?: SMapRasterLayerOptions);
|
|
115
|
+
setStyle(style: SMapStyle): this;
|
|
116
|
+
setLanguage(language: string): this;
|
|
117
|
+
getAccess(): SMapAccess;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The sMap badge as a Leaflet control. */
|
|
121
|
+
export declare class SMapBadge extends L.Control {}
|
|
122
|
+
|
|
123
|
+
export declare function smapLayer(options?: SMapLayerOptions): SMapLayer;
|
|
124
|
+
export declare function smapRasterLayer(options?: SMapRasterLayerOptions): SMapRasterLayer;
|
|
125
|
+
/** The vector map where WebGL 2 exists, raster tiles elsewhere (`mode`, default `auto`). */
|
|
126
|
+
export declare function smapBasemap(options?: (SMapLayerOptions & SMapRasterLayerOptions) & { mode?: 'auto' | 'vector' | 'raster' }): SMapLayer | SMapRasterLayer;
|
|
127
|
+
export declare function hasWebGL2(doc?: Document): boolean;
|
|
128
|
+
export declare function checkStyle(style?: string): SMapStyle;
|
|
129
|
+
export declare function rasterPath(style: SMapStyle, z: number, x: number, y: number, options?: { lang?: string; retina?: boolean }): string;
|
|
130
|
+
export declare function stylePath(style: SMapStyle, lang?: string): string;
|
|
131
|
+
export declare const styles: StyleFlavor[];
|
|
132
|
+
export declare const version: string;
|
|
133
|
+
export declare const DEFAULT_BASE: string;
|
|
134
|
+
export declare const OSM_ATTRIBUTION: string;
|
|
135
|
+
/** The sMap map calls an SMapLayer forwards. */
|
|
136
|
+
export declare const FORWARDED: string[];
|
|
137
|
+
|
|
138
|
+
declare const SMapLeaflet: {
|
|
139
|
+
version: string;
|
|
140
|
+
styles: StyleFlavor[];
|
|
141
|
+
SMapLayer: typeof SMapLayer;
|
|
142
|
+
smapLayer: typeof smapLayer;
|
|
143
|
+
SMapRasterLayer: typeof SMapRasterLayer;
|
|
144
|
+
smapRasterLayer: typeof smapRasterLayer;
|
|
145
|
+
smapBasemap: typeof smapBasemap;
|
|
146
|
+
SMapBadge: typeof SMapBadge;
|
|
147
|
+
SMapAccess: typeof SMapAccess;
|
|
148
|
+
hasWebGL2: typeof hasWebGL2;
|
|
149
|
+
rasterPath: typeof rasterPath;
|
|
150
|
+
checkStyle: typeof checkStyle;
|
|
151
|
+
};
|
|
152
|
+
export default SMapLeaflet;
|