@superrouteca/smap 1.0.1 → 1.0.5
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 +32 -0
- package/README.md +136 -4
- package/dist/SOURCE.json +8 -8
- package/dist/smap-client.cjs +2 -2
- package/dist/smap-client.js +2 -2
- package/dist/smap-client.mjs +2 -2
- package/dist/smap.cjs +643 -31
- package/dist/smap.css +54 -0
- package/dist/smap.js +634 -29
- package/dist/smap.mjs +643 -32
- package/package.json +1 -1
- package/types/smap.d.cts +111 -6
- package/types/smap.d.mts +111 -6
package/dist/smap.cjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* Generated by js/build.mjs from js/src (see SOURCE.json). Do not edit. */
|
|
2
2
|
'use strict';
|
|
3
|
-
/*! sMap Web SDK 1.0.
|
|
3
|
+
/*! sMap Web SDK 1.0.5. Bundles maplibre-gl 6.16.0 (BSD-3-Clause), see vendor-licenses.txt. */
|
|
4
4
|
|
|
5
5
|
var smapCore = null;
|
|
6
6
|
function smapCreateEngine() {
|
|
7
|
+
var SMAP_BASE_URL = (typeof document !== 'undefined' && document.baseURI) || (typeof location !== 'undefined' && location.href) || 'about:blank';
|
|
7
8
|
var smapEngineExports = (() => {
|
|
8
9
|
var __defProp = Object.defineProperty;
|
|
9
10
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -4599,7 +4600,7 @@ Use an identity property function instead: \`{ "type": "identity", "property": $
|
|
|
4599
4600
|
}
|
|
4600
4601
|
}
|
|
4601
4602
|
function el() {
|
|
4602
|
-
let e66 =
|
|
4603
|
+
let e66 = SMAP_BASE_URL;
|
|
4603
4604
|
if (!/^https?:/.test(e66)) return ``;
|
|
4604
4605
|
let t2 = e66.endsWith(`-dev.mjs`) ? `maplibre-gl-worker-dev.mjs` : `maplibre-gl-worker.mjs`, n2 = new URL(`./${t2}`, e66);
|
|
4605
4606
|
return n2.searchParams.set(`v`, l), n2.href;
|
|
@@ -4622,7 +4623,7 @@ Use an identity property function instead: \`{ "type": "identity", "property": $
|
|
|
4622
4623
|
return URL.createObjectURL(r2);
|
|
4623
4624
|
}
|
|
4624
4625
|
function il(e66) {
|
|
4625
|
-
let t2 = new Blob([`import ${JSON.stringify(new URL(e66,
|
|
4626
|
+
let t2 = new Blob([`import ${JSON.stringify(new URL(e66, SMAP_BASE_URL).href)}`], { type: `text/javascript` });
|
|
4626
4627
|
return URL.createObjectURL(t2);
|
|
4627
4628
|
}
|
|
4628
4629
|
async function al() {
|
|
@@ -26865,14 +26866,14 @@ function smapCreateSdk(engine) {
|
|
|
26865
26866
|
*/
|
|
26866
26867
|
(function (root, factory) {
|
|
26867
26868
|
if (typeof module === 'object' && module.exports) {
|
|
26868
|
-
module.exports = factory(root.maplibregl
|
|
26869
|
+
module.exports = factory(root.maplibregl !== undefined ? root.maplibregl : require('maplibre-gl'));
|
|
26869
26870
|
} else {
|
|
26870
26871
|
root.sMap = factory(root.maplibregl);
|
|
26871
26872
|
}
|
|
26872
26873
|
})(typeof self !== 'undefined' ? self : this, function (engine) {
|
|
26873
26874
|
'use strict';
|
|
26874
26875
|
|
|
26875
|
-
var VERSION = '1.0.
|
|
26876
|
+
var VERSION = '1.0.5';
|
|
26876
26877
|
var FLAVORS = ['light', 'white', 'grayscale', 'dark', 'black'];
|
|
26877
26878
|
var BASEMAP_SOURCE = 'basemap';
|
|
26878
26879
|
var BUILDINGS_LAYER = 'smap-buildings-3d';
|
|
@@ -26888,10 +26889,16 @@ function smapCreateSdk(engine) {
|
|
|
26888
26889
|
} catch (e) { return 0; }
|
|
26889
26890
|
}
|
|
26890
26891
|
|
|
26892
|
+
var GEOMETRY_TYPES = ['Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeometryCollection'];
|
|
26893
|
+
|
|
26891
26894
|
function toFeatureCollection(data) {
|
|
26892
26895
|
if (!data) { return { type: 'FeatureCollection', features: [] }; }
|
|
26893
26896
|
if (data.type === 'FeatureCollection') { return data; }
|
|
26894
26897
|
if (data.type === 'Feature') { return { type: 'FeatureCollection', features: [data] }; }
|
|
26898
|
+
// A bare geometry (a LineString from a directions answer, a Polygon, …) is GeoJSON too: one feature without properties.
|
|
26899
|
+
if (GEOMETRY_TYPES.indexOf(data.type) !== -1) {
|
|
26900
|
+
return { type: 'FeatureCollection', features: [{ type: 'Feature', properties: {}, geometry: data }] };
|
|
26901
|
+
}
|
|
26895
26902
|
if (Array.isArray(data)) {
|
|
26896
26903
|
var features = new Array(data.length);
|
|
26897
26904
|
for (var i = 0; i < data.length; i++) {
|
|
@@ -26965,6 +26972,203 @@ function smapCreateSdk(engine) {
|
|
|
26965
26972
|
};
|
|
26966
26973
|
}
|
|
26967
26974
|
|
|
26975
|
+
// ------------------------------------------------------------------ popups
|
|
26976
|
+
|
|
26977
|
+
var POPUP_OPTIONS = ['className', 'maxWidth', 'minWidth', 'offset', 'anchor', 'closeButton', 'closeOnClick', 'focusAfterOpen', 'autoPan'];
|
|
26978
|
+
// Shown while a popup's content is on its way: three pulsing dots, no text.
|
|
26979
|
+
var POPUP_LOADING = '<div class="smap-popup-loading" role="status"><span></span><span></span><span></span></div>';
|
|
26980
|
+
var HTML_ESCAPES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
26981
|
+
|
|
26982
|
+
function escapeHtml(value) {
|
|
26983
|
+
return String(value).replace(/[&<>"']/g, function (c) { return HTML_ESCAPES[c]; });
|
|
26984
|
+
}
|
|
26985
|
+
|
|
26986
|
+
/**
|
|
26987
|
+
* A popup function from a template: every {name} is replaced by the feature's property of that name, HTML-escaped
|
|
26988
|
+
* (empty when the feature has none). The template itself is trusted HTML. Compiled once; nothing runs until a click.
|
|
26989
|
+
* popupTemplate('<b>{name}</b><br>{address}')
|
|
26990
|
+
*/
|
|
26991
|
+
function popupTemplate(template) {
|
|
26992
|
+
// Even entries are the template's own text, odd entries property names.
|
|
26993
|
+
var parts = String(template).split(/\{([^{}]+)\}/);
|
|
26994
|
+
for (var i = 1; i < parts.length; i += 2) { parts[i] = parts[i].trim(); }
|
|
26995
|
+
return function (properties) {
|
|
26996
|
+
var out = parts[0];
|
|
26997
|
+
for (var j = 1; j < parts.length; j += 2) {
|
|
26998
|
+
var v = properties ? properties[parts[j]] : undefined;
|
|
26999
|
+
out += (v === undefined || v === null ? '' : escapeHtml(v)) + parts[j + 1];
|
|
27000
|
+
}
|
|
27001
|
+
return out;
|
|
27002
|
+
};
|
|
27003
|
+
}
|
|
27004
|
+
|
|
27005
|
+
function isThenable(v) { return !!v && (typeof v === 'object' || typeof v === 'function') && typeof v.then === 'function'; }
|
|
27006
|
+
|
|
27007
|
+
function cssSize(v) { return typeof v === 'number' ? v + 'px' : v; }
|
|
27008
|
+
|
|
27009
|
+
// ------------------------------------------------------------------ drawn images (pins, numbers, badges, arrows)
|
|
27010
|
+
|
|
27011
|
+
// A style value: a constant, {property: 'name'} (the feature's property, `default` when it has none), or an engine
|
|
27012
|
+
// expression (an array).
|
|
27013
|
+
function valueExpr(v, fallback) {
|
|
27014
|
+
if (v === undefined || v === null) { return fallback; }
|
|
27015
|
+
if (Array.isArray(v)) { return v; }
|
|
27016
|
+
if (typeof v === 'object' && v.property) {
|
|
27017
|
+
return v['default'] !== undefined ? ['coalesce', ['get', v.property], v['default']] : ['get', v.property];
|
|
27018
|
+
}
|
|
27019
|
+
return v;
|
|
27020
|
+
}
|
|
27021
|
+
|
|
27022
|
+
// Image ids carry what the image shows; the map draws each one the first time a feature needs it (see _drawImage).
|
|
27023
|
+
var IMAGE_PREFIX = 'smap-img|';
|
|
27024
|
+
var SQRT1_2 = Math.SQRT1_2;
|
|
27025
|
+
|
|
27026
|
+
// The pin of Superroute's route map: a square with three rounded corners turned 45 degrees, its sharp corner the tip.
|
|
27027
|
+
// tipToHead: from the position up to the centre of the round part (where the number goes); badge: the badge's centre
|
|
27028
|
+
// from the position (right of the round part, as the page's badge sits on the turned pin's corner).
|
|
27029
|
+
function markerGeometry(shape, size) {
|
|
27030
|
+
var k = size / 32;
|
|
27031
|
+
if (shape === 'circle') {
|
|
27032
|
+
return { k: k, tipToHead: 0, badge: [size * 0.36, -size * 0.36] };
|
|
27033
|
+
}
|
|
27034
|
+
var half = size * SQRT1_2; // the centre of the round part, above the tip
|
|
27035
|
+
return { k: k, tipToHead: half, badge: [14.1 * k, -half] };
|
|
27036
|
+
}
|
|
27037
|
+
|
|
27038
|
+
// A rounded rectangle path whose corners are ellipses: radii [[rx, ry] top-left, top-right, bottom-right, bottom-left].
|
|
27039
|
+
function roundedPath(ctx, x, y, w, h, r) {
|
|
27040
|
+
ctx.beginPath();
|
|
27041
|
+
ctx.moveTo(x + r[0][0], y);
|
|
27042
|
+
ctx.lineTo(x + w - r[1][0], y);
|
|
27043
|
+
if (r[1][0] && r[1][1]) { ctx.ellipse(x + w - r[1][0], y + r[1][1], r[1][0], r[1][1], 0, -Math.PI / 2, 0); }
|
|
27044
|
+
ctx.lineTo(x + w, y + h - r[2][1]);
|
|
27045
|
+
if (r[2][0] && r[2][1]) { ctx.ellipse(x + w - r[2][0], y + h - r[2][1], r[2][0], r[2][1], 0, 0, Math.PI / 2); }
|
|
27046
|
+
ctx.lineTo(x + r[3][0], y + h);
|
|
27047
|
+
if (r[3][0] && r[3][1]) { ctx.ellipse(x + r[3][0], y + h - r[3][1], r[3][0], r[3][1], 0, Math.PI / 2, Math.PI); }
|
|
27048
|
+
ctx.lineTo(x, y + r[0][1]);
|
|
27049
|
+
if (r[0][0] && r[0][1]) { ctx.ellipse(x + r[0][0], y + r[0][1], r[0][0], r[0][1], 0, Math.PI, Math.PI * 1.5); }
|
|
27050
|
+
ctx.closePath();
|
|
27051
|
+
}
|
|
27052
|
+
|
|
27053
|
+
var PIN_PAD = 6; // room for the shadow around a pin, in pixels
|
|
27054
|
+
|
|
27055
|
+
// Each draw function gets a 2D context already scaled to the pixel ratio and returns nothing; sizes are in pixels.
|
|
27056
|
+
var IMAGE_KINDS = {
|
|
27057
|
+
// pin|shape|fill|border|borderWidth|size
|
|
27058
|
+
pin: function (args) {
|
|
27059
|
+
var shape = args[0], fill = args[1], border = args[2], bw = Number(args[3]), size = Number(args[4]);
|
|
27060
|
+
var g = markerGeometry(shape, size);
|
|
27061
|
+
var reach = shape === 'circle' ? size / 2 : g.tipToHead; // farthest the shape goes from its centre
|
|
27062
|
+
var w = Math.ceil(2 * reach + 2 * PIN_PAD), h = w;
|
|
27063
|
+
return {
|
|
27064
|
+
width: w, height: h,
|
|
27065
|
+
draw: function (ctx, ratio) {
|
|
27066
|
+
var c = w / 2;
|
|
27067
|
+
ctx.save();
|
|
27068
|
+
ctx.translate(c, c);
|
|
27069
|
+
var s = size / 2;
|
|
27070
|
+
var outer, inner;
|
|
27071
|
+
if (shape === 'circle') {
|
|
27072
|
+
outer = function () { ctx.beginPath(); ctx.arc(0, 0, s, 0, Math.PI * 2); };
|
|
27073
|
+
inner = function () { ctx.beginPath(); ctx.arc(0, 0, Math.max(0, s - bw), 0, Math.PI * 2); };
|
|
27074
|
+
ctx.shadowOffsetY = 3 * ratio;
|
|
27075
|
+
} else {
|
|
27076
|
+
ctx.rotate(Math.PI / 4);
|
|
27077
|
+
outer = function () { roundedPath(ctx, -s, -s, size, size, [[s, s], [s, size], [0, 0], [size, s]]); };
|
|
27078
|
+
var t = Math.max(0, s - bw), r2 = Math.max(0, size - bw);
|
|
27079
|
+
inner = function () { roundedPath(ctx, -s + bw, -s + bw, size - 2 * bw, size - 2 * bw, [[t, t], [t, r2], [0, 0], [r2, t]]); };
|
|
27080
|
+
// The page's box-shadow (0 3px) turns with the pin.
|
|
27081
|
+
ctx.shadowOffsetX = -3 * SQRT1_2 * ratio;
|
|
27082
|
+
ctx.shadowOffsetY = 3 * SQRT1_2 * ratio;
|
|
27083
|
+
}
|
|
27084
|
+
ctx.shadowColor = 'rgba(0,0,0,0.3)';
|
|
27085
|
+
ctx.shadowBlur = 8 * ratio;
|
|
27086
|
+
outer();
|
|
27087
|
+
ctx.fillStyle = border;
|
|
27088
|
+
ctx.fill();
|
|
27089
|
+
ctx.shadowColor = 'transparent';
|
|
27090
|
+
inner();
|
|
27091
|
+
ctx.fillStyle = fill;
|
|
27092
|
+
ctx.fill();
|
|
27093
|
+
ctx.restore();
|
|
27094
|
+
},
|
|
27095
|
+
};
|
|
27096
|
+
},
|
|
27097
|
+
// num|color|size|text (bold, with the route map's text shadow)
|
|
27098
|
+
num: function (args, measure) {
|
|
27099
|
+
var color = args[0], size = Number(args[1]), text = args.slice(2).join('|');
|
|
27100
|
+
var px = 14 * size / 32;
|
|
27101
|
+
var font = 'bold ' + px + 'px monospace';
|
|
27102
|
+
var w = Math.ceil(measure(font, text) + 6), h = Math.ceil(px * 1.4 + 4);
|
|
27103
|
+
return {
|
|
27104
|
+
width: w, height: h,
|
|
27105
|
+
draw: function (ctx, ratio) {
|
|
27106
|
+
ctx.font = font;
|
|
27107
|
+
ctx.textAlign = 'center';
|
|
27108
|
+
ctx.textBaseline = 'middle';
|
|
27109
|
+
ctx.shadowColor = 'rgba(0,0,0,0.5)';
|
|
27110
|
+
ctx.shadowOffsetX = ratio;
|
|
27111
|
+
ctx.shadowOffsetY = ratio;
|
|
27112
|
+
ctx.shadowBlur = 2 * ratio;
|
|
27113
|
+
ctx.fillStyle = color;
|
|
27114
|
+
ctx.fillText(text, w / 2, h / 2);
|
|
27115
|
+
},
|
|
27116
|
+
};
|
|
27117
|
+
},
|
|
27118
|
+
// badge|text (red circle, white border, at least 18 px inside the border)
|
|
27119
|
+
badge: function (args, measure) {
|
|
27120
|
+
var text = args.join('|');
|
|
27121
|
+
var font = 'bold 10px sans-serif';
|
|
27122
|
+
var inner = Math.max(18, Math.ceil(measure(font, text) + 4));
|
|
27123
|
+
var w = inner + 4 + 6, h = 18 + 4 + 6;
|
|
27124
|
+
return {
|
|
27125
|
+
width: w, height: h,
|
|
27126
|
+
draw: function (ctx, ratio) {
|
|
27127
|
+
var bw = inner + 4, bh = 22, x = 3, y = 2;
|
|
27128
|
+
var r = bh / 2;
|
|
27129
|
+
ctx.shadowColor = 'rgba(0,0,0,0.4)';
|
|
27130
|
+
ctx.shadowOffsetY = 2 * ratio;
|
|
27131
|
+
ctx.shadowBlur = 4 * ratio;
|
|
27132
|
+
roundedPath(ctx, x, y, bw, bh, [[r, r], [r, r], [r, r], [r, r]]);
|
|
27133
|
+
ctx.fillStyle = '#ffffff';
|
|
27134
|
+
ctx.fill();
|
|
27135
|
+
ctx.shadowColor = 'transparent';
|
|
27136
|
+
roundedPath(ctx, x + 2, y + 2, bw - 4, bh - 4, [[r - 2, r - 2], [r - 2, r - 2], [r - 2, r - 2], [r - 2, r - 2]]);
|
|
27137
|
+
ctx.fillStyle = '#e74c3c';
|
|
27138
|
+
ctx.fill();
|
|
27139
|
+
ctx.font = font;
|
|
27140
|
+
ctx.textAlign = 'center';
|
|
27141
|
+
ctx.textBaseline = 'middle';
|
|
27142
|
+
ctx.fillStyle = '#ffffff';
|
|
27143
|
+
ctx.fillText(text, x + bw / 2, y + bh / 2 + 0.5);
|
|
27144
|
+
},
|
|
27145
|
+
};
|
|
27146
|
+
},
|
|
27147
|
+
// arrow|size|color (an open arrowhead pointing along the line, like the route map's)
|
|
27148
|
+
arrow: function (args) {
|
|
27149
|
+
var size = Number(args[0]), color = args.slice(1).join('|');
|
|
27150
|
+
var w = Math.ceil(size + 4), h = w;
|
|
27151
|
+
return {
|
|
27152
|
+
width: w, height: h,
|
|
27153
|
+
draw: function (ctx) {
|
|
27154
|
+
var len = size, back = len * Math.cos(Math.PI / 6), half = len * Math.sin(Math.PI / 6);
|
|
27155
|
+
var tipX = w / 2 + back / 2, cy = h / 2;
|
|
27156
|
+
ctx.strokeStyle = color;
|
|
27157
|
+
ctx.lineWidth = 2;
|
|
27158
|
+
ctx.lineCap = 'round';
|
|
27159
|
+
ctx.lineJoin = 'round';
|
|
27160
|
+
ctx.beginPath();
|
|
27161
|
+
ctx.moveTo(tipX - back, cy - half);
|
|
27162
|
+
ctx.lineTo(tipX, cy);
|
|
27163
|
+
ctx.lineTo(tipX - back, cy + half);
|
|
27164
|
+
ctx.stroke();
|
|
27165
|
+
},
|
|
27166
|
+
};
|
|
27167
|
+
},
|
|
27168
|
+
};
|
|
27169
|
+
|
|
27170
|
+
function drawnImageId(kind, parts) { return ['concat', IMAGE_PREFIX + kind].concat(parts.reduce(function (a, p) { return a.concat(['|', p]); }, [])); }
|
|
27171
|
+
|
|
26968
27172
|
var NODE_TTL = 3600 * 1000;
|
|
26969
27173
|
|
|
26970
27174
|
function timed(url, ms) {
|
|
@@ -27045,6 +27249,9 @@ function smapCreateSdk(engine) {
|
|
|
27045
27249
|
* @param {boolean} [options.nearest=true] read from the nearest sMap node (asked of baseUrl, remembered for an hour)
|
|
27046
27250
|
* @param {string[]} [options.nodes] node URLs known in advance, used while baseUrl is down
|
|
27047
27251
|
* @param {'mercator'|'globe'} [options.projection]
|
|
27252
|
+
* @param {Object} [options.popup] default popup options of every layer and openPopup (className, maxWidth,
|
|
27253
|
+
* minWidth, offset, anchor, closeButton, closeOnClick, focusAfterOpen)
|
|
27254
|
+
* @param {string|Node} [options.popupLoading] shown while a popup's content is a pending promise (default: dots)
|
|
27048
27255
|
* Every other option (center, zoom, pitch, bearing, minZoom, maxZoom, maxBounds, hash, …) goes to the engine.
|
|
27049
27256
|
*/
|
|
27050
27257
|
function SMap(options) {
|
|
@@ -27064,6 +27271,12 @@ function smapCreateSdk(engine) {
|
|
|
27064
27271
|
this._buildings = false;
|
|
27065
27272
|
this._handlers = {};
|
|
27066
27273
|
this._refreshTimer = null;
|
|
27274
|
+
this._popupDefaults = options.popup || {};
|
|
27275
|
+
this._popupLoading = options.popupLoading;
|
|
27276
|
+
this._autoZoom = options.autoZoom !== false;
|
|
27277
|
+
this._wasd = options.keyboard3D;
|
|
27278
|
+
this._popup = null;
|
|
27279
|
+
this._popupSeq = 0;
|
|
27067
27280
|
// True between a style's load and the next style change. The engine's own isStyleLoaded() turns false while any
|
|
27068
27281
|
// source is loading data, which would leave layers added meanwhile waiting for a load that never comes.
|
|
27069
27282
|
this._styleReady = false;
|
|
@@ -27071,7 +27284,7 @@ function smapCreateSdk(engine) {
|
|
|
27071
27284
|
var self = this;
|
|
27072
27285
|
var engineOptions = {};
|
|
27073
27286
|
for (var k in options) {
|
|
27074
|
-
if (Object.prototype.hasOwnProperty.call(options, k) && ['token', 'getToken', 'apiKey', 'style', 'language', 'baseUrl', 'projection', 'nearest', 'nodes'].indexOf(k) < 0) {
|
|
27287
|
+
if (Object.prototype.hasOwnProperty.call(options, k) && ['token', 'getToken', 'apiKey', 'style', 'language', 'baseUrl', 'projection', 'nearest', 'nodes', 'popup', 'popupLoading', 'autoZoom', 'keyboard3D'].indexOf(k) < 0) {
|
|
27075
27288
|
engineOptions[k] = options[k];
|
|
27076
27289
|
}
|
|
27077
27290
|
}
|
|
@@ -27104,6 +27317,14 @@ function smapCreateSdk(engine) {
|
|
|
27104
27317
|
badge._place();
|
|
27105
27318
|
}
|
|
27106
27319
|
self.engine.on('style.load', function () { self._styleReady = true; self._restore(); });
|
|
27320
|
+
self._keyboard3D();
|
|
27321
|
+
// One click handler for every layer's popup: the engine's hit test over those layers, the top feature opens.
|
|
27322
|
+
self.engine.on('click', function (e) { self._popupClick(e); });
|
|
27323
|
+
self._styleClass();
|
|
27324
|
+
// Pins, numbers, badges and arrows are drawn the first time a feature needs one (and again after a style change).
|
|
27325
|
+
if (self.engine.setMissingStyleImageResolver) {
|
|
27326
|
+
self.engine.setMissingStyleImageResolver(function (id) { self._drawImage(id); });
|
|
27327
|
+
}
|
|
27107
27328
|
return new Promise(function (resolve) { self.engine.once('load', function () { resolve(self); }); });
|
|
27108
27329
|
});
|
|
27109
27330
|
if (this._token && this._getToken) { this._scheduleRefresh(); }
|
|
@@ -27139,10 +27360,242 @@ function smapCreateSdk(engine) {
|
|
|
27139
27360
|
if (FLAVORS.indexOf(style) >= 0) { this._flavor = style; this._customStyle = null; } else { this._customStyle = style; }
|
|
27140
27361
|
this._styleReady = false;
|
|
27141
27362
|
this.engine.setStyle(this._styleUrl());
|
|
27363
|
+
this._styleClass();
|
|
27364
|
+
return this;
|
|
27365
|
+
};
|
|
27366
|
+
|
|
27367
|
+
// The map container carries smap-style-<scheme>, so the stylesheet can follow the scheme (dark popups).
|
|
27368
|
+
SMap.prototype._styleClass = function () {
|
|
27369
|
+
var c = this.engine && this.engine.getContainer && this.engine.getContainer();
|
|
27370
|
+
if (!c || !c.classList) { return; }
|
|
27371
|
+
for (var i = 0; i < FLAVORS.length; i++) { c.classList.remove('smap-style-' + FLAVORS[i]); }
|
|
27372
|
+
if (!this._customStyle) { c.classList.add('smap-style-' + this._flavor); }
|
|
27373
|
+
};
|
|
27374
|
+
|
|
27375
|
+
// Draws an image the SDK named (IMAGE_PREFIX + kind|args) and adds it to the map; other names are left alone.
|
|
27376
|
+
SMap.prototype._drawImage = function (id) {
|
|
27377
|
+
if (typeof id !== 'string' || id.indexOf(IMAGE_PREFIX) !== 0) { return; }
|
|
27378
|
+
var m = this.engine;
|
|
27379
|
+
if (m.hasImage(id)) { return; }
|
|
27380
|
+
var parts = id.slice(IMAGE_PREFIX.length).split('|');
|
|
27381
|
+
var kind = IMAGE_KINDS[parts[0]];
|
|
27382
|
+
if (!kind) { return; }
|
|
27383
|
+
var doc = m.getContainer().ownerDocument;
|
|
27384
|
+
var win = doc.defaultView || {};
|
|
27385
|
+
var ratio = Math.min(3, Math.max(1, Math.ceil(win.devicePixelRatio || 1)));
|
|
27386
|
+
var canvas = doc.createElement('canvas');
|
|
27387
|
+
var ctx = canvas.getContext('2d');
|
|
27388
|
+
var measure = function (font, text) { ctx.font = font; return ctx.measureText(text).width; };
|
|
27389
|
+
var image = kind(parts.slice(1), measure);
|
|
27390
|
+
canvas.width = Math.ceil(image.width * ratio);
|
|
27391
|
+
canvas.height = Math.ceil(image.height * ratio);
|
|
27392
|
+
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
27393
|
+
image.draw(ctx, ratio);
|
|
27394
|
+
m.addImage(id, ctx.getImageData(0, 0, canvas.width, canvas.height), { pixelRatio: ratio });
|
|
27395
|
+
this._imagesDrawn = (this._imagesDrawn || 0) + 1;
|
|
27396
|
+
};
|
|
27397
|
+
|
|
27398
|
+
/**
|
|
27399
|
+
* Opens the map's popup at a position, closing the one open before (there is one popup per map).
|
|
27400
|
+
* @param {Array|Object} lngLat [lng, lat] or {lng, lat}
|
|
27401
|
+
* @param {string|Node|Promise} content trusted HTML (inserted as is, never sanitized), a DOM node, or a promise
|
|
27402
|
+
* of either (popupLoading shows meanwhile; a rejection closes the popup
|
|
27403
|
+
* and is reported as the map's 'error' event)
|
|
27404
|
+
* @param {Object} [options] popup options over the map's `popup` defaults, and `loading`
|
|
27405
|
+
*/
|
|
27406
|
+
SMap.prototype.openPopup = function (lngLat, content, options) {
|
|
27407
|
+
var self = this;
|
|
27408
|
+
if (!this.engine) { this.ready.then(function () { self.openPopup(lngLat, content, options); }); return this; }
|
|
27409
|
+
this.closePopup();
|
|
27410
|
+
if (!content) { return this; }
|
|
27411
|
+
var seq = ++this._popupSeq;
|
|
27412
|
+
var o = Object.assign({}, this._popupDefaults, options || {});
|
|
27413
|
+
var engineOptions = { offset: 12, className: 'smap-popup' + (o.className ? ' ' + o.className : '') };
|
|
27414
|
+
for (var i = 0; i < POPUP_OPTIONS.length; i++) {
|
|
27415
|
+
var k = POPUP_OPTIONS[i];
|
|
27416
|
+
if (k !== 'className' && k !== 'minWidth' && k !== 'autoPan' && o[k] !== undefined) { engineOptions[k] = k === 'maxWidth' ? cssSize(o[k]) : o[k]; }
|
|
27417
|
+
}
|
|
27418
|
+
var popup = new engine.Popup(engineOptions).setLngLat(lngLat);
|
|
27419
|
+
this._popup = popup;
|
|
27420
|
+
var pending = isThenable(content);
|
|
27421
|
+
var loading = o.loading !== undefined ? o.loading : (this._popupLoading !== undefined ? this._popupLoading : POPUP_LOADING);
|
|
27422
|
+
this._popupContent(popup, pending ? loading : content);
|
|
27423
|
+
popup.addTo(this.engine);
|
|
27424
|
+
var el = popup.getElement && popup.getElement();
|
|
27425
|
+
if (el && o.minWidth !== undefined) { el.style.minWidth = cssSize(o.minWidth); }
|
|
27426
|
+
if (o.autoPan !== false) {
|
|
27427
|
+
// After the engine has placed it (next frame), so its box is final.
|
|
27428
|
+
var raf = (typeof requestAnimationFrame === 'function') ? requestAnimationFrame : function (fn) { return setTimeout(fn, 16); };
|
|
27429
|
+
raf(function () { if (self._popup === popup) { self._panIntoView(popup); } });
|
|
27430
|
+
}
|
|
27431
|
+
if (pending) {
|
|
27432
|
+
content.then(function (answer) {
|
|
27433
|
+
// A later popup, or a close, makes this answer stale.
|
|
27434
|
+
if (seq !== self._popupSeq || self._popup !== popup) { return; }
|
|
27435
|
+
if (answer) { self._popupContent(popup, answer); } else { self.closePopup(); }
|
|
27436
|
+
}, function (error) {
|
|
27437
|
+
if (seq !== self._popupSeq || self._popup !== popup) { return; }
|
|
27438
|
+
self.closePopup();
|
|
27439
|
+
self._reportError(error);
|
|
27440
|
+
});
|
|
27441
|
+
}
|
|
27442
|
+
return this;
|
|
27443
|
+
};
|
|
27444
|
+
|
|
27445
|
+
/** Pans the map so the popup is fully inside it, with a small margin (Leaflet's autoPan). */
|
|
27446
|
+
SMap.prototype._panIntoView = function (popup) {
|
|
27447
|
+
var m = this.engine, el = popup.getElement && popup.getElement();
|
|
27448
|
+
if (!el || !m.getContainer || !m.panBy || !el.getBoundingClientRect) { return; }
|
|
27449
|
+
var box = el.getBoundingClientRect(), frame = m.getContainer().getBoundingClientRect(), pad = 10, dx = 0, dy = 0;
|
|
27450
|
+
if (box.top < frame.top + pad) { dy = box.top - frame.top - pad; } else if (box.bottom > frame.bottom - pad) { dy = box.bottom - frame.bottom + pad; }
|
|
27451
|
+
if (box.left < frame.left + pad) { dx = box.left - frame.left - pad; } else if (box.right > frame.right - pad) { dx = box.right - frame.right + pad; }
|
|
27452
|
+
if (dx || dy) { m.panBy([dx, dy], { duration: 250 }); }
|
|
27453
|
+
};
|
|
27454
|
+
|
|
27455
|
+
/** Closes the map's popup, if one is open. */
|
|
27456
|
+
SMap.prototype.closePopup = function () {
|
|
27457
|
+
var popup = this._popup;
|
|
27458
|
+
this._popup = null;
|
|
27459
|
+
this._popupSeq++;
|
|
27460
|
+
if (popup) { popup.remove(); }
|
|
27142
27461
|
return this;
|
|
27143
27462
|
};
|
|
27144
27463
|
|
|
27464
|
+
SMap.prototype._popupContent = function (popup, content) {
|
|
27465
|
+
if (typeof content === 'string') { popup.setHTML(content); } else { popup.setDOMContent(content); }
|
|
27466
|
+
};
|
|
27467
|
+
|
|
27468
|
+
SMap.prototype._reportError = function (error) {
|
|
27469
|
+
var m = this.engine;
|
|
27470
|
+
if (m && engine.ErrorEvent && m.fire) { m.fire(new engine.ErrorEvent(error)); } else if (typeof console !== 'undefined') { console.error(error); }
|
|
27471
|
+
};
|
|
27472
|
+
|
|
27473
|
+
// A click on the map: the top feature of the layers with a popup, if any, opens its layer's popup.
|
|
27474
|
+
SMap.prototype._popupClick = function (e) {
|
|
27475
|
+
var m = this.engine;
|
|
27476
|
+
var ids = [];
|
|
27477
|
+
var byEngineLayer = {};
|
|
27478
|
+
for (var i = 0; i < this._order.length; i++) {
|
|
27479
|
+
var def = this._layers[this._order[i]];
|
|
27480
|
+
if (def.o.popup && def.popupLayer && !def.hidden && m.getLayer(def.popupLayer)) {
|
|
27481
|
+
ids.push(def.popupLayer);
|
|
27482
|
+
byEngineLayer[def.popupLayer] = def;
|
|
27483
|
+
if (def.hoverLayer && m.getLayer(def.hoverLayer)) { ids.push(def.hoverLayer); byEngineLayer[def.hoverLayer] = def; }
|
|
27484
|
+
}
|
|
27485
|
+
}
|
|
27486
|
+
if (!ids.length) { return; }
|
|
27487
|
+
var hits = m.queryRenderedFeatures(e.point, { layers: ids });
|
|
27488
|
+
if (!hits || !hits.length) { return; }
|
|
27489
|
+
var f = hits[0];
|
|
27490
|
+
var hit = byEngineLayer[f.layer && f.layer.id];
|
|
27491
|
+
if (!hit) { return; }
|
|
27492
|
+
// A tight pile of pins: zoom in around the click so they come apart, or, when they still overlap close up (or
|
|
27493
|
+
// share one spot), offer a chooser. A layer turns this off with marker.overlap === false.
|
|
27494
|
+
if (this._autoZoom && hit.o.marker && hit.o.marker.overlap !== false && e.point && m.project) {
|
|
27495
|
+
var ov = hit.o.marker.overlap || {}, near = ov.radius || 12, threshold = ov.threshold || 4;
|
|
27496
|
+
var box = [[e.point.x - near, e.point.y - near], [e.point.x + near, e.point.y + near]];
|
|
27497
|
+
var seen = {}, pile = [];
|
|
27498
|
+
(m.queryRenderedFeatures(box, { layers: ids }) || []).forEach(function (x) {
|
|
27499
|
+
var d = byEngineLayer[x.layer && x.layer.id];
|
|
27500
|
+
if (d !== hit || !x.geometry || x.geometry.type !== 'Point') { return; }
|
|
27501
|
+
var key = JSON.stringify(x.geometry.coordinates) + JSON.stringify(x.properties || {});
|
|
27502
|
+
if (!seen[key]) { seen[key] = true; pile.push(x); }
|
|
27503
|
+
});
|
|
27504
|
+
if (pile.length >= 2) {
|
|
27505
|
+
var spots = {};
|
|
27506
|
+
pile.forEach(function (x) { spots[x.geometry.coordinates.map(function (c) { return c.toFixed(6); }).join(',')] = true; });
|
|
27507
|
+
var oneSpot = Object.keys(spots).length === 1;
|
|
27508
|
+
var closeUp = m.getZoom() >= (ov.maxZoom || 17);
|
|
27509
|
+
if (pile.length >= threshold && !oneSpot && !closeUp) {
|
|
27510
|
+
m.easeTo({ center: e.lngLat, zoom: Math.min(m.getZoom() + (ov.zoomBy || 2), ov.maxZoom || 17), duration: 400 });
|
|
27511
|
+
return;
|
|
27512
|
+
}
|
|
27513
|
+
if (pile.length >= threshold || oneSpot) { this._chooser(hit, pile, e.lngLat); return; }
|
|
27514
|
+
}
|
|
27515
|
+
}
|
|
27516
|
+
this._openFeaturePopup(hit, f, e.lngLat);
|
|
27517
|
+
};
|
|
27518
|
+
|
|
27519
|
+
/** A small list of the pins under a click; choosing one opens its popup. */
|
|
27520
|
+
SMap.prototype._chooser = function (hit, pile, lngLat) {
|
|
27521
|
+
var self = this, mk = hit.o.marker, doc = this.engine.getContainer().ownerDocument;
|
|
27522
|
+
var list = doc.createElement('div');
|
|
27523
|
+
list.className = 'smap-chooser';
|
|
27524
|
+
var esc = function (v) { return String(v == null ? '' : v).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); };
|
|
27525
|
+
var plainValue = function (v, p) {
|
|
27526
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && v.property) { return p[v.property] !== undefined ? p[v.property] : v.default; }
|
|
27527
|
+
return Array.isArray(v) ? undefined : v;
|
|
27528
|
+
};
|
|
27529
|
+
pile.slice(0, 50).forEach(function (x, i) {
|
|
27530
|
+
var p = x.properties || {};
|
|
27531
|
+
var label = mk.overlap && typeof mk.overlap.label === 'function' ? mk.overlap.label(p, x) : null;
|
|
27532
|
+
var number = plainValue(mk.text, p), fill = plainValue(mk.fill, p) || '#ffffff', border = plainValue(mk.border, p) || hit.o.color || '#4f46e5';
|
|
27533
|
+
var item = doc.createElement('button');
|
|
27534
|
+
item.type = 'button';
|
|
27535
|
+
item.className = 'smap-chooser-item';
|
|
27536
|
+
item.innerHTML = '<span class="smap-chooser-pin" style="background:' + esc(fill) + ';border-color:' + esc(border) + '">' + esc(number == null ? '' : number) + '</span>'
|
|
27537
|
+
+ '<span class="smap-chooser-label">' + esc(label != null ? label : (number != null ? number : i + 1)) + '</span>';
|
|
27538
|
+
item.addEventListener('click', function (ev) { ev.stopPropagation(); self._openFeaturePopup(hit, x, x.geometry.coordinates); });
|
|
27539
|
+
list.appendChild(item);
|
|
27540
|
+
});
|
|
27541
|
+
this._unpin();
|
|
27542
|
+
this.openPopup(lngLat, list, { className: 'smap-chooser-popup', anchor: 'bottom', offset: 12 });
|
|
27543
|
+
};
|
|
27544
|
+
|
|
27545
|
+
/** Opens a feature's own popup (the layer's popup function), above a pin when the layer draws pins. */
|
|
27546
|
+
SMap.prototype._openFeaturePopup = function (hit, f, lngLat) {
|
|
27547
|
+
var m = this.engine;
|
|
27548
|
+
// Points open at their own position; lines and areas where they were clicked.
|
|
27549
|
+
var at = hit.kind === 'points' && f.geometry && f.geometry.type === 'Point' ? f.geometry.coordinates : lngLat;
|
|
27550
|
+
var content;
|
|
27551
|
+
try { content = hit.o.popup(f.properties || {}, f); } catch (error) { this._reportError(error); return; }
|
|
27552
|
+
var popupOptions = Object.assign({}, hit.o.popupOptions || {});
|
|
27553
|
+
if (hit.o.marker && popupOptions.offset === undefined && this._popupDefaults.offset === undefined) {
|
|
27554
|
+
// Just above the pin as it is drawn now (its scale may follow the zoom).
|
|
27555
|
+
var mk = hit.o.marker, size = mk.size || 32, k = scaleAt(mk.scale, m.getZoom ? m.getZoom() : 0);
|
|
27556
|
+
popupOptions.offset = mk.shape === 'circle' ? Math.round((size / 2) * k + 4) : Math.round((markerGeometry('pin', size).tipToHead + size / 2) * k + 2);
|
|
27557
|
+
}
|
|
27558
|
+
// Above the pin, always (as on Superroute's route map); the map pans to make room instead of flipping it below.
|
|
27559
|
+
if (hit.o.marker && popupOptions.anchor === undefined && this._popupDefaults.anchor === undefined) { popupOptions.anchor = 'bottom'; }
|
|
27560
|
+
if (hit.o.popupLoading !== undefined) { popupOptions.loading = hit.o.popupLoading; }
|
|
27561
|
+
this._unpin();
|
|
27562
|
+
this.openPopup(at, content, popupOptions);
|
|
27563
|
+
// The clicked pin stays in front while its popup is open (the route map's rule).
|
|
27564
|
+
if (hit.hoverSource && this._popup) {
|
|
27565
|
+
var self = this, popup = this._popup;
|
|
27566
|
+
this._setHover(null, hit, f, 'popup');
|
|
27567
|
+
hit.pinned = popup;
|
|
27568
|
+
this._pinnedDef = hit;
|
|
27569
|
+
if (popup.on) { popup.on('close', function () { if (hit.pinned === popup) { self._unpin(); } }); }
|
|
27570
|
+
}
|
|
27571
|
+
};
|
|
27572
|
+
|
|
27145
27573
|
/** Switches the label language; your layers are kept. */
|
|
27574
|
+
/**
|
|
27575
|
+
* A marker scale at a zoom: a number, or the linear / step zoom expressions the SDK documents
|
|
27576
|
+
* (['interpolate', ['linear'], ['zoom'], z, v, …], ['step', ['zoom'], v, z, v, …]); anything else counts as 1.
|
|
27577
|
+
*/
|
|
27578
|
+
function scaleAt(scale, zoom) {
|
|
27579
|
+
if (scale === undefined || scale === null) { return 1; }
|
|
27580
|
+
if (typeof scale === 'number') { return scale; }
|
|
27581
|
+
if (!Array.isArray(scale)) { return 1; }
|
|
27582
|
+
var isZoom = function (x) { return Array.isArray(x) && x[0] === 'zoom'; };
|
|
27583
|
+
if (scale[0] === 'interpolate' && isZoom(scale[2])) {
|
|
27584
|
+
var stops = scale.slice(3);
|
|
27585
|
+
if (zoom <= stops[0]) { return stops[1]; }
|
|
27586
|
+
for (var i = 2; i < stops.length; i += 2) {
|
|
27587
|
+
if (zoom <= stops[i]) { var t = (zoom - stops[i - 2]) / (stops[i] - stops[i - 2]); return stops[i - 1] + t * (stops[i + 1] - stops[i - 1]); }
|
|
27588
|
+
}
|
|
27589
|
+
return stops[stops.length - 1];
|
|
27590
|
+
}
|
|
27591
|
+
if (scale[0] === 'step' && isZoom(scale[1])) {
|
|
27592
|
+
var value = scale[2];
|
|
27593
|
+
for (var j = 3; j < scale.length; j += 2) { if (zoom >= scale[j]) { value = scale[j + 1]; } }
|
|
27594
|
+
return value;
|
|
27595
|
+
}
|
|
27596
|
+
return 1;
|
|
27597
|
+
}
|
|
27598
|
+
|
|
27146
27599
|
SMap.prototype.setLanguage = function (language) {
|
|
27147
27600
|
this._language = language;
|
|
27148
27601
|
if (!this._customStyle) { this._styleReady = false; this.engine.setStyle(this._styleUrl()); }
|
|
@@ -27157,6 +27610,33 @@ function smapCreateSdk(engine) {
|
|
|
27157
27610
|
};
|
|
27158
27611
|
|
|
27159
27612
|
/** Shows buildings in 3D (and tilts the map when it is flat). */
|
|
27613
|
+
/**
|
|
27614
|
+
* Auto Zoom: a click on pins piled up zooms the map in around it, or lists them close up (see marker.overlap).
|
|
27615
|
+
* On by default (`autoZoom: false` in the options turns it off).
|
|
27616
|
+
*/
|
|
27617
|
+
SMap.prototype.setAutoZoom = function (on) { this._autoZoom = !!on; return this; };
|
|
27618
|
+
SMap.prototype.autoZoom = function () { return this._autoZoom; };
|
|
27619
|
+
|
|
27620
|
+
/**
|
|
27621
|
+
* In 3D (3D buildings on, or the map tilted) W / S tilt the view and A / D turn it; Shift takes bigger steps. Only
|
|
27622
|
+
* while the map has the keyboard focus, so typing elsewhere on the page is never taken.
|
|
27623
|
+
*/
|
|
27624
|
+
SMap.prototype._keyboard3D = function () {
|
|
27625
|
+
var self = this, m = this.engine;
|
|
27626
|
+
var el = m.getCanvasContainer ? m.getCanvasContainer() : (m.getContainer ? m.getContainer() : null);
|
|
27627
|
+
if (!el || !el.addEventListener || this._wasd === false) { return; }
|
|
27628
|
+
el.addEventListener('keydown', function (e) {
|
|
27629
|
+
if (e.altKey || e.ctrlKey || e.metaKey) { return; }
|
|
27630
|
+
if (!self._buildings && m.getPitch() < 1) { return; }
|
|
27631
|
+
var big = e.shiftKey, key = (e.key || '').toLowerCase();
|
|
27632
|
+
var pitch = m.getPitch(), bearing = m.getBearing();
|
|
27633
|
+
if (key === 'w') { pitch = Math.min(85, pitch + (big ? 15 : 5)); } else if (key === 's') { pitch = Math.max(0, pitch - (big ? 15 : 5)); }
|
|
27634
|
+
else if (key === 'a') { bearing -= big ? 45 : 15; } else if (key === 'd') { bearing += big ? 45 : 15; } else { return; }
|
|
27635
|
+
e.preventDefault();
|
|
27636
|
+
m.easeTo({ pitch: pitch, bearing: bearing, duration: 200 });
|
|
27637
|
+
});
|
|
27638
|
+
};
|
|
27639
|
+
|
|
27160
27640
|
SMap.prototype.setBuildings3D = function (on, options) {
|
|
27161
27641
|
this._buildings = !!on;
|
|
27162
27642
|
this._buildingOptions = options || this._buildingOptions || {};
|
|
@@ -27195,8 +27675,9 @@ function smapCreateSdk(engine) {
|
|
|
27195
27675
|
* @param {Object|Array} data GeoJSON, or an array of {lng, lat, …properties}
|
|
27196
27676
|
* @param {Object} [o]
|
|
27197
27677
|
* cluster: true | {radius, maxZoom}; color; radius; strokeColor; icon (image URL); iconSize; label (property name);
|
|
27198
|
-
* minzoom; maxzoom; zIndex (higher draws on top); onClick(feature, event);
|
|
27199
|
-
* (inserted as is, never sanitized: escape feature data
|
|
27678
|
+
* minzoom; maxzoom; zIndex (higher draws on top); onClick(feature, event);
|
|
27679
|
+
* popup(properties, feature) → trusted HTML (inserted as is, never sanitized: escape feature data, or use
|
|
27680
|
+
* sMap.popupTemplate), a DOM node, or a promise of either; popupOptions (over the map's `popup`); popupLoading
|
|
27200
27681
|
*/
|
|
27201
27682
|
SMap.prototype.addPoints = function (id, data, o) {
|
|
27202
27683
|
o = o || {};
|
|
@@ -27224,6 +27705,7 @@ function smapCreateSdk(engine) {
|
|
|
27224
27705
|
var def = this._layers[id];
|
|
27225
27706
|
if (!def) { throw new Error('sMap: no layer ' + id); }
|
|
27226
27707
|
def.data = toFeatureCollection(data);
|
|
27708
|
+
if (def.hovered) { this._setHover(id, def, null); }
|
|
27227
27709
|
var src = this.engine.getSource('smap-' + id);
|
|
27228
27710
|
if (src) { src.setData(def.data); }
|
|
27229
27711
|
return this;
|
|
@@ -27302,7 +27784,9 @@ function smapCreateSdk(engine) {
|
|
|
27302
27784
|
if (!m) { return; }
|
|
27303
27785
|
def.engineLayers.forEach(function (l) { if (m.getLayer(l)) { m.removeLayer(l); } });
|
|
27304
27786
|
if (m.getSource('smap-' + id)) { m.removeSource('smap-' + id); }
|
|
27305
|
-
(
|
|
27787
|
+
if (m.getSource('smap-' + id + '-hover')) { m.removeSource('smap-' + id + '-hover'); }
|
|
27788
|
+
def.hoverLayer = null; def.hovered = null;
|
|
27789
|
+
(this._handlers[id] || []).forEach(function (h) { if (h[1]) { m.off(h[0], h[1], h[2]); } else { m.off(h[0], h[2]); } });
|
|
27306
27790
|
this._handlers[id] = [];
|
|
27307
27791
|
def.engineLayers = [];
|
|
27308
27792
|
};
|
|
@@ -27335,7 +27819,10 @@ function smapCreateSdk(engine) {
|
|
|
27335
27819
|
layer.source = src;
|
|
27336
27820
|
// The engine refuses a filter key whose value is undefined (unclustered points have none).
|
|
27337
27821
|
if (layer.filter === undefined) { delete layer.filter; }
|
|
27338
|
-
|
|
27822
|
+
if (layer.minzoom === undefined) { delete layer.minzoom; }
|
|
27823
|
+
if (layer.paint === undefined) { delete layer.paint; }
|
|
27824
|
+
// A layer's own minzoom (a marker's textMinzoom / badgeMinzoom) never shows it before the layer's own range.
|
|
27825
|
+
for (var k in range) { layer[k] = k === 'minzoom' && layer.minzoom !== undefined ? Math.max(layer.minzoom, range.minzoom) : range[k]; }
|
|
27339
27826
|
layer.layout = Object.assign({ visibility: visibility }, layer.layout || {});
|
|
27340
27827
|
m.addLayer(layer, before);
|
|
27341
27828
|
def.engineLayers.push(layer.id);
|
|
@@ -27365,7 +27852,57 @@ function smapCreateSdk(engine) {
|
|
|
27365
27852
|
});
|
|
27366
27853
|
}
|
|
27367
27854
|
var only = o.cluster ? ['!', ['has', 'point_count']] : undefined;
|
|
27368
|
-
|
|
27855
|
+
var both = function (extra) { return only ? ['all', only, extra] : extra; };
|
|
27856
|
+
if (o.marker) {
|
|
27857
|
+
// Pins like Superroute's route map, drawn on the GPU: one image per colour pair (and per number, per badge),
|
|
27858
|
+
// made the first time a feature shows it. Numbers and badges are their own layers above the pins.
|
|
27859
|
+
var mk = o.marker;
|
|
27860
|
+
var shape = mk.shape === 'circle' ? 'circle' : 'pin';
|
|
27861
|
+
var size = mk.size || 32;
|
|
27862
|
+
var bw = mk.borderWidth !== undefined ? mk.borderWidth : 3;
|
|
27863
|
+
var g = markerGeometry(shape, size);
|
|
27864
|
+
var str = function (v, d) { return ['to-string', valueExpr(v, d)]; };
|
|
27865
|
+
// One scale for the pin, its number and its badge (a number, or an engine expression such as one of the zoom),
|
|
27866
|
+
// so the three stay together at every size: the offsets are in icon units and scale with them.
|
|
27867
|
+
var scale = mk.scale !== undefined ? mk.scale : 1;
|
|
27868
|
+
// Opacity of the pin, its number and its badge together (the route map draws settled stops at .72).
|
|
27869
|
+
var markerPaint = mk.opacity !== undefined ? { 'icon-opacity': valueExpr(mk.opacity, 1) } : undefined;
|
|
27870
|
+
main = src + '-points';
|
|
27871
|
+
var markerSpecs = [];
|
|
27872
|
+
var addMarker = function (layer) { markerSpecs.push(JSON.parse(JSON.stringify(layer))); add(layer); };
|
|
27873
|
+
addMarker({
|
|
27874
|
+
id: main, type: 'symbol', filter: only, paint: markerPaint,
|
|
27875
|
+
layout: {
|
|
27876
|
+
'icon-image': drawnImageId('pin', [shape, str(mk.fill, '#ffffff'), str(mk.border, color), String(bw), String(size)]),
|
|
27877
|
+
'icon-anchor': shape === 'circle' ? 'center' : 'bottom',
|
|
27878
|
+
// The image has room for the shadow below the tip: the tip, not the image's edge, is at the position.
|
|
27879
|
+
'icon-offset': [0, shape === 'circle' ? 0 : PIN_PAD], 'icon-size': scale,
|
|
27880
|
+
'icon-allow-overlap': true, 'icon-ignore-placement': true,
|
|
27881
|
+
},
|
|
27882
|
+
});
|
|
27883
|
+
if (mk.text !== undefined) {
|
|
27884
|
+
var text = str(mk.text, '');
|
|
27885
|
+
addMarker({
|
|
27886
|
+
id: src + '-numbers', type: 'symbol', filter: both(['!=', text, '']), minzoom: mk.textMinzoom, paint: markerPaint,
|
|
27887
|
+
layout: {
|
|
27888
|
+
'icon-image': drawnImageId('num', [str(mk.textColor, '#ffffff'), String(size), text]),
|
|
27889
|
+
'icon-offset': [0, -g.tipToHead], 'icon-size': scale, 'icon-allow-overlap': true, 'icon-ignore-placement': true,
|
|
27890
|
+
},
|
|
27891
|
+
});
|
|
27892
|
+
}
|
|
27893
|
+
if (mk.badge !== undefined) {
|
|
27894
|
+
var badge = ['to-number', valueExpr(mk.badge, 0), 0];
|
|
27895
|
+
addMarker({
|
|
27896
|
+
id: src + '-badges', type: 'symbol', filter: both(['>', badge, 1]), minzoom: mk.badgeMinzoom, paint: markerPaint,
|
|
27897
|
+
layout: {
|
|
27898
|
+
'icon-image': drawnImageId('badge', [['to-string', badge]]),
|
|
27899
|
+
'icon-offset': g.badge, 'icon-size': scale,
|
|
27900
|
+
'icon-allow-overlap': true, 'icon-ignore-placement': true,
|
|
27901
|
+
},
|
|
27902
|
+
});
|
|
27903
|
+
}
|
|
27904
|
+
if (mk.hoverRaise !== false) { this._hoverRaise(id, def, src, markerSpecs, main); }
|
|
27905
|
+
} else if (o.icon) {
|
|
27369
27906
|
var imageId = src + '-icon';
|
|
27370
27907
|
main = src + '-points';
|
|
27371
27908
|
var placeIcon = function () {
|
|
@@ -27399,9 +27936,35 @@ function smapCreateSdk(engine) {
|
|
|
27399
27936
|
}
|
|
27400
27937
|
} else if (def.kind === 'lines') {
|
|
27401
27938
|
main = src + '-lines';
|
|
27402
|
-
var
|
|
27939
|
+
var lineColor = valueExpr(o.color, '#4f46e5');
|
|
27940
|
+
var round = { 'line-cap': 'round', 'line-join': 'round' };
|
|
27941
|
+
// Bottom to top: casing, shadow, the line, its arrows (as on Superroute's route map).
|
|
27942
|
+
if (o.casing) {
|
|
27943
|
+
var cs = o.casing === true ? {} : o.casing;
|
|
27944
|
+
add({ id: src + '-casing', type: 'line', layout: round, paint: {
|
|
27945
|
+
'line-color': valueExpr(cs.color, '#ffffff'), 'line-width': cs.width || 9, 'line-opacity': cs.opacity !== undefined ? cs.opacity : 1 } });
|
|
27946
|
+
}
|
|
27947
|
+
if (o.shadow) {
|
|
27948
|
+
var sh = o.shadow === true ? {} : o.shadow;
|
|
27949
|
+
add({ id: src + '-shadow', type: 'line', layout: round, paint: {
|
|
27950
|
+
'line-color': valueExpr(sh.color, '#000000'), 'line-width': sh.width || 7, 'line-opacity': sh.opacity !== undefined ? sh.opacity : 0.3 } });
|
|
27951
|
+
}
|
|
27952
|
+
var paint = { 'line-color': lineColor, 'line-width': o.width || 3, 'line-opacity': o.opacity !== undefined ? o.opacity : 1 };
|
|
27403
27953
|
if (o.dash) { paint['line-dasharray'] = o.dash; }
|
|
27404
|
-
add({ id: main, type: 'line', layout:
|
|
27954
|
+
add({ id: main, type: 'line', layout: round, paint: paint });
|
|
27955
|
+
if (o.arrows) {
|
|
27956
|
+
var ar = o.arrows === true ? {} : o.arrows;
|
|
27957
|
+
var arrowSize = ar.size || 14;
|
|
27958
|
+
add({
|
|
27959
|
+
id: src + '-arrows', type: 'symbol',
|
|
27960
|
+
layout: {
|
|
27961
|
+
'symbol-placement': 'line', 'symbol-spacing': ar.spacing || 80,
|
|
27962
|
+
'icon-image': drawnImageId('arrow', [String(arrowSize), ['to-string', valueExpr(ar.color, lineColor)]]),
|
|
27963
|
+
'icon-rotation-alignment': 'map', 'icon-allow-overlap': true, 'icon-ignore-placement': true,
|
|
27964
|
+
},
|
|
27965
|
+
paint: { 'icon-opacity': ar.opacity !== undefined ? ar.opacity : 0.8 },
|
|
27966
|
+
});
|
|
27967
|
+
}
|
|
27405
27968
|
} else {
|
|
27406
27969
|
if (o.extrude) {
|
|
27407
27970
|
main = src + '-extrusion';
|
|
@@ -27421,32 +27984,74 @@ function smapCreateSdk(engine) {
|
|
|
27421
27984
|
add({ id: src + '-outline', type: 'line', paint: { 'line-color': o.outline || o.fill || color, 'line-width': o.outlineWidth || 1.5 } });
|
|
27422
27985
|
}
|
|
27423
27986
|
}
|
|
27987
|
+
// The popup is opened by the map's one click handler (_popupClick), from this layer's own hit test.
|
|
27988
|
+
def.popupLayer = o.popup ? main : null;
|
|
27424
27989
|
if (main && (o.popup || o.onClick)) {
|
|
27425
|
-
|
|
27426
|
-
|
|
27427
|
-
|
|
27428
|
-
if (o.popup) {
|
|
27429
|
-
// A string is inserted as HTML as it is (never sanitized): it must be trusted, with feature data escaped.
|
|
27430
|
-
// A DOM node is inserted as it is.
|
|
27431
|
-
var content = o.popup(f.properties, f);
|
|
27432
|
-
if (content) {
|
|
27433
|
-
var popup = new engine.Popup({ offset: 12 }).setLngLat(e.lngLat);
|
|
27434
|
-
if (typeof content === 'string') { popup.setHTML(content); } else { popup.setDOMContent(content); }
|
|
27435
|
-
popup.addTo(m);
|
|
27436
|
-
}
|
|
27437
|
-
}
|
|
27438
|
-
});
|
|
27990
|
+
if (o.onClick) {
|
|
27991
|
+
this._listen(id, 'click', main, function (e) { o.onClick(e.features[0], e); });
|
|
27992
|
+
}
|
|
27439
27993
|
this._listen(id, 'mouseenter', main, function () { m.getCanvas().style.cursor = 'pointer'; });
|
|
27440
27994
|
this._listen(id, 'mouseleave', main, function () { m.getCanvas().style.cursor = ''; });
|
|
27441
27995
|
}
|
|
27442
27996
|
};
|
|
27443
27997
|
|
|
27998
|
+
/**
|
|
27999
|
+
* Overlapping pins: the one under the pointer comes to the front, as on Superroute's route map, so it can be read
|
|
28000
|
+
* and clicked. A copy of that one feature is drawn in its own source above everything else; nothing is done per
|
|
28001
|
+
* feature, only for the feature under the pointer.
|
|
28002
|
+
*/
|
|
28003
|
+
SMap.prototype._hoverRaise = function (id, def, src, specs, main) {
|
|
28004
|
+
var m = this.engine, self = this;
|
|
28005
|
+
var hoverSrc = src + '-hover';
|
|
28006
|
+
m.addSource(hoverSrc, { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
|
|
28007
|
+
var top = null;
|
|
28008
|
+
specs.forEach(function (spec) {
|
|
28009
|
+
var layer = JSON.parse(JSON.stringify(spec));
|
|
28010
|
+
layer.id = spec.id.replace(src + '-', src + '-hover-');
|
|
28011
|
+
layer.source = hoverSrc;
|
|
28012
|
+
if (layer.filter === undefined) { delete layer.filter; }
|
|
28013
|
+
if (layer.paint === undefined) { delete layer.paint; }
|
|
28014
|
+
if (layer.minzoom === undefined) { delete layer.minzoom; }
|
|
28015
|
+
m.addLayer(layer);
|
|
28016
|
+
def.engineLayers.push(layer.id);
|
|
28017
|
+
if (!top) { top = layer.id; }
|
|
28018
|
+
});
|
|
28019
|
+
def.hoverLayer = top;
|
|
28020
|
+
def.hoverSource = hoverSrc;
|
|
28021
|
+
var keyOf = function (f) { return JSON.stringify(f.geometry && f.geometry.coordinates) + JSON.stringify(f.properties || {}); };
|
|
28022
|
+
this._listen(id, 'mousemove', null, function (e) {
|
|
28023
|
+
if (def.hidden || !m.getLayer(main) || def.pinned) { return; }
|
|
28024
|
+
// Still over the raised pin: keep it. Otherwise raise the top pin under the pointer, if any.
|
|
28025
|
+
if (def.hovered && m.getLayer(top) && m.queryRenderedFeatures(e.point, { layers: [top] }).length) { return; }
|
|
28026
|
+
var hits = m.queryRenderedFeatures(e.point, { layers: [main] });
|
|
28027
|
+
var f = hits && hits[0];
|
|
28028
|
+
if (!f) { if (def.hovered) { self._setHover(id, def, null); } return; }
|
|
28029
|
+
if (def.hovered && def.hovered.key === keyOf(f)) { return; }
|
|
28030
|
+
self._setHover(id, def, f, keyOf(f));
|
|
28031
|
+
});
|
|
28032
|
+
this._listen(id, 'mouseout', null, function () { if (def.hovered && !def.pinned) { self._setHover(id, def, null); } });
|
|
28033
|
+
};
|
|
28034
|
+
|
|
28035
|
+
SMap.prototype._unpin = function () {
|
|
28036
|
+
var def = this._pinnedDef;
|
|
28037
|
+
this._pinnedDef = null;
|
|
28038
|
+
if (def) { def.pinned = null; this._setHover(null, def, null); }
|
|
28039
|
+
};
|
|
28040
|
+
|
|
28041
|
+
SMap.prototype._setHover = function (id, def, f, key) {
|
|
28042
|
+
var source = def.hoverSource && this.engine.getSource(def.hoverSource);
|
|
28043
|
+
def.hovered = f ? { key: key } : null;
|
|
28044
|
+
if (!source) { return; }
|
|
28045
|
+
// A plain copy: the engine's own feature objects cannot be sent to its worker.
|
|
28046
|
+
source.setData({ type: 'FeatureCollection', features: f ? [JSON.parse(JSON.stringify({ type: 'Feature', properties: f.properties || {}, geometry: f.geometry }))] : [] });
|
|
28047
|
+
};
|
|
28048
|
+
|
|
27444
28049
|
SMap.prototype._listen = function (id, type, layer, fn) {
|
|
27445
|
-
this.engine.on(type, layer, fn);
|
|
28050
|
+
if (layer) { this.engine.on(type, layer, fn); } else { this.engine.on(type, fn); }
|
|
27446
28051
|
(this._handlers[id] = this._handlers[id] || []).push([type, layer, fn]);
|
|
27447
28052
|
};
|
|
27448
28053
|
|
|
27449
|
-
return { Map: SMap, version: VERSION, styles: FLAVORS.slice(), engine: engine };
|
|
28054
|
+
return { Map: SMap, version: VERSION, styles: FLAVORS.slice(), engine: engine, popupTemplate: popupTemplate };
|
|
27450
28055
|
});
|
|
27451
28056
|
|
|
27452
28057
|
return module.exports;
|
|
@@ -27466,13 +28071,20 @@ function SMapMap(options) {
|
|
|
27466
28071
|
SMapMap.prototype = core.Map.prototype;
|
|
27467
28072
|
return new core.Map(options);
|
|
27468
28073
|
}
|
|
27469
|
-
var smapVersion = "1.0.
|
|
28074
|
+
var smapVersion = "1.0.5";
|
|
27470
28075
|
var smapStyles = ['light', 'white', 'grayscale', 'dark', 'black'];
|
|
27471
28076
|
/** The bundled rendering engine, for anything the SDK does not wrap (loaded on the first call). */
|
|
27472
28077
|
function smapGetEngine() { return smapLoad().engine; }
|
|
27473
|
-
|
|
28078
|
+
// Templates need no engine: before the first map they come from an SDK made without one.
|
|
28079
|
+
var smapNoEngine = null;
|
|
28080
|
+
function smapPopupTemplate(template) {
|
|
28081
|
+
if (!smapCore && !smapNoEngine) { smapNoEngine = smapCreateSdk(null); }
|
|
28082
|
+
return (smapCore || smapNoEngine).popupTemplate(template);
|
|
28083
|
+
}
|
|
28084
|
+
var smapDefault = { Map: SMapMap, version: smapVersion, styles: smapStyles, getEngine: smapGetEngine, popupTemplate: smapPopupTemplate };
|
|
27474
28085
|
exports.Map = SMapMap;
|
|
27475
28086
|
exports.version = smapVersion;
|
|
27476
28087
|
exports.styles = smapStyles;
|
|
27477
28088
|
exports.getEngine = smapGetEngine;
|
|
28089
|
+
exports.popupTemplate = smapPopupTemplate;
|
|
27478
28090
|
exports.default = smapDefault;
|