@superrouteca/smap 1.1.6 → 1.2.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 +59 -0
- package/README.md +128 -0
- 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 +3507 -65
- package/dist/smap.css +42 -0
- package/dist/smap.js +3499 -62
- package/dist/smap.mjs +3506 -66
- package/package.json +1 -1
- package/types/smap.d.cts +315 -7
- package/types/smap.d.mts +315 -7
package/dist/smap.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Generated by js/build.mjs from js/src (see SOURCE.json). Do not edit. */
|
|
2
|
-
/*! sMap Web SDK 1.
|
|
2
|
+
/*! sMap Web SDK 1.2.0. Bundles maplibre-gl 6.16.0 (BSD-3-Clause), see vendor-licenses.txt. */
|
|
3
3
|
|
|
4
4
|
var smapCore = null;
|
|
5
5
|
function smapCreateEngine() {
|
|
@@ -28282,6 +28282,131 @@ function smapCreateInterop() {
|
|
|
28282
28282
|
return json;
|
|
28283
28283
|
}
|
|
28284
28284
|
|
|
28285
|
+
// ------------------------------------------------------------------ geometry helpers (G14)
|
|
28286
|
+
|
|
28287
|
+
var EARTH_RADIUS = 6371008.8;
|
|
28288
|
+
function rad(d) { return d * Math.PI / 180; }
|
|
28289
|
+
function lngLatOf(v) {
|
|
28290
|
+
if (Array.isArray(v)) { return [+v[0], +v[1]]; }
|
|
28291
|
+
if (v && typeof v === 'object') {
|
|
28292
|
+
if (v.type === 'Feature') { return lngLatOf(v.geometry); }
|
|
28293
|
+
if (v.type === 'Point') { return lngLatOf(v.coordinates); }
|
|
28294
|
+
return [+(v.lng !== undefined ? v.lng : v.lon), +v.lat];
|
|
28295
|
+
}
|
|
28296
|
+
throw new Error('sMap: not a position');
|
|
28297
|
+
}
|
|
28298
|
+
function geometryOf(v) { return v && v.type === 'Feature' ? v.geometry : v; }
|
|
28299
|
+
|
|
28300
|
+
/** Great-circle distance in metres between two positions ([lng, lat], {lng, lat} or a Point). */
|
|
28301
|
+
function distance(a, b) {
|
|
28302
|
+
var p = lngLatOf(a), q = lngLatOf(b);
|
|
28303
|
+
var dLat = rad(q[1] - p[1]), dLng = rad(q[0] - p[0]);
|
|
28304
|
+
var h = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(rad(p[1])) * Math.cos(rad(q[1])) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
|
28305
|
+
return 2 * EARTH_RADIUS * Math.asin(Math.min(1, Math.sqrt(h)));
|
|
28306
|
+
}
|
|
28307
|
+
|
|
28308
|
+
function lineCoords(line) {
|
|
28309
|
+
var g = geometryOf(line);
|
|
28310
|
+
if (Array.isArray(g)) { return g.map(lngLatOf); }
|
|
28311
|
+
if (g && g.type === 'LineString') { return g.coordinates; }
|
|
28312
|
+
throw new Error('sMap: not a line');
|
|
28313
|
+
}
|
|
28314
|
+
|
|
28315
|
+
/** Length in metres of a line ([[lng, lat], …], a LineString or its Feature). */
|
|
28316
|
+
function length(line) {
|
|
28317
|
+
var c = lineCoords(line), total = 0;
|
|
28318
|
+
for (var i = 1; i < c.length; i++) { total += distance(c[i - 1], c[i]); }
|
|
28319
|
+
return total;
|
|
28320
|
+
}
|
|
28321
|
+
|
|
28322
|
+
/** [[west, south], [east, north]] of any GeoJSON (or null when it holds no position). */
|
|
28323
|
+
function bounds(geojson) {
|
|
28324
|
+
var b = null;
|
|
28325
|
+
(function walk(v) {
|
|
28326
|
+
if (!v) { return; }
|
|
28327
|
+
if (Array.isArray(v)) {
|
|
28328
|
+
if (typeof v[0] === 'number') {
|
|
28329
|
+
if (!b) { b = [[v[0], v[1]], [v[0], v[1]]]; return; }
|
|
28330
|
+
b[0][0] = Math.min(b[0][0], v[0]); b[0][1] = Math.min(b[0][1], v[1]);
|
|
28331
|
+
b[1][0] = Math.max(b[1][0], v[0]); b[1][1] = Math.max(b[1][1], v[1]);
|
|
28332
|
+
return;
|
|
28333
|
+
}
|
|
28334
|
+
v.forEach(walk);
|
|
28335
|
+
return;
|
|
28336
|
+
}
|
|
28337
|
+
if (v.type === 'FeatureCollection') { (v.features || []).forEach(walk); return; }
|
|
28338
|
+
if (v.type === 'Feature') { walk(v.geometry); return; }
|
|
28339
|
+
if (v.type === 'GeometryCollection') { (v.geometries || []).forEach(walk); return; }
|
|
28340
|
+
walk(v.coordinates);
|
|
28341
|
+
})(geojson);
|
|
28342
|
+
return b;
|
|
28343
|
+
}
|
|
28344
|
+
|
|
28345
|
+
function inRing(p, ring) {
|
|
28346
|
+
var inside = false;
|
|
28347
|
+
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
28348
|
+
var xi = ring[i][0], yi = ring[i][1], xj = ring[j][0], yj = ring[j][1];
|
|
28349
|
+
if ((yi > p[1]) !== (yj > p[1]) && p[0] < (xj - xi) * (p[1] - yi) / (yj - yi) + xi) { inside = !inside; }
|
|
28350
|
+
}
|
|
28351
|
+
return inside;
|
|
28352
|
+
}
|
|
28353
|
+
|
|
28354
|
+
/** Whether a position is inside a Polygon or MultiPolygon (holes excluded), a Feature of one, or a ring of positions. */
|
|
28355
|
+
function pointInPolygon(point, polygon) {
|
|
28356
|
+
var p = lngLatOf(point), g = geometryOf(polygon);
|
|
28357
|
+
var polys = Array.isArray(g) ? [[g.map(lngLatOf)]] : (g && g.type === 'Polygon' ? [g.coordinates] : (g && g.type === 'MultiPolygon' ? g.coordinates : null));
|
|
28358
|
+
if (!polys) { throw new Error('sMap: not a polygon'); }
|
|
28359
|
+
return polys.some(function (rings) {
|
|
28360
|
+
if (!rings.length || !inRing(p, rings[0])) { return false; }
|
|
28361
|
+
for (var k = 1; k < rings.length; k++) { if (inRing(p, rings[k])) { return false; } }
|
|
28362
|
+
return true;
|
|
28363
|
+
});
|
|
28364
|
+
}
|
|
28365
|
+
|
|
28366
|
+
/**
|
|
28367
|
+
* The point of a line nearest to a position: { point: [lng, lat], distance (metres from the position), index (of the
|
|
28368
|
+
* segment's first vertex), along (metres from the line's start) }. Measured in a local flat projection around the
|
|
28369
|
+
* position, which is exact to well under a metre for the distances a map shows.
|
|
28370
|
+
*/
|
|
28371
|
+
function nearestPointOnLine(line, point) {
|
|
28372
|
+
var c = lineCoords(line), p = lngLatOf(point);
|
|
28373
|
+
if (!c.length) { return null; }
|
|
28374
|
+
var kx = Math.cos(rad(p[1])) * rad(1) * EARTH_RADIUS, ky = rad(1) * EARTH_RADIUS;
|
|
28375
|
+
var best = null, walked = 0;
|
|
28376
|
+
for (var i = 0; i < Math.max(1, c.length - 1); i++) {
|
|
28377
|
+
var a = c[i], b = c[Math.min(i + 1, c.length - 1)];
|
|
28378
|
+
var ax = (a[0] - p[0]) * kx, ay = (a[1] - p[1]) * ky, bx = (b[0] - p[0]) * kx, by = (b[1] - p[1]) * ky;
|
|
28379
|
+
var dx = bx - ax, dy = by - ay, len2 = dx * dx + dy * dy;
|
|
28380
|
+
var t = len2 ? Math.max(0, Math.min(1, -(ax * dx + ay * dy) / len2)) : 0;
|
|
28381
|
+
var x = ax + t * dx, y = ay + t * dy, d = Math.sqrt(x * x + y * y);
|
|
28382
|
+
var seg = distance(a, b);
|
|
28383
|
+
if (!best || d < best.distance) {
|
|
28384
|
+
best = { point: [p[0] + x / kx, p[1] + y / ky], distance: d, index: i, along: walked + seg * t };
|
|
28385
|
+
}
|
|
28386
|
+
walked += seg;
|
|
28387
|
+
}
|
|
28388
|
+
return best;
|
|
28389
|
+
}
|
|
28390
|
+
|
|
28391
|
+
/** Metres from a position to the nearest point of a line. */
|
|
28392
|
+
function distanceToLine(line, point) { var n = nearestPointOnLine(line, point); return n ? n.distance : Infinity; }
|
|
28393
|
+
|
|
28394
|
+
var geo = {
|
|
28395
|
+
distance: distance, length: length, bounds: bounds, pointInPolygon: pointInPolygon,
|
|
28396
|
+
nearestPointOnLine: nearestPointOnLine, distanceToLine: distanceToLine,
|
|
28397
|
+
decodePolyline: function (str, precision) { return decodePolyline(str, precision); },
|
|
28398
|
+
encodePolyline: function (coords, precision) { return encodePolyline(coords, precision); },
|
|
28399
|
+
};
|
|
28400
|
+
|
|
28401
|
+
// three.js for 3D models (G12), handed over once by the page (never bundled, like deck.gl).
|
|
28402
|
+
var three = null;
|
|
28403
|
+
function useThree(THREE, GLTFLoader) {
|
|
28404
|
+
if (!THREE || !THREE.Scene || typeof GLTFLoader !== 'function') { throw new Error('sMap: useThree(THREE, GLTFLoader) takes three.js and its GLTFLoader'); }
|
|
28405
|
+
three = { THREE: THREE, GLTFLoader: GLTFLoader };
|
|
28406
|
+
return three;
|
|
28407
|
+
}
|
|
28408
|
+
function getThree() { return three; }
|
|
28409
|
+
|
|
28285
28410
|
return {
|
|
28286
28411
|
DEFAULT_BASE: DEFAULT_BASE,
|
|
28287
28412
|
SLOTS: SLOTS,
|
|
@@ -28304,6 +28429,9 @@ function smapCreateInterop() {
|
|
|
28304
28429
|
encodePolyline: encodePolyline,
|
|
28305
28430
|
},
|
|
28306
28431
|
convert: convert,
|
|
28432
|
+
geo: geo,
|
|
28433
|
+
useThree: useThree,
|
|
28434
|
+
getThree: getThree,
|
|
28307
28435
|
styleUrl: styleUrl,
|
|
28308
28436
|
rasterUrl: rasterUrl,
|
|
28309
28437
|
rasterTileJsonUrl: rasterTileJsonUrl,
|
|
@@ -28327,6 +28455,1342 @@ function smapCreateInterop() {
|
|
|
28327
28455
|
|
|
28328
28456
|
return module.exports;
|
|
28329
28457
|
}
|
|
28458
|
+
function smapCreateCanvas() {
|
|
28459
|
+
var module = { exports: {} };
|
|
28460
|
+
var exports = module.exports;
|
|
28461
|
+
var define;
|
|
28462
|
+
var self = {};
|
|
28463
|
+
// sMap canvas renderer (M61): the map drawn on a 2D canvas for browsers without WebGL 2. It answers the part of the
|
|
28464
|
+
// rendering engine's map interface the Web SDK uses — camera, events, sources, layers of the kinds the SDK makes
|
|
28465
|
+
// (raster, fill, line, circle, symbol, heatmap), hit tests, controls and popups — so the SDK's calls work unchanged.
|
|
28466
|
+
// The street map comes as raster tiles. What needs WebGL (3D buildings, terrain, tilting, turning, custom 3D layers,
|
|
28467
|
+
// hillshade) is left out and named in an 'smap.unsupported' event.
|
|
28468
|
+
(function (root, factory) {
|
|
28469
|
+
if (typeof module === 'object' && module.exports) { module.exports = factory(); } else { root.smapCanvas = factory(); }
|
|
28470
|
+
})(typeof self !== 'undefined' ? self : this, function () {
|
|
28471
|
+
'use strict';
|
|
28472
|
+
|
|
28473
|
+
var TILE = 512; // world size per zoom level, as the engine counts zoom (512 px tiles)
|
|
28474
|
+
var MAX_LAT = 85.051129;
|
|
28475
|
+
var raf = typeof requestAnimationFrame === 'function' ? requestAnimationFrame.bind(typeof window !== 'undefined' ? window : this) : function (fn) { return setTimeout(function () { fn(Date.now()); }, 16); };
|
|
28476
|
+
|
|
28477
|
+
// ------------------------------------------------------------------ geometry
|
|
28478
|
+
|
|
28479
|
+
function clampLat(lat) { return Math.max(-MAX_LAT, Math.min(MAX_LAT, lat)); }
|
|
28480
|
+
function lngX(lng, scale) { return (lng + 180) / 360 * scale; }
|
|
28481
|
+
function latY(lat, scale) { var s = Math.sin(clampLat(lat) * Math.PI / 180); return (0.5 - Math.log((1 + s) / (1 - s)) / (4 * Math.PI)) * scale; }
|
|
28482
|
+
function xLng(x, scale) { return x / scale * 360 - 180; }
|
|
28483
|
+
function yLat(y, scale) { var n = Math.PI - 2 * Math.PI * y / scale; return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); }
|
|
28484
|
+
function toLngLat(v) {
|
|
28485
|
+
if (Array.isArray(v)) { return { lng: +v[0], lat: +v[1] }; }
|
|
28486
|
+
if (v && v.lng !== undefined) { return { lng: +v.lng, lat: +v.lat }; }
|
|
28487
|
+
if (v && v.lon !== undefined) { return { lng: +v.lon, lat: +v.lat }; }
|
|
28488
|
+
return { lng: 0, lat: 0 };
|
|
28489
|
+
}
|
|
28490
|
+
|
|
28491
|
+
function LngLat(lng, lat) { this.lng = lng; this.lat = lat; }
|
|
28492
|
+
LngLat.prototype.toArray = function () { return [this.lng, this.lat]; };
|
|
28493
|
+
|
|
28494
|
+
function LngLatBounds(sw, ne) {
|
|
28495
|
+
this._sw = null; this._ne = null;
|
|
28496
|
+
if (sw) { this.extend(sw); }
|
|
28497
|
+
if (ne) { this.extend(ne); }
|
|
28498
|
+
}
|
|
28499
|
+
LngLatBounds.prototype.extend = function (v) {
|
|
28500
|
+
if (v instanceof LngLatBounds) { if (v._sw) { this.extend(v._sw); this.extend(v._ne); } return this; }
|
|
28501
|
+
if (Array.isArray(v) && Array.isArray(v[0])) { this.extend(v[0]); this.extend(v[1]); return this; }
|
|
28502
|
+
var p = toLngLat(v);
|
|
28503
|
+
if (!this._sw) { this._sw = new LngLat(p.lng, p.lat); this._ne = new LngLat(p.lng, p.lat); return this; }
|
|
28504
|
+
this._sw.lng = Math.min(this._sw.lng, p.lng); this._sw.lat = Math.min(this._sw.lat, p.lat);
|
|
28505
|
+
this._ne.lng = Math.max(this._ne.lng, p.lng); this._ne.lat = Math.max(this._ne.lat, p.lat);
|
|
28506
|
+
return this;
|
|
28507
|
+
};
|
|
28508
|
+
LngLatBounds.prototype.getWest = function () { return this._sw.lng; };
|
|
28509
|
+
LngLatBounds.prototype.getSouth = function () { return this._sw.lat; };
|
|
28510
|
+
LngLatBounds.prototype.getEast = function () { return this._ne.lng; };
|
|
28511
|
+
LngLatBounds.prototype.getNorth = function () { return this._ne.lat; };
|
|
28512
|
+
LngLatBounds.prototype.getSouthWest = function () { return this._sw; };
|
|
28513
|
+
LngLatBounds.prototype.getNorthEast = function () { return this._ne; };
|
|
28514
|
+
LngLatBounds.prototype.getCenter = function () { return new LngLat((this._sw.lng + this._ne.lng) / 2, (this._sw.lat + this._ne.lat) / 2); };
|
|
28515
|
+
LngLatBounds.prototype.toArray = function () { return [[this._sw.lng, this._sw.lat], [this._ne.lng, this._ne.lat]]; };
|
|
28516
|
+
LngLatBounds.prototype.isEmpty = function () { return !this._sw; };
|
|
28517
|
+
|
|
28518
|
+
function segDist(px, py, ax, ay, bx, by) {
|
|
28519
|
+
var dx = bx - ax, dy = by - ay, l2 = dx * dx + dy * dy;
|
|
28520
|
+
var t = l2 ? Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / l2)) : 0;
|
|
28521
|
+
var x = ax + t * dx - px, y = ay + t * dy - py;
|
|
28522
|
+
return Math.sqrt(x * x + y * y);
|
|
28523
|
+
}
|
|
28524
|
+
function inRing(x, y, ring) {
|
|
28525
|
+
var inside = false;
|
|
28526
|
+
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
28527
|
+
var xi = ring[i][0], yi = ring[i][1], xj = ring[j][0], yj = ring[j][1];
|
|
28528
|
+
if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) { inside = !inside; }
|
|
28529
|
+
}
|
|
28530
|
+
return inside;
|
|
28531
|
+
}
|
|
28532
|
+
|
|
28533
|
+
// ------------------------------------------------------------------ expressions (the Style Specification subset the SDK writes)
|
|
28534
|
+
|
|
28535
|
+
function typeName(g) { if (!g) { return null; } var t = g.type; return t === 'MultiPoint' ? 'Point' : (t === 'MultiLineString' ? 'LineString' : (t === 'MultiPolygon' ? 'Polygon' : t)); }
|
|
28536
|
+
|
|
28537
|
+
function parseColor(c) { return c; }
|
|
28538
|
+
|
|
28539
|
+
function evaluate(e, ctx) {
|
|
28540
|
+
if (!Array.isArray(e)) { return e; }
|
|
28541
|
+
var op = e[0], p = (ctx.f && ctx.f.properties) || {};
|
|
28542
|
+
var ev = function (x) { return evaluate(x, ctx); };
|
|
28543
|
+
switch (op) {
|
|
28544
|
+
case 'literal': return e[1];
|
|
28545
|
+
case 'get': return e.length > 2 ? (ev(e[2]) || {})[ev(e[1])] : p[ev(e[1])];
|
|
28546
|
+
case 'has': return Object.prototype.hasOwnProperty.call(p, ev(e[1])) && p[ev(e[1])] !== undefined;
|
|
28547
|
+
case '!': return !ev(e[1]);
|
|
28548
|
+
case '==': return ev(e[1]) === ev(e[2]);
|
|
28549
|
+
case '!=': return ev(e[1]) !== ev(e[2]);
|
|
28550
|
+
case '>': return ev(e[1]) > ev(e[2]);
|
|
28551
|
+
case '>=': return ev(e[1]) >= ev(e[2]);
|
|
28552
|
+
case '<': return ev(e[1]) < ev(e[2]);
|
|
28553
|
+
case '<=': return ev(e[1]) <= ev(e[2]);
|
|
28554
|
+
case 'all': for (var a = 1; a < e.length; a++) { if (!ev(e[a])) { return false; } } return true;
|
|
28555
|
+
case 'any': for (var b = 1; b < e.length; b++) { if (ev(e[b])) { return true; } } return false;
|
|
28556
|
+
case 'case':
|
|
28557
|
+
for (var c = 1; c < e.length - 1; c += 2) { if (ev(e[c])) { return ev(e[c + 1]); } }
|
|
28558
|
+
return ev(e[e.length - 1]);
|
|
28559
|
+
case 'match': {
|
|
28560
|
+
var v = ev(e[1]);
|
|
28561
|
+
for (var d = 2; d < e.length - 1; d += 2) {
|
|
28562
|
+
var lab = e[d];
|
|
28563
|
+
if (Array.isArray(lab) ? lab.indexOf(v) >= 0 : lab === v) { return ev(e[d + 1]); }
|
|
28564
|
+
}
|
|
28565
|
+
return ev(e[e.length - 1]);
|
|
28566
|
+
}
|
|
28567
|
+
case 'coalesce': for (var k = 1; k < e.length; k++) { var x = ev(e[k]); if (x !== null && x !== undefined) { return x; } } return null;
|
|
28568
|
+
case 'to-string': { var s = ev(e[1]); return s === null || s === undefined ? '' : String(s); }
|
|
28569
|
+
case 'to-number': {
|
|
28570
|
+
for (var n = 1; n < e.length; n++) { var num = Number(ev(e[n])); if (!isNaN(num) && ev(e[n]) !== null && ev(e[n]) !== '') { return num; } }
|
|
28571
|
+
return 0;
|
|
28572
|
+
}
|
|
28573
|
+
case 'to-color': for (var q = 1; q < e.length; q++) { var col = ev(e[q]); if (typeof col === 'string' && col) { return col; } } return '#000000';
|
|
28574
|
+
case 'concat': { var out = ''; for (var r = 1; r < e.length; r++) { var part = ev(e[r]); out += part === null || part === undefined ? '' : String(part); } return out; }
|
|
28575
|
+
case 'zoom': return ctx.zoom;
|
|
28576
|
+
case 'heatmap-density': return ctx.density || 0;
|
|
28577
|
+
case 'geometry-type': return typeName(ctx.f && ctx.f.geometry);
|
|
28578
|
+
case 'id': return ctx.f ? ctx.f.id : null;
|
|
28579
|
+
case 'step': {
|
|
28580
|
+
var input = ev(e[1]), val = ev(e[2]);
|
|
28581
|
+
for (var st = 3; st < e.length; st += 2) { if (input >= e[st]) { val = ev(e[st + 1]); } else { break; } }
|
|
28582
|
+
return val;
|
|
28583
|
+
}
|
|
28584
|
+
case 'interpolate': {
|
|
28585
|
+
var at = ev(e[2]), stops = e.slice(3);
|
|
28586
|
+
if (at <= stops[0]) { return ev(stops[1]); }
|
|
28587
|
+
for (var i = 2; i < stops.length; i += 2) {
|
|
28588
|
+
if (at <= stops[i]) {
|
|
28589
|
+
var lo = ev(stops[i - 1]), hi = ev(stops[i]), t = (at - stops[i - 2]) / (stops[i] - stops[i - 2]);
|
|
28590
|
+
hi = ev(stops[i + 1]);
|
|
28591
|
+
if (typeof lo === 'number' && typeof hi === 'number') { return lo + t * (hi - lo); }
|
|
28592
|
+
return t < 0.5 ? lo : hi;
|
|
28593
|
+
}
|
|
28594
|
+
}
|
|
28595
|
+
return ev(stops[stops.length - 1]);
|
|
28596
|
+
}
|
|
28597
|
+
case '+': { var sum = 0; for (var u = 1; u < e.length; u++) { sum += ev(e[u]); } return sum; }
|
|
28598
|
+
case '*': { var prod = 1; for (var w = 1; w < e.length; w++) { prod *= ev(e[w]); } return prod; }
|
|
28599
|
+
case '-': return e.length === 2 ? -ev(e[1]) : ev(e[1]) - ev(e[2]);
|
|
28600
|
+
case '/': return ev(e[1]) / ev(e[2]);
|
|
28601
|
+
default:
|
|
28602
|
+
// A plain array value (an offset, a dash): every member evaluated.
|
|
28603
|
+
return e.map(ev);
|
|
28604
|
+
}
|
|
28605
|
+
}
|
|
28606
|
+
|
|
28607
|
+
// Legacy filters (['==', 'key', value]) are read as expressions too.
|
|
28608
|
+
function passes(filter, ctx) {
|
|
28609
|
+
if (!filter) { return true; }
|
|
28610
|
+
return !!evaluate(filter, ctx);
|
|
28611
|
+
}
|
|
28612
|
+
|
|
28613
|
+
// ------------------------------------------------------------------ clustering (a grid per zoom, like the engine's)
|
|
28614
|
+
|
|
28615
|
+
function clusterAt(features, zoom, radius, maxZoom) {
|
|
28616
|
+
var z = Math.floor(zoom);
|
|
28617
|
+
if (z > maxZoom) { return { features: features, clusters: {} }; }
|
|
28618
|
+
var scale = TILE * Math.pow(2, z), cell = radius;
|
|
28619
|
+
var grid = {}, out = [], clusters = {};
|
|
28620
|
+
features.forEach(function (f) {
|
|
28621
|
+
var g = f.geometry;
|
|
28622
|
+
if (!g || g.type !== 'Point') { out.push(f); return; }
|
|
28623
|
+
var x = lngX(g.coordinates[0], scale), y = latY(g.coordinates[1], scale);
|
|
28624
|
+
var key = Math.floor(x / cell) + ':' + Math.floor(y / cell);
|
|
28625
|
+
(grid[key] = grid[key] || []).push({ f: f, x: x, y: y });
|
|
28626
|
+
});
|
|
28627
|
+
var seq = 0;
|
|
28628
|
+
Object.keys(grid).forEach(function (key) {
|
|
28629
|
+
var members = grid[key];
|
|
28630
|
+
if (members.length === 1) { out.push(members[0].f); return; }
|
|
28631
|
+
var sx = 0, sy = 0;
|
|
28632
|
+
members.forEach(function (m) { sx += m.x; sy += m.y; });
|
|
28633
|
+
var id = z * 100000 + (++seq);
|
|
28634
|
+
var count = members.length;
|
|
28635
|
+
clusters[id] = { leaves: members.map(function (m) { return m.f; }), zoom: z };
|
|
28636
|
+
out.push({ type: 'Feature', id: id, properties: { cluster: true, cluster_id: id, point_count: count, point_count_abbreviated: count >= 1000 ? Math.round(count / 100) / 10 + 'k' : String(count) },
|
|
28637
|
+
geometry: { type: 'Point', coordinates: [xLng(sx / count, scale), yLat(sy / count, scale)] } });
|
|
28638
|
+
});
|
|
28639
|
+
return { features: out, clusters: clusters };
|
|
28640
|
+
}
|
|
28641
|
+
|
|
28642
|
+
function GeoJSONSource(map, id, spec) {
|
|
28643
|
+
this.map = map; this.id = id; this.type = 'geojson'; this.spec = spec;
|
|
28644
|
+
this.setData(spec.data);
|
|
28645
|
+
}
|
|
28646
|
+
GeoJSONSource.prototype.setData = function (data) {
|
|
28647
|
+
var fc = !data ? { type: 'FeatureCollection', features: [] } : (data.type === 'FeatureCollection' ? data : (data.type === 'Feature' ? { type: 'FeatureCollection', features: [data] } : { type: 'FeatureCollection', features: [{ type: 'Feature', properties: {}, geometry: data }] }));
|
|
28648
|
+
var spec = this.spec, idp = spec.promoteId;
|
|
28649
|
+
this.features = fc.features.map(function (f, i) {
|
|
28650
|
+
var id = f.id;
|
|
28651
|
+
if (spec.generateId) { id = i; } else if (idp && f.properties && f.properties[idp] !== undefined) { id = f.properties[idp]; }
|
|
28652
|
+
return id === f.id ? f : Object.assign({}, f, { id: id });
|
|
28653
|
+
});
|
|
28654
|
+
this._clusterZoom = null;
|
|
28655
|
+
this.map._changed();
|
|
28656
|
+
var self = this;
|
|
28657
|
+
setTimeout(function () { self.map._fire('sourcedata', { sourceId: self.id, isSourceLoaded: true, dataType: 'source' }); }, 0);
|
|
28658
|
+
return this;
|
|
28659
|
+
};
|
|
28660
|
+
GeoJSONSource.prototype._shown = function (zoom) {
|
|
28661
|
+
if (!this.spec.cluster) { return this.features; }
|
|
28662
|
+
var z = Math.floor(zoom);
|
|
28663
|
+
if (this._clusterZoom !== z) {
|
|
28664
|
+
var c = clusterAt(this.features, z, (this.spec.clusterRadius || 50) * 1, this.spec.clusterMaxZoom === undefined ? 14 : this.spec.clusterMaxZoom);
|
|
28665
|
+
this._cluster = c; this._clusterZoom = z;
|
|
28666
|
+
}
|
|
28667
|
+
return this._cluster.features;
|
|
28668
|
+
};
|
|
28669
|
+
GeoJSONSource.prototype.getClusterLeaves = function (id, limit, offset) {
|
|
28670
|
+
var c = this._cluster && this._cluster.clusters[id];
|
|
28671
|
+
var leaves = c ? c.leaves : [];
|
|
28672
|
+
return Promise.resolve(leaves.slice(offset || 0, (offset || 0) + (limit === undefined || limit === Infinity ? leaves.length : limit)));
|
|
28673
|
+
};
|
|
28674
|
+
GeoJSONSource.prototype.getClusterExpansionZoom = function (id) {
|
|
28675
|
+
var c = this._cluster && this._cluster.clusters[id];
|
|
28676
|
+
return Promise.resolve(c ? Math.min(c.zoom + 2, (this.spec.clusterMaxZoom === undefined ? 14 : this.spec.clusterMaxZoom) + 1) : this.map.getZoom() + 2);
|
|
28677
|
+
};
|
|
28678
|
+
|
|
28679
|
+
// Raster tiles: loaded as images, kept in a small cache, a parent tile drawn while a tile is on its way.
|
|
28680
|
+
function RasterSource(map, id, spec) {
|
|
28681
|
+
this.map = map; this.id = id; this.type = spec.type; this.spec = spec;
|
|
28682
|
+
this.tiles = {};
|
|
28683
|
+
this.order = [];
|
|
28684
|
+
}
|
|
28685
|
+
RasterSource.prototype._url = function (z, x, y) {
|
|
28686
|
+
var t = this.spec.tiles[(x + y) % this.spec.tiles.length];
|
|
28687
|
+
if (this.spec.scheme === 'tms') { y = Math.pow(2, z) - 1 - y; }
|
|
28688
|
+
return t.replace('{z}', z).replace('{x}', x).replace('{y}', y).replace('{ratio}', '');
|
|
28689
|
+
};
|
|
28690
|
+
RasterSource.prototype.tile = function (z, x, y) {
|
|
28691
|
+
var key = z + '/' + x + '/' + y, t = this.tiles[key];
|
|
28692
|
+
if (t) { return t; }
|
|
28693
|
+
var self = this, url = this._url(z, x, y);
|
|
28694
|
+
t = this.tiles[key] = { img: null, ready: false, failed: false };
|
|
28695
|
+
this.order.push(key);
|
|
28696
|
+
if (this.order.length > 400) { delete this.tiles[this.order.shift()]; }
|
|
28697
|
+
var doc = this.map._doc;
|
|
28698
|
+
var done = function (img) { t.img = img; t.ready = true; self.map._changed(); self.map._fire('sourcedata', { sourceId: self.id, isSourceLoaded: true, tile: { key: key } }); };
|
|
28699
|
+
var fail = function (err) { t.failed = true; self.map._fire('error', { type: 'error', sourceId: self.id, error: err || new Error('tile failed: ' + url) }); };
|
|
28700
|
+
var scheme = /^([a-z][a-z0-9+.-]*):\/\//i.exec(url);
|
|
28701
|
+
var proto = scheme && protocols[scheme[1].toLowerCase()];
|
|
28702
|
+
if (proto) {
|
|
28703
|
+
Promise.resolve(proto({ url: url, type: 'image' }, { signal: null })).then(function (r) {
|
|
28704
|
+
var blob = new Blob([r.data], { type: 'image/png' });
|
|
28705
|
+
var img = new (doc.defaultView.Image)();
|
|
28706
|
+
img.onload = function () { done(img); };
|
|
28707
|
+
img.onerror = function () { fail(); };
|
|
28708
|
+
img.src = URL.createObjectURL(blob);
|
|
28709
|
+
}, fail);
|
|
28710
|
+
return t;
|
|
28711
|
+
}
|
|
28712
|
+
var img = new (doc.defaultView.Image)();
|
|
28713
|
+
img.crossOrigin = 'anonymous';
|
|
28714
|
+
img.onload = function () { done(img); };
|
|
28715
|
+
img.onerror = function () { fail(); };
|
|
28716
|
+
var request = this.map._transform ? this.map._transform(url, 'Tile') : { url: url };
|
|
28717
|
+
img.src = request && request.url ? request.url : url;
|
|
28718
|
+
return t;
|
|
28719
|
+
};
|
|
28720
|
+
|
|
28721
|
+
var protocols = {};
|
|
28722
|
+
function addProtocol(name, fn) { protocols[String(name).toLowerCase()] = fn; }
|
|
28723
|
+
function removeProtocol(name) { delete protocols[String(name).toLowerCase()]; }
|
|
28724
|
+
|
|
28725
|
+
// ------------------------------------------------------------------ the map
|
|
28726
|
+
|
|
28727
|
+
var CORNERS = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
|
|
28728
|
+
|
|
28729
|
+
function Handler(map, name) { this.map = map; this.name = name; this.on = true; }
|
|
28730
|
+
Handler.prototype.enable = function () { this.on = true; };
|
|
28731
|
+
Handler.prototype.disable = function () { this.on = false; if (this.name === 'dragPan') { this.map._drag = null; } };
|
|
28732
|
+
Handler.prototype.isEnabled = function () { return this.on; };
|
|
28733
|
+
|
|
28734
|
+
/**
|
|
28735
|
+
* The canvas map. Options as the engine's: container, style ({ sources, layers } with raster sources), center,
|
|
28736
|
+
* zoom, minZoom, maxZoom, attributionControl, transformRequest.
|
|
28737
|
+
*/
|
|
28738
|
+
function CanvasMap(options) {
|
|
28739
|
+
var self = this;
|
|
28740
|
+
this._doc = null;
|
|
28741
|
+
var container = typeof options.container === 'string' ? document.getElementById(options.container) : options.container;
|
|
28742
|
+
if (!container) { throw new Error('sMap: no map container'); }
|
|
28743
|
+
this._doc = container.ownerDocument;
|
|
28744
|
+
this._container = container;
|
|
28745
|
+
this._transform = options.transformRequest || null;
|
|
28746
|
+
this._events = {};
|
|
28747
|
+
this._layerEvents = [];
|
|
28748
|
+
this._sources = {};
|
|
28749
|
+
this._layers = [];
|
|
28750
|
+
this._images = {};
|
|
28751
|
+
this._controls = [];
|
|
28752
|
+
this._center = toLngLat(options.center || [0, 0]);
|
|
28753
|
+
this._zoom = options.zoom || 0;
|
|
28754
|
+
this._minZoom = options.minZoom !== undefined ? options.minZoom : 0;
|
|
28755
|
+
this._maxZoom = options.maxZoom !== undefined ? options.maxZoom : 22;
|
|
28756
|
+
this._drawn = [];
|
|
28757
|
+
this._dirty = false;
|
|
28758
|
+
container.classList.add('maplibregl-map', 'smap-canvas-map');
|
|
28759
|
+
var canvasContainer = this._doc.createElement('div');
|
|
28760
|
+
canvasContainer.className = 'maplibregl-canvas-container maplibregl-interactive';
|
|
28761
|
+
var canvas = this._doc.createElement('canvas');
|
|
28762
|
+
canvas.className = 'maplibregl-canvas';
|
|
28763
|
+
canvas.setAttribute('tabindex', '0');
|
|
28764
|
+
canvas.style.position = 'absolute'; canvas.style.left = '0'; canvas.style.top = '0';
|
|
28765
|
+
canvasContainer.appendChild(canvas);
|
|
28766
|
+
container.appendChild(canvasContainer);
|
|
28767
|
+
var controls = this._doc.createElement('div');
|
|
28768
|
+
controls.className = 'maplibregl-control-container';
|
|
28769
|
+
this._corners = {};
|
|
28770
|
+
CORNERS.forEach(function (c) {
|
|
28771
|
+
var el = self._doc.createElement('div');
|
|
28772
|
+
el.className = 'maplibregl-ctrl-' + c;
|
|
28773
|
+
controls.appendChild(el);
|
|
28774
|
+
self._corners[c] = el;
|
|
28775
|
+
});
|
|
28776
|
+
container.appendChild(controls);
|
|
28777
|
+
this._canvas = canvas;
|
|
28778
|
+
this._canvasContainer = canvasContainer;
|
|
28779
|
+
this.dragPan = new Handler(this, 'dragPan');
|
|
28780
|
+
this.scrollZoom = new Handler(this, 'scrollZoom');
|
|
28781
|
+
this.boxZoom = new Handler(this, 'boxZoom');
|
|
28782
|
+
this.doubleClickZoom = new Handler(this, 'doubleClickZoom');
|
|
28783
|
+
this.touchZoomRotate = new Handler(this, 'touchZoomRotate');
|
|
28784
|
+
this.keyboard = new Handler(this, 'keyboard');
|
|
28785
|
+
this.dragRotate = new Handler(this, 'dragRotate');
|
|
28786
|
+
this._resize();
|
|
28787
|
+
this._input();
|
|
28788
|
+
if (options.attributionControl !== false) { this.addControl(new AttributionControl(options.attributionControl || {}), 'bottom-right'); }
|
|
28789
|
+
this.setStyle(options.style);
|
|
28790
|
+
var win = this._doc.defaultView;
|
|
28791
|
+
if (win && win.ResizeObserver) {
|
|
28792
|
+
this._ro = new win.ResizeObserver(function () { self.resize(); });
|
|
28793
|
+
this._ro.observe(container);
|
|
28794
|
+
} else if (win) { win.addEventListener('resize', function () { self.resize(); }); }
|
|
28795
|
+
setTimeout(function () { self._fire('load', {}); }, 0);
|
|
28796
|
+
}
|
|
28797
|
+
|
|
28798
|
+
CanvasMap.prototype.isCanvas = true;
|
|
28799
|
+
|
|
28800
|
+
// ---- events
|
|
28801
|
+
CanvasMap.prototype.on = function (type, layer, fn) {
|
|
28802
|
+
if (typeof layer === 'function') { (this._events[type] = this._events[type] || []).push(layer); return this; }
|
|
28803
|
+
this._layerEvents.push({ type: type, layer: layer, fn: fn, inside: false });
|
|
28804
|
+
return this;
|
|
28805
|
+
};
|
|
28806
|
+
CanvasMap.prototype.off = function (type, layer, fn) {
|
|
28807
|
+
if (typeof layer === 'function') { this._events[type] = (this._events[type] || []).filter(function (f) { return f !== layer; }); return this; }
|
|
28808
|
+
this._layerEvents = this._layerEvents.filter(function (h) { return !(h.type === type && h.layer === layer && h.fn === fn); });
|
|
28809
|
+
return this;
|
|
28810
|
+
};
|
|
28811
|
+
CanvasMap.prototype.once = function (type, fn) {
|
|
28812
|
+
var self = this;
|
|
28813
|
+
var wrapped = function (e) { self.off(type, wrapped); if (fn) { fn(e); } };
|
|
28814
|
+
if (!fn) { return new Promise(function (resolve) { fn = resolve; self.on(type, wrapped); }); }
|
|
28815
|
+
return this.on(type, wrapped);
|
|
28816
|
+
};
|
|
28817
|
+
CanvasMap.prototype.fire = function (e) { this._fire(e.type, e); return this; };
|
|
28818
|
+
CanvasMap.prototype._fire = function (type, data) {
|
|
28819
|
+
var e = data || {};
|
|
28820
|
+
e.type = type;
|
|
28821
|
+
e.target = this;
|
|
28822
|
+
(this._events[type] || []).slice().forEach(function (fn) { fn(e); });
|
|
28823
|
+
return e;
|
|
28824
|
+
};
|
|
28825
|
+
|
|
28826
|
+
// ---- camera
|
|
28827
|
+
CanvasMap.prototype._scale = function (z) { return TILE * Math.pow(2, z === undefined ? this._zoom : z); };
|
|
28828
|
+
CanvasMap.prototype.getContainer = function () { return this._container; };
|
|
28829
|
+
CanvasMap.prototype.getCanvas = function () { return this._canvas; };
|
|
28830
|
+
CanvasMap.prototype.getCanvasContainer = function () { return this._canvasContainer; };
|
|
28831
|
+
CanvasMap.prototype.getCenter = function () { return new LngLat(this._center.lng, this._center.lat); };
|
|
28832
|
+
CanvasMap.prototype.getZoom = function () { return this._zoom; };
|
|
28833
|
+
CanvasMap.prototype.getBearing = function () { return 0; };
|
|
28834
|
+
CanvasMap.prototype.getPitch = function () { return 0; };
|
|
28835
|
+
CanvasMap.prototype.getMinZoom = function () { return this._minZoom; };
|
|
28836
|
+
CanvasMap.prototype.getMaxZoom = function () { return this._maxZoom; };
|
|
28837
|
+
CanvasMap.prototype.setMinZoom = function (z) { this._minZoom = z; return this; };
|
|
28838
|
+
CanvasMap.prototype.setMaxZoom = function (z) { this._maxZoom = z; return this; };
|
|
28839
|
+
CanvasMap.prototype.project = function (v) {
|
|
28840
|
+
var p = toLngLat(v), s = this._scale();
|
|
28841
|
+
return { x: lngX(p.lng, s) - lngX(this._center.lng, s) + this._w / 2, y: latY(p.lat, s) - latY(this._center.lat, s) + this._h / 2 };
|
|
28842
|
+
};
|
|
28843
|
+
CanvasMap.prototype.unproject = function (pt) {
|
|
28844
|
+
var x = Array.isArray(pt) ? pt[0] : pt.x, y = Array.isArray(pt) ? pt[1] : pt.y, s = this._scale();
|
|
28845
|
+
return new LngLat(xLng(lngX(this._center.lng, s) + x - this._w / 2, s), yLat(latY(this._center.lat, s) + y - this._h / 2, s));
|
|
28846
|
+
};
|
|
28847
|
+
CanvasMap.prototype.getBounds = function () {
|
|
28848
|
+
var nw = this.unproject([0, 0]), se = this.unproject([this._w, this._h]);
|
|
28849
|
+
return new LngLatBounds([nw.lng, se.lat], [se.lng, nw.lat]);
|
|
28850
|
+
};
|
|
28851
|
+
CanvasMap.prototype._set = function (center, zoom) {
|
|
28852
|
+
var zoomed = zoom !== undefined && zoom !== this._zoom;
|
|
28853
|
+
if (center) { var c = toLngLat(center); this._center = { lng: ((c.lng + 540) % 360) - 180, lat: clampLat(c.lat) }; }
|
|
28854
|
+
if (zoom !== undefined) { this._zoom = Math.max(this._minZoom, Math.min(this._maxZoom, zoom)); }
|
|
28855
|
+
this._changed(true);
|
|
28856
|
+
this._fire('move', {});
|
|
28857
|
+
if (zoomed) { this._fire('zoom', {}); }
|
|
28858
|
+
};
|
|
28859
|
+
CanvasMap.prototype._end = function (zoomed) { this._fire('moveend', {}); if (zoomed) { this._fire('zoomend', {}); } this._fire('idle', {}); };
|
|
28860
|
+
CanvasMap.prototype.jumpTo = function (o) {
|
|
28861
|
+
var z0 = this._zoom;
|
|
28862
|
+
this._set(o.center, o.zoom);
|
|
28863
|
+
this._end(this._zoom !== z0);
|
|
28864
|
+
return this;
|
|
28865
|
+
};
|
|
28866
|
+
CanvasMap.prototype.easeTo = function (o) {
|
|
28867
|
+
var self = this, from = this.getCenter(), z0 = this._zoom;
|
|
28868
|
+
var to = o.center ? toLngLat(o.center) : from, z1 = o.zoom !== undefined ? o.zoom : z0;
|
|
28869
|
+
if (o.offset) { var p = this.project(to); to = this.unproject([p.x - o.offset[0], p.y - o.offset[1]]); }
|
|
28870
|
+
var duration = o.duration === undefined ? 300 : o.duration, start = null, seq = ++this._anim;
|
|
28871
|
+
if (!duration) { return this.jumpTo({ center: to, zoom: z1 }); }
|
|
28872
|
+
var step = function (now) {
|
|
28873
|
+
if (seq !== self._anim) { return; }
|
|
28874
|
+
if (start === null) { start = now; }
|
|
28875
|
+
var t = Math.min(1, (now - start) / duration), k = 1 - Math.pow(1 - t, 3);
|
|
28876
|
+
// Zoom moves evenly in its own scale; the centre follows in world pixels of the zoom reached.
|
|
28877
|
+
var z = z0 + (z1 - z0) * k;
|
|
28878
|
+
var s = TILE * Math.pow(2, z);
|
|
28879
|
+
var x = lngX(from.lng, s) + (lngX(to.lng, s) - lngX(from.lng, s)) * k, y = latY(from.lat, s) + (latY(to.lat, s) - latY(from.lat, s)) * k;
|
|
28880
|
+
self._set([xLng(x, s), yLat(y, s)], z);
|
|
28881
|
+
if (t < 1) { raf(step); } else { self._end(z1 !== z0); }
|
|
28882
|
+
};
|
|
28883
|
+
raf(step);
|
|
28884
|
+
return this;
|
|
28885
|
+
};
|
|
28886
|
+
CanvasMap.prototype._anim = 0;
|
|
28887
|
+
CanvasMap.prototype.flyTo = function (o) { return this.easeTo(Object.assign({ duration: o.duration === undefined ? 800 : o.duration }, o)); };
|
|
28888
|
+
CanvasMap.prototype.panBy = function (offset, o) {
|
|
28889
|
+
var c = this.project(this._center);
|
|
28890
|
+
return this.easeTo(Object.assign({ center: this.unproject([c.x + offset[0], c.y + offset[1]]) }, o || {}));
|
|
28891
|
+
};
|
|
28892
|
+
CanvasMap.prototype.panTo = function (center, o) { return this.easeTo(Object.assign({ center: center }, o || {})); };
|
|
28893
|
+
CanvasMap.prototype.zoomTo = function (z, o) { return this.easeTo(Object.assign({ zoom: z }, o || {})); };
|
|
28894
|
+
CanvasMap.prototype.zoomIn = function (o) { return this.zoomTo(this._zoom + 1, o); };
|
|
28895
|
+
CanvasMap.prototype.zoomOut = function (o) { return this.zoomTo(this._zoom - 1, o); };
|
|
28896
|
+
CanvasMap.prototype.setCenter = function (c) { return this.jumpTo({ center: c }); };
|
|
28897
|
+
CanvasMap.prototype.setZoom = function (z) { return this.jumpTo({ zoom: z }); };
|
|
28898
|
+
CanvasMap.prototype.cameraForBounds = function (bounds, o) {
|
|
28899
|
+
var b = bounds instanceof LngLatBounds ? bounds : new LngLatBounds(bounds);
|
|
28900
|
+
if (b.isEmpty()) { return null; }
|
|
28901
|
+
o = o || {};
|
|
28902
|
+
var pad = typeof o.padding === 'number' ? { top: o.padding, bottom: o.padding, left: o.padding, right: o.padding } : Object.assign({ top: 0, bottom: 0, left: 0, right: 0 }, o.padding || {});
|
|
28903
|
+
var w = Math.max(1, this._w - pad.left - pad.right), h = Math.max(1, this._h - pad.top - pad.bottom);
|
|
28904
|
+
var s1 = TILE, dx = Math.abs(lngX(b.getEast(), s1) - lngX(b.getWest(), s1)), dy = Math.abs(latY(b.getSouth(), s1) - latY(b.getNorth(), s1));
|
|
28905
|
+
var z = Math.min(Math.log2(w / Math.max(dx, 1e-9)), Math.log2(h / Math.max(dy, 1e-9)));
|
|
28906
|
+
z = Math.min(z, o.maxZoom !== undefined ? o.maxZoom : this._maxZoom);
|
|
28907
|
+
var c = b.getCenter(), s = TILE * Math.pow(2, z);
|
|
28908
|
+
// The padding moves the centre.
|
|
28909
|
+
var cx = lngX(c.lng, s) - (pad.left - pad.right) / 2, cy = latY(c.lat, s) - (pad.top - pad.bottom) / 2;
|
|
28910
|
+
return { center: [xLng(cx, s), yLat(cy, s)], zoom: z };
|
|
28911
|
+
};
|
|
28912
|
+
CanvasMap.prototype.fitBounds = function (bounds, o) {
|
|
28913
|
+
var cam = this.cameraForBounds(bounds, o);
|
|
28914
|
+
if (!cam) { return this; }
|
|
28915
|
+
return this.easeTo(Object.assign({}, cam, { duration: o && o.duration !== undefined ? o.duration : (o && o.animate === false ? 0 : 300) }));
|
|
28916
|
+
};
|
|
28917
|
+
CanvasMap.prototype.setProjection = function () { return this; };
|
|
28918
|
+
CanvasMap.prototype.getProjection = function () { return { type: 'mercator' }; };
|
|
28919
|
+
CanvasMap.prototype.setTerrain = function (t) { if (t) { this._unsupported('terrain'); } return this; };
|
|
28920
|
+
CanvasMap.prototype._unsupported = function (what) {
|
|
28921
|
+
this._said = this._said || {};
|
|
28922
|
+
if (this._said[what]) { return; }
|
|
28923
|
+
this._said[what] = true;
|
|
28924
|
+
this._fire('smap.unsupported', { feature: what });
|
|
28925
|
+
};
|
|
28926
|
+
|
|
28927
|
+
CanvasMap.prototype.resize = function () { if (this._resize()) { this._changed(true); this._fire('resize', {}); this._fire('moveend', {}); } return this; };
|
|
28928
|
+
CanvasMap.prototype._resize = function () {
|
|
28929
|
+
var w = this._container.clientWidth || 300, h = this._container.clientHeight || 150;
|
|
28930
|
+
var win = this._doc.defaultView || {}, ratio = Math.min(3, win.devicePixelRatio || 1);
|
|
28931
|
+
if (w === this._w && h === this._h && ratio === this._ratio) { return false; }
|
|
28932
|
+
this._w = w; this._h = h; this._ratio = ratio;
|
|
28933
|
+
this._canvas.width = Math.round(w * ratio); this._canvas.height = Math.round(h * ratio);
|
|
28934
|
+
this._canvas.style.width = w + 'px'; this._canvas.style.height = h + 'px';
|
|
28935
|
+
return true;
|
|
28936
|
+
};
|
|
28937
|
+
CanvasMap.prototype.triggerRepaint = function () { this._changed(); };
|
|
28938
|
+
CanvasMap.prototype._changed = function () {
|
|
28939
|
+
if (this._dirty || this._removed) { return; }
|
|
28940
|
+
this._dirty = true;
|
|
28941
|
+
var self = this;
|
|
28942
|
+
raf(function () { self._dirty = false; self._render(); });
|
|
28943
|
+
};
|
|
28944
|
+
CanvasMap.prototype.loaded = function () { return true; };
|
|
28945
|
+
CanvasMap.prototype.isStyleLoaded = function () { return true; };
|
|
28946
|
+
CanvasMap.prototype.remove = function () {
|
|
28947
|
+
this._removed = true;
|
|
28948
|
+
if (this._ro) { this._ro.disconnect(); }
|
|
28949
|
+
if (this._canvasContainer.parentNode) { this._canvasContainer.parentNode.removeChild(this._canvasContainer); }
|
|
28950
|
+
var cc = this._container.querySelector('.maplibregl-control-container');
|
|
28951
|
+
if (cc) { cc.parentNode.removeChild(cc); }
|
|
28952
|
+
this._fire('remove', {});
|
|
28953
|
+
};
|
|
28954
|
+
|
|
28955
|
+
// ---- style, sources, layers
|
|
28956
|
+
CanvasMap.prototype.setStyle = function (style) {
|
|
28957
|
+
var self = this;
|
|
28958
|
+
this._sources = {};
|
|
28959
|
+
this._layers = [];
|
|
28960
|
+
style = style && typeof style === 'object' ? style : { sources: {}, layers: [] };
|
|
28961
|
+
Object.keys(style.sources || {}).forEach(function (id) { self.addSource(id, style.sources[id]); });
|
|
28962
|
+
(style.layers || []).forEach(function (l) { self.addLayer(l); });
|
|
28963
|
+
this._changed();
|
|
28964
|
+
setTimeout(function () { self._fire('style.load', {}); self._fire('styledata', {}); }, 0);
|
|
28965
|
+
return this;
|
|
28966
|
+
};
|
|
28967
|
+
CanvasMap.prototype.getStyle = function () {
|
|
28968
|
+
var self = this, sources = {};
|
|
28969
|
+
Object.keys(this._sources).forEach(function (id) { sources[id] = self._sources[id].spec; });
|
|
28970
|
+
return { version: 8, sources: sources, layers: this._layers.map(function (l) { return l; }) };
|
|
28971
|
+
};
|
|
28972
|
+
CanvasMap.prototype.addSource = function (id, spec) {
|
|
28973
|
+
if (this._sources[id]) { throw new Error('There is already a source with this ID'); }
|
|
28974
|
+
if (spec.type === 'geojson') { this._sources[id] = new GeoJSONSource(this, id, spec); }
|
|
28975
|
+
else if (spec.type === 'raster' && spec.tiles) { this._sources[id] = new RasterSource(this, id, spec); }
|
|
28976
|
+
else { this._sources[id] = { id: id, type: spec.type, spec: spec, setData: function () {} }; if (spec.type !== 'raster-dem') { this._unsupported('source:' + spec.type); } }
|
|
28977
|
+
this._updateAttribution();
|
|
28978
|
+
return this;
|
|
28979
|
+
};
|
|
28980
|
+
CanvasMap.prototype.getSource = function (id) { return this._sources[id]; };
|
|
28981
|
+
CanvasMap.prototype.removeSource = function (id) { delete this._sources[id]; this._updateAttribution(); this._changed(); return this; };
|
|
28982
|
+
CanvasMap.prototype.addLayer = function (layer, before) {
|
|
28983
|
+
if (this.getLayer(layer.id)) { throw new Error('Layer with id "' + layer.id + '" already exists'); }
|
|
28984
|
+
var l = layer.type === 'custom' ? layer : JSON.parse(JSON.stringify(layer));
|
|
28985
|
+
if (typeof l.source === 'object' && l.source) { this.addSource(l.id, l.source); l.source = l.id; }
|
|
28986
|
+
if (['raster', 'fill', 'line', 'circle', 'symbol', 'heatmap', 'background', 'fill-extrusion'].indexOf(l.type) < 0) { this._unsupported('layer:' + l.type); }
|
|
28987
|
+
var at = before ? this._layers.findIndex(function (x) { return x.id === before; }) : -1;
|
|
28988
|
+
if (at >= 0) { this._layers.splice(at, 0, l); } else { this._layers.push(l); }
|
|
28989
|
+
if (l.type === 'custom' && l.onAdd) { this._unsupported('3d'); }
|
|
28990
|
+
this._changed();
|
|
28991
|
+
return this;
|
|
28992
|
+
};
|
|
28993
|
+
CanvasMap.prototype.getLayer = function (id) { for (var i = 0; i < this._layers.length; i++) { if (this._layers[i].id === id) { return this._layers[i]; } } return undefined; };
|
|
28994
|
+
CanvasMap.prototype.removeLayer = function (id) { this._layers = this._layers.filter(function (l) { return l.id !== id; }); this._changed(); return this; };
|
|
28995
|
+
CanvasMap.prototype.moveLayer = function (id, before) {
|
|
28996
|
+
var l = this.getLayer(id);
|
|
28997
|
+
if (!l) { return this; }
|
|
28998
|
+
this._layers = this._layers.filter(function (x) { return x !== l; });
|
|
28999
|
+
var at = before ? this._layers.findIndex(function (x) { return x.id === before; }) : -1;
|
|
29000
|
+
if (at >= 0) { this._layers.splice(at, 0, l); } else { this._layers.push(l); }
|
|
29001
|
+
this._changed();
|
|
29002
|
+
return this;
|
|
29003
|
+
};
|
|
29004
|
+
CanvasMap.prototype.setLayoutProperty = function (id, k, v) { var l = this.getLayer(id); if (l) { l.layout = Object.assign({}, l.layout, {}); l.layout[k] = v; this._changed(); } return this; };
|
|
29005
|
+
CanvasMap.prototype.getLayoutProperty = function (id, k) { var l = this.getLayer(id); return l && l.layout ? l.layout[k] : undefined; };
|
|
29006
|
+
CanvasMap.prototype.setPaintProperty = function (id, k, v) { var l = this.getLayer(id); if (l) { l.paint = Object.assign({}, l.paint, {}); l.paint[k] = v; this._changed(); } return this; };
|
|
29007
|
+
CanvasMap.prototype.getPaintProperty = function (id, k) { var l = this.getLayer(id); return l && l.paint ? l.paint[k] : undefined; };
|
|
29008
|
+
CanvasMap.prototype.setFilter = function (id, f) { var l = this.getLayer(id); if (l) { l.filter = f || undefined; this._changed(); } return this; };
|
|
29009
|
+
CanvasMap.prototype.getFilter = function (id) { var l = this.getLayer(id); return l ? l.filter : undefined; };
|
|
29010
|
+
CanvasMap.prototype.setLayerZoomRange = function (id, min, max) { var l = this.getLayer(id); if (l) { l.minzoom = min; l.maxzoom = max; this._changed(); } return this; };
|
|
29011
|
+
|
|
29012
|
+
// ---- images (the SDK's drawn pins, numbers, badges and arrows; icons)
|
|
29013
|
+
CanvasMap.prototype.hasImage = function (id) { return !!this._images[id]; };
|
|
29014
|
+
CanvasMap.prototype.addImage = function (id, image, o) {
|
|
29015
|
+
var ratio = (o && o.pixelRatio) || 1, canvas;
|
|
29016
|
+
if (image && image.data && image.width !== undefined && !(image.getContext)) {
|
|
29017
|
+
canvas = this._doc.createElement('canvas');
|
|
29018
|
+
canvas.width = image.width; canvas.height = image.height;
|
|
29019
|
+
var ctx = canvas.getContext('2d');
|
|
29020
|
+
var data = ctx.createImageData(image.width, image.height);
|
|
29021
|
+
data.data.set(image.data);
|
|
29022
|
+
ctx.putImageData(data, 0, 0);
|
|
29023
|
+
} else { canvas = image; }
|
|
29024
|
+
this._images[id] = { source: canvas, ratio: ratio, w: (canvas.width || canvas.naturalWidth) / ratio, h: (canvas.height || canvas.naturalHeight) / ratio };
|
|
29025
|
+
this._changed();
|
|
29026
|
+
return this;
|
|
29027
|
+
};
|
|
29028
|
+
CanvasMap.prototype.removeImage = function (id) { delete this._images[id]; return this; };
|
|
29029
|
+
CanvasMap.prototype.loadImage = function (url) {
|
|
29030
|
+
var doc = this._doc;
|
|
29031
|
+
return new Promise(function (resolve, reject) {
|
|
29032
|
+
var img = new (doc.defaultView.Image)();
|
|
29033
|
+
img.crossOrigin = 'anonymous';
|
|
29034
|
+
img.onload = function () { resolve({ data: img }); };
|
|
29035
|
+
img.onerror = function () { reject(new Error('sMap: image failed ' + url)); };
|
|
29036
|
+
img.src = url;
|
|
29037
|
+
});
|
|
29038
|
+
};
|
|
29039
|
+
CanvasMap.prototype.setMissingStyleImageResolver = function (fn) { this._missing = fn; return this; };
|
|
29040
|
+
CanvasMap.prototype._image = function (id) {
|
|
29041
|
+
if (!this._images[id] && this._missing) { this._missing(id); }
|
|
29042
|
+
return this._images[id] || null;
|
|
29043
|
+
};
|
|
29044
|
+
|
|
29045
|
+
// ---- controls
|
|
29046
|
+
CanvasMap.prototype.addControl = function (control, position) {
|
|
29047
|
+
var pos = position || (control.getDefaultPosition && control.getDefaultPosition()) || 'top-right';
|
|
29048
|
+
var el = control.onAdd(this);
|
|
29049
|
+
var corner = this._corners[pos];
|
|
29050
|
+
if (pos.indexOf('bottom') === 0) { corner.insertBefore(el, corner.firstChild); } else { corner.appendChild(el); }
|
|
29051
|
+
this._controls.push(control);
|
|
29052
|
+
return this;
|
|
29053
|
+
};
|
|
29054
|
+
CanvasMap.prototype.removeControl = function (control) {
|
|
29055
|
+
this._controls = this._controls.filter(function (c) { return c !== control; });
|
|
29056
|
+
if (control.onRemove) { control.onRemove(this); }
|
|
29057
|
+
return this;
|
|
29058
|
+
};
|
|
29059
|
+
CanvasMap.prototype.hasControl = function (control) { return this._controls.indexOf(control) >= 0; };
|
|
29060
|
+
CanvasMap.prototype._updateAttribution = function () {
|
|
29061
|
+
var self = this, seen = {}, parts = [];
|
|
29062
|
+
Object.keys(this._sources).forEach(function (id) { var a = self._sources[id].spec && self._sources[id].spec.attribution; if (a && !seen[a]) { seen[a] = true; parts.push(a); } });
|
|
29063
|
+
this._attribution = parts.join(' | ');
|
|
29064
|
+
this._controls.forEach(function (c) { if (c instanceof AttributionControl) { c._update(); } });
|
|
29065
|
+
};
|
|
29066
|
+
|
|
29067
|
+
// ---- drawing
|
|
29068
|
+
|
|
29069
|
+
CanvasMap.prototype._visible = function (l) {
|
|
29070
|
+
if (l.layout && l.layout.visibility === 'none') { return false; }
|
|
29071
|
+
if (l.minzoom !== undefined && this._zoom < l.minzoom) { return false; }
|
|
29072
|
+
if (l.maxzoom !== undefined && this._zoom >= l.maxzoom) { return false; }
|
|
29073
|
+
return true;
|
|
29074
|
+
};
|
|
29075
|
+
|
|
29076
|
+
CanvasMap.prototype._render = function () {
|
|
29077
|
+
var ctx = this._canvas.getContext('2d');
|
|
29078
|
+
if (!ctx) { return; }
|
|
29079
|
+
var r = this._ratio;
|
|
29080
|
+
ctx.setTransform(r, 0, 0, r, 0, 0);
|
|
29081
|
+
ctx.clearRect(0, 0, this._w, this._h);
|
|
29082
|
+
this._drawn = [];
|
|
29083
|
+
var self = this, zoom = this._zoom;
|
|
29084
|
+
// Raster layers first (the street map, imagery), then every other layer in its order.
|
|
29085
|
+
var rasters = this._layers.filter(function (l) { return l.type === 'raster' || l.type === 'background'; });
|
|
29086
|
+
var others = this._layers.filter(function (l) { return l.type !== 'raster' && l.type !== 'background'; });
|
|
29087
|
+
rasters.concat(others).forEach(function (l) {
|
|
29088
|
+
if (!self._visible(l)) { return; }
|
|
29089
|
+
if (l.type === 'background') { ctx.fillStyle = evaluate((l.paint || {})['background-color'], { zoom: zoom }) || '#ffffff'; ctx.fillRect(0, 0, self._w, self._h); return; }
|
|
29090
|
+
if (l.type === 'raster') { self._drawRaster(ctx, l); return; }
|
|
29091
|
+
var src = self._sources[l.source];
|
|
29092
|
+
if (!src || !src._shown) { return; }
|
|
29093
|
+
var features = src._shown(zoom);
|
|
29094
|
+
if (l.type === 'heatmap') { self._drawHeatmap(ctx, l, features); return; }
|
|
29095
|
+
for (var i = 0; i < features.length; i++) {
|
|
29096
|
+
var f = features[i], c = { zoom: zoom, f: f };
|
|
29097
|
+
if (!f.geometry || !passes(l.filter, c)) { continue; }
|
|
29098
|
+
if (l.type === 'fill' || l.type === 'fill-extrusion') { self._drawFill(ctx, l, f, c); }
|
|
29099
|
+
else if (l.type === 'line') { self._drawLine(ctx, l, f, c); }
|
|
29100
|
+
else if (l.type === 'circle') { self._drawCircle(ctx, l, f, c); }
|
|
29101
|
+
else if (l.type === 'symbol') { self._drawSymbol(ctx, l, f, c); }
|
|
29102
|
+
}
|
|
29103
|
+
});
|
|
29104
|
+
this._fire('render', {});
|
|
29105
|
+
};
|
|
29106
|
+
|
|
29107
|
+
CanvasMap.prototype._drawRaster = function (ctx, l) {
|
|
29108
|
+
var src = this._sources[l.source];
|
|
29109
|
+
if (!src || !src.tile) { return; }
|
|
29110
|
+
var spec = src.spec, ts = spec.tileSize || 256;
|
|
29111
|
+
// The tile zoom whose tiles are closest to their own size at this zoom.
|
|
29112
|
+
var tz = Math.round(this._zoom + Math.log2(TILE / ts));
|
|
29113
|
+
tz = Math.max(spec.minzoom || 0, Math.min(tz, spec.maxzoom === undefined ? 22 : spec.maxzoom));
|
|
29114
|
+
var n = Math.pow(2, tz), size = this._scale() / n;
|
|
29115
|
+
var s = this._scale();
|
|
29116
|
+
var ox = lngX(this._center.lng, s) - this._w / 2, oy = latY(this._center.lat, s) - this._h / 2;
|
|
29117
|
+
var x0 = Math.floor(ox / size), x1 = Math.floor((ox + this._w) / size), y0 = Math.max(0, Math.floor(oy / size)), y1 = Math.min(n - 1, Math.floor((oy + this._h) / size));
|
|
29118
|
+
var opacity = (l.paint && l.paint['raster-opacity'] !== undefined) ? l.paint['raster-opacity'] : 1;
|
|
29119
|
+
ctx.save();
|
|
29120
|
+
ctx.globalAlpha = opacity;
|
|
29121
|
+
ctx.imageSmoothingEnabled = true;
|
|
29122
|
+
for (var x = x0; x <= x1; x++) {
|
|
29123
|
+
for (var y = y0; y <= y1; y++) {
|
|
29124
|
+
var wx = ((x % n) + n) % n;
|
|
29125
|
+
var px = x * size - ox, py = y * size - oy;
|
|
29126
|
+
var t = src.tile(tz, wx, y);
|
|
29127
|
+
if (t.ready) { ctx.drawImage(t.img, px, py, size + 0.5, size + 0.5); continue; }
|
|
29128
|
+
// A parent tile already loaded, enlarged, while this one is on its way.
|
|
29129
|
+
for (var up = 1; up <= 4 && tz - up >= 0; up++) {
|
|
29130
|
+
var pz = tz - up, k = Math.pow(2, up), pt = src.tiles[pz + '/' + Math.floor(wx / k) + '/' + Math.floor(y / k)];
|
|
29131
|
+
if (pt && pt.ready) {
|
|
29132
|
+
var sw = pt.img.width / k, sx = (wx % k) * sw, sy = (y % k) * (pt.img.height / k);
|
|
29133
|
+
ctx.drawImage(pt.img, sx, sy, sw, pt.img.height / k, px, py, size + 0.5, size + 0.5);
|
|
29134
|
+
break;
|
|
29135
|
+
}
|
|
29136
|
+
}
|
|
29137
|
+
}
|
|
29138
|
+
}
|
|
29139
|
+
ctx.restore();
|
|
29140
|
+
};
|
|
29141
|
+
|
|
29142
|
+
// Screen points of a geometry's parts: [{ type, rings: [[[x, y], …]] }].
|
|
29143
|
+
CanvasMap.prototype._screen = function (g) {
|
|
29144
|
+
var self = this, s = this._scale();
|
|
29145
|
+
var ox = lngX(this._center.lng, s) - this._w / 2, oy = latY(this._center.lat, s) - this._h / 2;
|
|
29146
|
+
var pt = function (c) { return [lngX(c[0], s) - ox, latY(c[1], s) - oy]; };
|
|
29147
|
+
var line = function (cs) { return cs.map(pt); };
|
|
29148
|
+
switch (g.type) {
|
|
29149
|
+
case 'Point': return [pt(g.coordinates)];
|
|
29150
|
+
case 'MultiPoint': return g.coordinates.map(pt);
|
|
29151
|
+
case 'LineString': return [line(g.coordinates)];
|
|
29152
|
+
case 'MultiLineString': return g.coordinates.map(line);
|
|
29153
|
+
case 'Polygon': return [g.coordinates.map(line)];
|
|
29154
|
+
case 'MultiPolygon': return g.coordinates.map(function (p) { return p.map(line); });
|
|
29155
|
+
default: return [];
|
|
29156
|
+
}
|
|
29157
|
+
};
|
|
29158
|
+
|
|
29159
|
+
function paintOf(l, k, c, d) { var v = l.paint ? l.paint[k] : undefined; v = v === undefined ? d : evaluate(v, c); return v === undefined || v === null ? d : v; }
|
|
29160
|
+
function layoutOf(l, k, c, d) { var v = l.layout ? l.layout[k] : undefined; v = v === undefined ? d : evaluate(v, c); return v === undefined || v === null ? d : v; }
|
|
29161
|
+
|
|
29162
|
+
CanvasMap.prototype._record = function (l, f, hit) { this._drawn.push({ layer: l, f: f, hit: hit }); };
|
|
29163
|
+
|
|
29164
|
+
CanvasMap.prototype._drawFill = function (ctx, l, f, c) {
|
|
29165
|
+
var g = f.geometry;
|
|
29166
|
+
if (g.type !== 'Polygon' && g.type !== 'MultiPolygon') { return; }
|
|
29167
|
+
var polys = this._screen(g);
|
|
29168
|
+
ctx.save();
|
|
29169
|
+
ctx.globalAlpha = paintOf(l, l.type === 'fill' ? 'fill-opacity' : 'fill-extrusion-opacity', c, 1);
|
|
29170
|
+
ctx.fillStyle = paintOf(l, l.type === 'fill' ? 'fill-color' : 'fill-extrusion-color', c, '#000000');
|
|
29171
|
+
ctx.beginPath();
|
|
29172
|
+
polys.forEach(function (rings) { rings.forEach(function (ring) { ring.forEach(function (p, i) { if (i) { ctx.lineTo(p[0], p[1]); } else { ctx.moveTo(p[0], p[1]); } }); ctx.closePath(); }); });
|
|
29173
|
+
ctx.fill('evenodd');
|
|
29174
|
+
ctx.restore();
|
|
29175
|
+
this._record(l, f, { kind: 'fill', polys: polys });
|
|
29176
|
+
};
|
|
29177
|
+
|
|
29178
|
+
CanvasMap.prototype._drawLine = function (ctx, l, f, c) {
|
|
29179
|
+
var g = f.geometry, lines;
|
|
29180
|
+
if (g.type === 'LineString' || g.type === 'MultiLineString') { lines = this._screen(g); }
|
|
29181
|
+
else if (g.type === 'Polygon' || g.type === 'MultiPolygon') { lines = []; this._screen(g).forEach(function (rings) { rings.forEach(function (r) { lines.push(r); }); }); }
|
|
29182
|
+
else { return; }
|
|
29183
|
+
var width = paintOf(l, 'line-width', c, 1);
|
|
29184
|
+
ctx.save();
|
|
29185
|
+
ctx.globalAlpha = paintOf(l, 'line-opacity', c, 1);
|
|
29186
|
+
ctx.strokeStyle = paintOf(l, 'line-color', c, '#000000');
|
|
29187
|
+
ctx.lineWidth = width;
|
|
29188
|
+
ctx.lineCap = layoutOf(l, 'line-cap', c, 'butt');
|
|
29189
|
+
ctx.lineJoin = layoutOf(l, 'line-join', c, 'miter');
|
|
29190
|
+
var dash = paintOf(l, 'line-dasharray', c, null);
|
|
29191
|
+
if (Array.isArray(dash)) { ctx.setLineDash(dash.map(function (d) { return d * width; })); }
|
|
29192
|
+
ctx.beginPath();
|
|
29193
|
+
lines.forEach(function (line) { line.forEach(function (p, i) { if (i) { ctx.lineTo(p[0], p[1]); } else { ctx.moveTo(p[0], p[1]); } }); });
|
|
29194
|
+
ctx.stroke();
|
|
29195
|
+
ctx.restore();
|
|
29196
|
+
this._record(l, f, { kind: 'line', lines: lines, width: width });
|
|
29197
|
+
};
|
|
29198
|
+
|
|
29199
|
+
CanvasMap.prototype._drawCircle = function (ctx, l, f, c) {
|
|
29200
|
+
var self = this, radius = paintOf(l, 'circle-radius', c, 5), sw = paintOf(l, 'circle-stroke-width', c, 0);
|
|
29201
|
+
this._screen(f.geometry).forEach(function (p) {
|
|
29202
|
+
if (!Array.isArray(p) || typeof p[0] !== 'number') { return; }
|
|
29203
|
+
if (p[0] < -radius - 50 || p[1] < -radius - 50 || p[0] > self._w + radius + 50 || p[1] > self._h + radius + 50) { return; }
|
|
29204
|
+
ctx.save();
|
|
29205
|
+
ctx.globalAlpha = paintOf(l, 'circle-opacity', c, 1);
|
|
29206
|
+
ctx.beginPath();
|
|
29207
|
+
ctx.arc(p[0], p[1], radius, 0, Math.PI * 2);
|
|
29208
|
+
ctx.fillStyle = paintOf(l, 'circle-color', c, '#000000');
|
|
29209
|
+
ctx.fill();
|
|
29210
|
+
if (sw > 0) { ctx.lineWidth = sw; ctx.strokeStyle = paintOf(l, 'circle-stroke-color', c, '#000000'); ctx.globalAlpha = paintOf(l, 'circle-stroke-opacity', c, 1); ctx.stroke(); }
|
|
29211
|
+
ctx.restore();
|
|
29212
|
+
self._record(l, f, { kind: 'circle', x: p[0], y: p[1], r: radius + sw / 2 });
|
|
29213
|
+
});
|
|
29214
|
+
};
|
|
29215
|
+
|
|
29216
|
+
var ANCHOR_SHIFT = {
|
|
29217
|
+
center: [-0.5, -0.5], top: [-0.5, 0], bottom: [-0.5, -1], left: [0, -0.5], right: [-1, -0.5],
|
|
29218
|
+
'top-left': [0, 0], 'top-right': [-1, 0], 'bottom-left': [0, -1], 'bottom-right': [-1, -1],
|
|
29219
|
+
};
|
|
29220
|
+
|
|
29221
|
+
CanvasMap.prototype._drawSymbol = function (ctx, l, f, c) {
|
|
29222
|
+
var self = this;
|
|
29223
|
+
var placement = layoutOf(l, 'symbol-placement', c, 'point');
|
|
29224
|
+
var iconId = layoutOf(l, 'icon-image', c, null);
|
|
29225
|
+
var text = layoutOf(l, 'text-field', c, null);
|
|
29226
|
+
if (Array.isArray(text)) { text = text.join(''); }
|
|
29227
|
+
if (placement === 'line') {
|
|
29228
|
+
var g = f.geometry;
|
|
29229
|
+
if (g.type !== 'LineString' && g.type !== 'MultiLineString') { return; }
|
|
29230
|
+
var spacing = layoutOf(l, 'symbol-spacing', c, 250);
|
|
29231
|
+
this._screen(g).forEach(function (line) {
|
|
29232
|
+
var walked = spacing / 2;
|
|
29233
|
+
for (var i = 1; i < line.length; i++) {
|
|
29234
|
+
var a = line[i - 1], b = line[i], len = Math.sqrt(Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2));
|
|
29235
|
+
while (walked <= len) {
|
|
29236
|
+
var t = walked / len, x = a[0] + (b[0] - a[0]) * t, y = a[1] + (b[1] - a[1]) * t;
|
|
29237
|
+
var angle = Math.atan2(b[1] - a[1], b[0] - a[0]);
|
|
29238
|
+
if (x > -50 && y > -50 && x < self._w + 50 && y < self._h + 50) {
|
|
29239
|
+
if (iconId) { self._icon(ctx, l, c, iconId, x, y, angle); }
|
|
29240
|
+
if (text) { self._text(ctx, l, c, String(text), x, y, angle > Math.PI / 2 || angle < -Math.PI / 2 ? angle + Math.PI : angle); }
|
|
29241
|
+
}
|
|
29242
|
+
walked += spacing;
|
|
29243
|
+
}
|
|
29244
|
+
walked -= len;
|
|
29245
|
+
}
|
|
29246
|
+
});
|
|
29247
|
+
return;
|
|
29248
|
+
}
|
|
29249
|
+
this._screen(f.geometry).forEach(function (p) {
|
|
29250
|
+
if (!Array.isArray(p) || typeof p[0] !== 'number') { return; }
|
|
29251
|
+
if (p[0] < -100 || p[1] < -100 || p[0] > self._w + 100 || p[1] > self._h + 100) { return; }
|
|
29252
|
+
var box = null;
|
|
29253
|
+
if (iconId) { box = self._icon(ctx, l, c, iconId, p[0], p[1], 0); }
|
|
29254
|
+
if (text !== null && text !== undefined && text !== '') { var tb = self._text(ctx, l, c, String(text), p[0], p[1], 0); box = box || tb; }
|
|
29255
|
+
if (box) { self._record(l, f, { kind: 'box', box: box }); }
|
|
29256
|
+
});
|
|
29257
|
+
};
|
|
29258
|
+
|
|
29259
|
+
CanvasMap.prototype._icon = function (ctx, l, c, id, x, y, angle) {
|
|
29260
|
+
var img = this._image(String(id));
|
|
29261
|
+
if (!img) { return null; }
|
|
29262
|
+
var size = layoutOf(l, 'icon-size', c, 1);
|
|
29263
|
+
var w = img.w * size, h = img.h * size;
|
|
29264
|
+
var anchor = ANCHOR_SHIFT[layoutOf(l, 'icon-anchor', c, 'center')] || ANCHOR_SHIFT.center;
|
|
29265
|
+
var off = layoutOf(l, 'icon-offset', c, [0, 0]);
|
|
29266
|
+
var dx = off[0] * size + anchor[0] * w, dy = off[1] * size + anchor[1] * h;
|
|
29267
|
+
ctx.save();
|
|
29268
|
+
ctx.globalAlpha = paintOf(l, 'icon-opacity', c, 1);
|
|
29269
|
+
ctx.translate(x, y);
|
|
29270
|
+
if (angle) { ctx.rotate(angle); }
|
|
29271
|
+
ctx.drawImage(img.source, dx, dy, w, h);
|
|
29272
|
+
ctx.restore();
|
|
29273
|
+
return angle ? null : [x + dx, y + dy, x + dx + w, y + dy + h];
|
|
29274
|
+
};
|
|
29275
|
+
|
|
29276
|
+
CanvasMap.prototype._text = function (ctx, l, c, text, x, y, angle) {
|
|
29277
|
+
var size = layoutOf(l, 'text-size', c, 16);
|
|
29278
|
+
var font = layoutOf(l, 'text-font', c, ['Noto Sans Regular']);
|
|
29279
|
+
var bold = /Bold|Medium|Semibold/i.test(Array.isArray(font) ? font[0] : font);
|
|
29280
|
+
ctx.save();
|
|
29281
|
+
ctx.font = (bold ? '600 ' : '') + size + 'px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Arial, sans-serif';
|
|
29282
|
+
var w = ctx.measureText(text).width, h = size * 1.2;
|
|
29283
|
+
var anchor = ANCHOR_SHIFT[layoutOf(l, 'text-anchor', c, 'center')] || ANCHOR_SHIFT.center;
|
|
29284
|
+
var off = layoutOf(l, 'text-offset', c, [0, 0]);
|
|
29285
|
+
var dx = off[0] * size + anchor[0] * w, dy = off[1] * size + anchor[1] * h;
|
|
29286
|
+
ctx.translate(x, y);
|
|
29287
|
+
if (angle) { ctx.rotate(angle); }
|
|
29288
|
+
ctx.textBaseline = 'middle';
|
|
29289
|
+
var halo = paintOf(l, 'text-halo-width', c, 0);
|
|
29290
|
+
if (halo > 0) { ctx.lineWidth = halo * 2; ctx.strokeStyle = paintOf(l, 'text-halo-color', c, '#ffffff'); ctx.lineJoin = 'round'; ctx.strokeText(text, dx, dy + h / 2); }
|
|
29291
|
+
ctx.globalAlpha = paintOf(l, 'text-opacity', c, 1);
|
|
29292
|
+
ctx.fillStyle = paintOf(l, 'text-color', c, '#000000');
|
|
29293
|
+
ctx.fillText(text, dx, dy + h / 2);
|
|
29294
|
+
ctx.restore();
|
|
29295
|
+
return angle ? null : [x + dx, y + dy, x + dx + w, y + dy + h];
|
|
29296
|
+
};
|
|
29297
|
+
|
|
29298
|
+
// A heat map: each point a blurred spot of its weight on an offscreen canvas, then coloured by density.
|
|
29299
|
+
CanvasMap.prototype._drawHeatmap = function (ctx, l, features) {
|
|
29300
|
+
var self = this, zoom = this._zoom, w = this._w, h = this._h;
|
|
29301
|
+
var off = this._heatCanvas = this._heatCanvas || this._doc.createElement('canvas');
|
|
29302
|
+
off.width = w; off.height = h;
|
|
29303
|
+
var hc = off.getContext('2d');
|
|
29304
|
+
if (!hc) { return; }
|
|
29305
|
+
hc.clearRect(0, 0, w, h);
|
|
29306
|
+
var radius = paintOf(l, 'heatmap-radius', { zoom: zoom }, 30), intensity = paintOf(l, 'heatmap-intensity', { zoom: zoom }, 1);
|
|
29307
|
+
features.forEach(function (f) {
|
|
29308
|
+
var c = { zoom: zoom, f: f };
|
|
29309
|
+
if (!f.geometry || !passes(l.filter, c)) { return; }
|
|
29310
|
+
var weight = Math.max(0, Math.min(1, paintOf(l, 'heatmap-weight', c, 1) * intensity * 0.35));
|
|
29311
|
+
self._screen(f.geometry).forEach(function (p) {
|
|
29312
|
+
if (!Array.isArray(p) || typeof p[0] !== 'number' || p[0] < -radius || p[1] < -radius || p[0] > w + radius || p[1] > h + radius) { return; }
|
|
29313
|
+
// A falloff close to the engine's Gaussian kernel of the same radius.
|
|
29314
|
+
var g = hc.createRadialGradient(p[0], p[1], 0, p[0], p[1], radius);
|
|
29315
|
+
g.addColorStop(0, 'rgba(0,0,0,' + weight + ')');
|
|
29316
|
+
g.addColorStop(0.35, 'rgba(0,0,0,' + (weight * 0.45) + ')');
|
|
29317
|
+
g.addColorStop(0.7, 'rgba(0,0,0,' + (weight * 0.08) + ')');
|
|
29318
|
+
g.addColorStop(1, 'rgba(0,0,0,0)');
|
|
29319
|
+
hc.fillStyle = g;
|
|
29320
|
+
hc.fillRect(p[0] - radius, p[1] - radius, radius * 2, radius * 2);
|
|
29321
|
+
});
|
|
29322
|
+
});
|
|
29323
|
+
var img = hc.getImageData(0, 0, w, h), d = img.data;
|
|
29324
|
+
// The colour ramp, sampled once for 256 densities.
|
|
29325
|
+
var ramp = this._ramp(l), opacity = paintOf(l, 'heatmap-opacity', { zoom: zoom }, 1);
|
|
29326
|
+
for (var i = 0; i < d.length; i += 4) {
|
|
29327
|
+
var a = d[i + 3];
|
|
29328
|
+
if (!a) { continue; }
|
|
29329
|
+
var k = a * 4;
|
|
29330
|
+
d[i] = ramp[k]; d[i + 1] = ramp[k + 1]; d[i + 2] = ramp[k + 2]; d[i + 3] = ramp[k + 3] * opacity;
|
|
29331
|
+
}
|
|
29332
|
+
hc.putImageData(img, 0, 0);
|
|
29333
|
+
ctx.save();
|
|
29334
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
29335
|
+
ctx.drawImage(off, 0, 0, w, h, 0, 0, this._canvas.width, this._canvas.height);
|
|
29336
|
+
ctx.restore();
|
|
29337
|
+
};
|
|
29338
|
+
|
|
29339
|
+
CanvasMap.prototype._ramp = function (l) {
|
|
29340
|
+
var expr = l.paint && l.paint['heatmap-color'];
|
|
29341
|
+
var key = JSON.stringify(expr);
|
|
29342
|
+
if (this._rampKey === key && this._rampData) { return this._rampData; }
|
|
29343
|
+
var c = this._doc.createElement('canvas');
|
|
29344
|
+
c.width = 256; c.height = 1;
|
|
29345
|
+
var x = c.getContext('2d');
|
|
29346
|
+
var stops = Array.isArray(expr) && expr[0] === 'interpolate' ? expr.slice(3) : [0, 'rgba(0,0,255,0)', 0.5, 'rgb(0,255,0)', 1, 'rgb(255,0,0)'];
|
|
29347
|
+
var grad = x.createLinearGradient(0, 0, 256, 0);
|
|
29348
|
+
for (var i = 0; i < stops.length; i += 2) { grad.addColorStop(Math.max(0, Math.min(1, stops[i])), stops[i + 1]); }
|
|
29349
|
+
x.fillStyle = grad;
|
|
29350
|
+
x.fillRect(0, 0, 256, 1);
|
|
29351
|
+
this._rampKey = key;
|
|
29352
|
+
this._rampData = x.getImageData(0, 0, 256, 1).data;
|
|
29353
|
+
return this._rampData;
|
|
29354
|
+
};
|
|
29355
|
+
|
|
29356
|
+
// ---- hit tests
|
|
29357
|
+
|
|
29358
|
+
function hits(entry, x0, y0, x1, y1) {
|
|
29359
|
+
var h = entry.hit, cx = (x0 + x1) / 2, cy = (y0 + y1) / 2, pad = Math.max(x1 - x0, y1 - y0) / 2;
|
|
29360
|
+
if (h.kind === 'circle') { return Math.sqrt(Math.pow(h.x - cx, 2) + Math.pow(h.y - cy, 2)) <= h.r + pad; }
|
|
29361
|
+
if (h.kind === 'box') { return h.box[0] <= x1 && h.box[2] >= x0 && h.box[1] <= y1 && h.box[3] >= y0; }
|
|
29362
|
+
if (h.kind === 'line') {
|
|
29363
|
+
var reach = h.width / 2 + Math.max(3, pad);
|
|
29364
|
+
for (var i = 0; i < h.lines.length; i++) {
|
|
29365
|
+
var ln = h.lines[i];
|
|
29366
|
+
for (var j = 1; j < ln.length; j++) { if (segDist(cx, cy, ln[j - 1][0], ln[j - 1][1], ln[j][0], ln[j][1]) <= reach) { return true; } }
|
|
29367
|
+
}
|
|
29368
|
+
return false;
|
|
29369
|
+
}
|
|
29370
|
+
if (h.kind === 'fill') {
|
|
29371
|
+
return h.polys.some(function (rings) {
|
|
29372
|
+
if (!rings.length || !inRing(cx, cy, rings[0])) { return false; }
|
|
29373
|
+
for (var k = 1; k < rings.length; k++) { if (inRing(cx, cy, rings[k])) { return false; } }
|
|
29374
|
+
return true;
|
|
29375
|
+
});
|
|
29376
|
+
}
|
|
29377
|
+
return false;
|
|
29378
|
+
}
|
|
29379
|
+
|
|
29380
|
+
/** Features drawn at a point or in a box, top first: { type, id, properties, geometry, layer: { id }, source }. */
|
|
29381
|
+
CanvasMap.prototype.queryRenderedFeatures = function (at, o) {
|
|
29382
|
+
if (at && !Array.isArray(at) && at.layers) { o = at; at = null; }
|
|
29383
|
+
o = o || {};
|
|
29384
|
+
var x0, y0, x1, y1;
|
|
29385
|
+
if (!at) { x0 = 0; y0 = 0; x1 = this._w; y1 = this._h; }
|
|
29386
|
+
else if (Array.isArray(at) && Array.isArray(at[0])) { x0 = Math.min(at[0][0], at[1][0]); x1 = Math.max(at[0][0], at[1][0]); y0 = Math.min(at[0][1], at[1][1]); y1 = Math.max(at[0][1], at[1][1]); }
|
|
29387
|
+
else { var p = Array.isArray(at) ? { x: at[0], y: at[1] } : at; x0 = x1 = p.x; y0 = y1 = p.y; }
|
|
29388
|
+
var out = [], seen = [];
|
|
29389
|
+
for (var i = this._drawn.length - 1; i >= 0; i--) {
|
|
29390
|
+
var d = this._drawn[i];
|
|
29391
|
+
if (o.layers && o.layers.indexOf(d.layer.id) < 0) { continue; }
|
|
29392
|
+
if (!hits(d, x0, y0, x1, y1)) { continue; }
|
|
29393
|
+
if (seen.indexOf(d) >= 0) { continue; }
|
|
29394
|
+
seen.push(d);
|
|
29395
|
+
out.push({ type: 'Feature', id: d.f.id, properties: d.f.properties || {}, geometry: d.f.geometry, layer: { id: d.layer.id, type: d.layer.type }, source: d.layer.source });
|
|
29396
|
+
}
|
|
29397
|
+
return out;
|
|
29398
|
+
};
|
|
29399
|
+
|
|
29400
|
+
CanvasMap.prototype.querySourceFeatures = function (id, o) {
|
|
29401
|
+
var src = this._sources[id];
|
|
29402
|
+
if (!src || !src._shown) { return []; }
|
|
29403
|
+
var zoom = this._zoom, filter = o && o.filter;
|
|
29404
|
+
return src._shown(zoom).filter(function (f) { return passes(filter, { zoom: zoom, f: f }); });
|
|
29405
|
+
};
|
|
29406
|
+
|
|
29407
|
+
// ---- input: pointer, wheel, touch, keyboard
|
|
29408
|
+
|
|
29409
|
+
CanvasMap.prototype._event = function (type, original, point) {
|
|
29410
|
+
var self = this;
|
|
29411
|
+
var e = {
|
|
29412
|
+
type: type, originalEvent: original, point: point, lngLat: this.unproject(point), target: this, defaultPrevented: false,
|
|
29413
|
+
preventDefault: function () { e.defaultPrevented = true; if (type === 'mousedown' || type === 'touchstart') { self._drag = null; } },
|
|
29414
|
+
};
|
|
29415
|
+
if (original && original.touches) { e.points = Array.prototype.map.call(original.touches, function (t) { return self._point(t); }); }
|
|
29416
|
+
return e;
|
|
29417
|
+
};
|
|
29418
|
+
|
|
29419
|
+
CanvasMap.prototype._point = function (ev) {
|
|
29420
|
+
var r = this._container.getBoundingClientRect();
|
|
29421
|
+
return { x: ev.clientX - r.left, y: ev.clientY - r.top };
|
|
29422
|
+
};
|
|
29423
|
+
|
|
29424
|
+
// A map event to the map's listeners and to the layers' listeners whose features are under the point.
|
|
29425
|
+
CanvasMap.prototype._dispatch = function (type, original, point) {
|
|
29426
|
+
var e = this._event(type, original, point);
|
|
29427
|
+
var self = this;
|
|
29428
|
+
var layerTypes = { click: 1, dblclick: 1, mousedown: 1, mouseup: 1, mousemove: 1, touchstart: 1, touchend: 1, contextmenu: 1 };
|
|
29429
|
+
if (layerTypes[type]) {
|
|
29430
|
+
this._layerEvents.forEach(function (h) {
|
|
29431
|
+
if (h.type !== type) { return; }
|
|
29432
|
+
var features = self.queryRenderedFeatures(point, { layers: [h.layer] });
|
|
29433
|
+
if (!features.length) { return; }
|
|
29434
|
+
var le = self._event(type, original, point);
|
|
29435
|
+
le.features = features;
|
|
29436
|
+
h.fn(le);
|
|
29437
|
+
if (le.defaultPrevented) { e.preventDefault(); }
|
|
29438
|
+
});
|
|
29439
|
+
}
|
|
29440
|
+
if (type === 'mousemove') {
|
|
29441
|
+
// mouseenter / mouseleave per layer.
|
|
29442
|
+
this._layerEvents.forEach(function (h) {
|
|
29443
|
+
if (h.type !== 'mouseenter' && h.type !== 'mouseleave') { return; }
|
|
29444
|
+
var features = self.queryRenderedFeatures(point, { layers: [h.layer] });
|
|
29445
|
+
var inside = features.length > 0;
|
|
29446
|
+
if (inside && !h.inside && h.type === 'mouseenter') { var en = self._event('mouseenter', original, point); en.features = features; h.fn(en); }
|
|
29447
|
+
if (!inside && h.inside && h.type === 'mouseleave') { h.fn(self._event('mouseleave', original, point)); }
|
|
29448
|
+
h.inside = inside;
|
|
29449
|
+
});
|
|
29450
|
+
}
|
|
29451
|
+
(this._events[type] || []).slice().forEach(function (fn) { fn(e); });
|
|
29452
|
+
return e;
|
|
29453
|
+
};
|
|
29454
|
+
|
|
29455
|
+
CanvasMap.prototype._input = function () {
|
|
29456
|
+
var self = this, el = this._canvasContainer, doc = this._doc;
|
|
29457
|
+
var down = null, moved = false, lastClick = 0;
|
|
29458
|
+
var start = function (ev, point) {
|
|
29459
|
+
var e = self._dispatch(ev.touches ? 'touchstart' : 'mousedown', ev, point);
|
|
29460
|
+
moved = false;
|
|
29461
|
+
down = point;
|
|
29462
|
+
self._drag = e.defaultPrevented || !self.dragPan.on ? null : { from: point, center: self.project(self._center) };
|
|
29463
|
+
};
|
|
29464
|
+
el.addEventListener('mousedown', function (ev) {
|
|
29465
|
+
if (ev.button !== 0 || ev.target !== self._canvas) { return; }
|
|
29466
|
+
start(ev, self._point(ev));
|
|
29467
|
+
});
|
|
29468
|
+
doc.addEventListener('mousemove', function (ev) {
|
|
29469
|
+
var point = self._point(ev);
|
|
29470
|
+
if (self._drag) {
|
|
29471
|
+
var d = self._drag;
|
|
29472
|
+
if (!moved && Math.abs(point.x - d.from.x) + Math.abs(point.y - d.from.y) < 3) { return; }
|
|
29473
|
+
if (!moved) { self._fire('movestart', {}); self._fire('dragstart', {}); }
|
|
29474
|
+
moved = true;
|
|
29475
|
+
self._set(self.unproject([d.center.x - (point.x - d.from.x), d.center.y - (point.y - d.from.y)]));
|
|
29476
|
+
self._fire('drag', {});
|
|
29477
|
+
return;
|
|
29478
|
+
}
|
|
29479
|
+
if (down) { moved = moved || Math.abs(point.x - down.x) + Math.abs(point.y - down.y) >= 3; }
|
|
29480
|
+
var inside = point.x >= 0 && point.y >= 0 && point.x <= self._w && point.y <= self._h;
|
|
29481
|
+
if (inside || down) { self._dispatch('mousemove', ev, point); }
|
|
29482
|
+
else if (self._wasInside) { self._dispatch('mouseout', ev, point); }
|
|
29483
|
+
self._wasInside = inside;
|
|
29484
|
+
});
|
|
29485
|
+
doc.addEventListener('mouseup', function (ev) {
|
|
29486
|
+
if (!down) { return; }
|
|
29487
|
+
var point = self._point(ev);
|
|
29488
|
+
var wasDrag = !!self._drag && moved;
|
|
29489
|
+
self._drag = null;
|
|
29490
|
+
self._dispatch('mouseup', ev, point);
|
|
29491
|
+
if (wasDrag) { self._fire('dragend', {}); self._end(false); }
|
|
29492
|
+
else if (!moved && ev.target && (ev.target === self._canvas)) {
|
|
29493
|
+
var now = Date.now();
|
|
29494
|
+
self._dispatch('click', ev, point);
|
|
29495
|
+
if (now - lastClick < 300) {
|
|
29496
|
+
var dbl = self._dispatch('dblclick', ev, point);
|
|
29497
|
+
if (!dbl.defaultPrevented && self.doubleClickZoom.on) { self.easeTo({ zoom: self._zoom + (ev.shiftKey ? -1 : 1), center: self.unproject(point), duration: 250 }); }
|
|
29498
|
+
lastClick = 0;
|
|
29499
|
+
} else { lastClick = now; }
|
|
29500
|
+
}
|
|
29501
|
+
down = null;
|
|
29502
|
+
});
|
|
29503
|
+
el.addEventListener('wheel', function (ev) {
|
|
29504
|
+
if (!self.scrollZoom.on) { return; }
|
|
29505
|
+
ev.preventDefault();
|
|
29506
|
+
var point = self._point(ev);
|
|
29507
|
+
var dz = -ev.deltaY * (ev.deltaMode === 1 ? 0.05 : 0.0025);
|
|
29508
|
+
dz = Math.max(-1, Math.min(1, dz));
|
|
29509
|
+
var before = self.unproject(point);
|
|
29510
|
+
self._set(null, self._zoom + dz);
|
|
29511
|
+
// Keep the place under the pointer where it was.
|
|
29512
|
+
var p = self.project(before), c = self.project(self._center);
|
|
29513
|
+
self._set(self.unproject([c.x + p.x - point.x, c.y + p.y - point.y]));
|
|
29514
|
+
clearTimeout(self._wheelEnd);
|
|
29515
|
+
self._wheelEnd = setTimeout(function () { self._end(true); }, 150);
|
|
29516
|
+
}, { passive: false });
|
|
29517
|
+
// Touch: one finger pans, two fingers pinch.
|
|
29518
|
+
var pinch = null;
|
|
29519
|
+
el.addEventListener('touchstart', function (ev) {
|
|
29520
|
+
if (ev.touches.length === 1) { start(ev, self._point(ev.touches[0])); }
|
|
29521
|
+
else if (ev.touches.length === 2 && self.touchZoomRotate.on) {
|
|
29522
|
+
self._drag = null;
|
|
29523
|
+
var a = self._point(ev.touches[0]), b = self._point(ev.touches[1]);
|
|
29524
|
+
pinch = { d: Math.hypot(a.x - b.x, a.y - b.y), zoom: self._zoom, mid: self.unproject([(a.x + b.x) / 2, (a.y + b.y) / 2]) };
|
|
29525
|
+
}
|
|
29526
|
+
}, { passive: true });
|
|
29527
|
+
el.addEventListener('touchmove', function (ev) {
|
|
29528
|
+
if (pinch && ev.touches.length === 2) {
|
|
29529
|
+
ev.preventDefault();
|
|
29530
|
+
var a = self._point(ev.touches[0]), b = self._point(ev.touches[1]);
|
|
29531
|
+
var d = Math.hypot(a.x - b.x, a.y - b.y), mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
29532
|
+
self._set(null, pinch.zoom + Math.log2(d / Math.max(1, pinch.d)));
|
|
29533
|
+
var p = self.project(pinch.mid), c = self.project(self._center);
|
|
29534
|
+
self._set(self.unproject([c.x + p.x - mid.x, c.y + p.y - mid.y]));
|
|
29535
|
+
return;
|
|
29536
|
+
}
|
|
29537
|
+
if (ev.touches.length !== 1) { return; }
|
|
29538
|
+
var point = self._point(ev.touches[0]);
|
|
29539
|
+
if (self._drag) {
|
|
29540
|
+
ev.preventDefault();
|
|
29541
|
+
var dr = self._drag;
|
|
29542
|
+
moved = moved || Math.abs(point.x - dr.from.x) + Math.abs(point.y - dr.from.y) >= 3;
|
|
29543
|
+
self._set(self.unproject([dr.center.x - (point.x - dr.from.x), dr.center.y - (point.y - dr.from.y)]));
|
|
29544
|
+
return;
|
|
29545
|
+
}
|
|
29546
|
+
ev.preventDefault();
|
|
29547
|
+
moved = true;
|
|
29548
|
+
self._dispatch('touchmove', ev, point);
|
|
29549
|
+
}, { passive: false });
|
|
29550
|
+
el.addEventListener('touchend', function (ev) {
|
|
29551
|
+
if (pinch && ev.touches.length < 2) { pinch = null; self._end(true); return; }
|
|
29552
|
+
var t = ev.changedTouches[0];
|
|
29553
|
+
var point = t ? self._point(t) : down || { x: 0, y: 0 };
|
|
29554
|
+
var wasDrag = !!self._drag && moved;
|
|
29555
|
+
self._drag = null;
|
|
29556
|
+
self._dispatch('touchend', ev, point);
|
|
29557
|
+
if (wasDrag) { self._end(false); } else if (!moved) { self._dispatch('click', ev, point); }
|
|
29558
|
+
down = null;
|
|
29559
|
+
});
|
|
29560
|
+
this._canvas.addEventListener('keydown', function (ev) {
|
|
29561
|
+
if (!self.keyboard.on) { return; }
|
|
29562
|
+
var k = ev.key, step = 100;
|
|
29563
|
+
if (k === '+' || k === '=') { self.zoomIn(); } else if (k === '-') { self.zoomOut(); }
|
|
29564
|
+
else if (k === 'ArrowLeft') { self.panBy([-step, 0]); } else if (k === 'ArrowRight') { self.panBy([step, 0]); }
|
|
29565
|
+
else if (k === 'ArrowUp') { self.panBy([0, -step]); } else if (k === 'ArrowDown') { self.panBy([0, step]); }
|
|
29566
|
+
else { return; }
|
|
29567
|
+
ev.preventDefault();
|
|
29568
|
+
});
|
|
29569
|
+
};
|
|
29570
|
+
|
|
29571
|
+
// ------------------------------------------------------------------ popups
|
|
29572
|
+
|
|
29573
|
+
function Popup(options) {
|
|
29574
|
+
this.options = Object.assign({ closeButton: true, closeOnClick: true, offset: 0 }, options || {});
|
|
29575
|
+
this._events = {};
|
|
29576
|
+
}
|
|
29577
|
+
Popup.prototype.setLngLat = function (ll) { this._lngLat = toLngLat(ll); this._update(); return this; };
|
|
29578
|
+
Popup.prototype.getLngLat = function () { return this._lngLat ? new LngLat(this._lngLat.lng, this._lngLat.lat) : null; };
|
|
29579
|
+
Popup.prototype._ensure = function (doc) {
|
|
29580
|
+
if (this._el) { return; }
|
|
29581
|
+
var self = this;
|
|
29582
|
+
var el = this._el = doc.createElement('div');
|
|
29583
|
+
el.className = 'maplibregl-popup' + (this.options.className ? ' ' + this.options.className : '');
|
|
29584
|
+
el.style.position = 'absolute'; el.style.left = '0'; el.style.top = '0';
|
|
29585
|
+
var tip = doc.createElement('div'); tip.className = 'maplibregl-popup-tip';
|
|
29586
|
+
var content = this._content = doc.createElement('div'); content.className = 'maplibregl-popup-content';
|
|
29587
|
+
el.appendChild(tip); el.appendChild(content);
|
|
29588
|
+
if (this.options.maxWidth) { el.style.maxWidth = this.options.maxWidth; }
|
|
29589
|
+
if (this.options.closeButton) {
|
|
29590
|
+
var b = doc.createElement('button');
|
|
29591
|
+
b.type = 'button'; b.className = 'maplibregl-popup-close-button'; b.setAttribute('aria-label', 'Close popup'); b.innerHTML = '×';
|
|
29592
|
+
b.addEventListener('click', function () { self.remove(); });
|
|
29593
|
+
this._closeButton = b;
|
|
29594
|
+
}
|
|
29595
|
+
};
|
|
29596
|
+
Popup.prototype.setHTML = function (html) {
|
|
29597
|
+
var doc = (this._map && this._map._doc) || document;
|
|
29598
|
+
this._ensure(doc);
|
|
29599
|
+
this._content.innerHTML = html;
|
|
29600
|
+
if (this._closeButton) { this._content.appendChild(this._closeButton); }
|
|
29601
|
+
this._update();
|
|
29602
|
+
return this;
|
|
29603
|
+
};
|
|
29604
|
+
Popup.prototype.setDOMContent = function (node) {
|
|
29605
|
+
var doc = (this._map && this._map._doc) || node.ownerDocument || document;
|
|
29606
|
+
this._ensure(doc);
|
|
29607
|
+
while (this._content.firstChild) { this._content.removeChild(this._content.firstChild); }
|
|
29608
|
+
this._content.appendChild(node);
|
|
29609
|
+
if (this._closeButton) { this._content.appendChild(this._closeButton); }
|
|
29610
|
+
this._update();
|
|
29611
|
+
return this;
|
|
29612
|
+
};
|
|
29613
|
+
Popup.prototype.setText = function (t) { return this.setHTML(String(t).replace(/[&<>"']/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; })); };
|
|
29614
|
+
Popup.prototype.getElement = function () { return this._el || null; };
|
|
29615
|
+
Popup.prototype.isOpen = function () { return !!this._map; };
|
|
29616
|
+
Popup.prototype.addTo = function (map) {
|
|
29617
|
+
var self = this;
|
|
29618
|
+
this._map = map;
|
|
29619
|
+
this._ensure(map._doc);
|
|
29620
|
+
map.getCanvasContainer().appendChild(this._el);
|
|
29621
|
+
this._onMove = function () { self._update(); };
|
|
29622
|
+
map.on('move', this._onMove);
|
|
29623
|
+
map.on('resize', this._onMove);
|
|
29624
|
+
if (this.options.closeOnClick) {
|
|
29625
|
+
this._onClick = function () { self.remove(); };
|
|
29626
|
+
setTimeout(function () { if (self._map) { map.on('click', self._onClick); } }, 0);
|
|
29627
|
+
}
|
|
29628
|
+
this._update();
|
|
29629
|
+
this._fire('open');
|
|
29630
|
+
return this;
|
|
29631
|
+
};
|
|
29632
|
+
Popup.prototype.remove = function () {
|
|
29633
|
+
var map = this._map;
|
|
29634
|
+
if (!map) { return this; }
|
|
29635
|
+
if (this._el && this._el.parentNode) { this._el.parentNode.removeChild(this._el); }
|
|
29636
|
+
map.off('move', this._onMove);
|
|
29637
|
+
map.off('resize', this._onMove);
|
|
29638
|
+
if (this._onClick) { map.off('click', this._onClick); }
|
|
29639
|
+
this._map = null;
|
|
29640
|
+
this._fire('close');
|
|
29641
|
+
return this;
|
|
29642
|
+
};
|
|
29643
|
+
Popup.prototype.on = function (type, fn) { (this._events[type] = this._events[type] || []).push(fn); return this; };
|
|
29644
|
+
Popup.prototype.off = function (type, fn) { this._events[type] = (this._events[type] || []).filter(function (f) { return f !== fn; }); return this; };
|
|
29645
|
+
Popup.prototype._fire = function (type) { var self = this; (this._events[type] || []).slice().forEach(function (fn) { fn({ type: type, target: self }); }); };
|
|
29646
|
+
Popup.prototype._update = function () {
|
|
29647
|
+
if (!this._map || !this._el || !this._lngLat) { return; }
|
|
29648
|
+
var p = this._map.project(this._lngLat);
|
|
29649
|
+
var anchor = this.options.anchor || (p.y < this._el.offsetHeight + 20 ? 'top' : 'bottom');
|
|
29650
|
+
var off = this.options.offset, o = typeof off === 'number' ? { top: [0, off], bottom: [0, -off], left: [off, 0], right: [-off, 0] }[anchor.split('-')[0]] || [0, 0] : (Array.isArray(off) ? off : (off && off[anchor]) || [0, 0]);
|
|
29651
|
+
var shift = { center: [-50, -50], top: [-50, 0], bottom: [-50, -100], left: [0, -50], right: [-100, -50], 'top-left': [0, 0], 'top-right': [-100, 0], 'bottom-left': [0, -100], 'bottom-right': [-100, -100] }[anchor] || [-50, -100];
|
|
29652
|
+
var cls = this._el.className.replace(/\s*maplibregl-popup-anchor-[a-z-]+/g, '');
|
|
29653
|
+
this._el.className = cls + ' maplibregl-popup-anchor-' + anchor;
|
|
29654
|
+
this._el.style.transform = 'translate(' + shift[0] + '%, ' + shift[1] + '%) translate(' + Math.round(p.x + o[0]) + 'px, ' + Math.round(p.y + o[1]) + 'px)';
|
|
29655
|
+
};
|
|
29656
|
+
|
|
29657
|
+
// ------------------------------------------------------------------ controls (the engine's class names, so its stylesheet applies)
|
|
29658
|
+
|
|
29659
|
+
function NavigationControl(options) { this.options = Object.assign({ showZoom: true, showCompass: true }, options || {}); }
|
|
29660
|
+
NavigationControl.prototype.onAdd = function (map) {
|
|
29661
|
+
var doc = map._doc, el = this._el = doc.createElement('div');
|
|
29662
|
+
el.className = 'maplibregl-ctrl maplibregl-ctrl-group';
|
|
29663
|
+
var button = function (cls, label, fn) {
|
|
29664
|
+
var b = doc.createElement('button');
|
|
29665
|
+
b.type = 'button'; b.className = cls; b.title = label; b.setAttribute('aria-label', label);
|
|
29666
|
+
var icon = doc.createElement('span'); icon.className = 'maplibregl-ctrl-icon'; icon.setAttribute('aria-hidden', 'true');
|
|
29667
|
+
b.appendChild(icon);
|
|
29668
|
+
b.addEventListener('click', fn);
|
|
29669
|
+
el.appendChild(b);
|
|
29670
|
+
};
|
|
29671
|
+
if (this.options.showZoom) {
|
|
29672
|
+
button('maplibregl-ctrl-zoom-in', 'Zoom in', function () { map.zoomIn(); });
|
|
29673
|
+
button('maplibregl-ctrl-zoom-out', 'Zoom out', function () { map.zoomOut(); });
|
|
29674
|
+
}
|
|
29675
|
+
// The map never turns: no compass.
|
|
29676
|
+
return el;
|
|
29677
|
+
};
|
|
29678
|
+
NavigationControl.prototype.onRemove = function () { if (this._el && this._el.parentNode) { this._el.parentNode.removeChild(this._el); } };
|
|
29679
|
+
|
|
29680
|
+
function ScaleControl(options) { this.options = Object.assign({ maxWidth: 100, unit: 'metric' }, options || {}); }
|
|
29681
|
+
ScaleControl.prototype.onAdd = function (map) {
|
|
29682
|
+
var self = this, el = this._el = map._doc.createElement('div');
|
|
29683
|
+
el.className = 'maplibregl-ctrl maplibregl-ctrl-scale';
|
|
29684
|
+
this._map = map;
|
|
29685
|
+
this._update = function () {
|
|
29686
|
+
var y = map._h / 2, a = map.unproject([0, y]), b = map.unproject([self.options.maxWidth, y]);
|
|
29687
|
+
var R = 6371008.8, rad = Math.PI / 180;
|
|
29688
|
+
var m = 2 * R * Math.asin(Math.sqrt(Math.pow(Math.sin((b.lat - a.lat) * rad / 2), 2) + Math.cos(a.lat * rad) * Math.cos(b.lat * rad) * Math.pow(Math.sin((b.lng - a.lng) * rad / 2), 2)));
|
|
29689
|
+
var imperial = self.options.unit === 'imperial', v = imperial ? m * 3.28084 : m, unit = imperial ? 'ft' : 'm';
|
|
29690
|
+
if (imperial && v > 5280) { v /= 5280; unit = 'mi'; } else if (!imperial && v >= 1000) { v /= 1000; unit = 'km'; }
|
|
29691
|
+
var pow = Math.pow(10, Math.floor(Math.log10(v))), d = v / pow, nice = (d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1) * pow;
|
|
29692
|
+
el.style.width = Math.round(self.options.maxWidth * nice / v) + 'px';
|
|
29693
|
+
el.textContent = nice + ' ' + unit;
|
|
29694
|
+
};
|
|
29695
|
+
map.on('move', this._update);
|
|
29696
|
+
this._update();
|
|
29697
|
+
return el;
|
|
29698
|
+
};
|
|
29699
|
+
ScaleControl.prototype.onRemove = function () { if (this._map) { this._map.off('move', this._update); } if (this._el && this._el.parentNode) { this._el.parentNode.removeChild(this._el); } };
|
|
29700
|
+
|
|
29701
|
+
function FullscreenControl(options) { this.options = options || {}; }
|
|
29702
|
+
FullscreenControl.prototype.onAdd = function (map) {
|
|
29703
|
+
var doc = map._doc, self = this, el = this._el = doc.createElement('div');
|
|
29704
|
+
el.className = 'maplibregl-ctrl maplibregl-ctrl-group';
|
|
29705
|
+
var b = doc.createElement('button');
|
|
29706
|
+
b.type = 'button'; b.className = 'maplibregl-ctrl-fullscreen'; b.title = 'Enter fullscreen'; b.setAttribute('aria-label', 'Enter fullscreen');
|
|
29707
|
+
var icon = doc.createElement('span'); icon.className = 'maplibregl-ctrl-icon'; icon.setAttribute('aria-hidden', 'true');
|
|
29708
|
+
b.appendChild(icon);
|
|
29709
|
+
var target = this.options.container || map.getContainer();
|
|
29710
|
+
b.addEventListener('click', function () {
|
|
29711
|
+
if (doc.fullscreenElement) { doc.exitFullscreen(); } else if (target.requestFullscreen) { target.requestFullscreen(); }
|
|
29712
|
+
});
|
|
29713
|
+
this._change = function () {
|
|
29714
|
+
var on = doc.fullscreenElement === target;
|
|
29715
|
+
b.className = on ? 'maplibregl-ctrl-shrink' : 'maplibregl-ctrl-fullscreen';
|
|
29716
|
+
b.title = on ? 'Exit fullscreen' : 'Enter fullscreen';
|
|
29717
|
+
setTimeout(function () { map.resize(); }, 50);
|
|
29718
|
+
};
|
|
29719
|
+
doc.addEventListener('fullscreenchange', this._change);
|
|
29720
|
+
el.appendChild(b);
|
|
29721
|
+
self._doc = doc;
|
|
29722
|
+
return el;
|
|
29723
|
+
};
|
|
29724
|
+
FullscreenControl.prototype.onRemove = function () { if (this._doc) { this._doc.removeEventListener('fullscreenchange', this._change); } if (this._el && this._el.parentNode) { this._el.parentNode.removeChild(this._el); } };
|
|
29725
|
+
|
|
29726
|
+
function GeolocateControl(options) { this.options = options || {}; }
|
|
29727
|
+
GeolocateControl.prototype.onAdd = function (map) {
|
|
29728
|
+
var doc = map._doc, el = this._el = doc.createElement('div');
|
|
29729
|
+
el.className = 'maplibregl-ctrl maplibregl-ctrl-group';
|
|
29730
|
+
var b = doc.createElement('button');
|
|
29731
|
+
b.type = 'button'; b.className = 'maplibregl-ctrl-geolocate'; b.title = 'Find my location'; b.setAttribute('aria-label', 'Find my location');
|
|
29732
|
+
var icon = doc.createElement('span'); icon.className = 'maplibregl-ctrl-icon'; icon.setAttribute('aria-hidden', 'true');
|
|
29733
|
+
b.appendChild(icon);
|
|
29734
|
+
var geo = doc.defaultView && doc.defaultView.navigator && doc.defaultView.navigator.geolocation;
|
|
29735
|
+
if (!geo) { b.disabled = true; }
|
|
29736
|
+
b.addEventListener('click', function () {
|
|
29737
|
+
if (!geo) { return; }
|
|
29738
|
+
geo.getCurrentPosition(function (pos) {
|
|
29739
|
+
map.flyTo({ center: [pos.coords.longitude, pos.coords.latitude], zoom: Math.max(map.getZoom(), 15) });
|
|
29740
|
+
map._fire('geolocate', { coords: pos.coords });
|
|
29741
|
+
}, function (err) { map._fire('error', { error: err }); }, { enableHighAccuracy: true, timeout: 10000 });
|
|
29742
|
+
});
|
|
29743
|
+
el.appendChild(b);
|
|
29744
|
+
return el;
|
|
29745
|
+
};
|
|
29746
|
+
GeolocateControl.prototype.onRemove = function () { if (this._el && this._el.parentNode) { this._el.parentNode.removeChild(this._el); } };
|
|
29747
|
+
|
|
29748
|
+
function AttributionControl(options) { this.options = options || {}; }
|
|
29749
|
+
AttributionControl.prototype.onAdd = function (map) {
|
|
29750
|
+
var el = this._el = map._doc.createElement('details');
|
|
29751
|
+
el.className = 'maplibregl-ctrl maplibregl-ctrl-attrib maplibregl-compact';
|
|
29752
|
+
var summary = map._doc.createElement('summary');
|
|
29753
|
+
summary.className = 'maplibregl-ctrl-attrib-button';
|
|
29754
|
+
summary.title = 'Toggle attribution';
|
|
29755
|
+
summary.setAttribute('aria-label', 'Toggle attribution');
|
|
29756
|
+
var inner = this._inner = map._doc.createElement('div');
|
|
29757
|
+
inner.className = 'maplibregl-ctrl-attrib-inner';
|
|
29758
|
+
el.appendChild(summary);
|
|
29759
|
+
el.appendChild(inner);
|
|
29760
|
+
this._map = map;
|
|
29761
|
+
// Open where there is room, as the engine's own control.
|
|
29762
|
+
if (map._w >= 640) { el.open = true; }
|
|
29763
|
+
this._update();
|
|
29764
|
+
return el;
|
|
29765
|
+
};
|
|
29766
|
+
AttributionControl.prototype._update = function () {
|
|
29767
|
+
if (!this._inner || !this._map) { return; }
|
|
29768
|
+
var parts = [this._map._attribution, this.options.customAttribution].filter(Boolean);
|
|
29769
|
+
this._inner.innerHTML = parts.join(' | ');
|
|
29770
|
+
};
|
|
29771
|
+
AttributionControl.prototype.onRemove = function () { if (this._el && this._el.parentNode) { this._el.parentNode.removeChild(this._el); } };
|
|
29772
|
+
|
|
29773
|
+
function ErrorEvent(error) { this.type = 'error'; this.error = error; }
|
|
29774
|
+
|
|
29775
|
+
/** Whether this browser can draw the WebGL map (WebGL 2). */
|
|
29776
|
+
function webgl2Available(doc) {
|
|
29777
|
+
try {
|
|
29778
|
+
var c = (doc || document).createElement('canvas');
|
|
29779
|
+
return !!(c.getContext && c.getContext('webgl2'));
|
|
29780
|
+
} catch (e) { return false; }
|
|
29781
|
+
}
|
|
29782
|
+
|
|
29783
|
+
return {
|
|
29784
|
+
Map: CanvasMap, Popup: Popup, LngLat: LngLat, LngLatBounds: LngLatBounds,
|
|
29785
|
+
NavigationControl: NavigationControl, ScaleControl: ScaleControl, FullscreenControl: FullscreenControl,
|
|
29786
|
+
GeolocateControl: GeolocateControl, AttributionControl: AttributionControl, ErrorEvent: ErrorEvent,
|
|
29787
|
+
addProtocol: addProtocol, removeProtocol: removeProtocol, webgl2Available: webgl2Available,
|
|
29788
|
+
evaluate: evaluate, clusterAt: clusterAt,
|
|
29789
|
+
};
|
|
29790
|
+
});
|
|
29791
|
+
|
|
29792
|
+
return module.exports;
|
|
29793
|
+
}
|
|
28330
29794
|
function smapCreatePlan() {
|
|
28331
29795
|
var module = { exports: {} };
|
|
28332
29796
|
var exports = module.exports;
|
|
@@ -28363,6 +29827,24 @@ function smapCreatePlan() {
|
|
|
28363
29827
|
// its Origin; a native app also names itself with X-SMap-App (and X-SMap-App-Cert on Android).
|
|
28364
29828
|
|
|
28365
29829
|
var DEFAULT_BASE = 'https://open.superroute.ca';
|
|
29830
|
+
// The sMap service a map key's configuration is read from (GET <smap>/plan/v1/config), unless the editor is told another.
|
|
29831
|
+
var DEFAULT_SMAP = 'https://tile.srsr.ca';
|
|
29832
|
+
// Fast path (OPEN-ROUTE-EDIT E19, §2f.5): a call there not answered within this long is repeated on the standard path.
|
|
29833
|
+
// Per call, in this one place: every call 2 s, except refine (its search runs up to 2 s inside sRoad, and sMap's own
|
|
29834
|
+
// refine budget is 4 s) 5 s.
|
|
29835
|
+
var FAST_PATH_TIMEOUT_MS = 2000;
|
|
29836
|
+
var FAST_PATH_REFINE_TIMEOUT_MS = 5000;
|
|
29837
|
+
/** The fast-path time limit of a call (`path` after /v1/plan), from the defaults or the options that replace them. */
|
|
29838
|
+
function fastPathTimeoutFor(path, o) {
|
|
29839
|
+
var refine = path === '/refine';
|
|
29840
|
+
var given = refine ? o.fastPathRefineTimeout : o.fastPathTimeout;
|
|
29841
|
+
if (typeof given === 'number' && given > 0) { return given; }
|
|
29842
|
+
return refine ? FAST_PATH_REFINE_TIMEOUT_MS : FAST_PATH_TIMEOUT_MS;
|
|
29843
|
+
}
|
|
29844
|
+
// A map key's configuration is kept this long; one that could not be read is asked for again after RETRY.
|
|
29845
|
+
var KEY_CONFIG_TTL_MS = 600000;
|
|
29846
|
+
var KEY_CONFIG_RETRY_MS = 60000;
|
|
29847
|
+
var PLAN_PREFIX = '/v1/plan';
|
|
28366
29848
|
// A token whose expiry can be read is renewed this many seconds before it ends.
|
|
28367
29849
|
var RENEW_BEFORE = 30;
|
|
28368
29850
|
var TOKEN_PREFIX = 'sr_pet_';
|
|
@@ -28385,19 +29867,27 @@ function smapCreatePlan() {
|
|
|
28385
29867
|
/**
|
|
28386
29868
|
* What a token provider gave: the token string, or the token answer of POST /v1/plan/token
|
|
28387
29869
|
* ({ access_token, token_type, expires_in, expires_at, scopes, origins }, or that answer's `{ data }` envelope).
|
|
28388
|
-
* Returns { token, exp } with exp in Unix seconds (0 when it cannot be told)
|
|
29870
|
+
* Returns { token, exp, fastPath } with exp in Unix seconds (0 when it cannot be told) and fastPath the answer's
|
|
29871
|
+
* `fast_path.url` (null without one: every call goes to the standard path).
|
|
28389
29872
|
*/
|
|
28390
29873
|
function readToken(t) {
|
|
28391
29874
|
if (t && typeof t === 'object' && t.data && typeof t.data === 'object' && !t.access_token) { t = t.data; }
|
|
28392
|
-
if (typeof t === 'string' && t) { return { token: t, exp: tokenExpiry(t) }; }
|
|
29875
|
+
if (typeof t === 'string' && t) { return { token: t, exp: tokenExpiry(t), fastPath: null }; }
|
|
28393
29876
|
if (t && typeof t === 'object' && typeof t.access_token === 'string' && t.access_token) {
|
|
28394
29877
|
var exp = t.expires_at ? Date.parse(t.expires_at) / 1000 : 0;
|
|
28395
29878
|
if (!exp && typeof t.expires_in === 'number') { exp = Date.now() / 1000 + t.expires_in; }
|
|
28396
|
-
return { token: t.access_token, exp: exp || tokenExpiry(t.access_token) };
|
|
29879
|
+
return { token: t.access_token, exp: exp || tokenExpiry(t.access_token), fastPath: fastPathOf(t) };
|
|
28397
29880
|
}
|
|
28398
29881
|
return null;
|
|
28399
29882
|
}
|
|
28400
29883
|
|
|
29884
|
+
/** The fast-path address of a token answer or a map key's configuration (`fast_path.url`), or null. */
|
|
29885
|
+
function fastPathOf(o) {
|
|
29886
|
+
var f = o && typeof o === 'object' ? o.fast_path : null;
|
|
29887
|
+
var url = f && typeof f === 'object' && typeof f.url === 'string' ? f.url.replace(/\/+$/, '') : '';
|
|
29888
|
+
return url ? url : null;
|
|
29889
|
+
}
|
|
29890
|
+
|
|
28401
29891
|
/** The app headers of a native app: options.app is 'android:<package>' / 'apple:<bundle id>' / 'windows:<id>', or { id, cert }. */
|
|
28402
29892
|
function appHeaders(app) {
|
|
28403
29893
|
var h = {};
|
|
@@ -28416,6 +29906,7 @@ function smapCreatePlan() {
|
|
|
28416
29906
|
if (c && typeof c.token === 'function') {
|
|
28417
29907
|
var token = null;
|
|
28418
29908
|
var exp = 0;
|
|
29909
|
+
var fast = null;
|
|
28419
29910
|
var pending = null;
|
|
28420
29911
|
var fetchToken = function () {
|
|
28421
29912
|
if (!pending) {
|
|
@@ -28425,6 +29916,7 @@ function smapCreatePlan() {
|
|
|
28425
29916
|
if (!read) { throw new Error('sMap plan editor: the token provider gave no token'); }
|
|
28426
29917
|
token = read.token;
|
|
28427
29918
|
exp = read.exp;
|
|
29919
|
+
fast = read.fastPath;
|
|
28428
29920
|
return token;
|
|
28429
29921
|
}, function (e) { pending = null; throw e; });
|
|
28430
29922
|
}
|
|
@@ -28438,6 +29930,8 @@ function smapCreatePlan() {
|
|
|
28438
29930
|
},
|
|
28439
29931
|
// After a 401 SESSION_EXPIRED: forget the token so the next call asks the provider again. True when a retry may help.
|
|
28440
29932
|
renew: function () { token = null; exp = 0; return true; },
|
|
29933
|
+
// The fast-path address the latest token answer gave (read after headers()), or null.
|
|
29934
|
+
fastPath: function () { return Promise.resolve(fast); },
|
|
28441
29935
|
};
|
|
28442
29936
|
}
|
|
28443
29937
|
if (c && typeof c.key === 'string' && c.key) {
|
|
@@ -28445,6 +29939,7 @@ function smapCreatePlan() {
|
|
|
28445
29939
|
kind: 'key',
|
|
28446
29940
|
headers: function () { return Promise.resolve({ Authorization: 'Bearer ' + c.key }); },
|
|
28447
29941
|
renew: function () { return false; },
|
|
29942
|
+
fastPath: null, // set by the transport: the key's configuration on sMap
|
|
28448
29943
|
};
|
|
28449
29944
|
}
|
|
28450
29945
|
throw new Error('sMap plan editor: credentials are { token: () => Promise<string | TokenAnswer> } or { key: string }');
|
|
@@ -28467,44 +29962,155 @@ function smapCreatePlan() {
|
|
|
28467
29962
|
function cancelled() { var e = new Error('cancelled'); e.name = 'AbortError'; e.cancelled = true; return e; }
|
|
28468
29963
|
|
|
28469
29964
|
/**
|
|
28470
|
-
* The transport: send(method, path, body, signal) resolves { status, body, data, matrix } (matrix: the
|
|
28471
|
-
* header
|
|
28472
|
-
*
|
|
29965
|
+
* The transport: send(method, path, body, signal) resolves { status, body, data, matrix, path } (matrix: the
|
|
29966
|
+
* X-Plan-Matrix header; path: 'fast' or 'standard', where the answer came from) or rejects with a PlanError. A 401
|
|
29967
|
+
* SESSION_EXPIRED with a token provider asks for a new token and repeats the call once; every other refusal
|
|
29968
|
+
* (SESSION_REVOKED, TOKEN_INVALID, 403 NOT_AUTHORIZED with its reason) is raised.
|
|
29969
|
+
*
|
|
29970
|
+
* Fast path (OPEN-ROUTE-EDIT E19, §2f.5): when the credential names a fast-path address (the token answer's
|
|
29971
|
+
* `fast_path.url`, or a map key's configuration from GET <smapUrl>/plan/v1/config), each call goes to
|
|
29972
|
+
* <url>/<same path> with the same method, credential, headers and body. A 409 USE_STANDARD_PATH (or the header
|
|
29973
|
+
* X-Plan-Path: standard), any 5xx, a network error or no answer within 2 s (refine: 5 s) repeats the same call once on the standard
|
|
29974
|
+
* path (<baseUrl>/v1/plan/<same path>); the next call tries the fast path again. 402, 429 and every other 4xx are final.
|
|
28473
29975
|
*/
|
|
28474
29976
|
function createTransport(o) {
|
|
28475
29977
|
o = o || {};
|
|
28476
29978
|
var base = String(o.baseUrl || DEFAULT_BASE).replace(/\/$/, '');
|
|
29979
|
+
var smap = String(o.smapUrl || DEFAULT_SMAP).replace(/\/$/, '');
|
|
29980
|
+
var fastOn = o.fastPath !== false;
|
|
29981
|
+
var timeoutMs = fastPathTimeoutFor('', o);
|
|
28477
29982
|
var creds = credentialsOf(o.credentials);
|
|
28478
29983
|
var app = appHeaders(o.app);
|
|
28479
29984
|
var fetchFn = o.fetch || (typeof fetch === 'function' ? function (url, init) { return fetch(url, init); } : null);
|
|
28480
29985
|
if (!fetchFn) { throw new Error('sMap plan editor: no fetch; pass options.fetch'); }
|
|
28481
29986
|
|
|
29987
|
+
function headersWith(h, json) {
|
|
29988
|
+
var headers = { Accept: 'application/json' };
|
|
29989
|
+
Object.keys(app).forEach(function (k) { headers[k] = app[k]; });
|
|
29990
|
+
Object.keys(h).forEach(function (k) { headers[k] = h[k]; });
|
|
29991
|
+
if (json) { headers['Content-Type'] = 'application/json'; }
|
|
29992
|
+
return headers;
|
|
29993
|
+
}
|
|
29994
|
+
|
|
29995
|
+
// One request, read to the end. With `limit` it gives up after that many ms ({ timedOut: true }); a network error
|
|
29996
|
+
// resolves { failed: error } unless the caller's own signal ended it (then it rejects, cancelled).
|
|
29997
|
+
function exchange(url, init, signal, limit) {
|
|
29998
|
+
if (!limit) {
|
|
29999
|
+
return fetchFn(url, init).then(read);
|
|
30000
|
+
}
|
|
30001
|
+
var inner = newController();
|
|
30002
|
+
var timedOut = false;
|
|
30003
|
+
var timer = null;
|
|
30004
|
+
var onAbort = function () { if (inner) { inner.abort(); } };
|
|
30005
|
+
if (signal) {
|
|
30006
|
+
if (signal.aborted) { return Promise.reject(cancelled()); }
|
|
30007
|
+
if (signal.addEventListener) { signal.addEventListener('abort', onAbort); }
|
|
30008
|
+
}
|
|
30009
|
+
var own = {};
|
|
30010
|
+
Object.keys(init).forEach(function (k) { own[k] = init[k]; });
|
|
30011
|
+
if (inner) { own.signal = inner.signal; }
|
|
30012
|
+
var done = function () {
|
|
30013
|
+
if (timer) { clearTimeout(timer); }
|
|
30014
|
+
if (signal && signal.removeEventListener) { signal.removeEventListener('abort', onAbort); }
|
|
30015
|
+
};
|
|
30016
|
+
var timeout = new Promise(function (resolve) {
|
|
30017
|
+
timer = setTimeout(function () { timedOut = true; if (inner) { inner.abort(); } resolve({ timedOut: true }); }, limit);
|
|
30018
|
+
});
|
|
30019
|
+
var answer = Promise.resolve().then(function () { return fetchFn(url, own); }).then(read).then(function (r) { return r; }, function (e) {
|
|
30020
|
+
if (signal && signal.aborted) { throw cancelled(); }
|
|
30021
|
+
if (timedOut) { return { timedOut: true }; }
|
|
30022
|
+
return { failed: e };
|
|
30023
|
+
});
|
|
30024
|
+
return Promise.race([answer, timeout]).then(function (r) {
|
|
30025
|
+
done();
|
|
30026
|
+
if (signal && signal.aborted) { throw cancelled(); }
|
|
30027
|
+
return r;
|
|
30028
|
+
}, function (e) { done(); throw e; });
|
|
30029
|
+
}
|
|
30030
|
+
|
|
30031
|
+
function read(res) {
|
|
30032
|
+
return res.text().then(function (text) {
|
|
30033
|
+
var json = null;
|
|
30034
|
+
try { json = text ? JSON.parse(text) : null; } catch (e) { json = null; }
|
|
30035
|
+
var get = function (k) { return res.headers && typeof res.headers.get === 'function' ? res.headers.get(k) : null; };
|
|
30036
|
+
return { status: res.status, ok: res.ok, json: json, matrix: get('X-Plan-Matrix'), planPath: get('X-Plan-Path') };
|
|
30037
|
+
});
|
|
30038
|
+
}
|
|
30039
|
+
|
|
30040
|
+
// A map key's configuration on sMap: GET <smap>/plan/v1/config -> {"fast_path": {"url"}} or {}.
|
|
30041
|
+
var keyConfig = { url: null, until: 0, pending: null };
|
|
30042
|
+
function keyFastPath(h) {
|
|
30043
|
+
if (Date.now() < keyConfig.until) { return Promise.resolve(keyConfig.url); }
|
|
30044
|
+
if (keyConfig.pending) { return keyConfig.pending; }
|
|
30045
|
+
keyConfig.pending = exchange(smap + '/plan/v1/config', { method: 'GET', headers: headersWith(h, false) }, null, timeoutMs).then(function (r) {
|
|
30046
|
+
if (r.status >= 200 && r.status < 300) {
|
|
30047
|
+
keyConfig.url = fastPathOf(r.json);
|
|
30048
|
+
keyConfig.until = Date.now() + KEY_CONFIG_TTL_MS;
|
|
30049
|
+
} else {
|
|
30050
|
+
// A refusal is an answer (no fast path for this key); a failure is asked again after a minute.
|
|
30051
|
+
keyConfig.url = null;
|
|
30052
|
+
keyConfig.until = Date.now() + (r.status >= 400 && r.status < 500 ? KEY_CONFIG_TTL_MS : KEY_CONFIG_RETRY_MS);
|
|
30053
|
+
}
|
|
30054
|
+
return keyConfig.url;
|
|
30055
|
+
}, function () {
|
|
30056
|
+
keyConfig.url = null;
|
|
30057
|
+
keyConfig.until = Date.now() + KEY_CONFIG_RETRY_MS;
|
|
30058
|
+
return null;
|
|
30059
|
+
}).then(function (url) { keyConfig.pending = null; return url; });
|
|
30060
|
+
return keyConfig.pending;
|
|
30061
|
+
}
|
|
30062
|
+
|
|
30063
|
+
function fastPathFor(h) {
|
|
30064
|
+
if (!fastOn) { return Promise.resolve(null); }
|
|
30065
|
+
return creds.fastPath ? creds.fastPath() : keyFastPath(h);
|
|
30066
|
+
}
|
|
30067
|
+
|
|
30068
|
+
// Repeat on the standard path (§2f.5)?
|
|
30069
|
+
function useStandard(r) {
|
|
30070
|
+
if (r.timedOut || r.failed) { return true; }
|
|
30071
|
+
if (r.status >= 500) { return true; }
|
|
30072
|
+
if (r.status !== 409) { return false; }
|
|
30073
|
+
return !!(r.json && r.json.error && r.json.error.code === 'USE_STANDARD_PATH') || String(r.planPath || '').toLowerCase() === 'standard';
|
|
30074
|
+
}
|
|
30075
|
+
|
|
28482
30076
|
function send(method, path, body, signal, retried) {
|
|
28483
|
-
|
|
28484
|
-
|
|
28485
|
-
|
|
28486
|
-
|
|
28487
|
-
|
|
28488
|
-
if (
|
|
28489
|
-
|
|
28490
|
-
|
|
28491
|
-
|
|
28492
|
-
|
|
28493
|
-
|
|
28494
|
-
|
|
28495
|
-
|
|
28496
|
-
|
|
28497
|
-
|
|
28498
|
-
|
|
28499
|
-
|
|
28500
|
-
|
|
28501
|
-
|
|
28502
|
-
|
|
28503
|
-
return
|
|
30077
|
+
var h = null;
|
|
30078
|
+
return creds.headers().then(function (got) {
|
|
30079
|
+
h = got;
|
|
30080
|
+
return fastPathFor(h);
|
|
30081
|
+
}).then(function (fast) {
|
|
30082
|
+
if (signal && signal.aborted) { throw cancelled(); }
|
|
30083
|
+
var init = { method: method, headers: headersWith(h, body !== undefined) };
|
|
30084
|
+
if (body !== undefined) { init.body = JSON.stringify(body); }
|
|
30085
|
+
var standard = function () {
|
|
30086
|
+
var s = {};
|
|
30087
|
+
Object.keys(init).forEach(function (k) { s[k] = init[k]; });
|
|
30088
|
+
s.headers = headersWith(h, body !== undefined);
|
|
30089
|
+
if (signal) { s.signal = signal; }
|
|
30090
|
+
return exchange(base + path, s, signal, 0).then(function (r) { r.via = 'standard'; return r; });
|
|
30091
|
+
};
|
|
30092
|
+
if (!fast || path.indexOf(PLAN_PREFIX + '/') !== 0) { return standard(); }
|
|
30093
|
+
var rest = path.slice(PLAN_PREFIX.length);
|
|
30094
|
+
return exchange(fast + rest, init, signal, fastPathTimeoutFor(rest, o)).then(function (r) {
|
|
30095
|
+
if (useStandard(r)) { return standard(); }
|
|
30096
|
+
r.via = 'fast';
|
|
30097
|
+
return r;
|
|
28504
30098
|
});
|
|
30099
|
+
}).then(function (r) {
|
|
30100
|
+
if (!r.ok) {
|
|
30101
|
+
var json = r.json;
|
|
30102
|
+
var err = (json && json.error) || {};
|
|
30103
|
+
if (r.status === 401 && err.code === 'SESSION_EXPIRED' && !retried && creds.renew()) { return send(method, path, body, signal, true); }
|
|
30104
|
+
var e = PlanError(err.message || ('HTTP ' + r.status), {
|
|
30105
|
+
status: r.status, code: err.code || null, details: err.details || null, reason: err.reason || (json && json.reason) || null, path: path,
|
|
30106
|
+
});
|
|
30107
|
+
e.via = r.via;
|
|
30108
|
+
throw e;
|
|
30109
|
+
}
|
|
30110
|
+
return { status: r.status, body: r.json, data: r.json && r.json.data, matrix: r.matrix, path: r.via };
|
|
28505
30111
|
});
|
|
28506
30112
|
}
|
|
28507
|
-
return { send: send, kind: creds.kind, baseUrl: base };
|
|
30113
|
+
return { send: send, kind: creds.kind, baseUrl: base, smapUrl: smap };
|
|
28508
30114
|
}
|
|
28509
30115
|
|
|
28510
30116
|
// ------------------------------------------------------------------ plans
|
|
@@ -28657,7 +30263,12 @@ function smapCreatePlan() {
|
|
|
28657
30263
|
* @param {Object} options
|
|
28658
30264
|
* @param {Object} options.credentials { token: () => Promise<string | TokenAnswer> } or { key: string }
|
|
28659
30265
|
* @param {string|Object} [options.app] a native app's identity: 'android:<package>' / 'apple:<bundle id>' / 'windows:<id>', or { id, cert }
|
|
28660
|
-
* @param {string} [options.baseUrl] the Open Platform (default https://open.superroute.ca)
|
|
30266
|
+
* @param {string} [options.baseUrl] the Open Platform (default https://open.superroute.ca): the standard path
|
|
30267
|
+
* @param {string} [options.smapUrl] the sMap service a map key's configuration is read from (default
|
|
30268
|
+
* https://tile.srsr.ca; map.planEditor passes the map's own sMap address)
|
|
30269
|
+
* @param {boolean} [options.fastPath=true] use the fast path when the credential names one (false: standard only)
|
|
30270
|
+
* @param {number} [options.fastPathTimeout=2000] ms a fast-path call may take before it is repeated on the standard path
|
|
30271
|
+
* @param {number} [options.fastPathRefineTimeout=5000] the same for refine (its search takes longer)
|
|
28661
30272
|
* @param {Function} [options.fetch] fetch to use (default the page's)
|
|
28662
30273
|
* @param {Object} [options.plan] the starting plan: { vehicles, jobs, routes, region?, profile?, departed_at? }
|
|
28663
30274
|
* @param {string} [options.region] used when the plan names none
|
|
@@ -28693,6 +30304,7 @@ function smapCreatePlan() {
|
|
|
28693
30304
|
this._opening = null;
|
|
28694
30305
|
this._openingKey = null;
|
|
28695
30306
|
this._matrixRetryAt = 0;
|
|
30307
|
+
this._path = null;
|
|
28696
30308
|
this._destroyed = false;
|
|
28697
30309
|
if (options.plan) { this.setPlan(options.plan).catch(function () {}); }
|
|
28698
30310
|
}
|
|
@@ -28761,7 +30373,7 @@ function smapCreatePlan() {
|
|
|
28761
30373
|
return {
|
|
28762
30374
|
plan: this.getPlan(), result: this.getResult(), busy: this._busy, error: this._error, canUndo: this.canUndo(),
|
|
28763
30375
|
canRedo: this.canRedo(), choices: clone(this._choices), locked: this.locked(), last: clone(this._last),
|
|
28764
|
-
preview: this._previewResult ? clone(this._previewResult) : null,
|
|
30376
|
+
preview: this._previewResult ? clone(this._previewResult) : null, path: this._path,
|
|
28765
30377
|
};
|
|
28766
30378
|
};
|
|
28767
30379
|
|
|
@@ -28971,7 +30583,7 @@ function smapCreatePlan() {
|
|
|
28971
30583
|
var e = this._history[this._at] || {};
|
|
28972
30584
|
this._emit('change', {
|
|
28973
30585
|
kind: kind, plan: this.getPlan(), result: this.getResult(), answer: this._last ? clone(this._last.answer) : null,
|
|
28974
|
-
pending: !e.result, canUndo: this.canUndo(), canRedo: this.canRedo(),
|
|
30586
|
+
pending: !e.result, canUndo: this.canUndo(), canRedo: this.canRedo(), path: this._path,
|
|
28975
30587
|
});
|
|
28976
30588
|
};
|
|
28977
30589
|
|
|
@@ -29071,7 +30683,7 @@ function smapCreatePlan() {
|
|
|
29071
30683
|
self._error = null;
|
|
29072
30684
|
self._setBusy(false);
|
|
29073
30685
|
if (self._history[self._at] === target || kind !== 'recost') { self._changed(kind); }
|
|
29074
|
-
return { kind: kind, plan: self.getPlan(), result: self.getResult(), answer: clone(answer) };
|
|
30686
|
+
return { kind: kind, plan: self.getPlan(), result: self.getResult(), answer: clone(answer), path: self._path };
|
|
29075
30687
|
}, function (err) {
|
|
29076
30688
|
var current = self._inflight === ticket;
|
|
29077
30689
|
if (current) { self._inflight = null; self._setBusy(false); }
|
|
@@ -29102,6 +30714,7 @@ function smapCreatePlan() {
|
|
|
29102
30714
|
if (matrix) { body.matrix = matrix; } else { delete body.matrix; }
|
|
29103
30715
|
return self._transport.send('POST', path, body, signal);
|
|
29104
30716
|
}).then(function (res) {
|
|
30717
|
+
if (!self._destroyed) { self._path = res.path || null; }
|
|
29105
30718
|
if (body.matrix) { self._snapshotSeen(res.matrix, body.matrix); }
|
|
29106
30719
|
return res;
|
|
29107
30720
|
}, function (err) {
|
|
@@ -29153,7 +30766,7 @@ function smapCreatePlan() {
|
|
|
29153
30766
|
}
|
|
29154
30767
|
self._matrixRetryAt = 0;
|
|
29155
30768
|
if (self._openingKey === key) { self._matrix = id; self._matrixKey = key; }
|
|
29156
|
-
self._emit('snapshot', { status: 'opened', matrix: id, expires_at: res.data.expires_at || null, node: res.data.node || null });
|
|
30769
|
+
self._emit('snapshot', { status: 'opened', matrix: id, expires_at: res.data.expires_at || null, node: res.data.node || null, path: res.path || null });
|
|
29157
30770
|
return id;
|
|
29158
30771
|
}, function (err) {
|
|
29159
30772
|
self._matrixRetryAt = Date.now() + SNAPSHOT_RETRY_MS;
|
|
@@ -29889,7 +31502,7 @@ function smapCreatePlan() {
|
|
|
29889
31502
|
*/
|
|
29890
31503
|
function attach(map, options) {
|
|
29891
31504
|
options = options || {};
|
|
29892
|
-
var editor = new PlanEditor(Object.assign({}, options, { plan: null }));
|
|
31505
|
+
var editor = new PlanEditor(Object.assign({}, options, { plan: null, smapUrl: options.smapUrl || map._base }));
|
|
29893
31506
|
var layer = attachToMap(map, editor, options);
|
|
29894
31507
|
var toolId = null;
|
|
29895
31508
|
var destroyed = false;
|
|
@@ -29949,12 +31562,12 @@ function smapCreatePlan() {
|
|
|
29949
31562
|
|
|
29950
31563
|
return module.exports;
|
|
29951
31564
|
}
|
|
29952
|
-
function smapCreateSdk(engine, interop, plan) {
|
|
31565
|
+
function smapCreateSdk(engine, interop, canvas, plan) {
|
|
29953
31566
|
var module = { exports: {} };
|
|
29954
31567
|
var exports = module.exports;
|
|
29955
31568
|
var define;
|
|
29956
31569
|
var require = function (name) { throw new Error('sMap: unexpected require of ' + name); };
|
|
29957
|
-
var self = { maplibregl: engine, smapInterop: interop, smapPlan: plan };
|
|
31570
|
+
var self = { maplibregl: engine, smapInterop: interop, smapCanvas: canvas, smapPlan: plan };
|
|
29958
31571
|
/*!
|
|
29959
31572
|
* sMap Web SDK — maps from Superroute's sMap service.
|
|
29960
31573
|
*
|
|
@@ -29968,14 +31581,15 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
29968
31581
|
if (typeof module === 'object' && module.exports) {
|
|
29969
31582
|
module.exports = factory(root.maplibregl !== undefined ? root.maplibregl : require('maplibre-gl'),
|
|
29970
31583
|
root.smapInterop !== undefined ? root.smapInterop : require('./smap-interop.js'),
|
|
31584
|
+
root.smapCanvas !== undefined ? root.smapCanvas : (typeof require === 'function' ? require('./smap-canvas.js') : null),
|
|
29971
31585
|
root.smapPlan !== undefined ? root.smapPlan : (typeof require === 'function' ? require('./smap-plan.js') : null));
|
|
29972
31586
|
} else {
|
|
29973
|
-
root.sMap = factory(root.maplibregl, root.smapInterop, root.smapPlan);
|
|
31587
|
+
root.sMap = factory(root.maplibregl, root.smapInterop, root.smapCanvas, root.smapPlan);
|
|
29974
31588
|
}
|
|
29975
|
-
})(typeof self !== 'undefined' ? self : this, function (engine, interop, plan) {
|
|
31589
|
+
})(typeof self !== 'undefined' ? self : this, function (engine, interop, canvasLib, plan) {
|
|
29976
31590
|
'use strict';
|
|
29977
31591
|
|
|
29978
|
-
var VERSION = '1.
|
|
31592
|
+
var VERSION = '1.2.0';
|
|
29979
31593
|
var FLAVORS = ['light', 'white', 'grayscale', 'dark', 'black'];
|
|
29980
31594
|
var BASEMAP_SOURCE = 'basemap';
|
|
29981
31595
|
var BUILDINGS_LAYER = 'smap-buildings-3d';
|
|
@@ -30386,12 +32000,14 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30386
32000
|
* @param {Object} [options.popup] default popup options of every layer and openPopup (className, maxWidth,
|
|
30387
32001
|
* minWidth, offset, anchor, closeButton, closeOnClick, focusAfterOpen)
|
|
30388
32002
|
* @param {string|Node} [options.popupLoading] shown while a popup's content is a pending promise (default: dots)
|
|
32003
|
+
* @param {'auto'|'webgl'|'canvas'} [options.renderer] 'auto' (default): the WebGL map, or the canvas map (raster
|
|
32004
|
+
* tiles, layers drawn in 2D) where the browser has no WebGL 2 (M61)
|
|
30389
32005
|
* Every other option (center, zoom, pitch, bearing, minZoom, maxZoom, maxBounds, hash, …) goes to the engine.
|
|
30390
32006
|
*/
|
|
30391
32007
|
function SMap(options) {
|
|
30392
32008
|
if (!(this instanceof SMap)) { return new SMap(options); }
|
|
30393
32009
|
options = options || {};
|
|
30394
|
-
if (!engine) { throw new Error('sMap: the rendering engine is not loaded'); }
|
|
32010
|
+
if (!engine && !canvasLib) { throw new Error('sMap: the rendering engine is not loaded'); }
|
|
30395
32011
|
this._base = (options.baseUrl || DEFAULT_BASE).replace(/\/$/, '');
|
|
30396
32012
|
this._token = options.token || null;
|
|
30397
32013
|
this._getToken = options.getToken || null;
|
|
@@ -30441,9 +32057,16 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30441
32057
|
this._toolHandlers = [];
|
|
30442
32058
|
|
|
30443
32059
|
var self = this;
|
|
32060
|
+
// The renderer (M61): the WebGL engine, or the canvas map where there is no WebGL 2 (or when asked for).
|
|
32061
|
+
var want = options.renderer || 'auto';
|
|
32062
|
+
// Asked of the page itself; without a page (a server, a test) the WebGL engine is kept.
|
|
32063
|
+
var hasPage = typeof document !== 'undefined' && !!document && typeof document.createElement === 'function';
|
|
32064
|
+
var useCanvas = !!canvasLib && (want === 'canvas' || (want === 'auto' && (!engine || (hasPage && !canvasLib.webgl2Available(document)))));
|
|
32065
|
+
this._lib = useCanvas ? canvasLib : engine;
|
|
32066
|
+
this.renderer = useCanvas ? 'canvas' : 'webgl';
|
|
30444
32067
|
var engineOptions = {};
|
|
30445
32068
|
for (var k in options) {
|
|
30446
|
-
if (Object.prototype.hasOwnProperty.call(options, k) && ['token', 'getToken', 'apiKey', 'style', 'language', 'baseUrl', 'projection', 'nearest', 'nodes', 'popup', 'popupLoading', 'autoZoom', 'keyboard3D', 'tools'].indexOf(k) < 0) {
|
|
32069
|
+
if (Object.prototype.hasOwnProperty.call(options, k) && ['token', 'getToken', 'apiKey', 'style', 'language', 'baseUrl', 'projection', 'nearest', 'nodes', 'popup', 'popupLoading', 'autoZoom', 'keyboard3D', 'tools', 'renderer'].indexOf(k) < 0) {
|
|
30447
32070
|
engineOptions[k] = options[k];
|
|
30448
32071
|
}
|
|
30449
32072
|
}
|
|
@@ -30451,6 +32074,11 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30451
32074
|
// The page's origin is its identity: no app header is sent from a browser. The key travels in the style URL
|
|
30452
32075
|
// (the service carries it into the tile URLs) and, for a style given as a URL or an object, as a header.
|
|
30453
32076
|
engineOptions.transformRequest = function (url) {
|
|
32077
|
+
// Tiles of the canvas map are images: the token rides in the query (sMap reads `token` there too).
|
|
32078
|
+
if (self.renderer === 'canvas') {
|
|
32079
|
+
if (self._token && url.indexOf(self._base) === 0 && !/[?&]token=/.test(url)) { url += (url.indexOf('?') >= 0 ? '&' : '?') + 'token=' + encodeURIComponent(self._token); }
|
|
32080
|
+
return { url: url };
|
|
32081
|
+
}
|
|
30454
32082
|
if ((self._token || self._apiKey) && url.indexOf(self._base) === 0) {
|
|
30455
32083
|
var headers = {};
|
|
30456
32084
|
if (self._token) { headers.Authorization = 'Bearer ' + self._token; }
|
|
@@ -30468,8 +32096,17 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30468
32096
|
options.nearest === false ? Promise.resolve() : nearestNode(this._base, options.nodes).then(function (base) { self._base = base; })
|
|
30469
32097
|
]);
|
|
30470
32098
|
this.ready = start.then(function () {
|
|
30471
|
-
engineOptions.style = self.
|
|
30472
|
-
|
|
32099
|
+
engineOptions.style = self._engineStyle();
|
|
32100
|
+
try {
|
|
32101
|
+
self.engine = new self._lib.Map(engineOptions);
|
|
32102
|
+
} catch (error) {
|
|
32103
|
+
// A WebGL context the browser refuses (a blocked GPU, too many maps): the canvas map instead, when allowed.
|
|
32104
|
+
if (self.renderer !== 'webgl' || want !== 'auto' || !canvasLib) { throw error; }
|
|
32105
|
+
self._lib = canvasLib;
|
|
32106
|
+
self.renderer = 'canvas';
|
|
32107
|
+
engineOptions.style = self._engineStyle();
|
|
32108
|
+
self.engine = new canvasLib.Map(engineOptions);
|
|
32109
|
+
}
|
|
30473
32110
|
deckCompat(self.engine);
|
|
30474
32111
|
if (self.engine.addControl) {
|
|
30475
32112
|
var badge = badgeControl();
|
|
@@ -30498,6 +32135,20 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30498
32135
|
else if (style) { this._customStyle = style; this._serverStyle = null; }
|
|
30499
32136
|
};
|
|
30500
32137
|
|
|
32138
|
+
// The style handed to the renderer: the vector style, or for the canvas map sMap's raster tiles of the same scheme.
|
|
32139
|
+
SMap.prototype._engineStyle = function () {
|
|
32140
|
+
if (this.renderer !== 'canvas') { return this._styleUrl(); }
|
|
32141
|
+
if (this._customStyle && typeof this._customStyle === 'object') { return this._customStyle; }
|
|
32142
|
+
var name = this._serverStyle || this._flavor;
|
|
32143
|
+
var win = typeof window !== 'undefined' ? window : {};
|
|
32144
|
+
var tiles = interop.rasterUrl(name, { baseUrl: this._base, lang: this._language, apiKey: this._apiKey, retina: (win.devicePixelRatio || 1) > 1 });
|
|
32145
|
+
return {
|
|
32146
|
+
version: 8,
|
|
32147
|
+
sources: { 'smap-raster': { type: 'raster', tiles: [tiles], tileSize: 256, maxzoom: 19, attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' } },
|
|
32148
|
+
layers: [{ id: 'smap-raster', type: 'raster', source: 'smap-raster' }],
|
|
32149
|
+
};
|
|
32150
|
+
};
|
|
32151
|
+
|
|
30501
32152
|
SMap.prototype._styleUrl = function () {
|
|
30502
32153
|
if (this._customStyle) { return this._customStyle; }
|
|
30503
32154
|
if (this._serverStyle) { return interop.styleUrl(this._serverStyle, { baseUrl: this._base, lang: this._language, apiKey: this._apiKey }); }
|
|
@@ -30528,7 +32179,7 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30528
32179
|
SMap.prototype.setStyle = function (style) {
|
|
30529
32180
|
this._takeStyle(style);
|
|
30530
32181
|
this._styleReady = false;
|
|
30531
|
-
this.engine.setStyle(this.
|
|
32182
|
+
this.engine.setStyle(this._engineStyle());
|
|
30532
32183
|
this._styleClass();
|
|
30533
32184
|
return this;
|
|
30534
32185
|
};
|
|
@@ -30605,7 +32256,7 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30605
32256
|
var k = POPUP_OPTIONS[i];
|
|
30606
32257
|
if (k !== 'className' && k !== 'minWidth' && k !== 'autoPan' && o[k] !== undefined) { engineOptions[k] = k === 'maxWidth' ? cssSize(o[k]) : o[k]; }
|
|
30607
32258
|
}
|
|
30608
|
-
var popup = new
|
|
32259
|
+
var popup = new this._lib.Popup(engineOptions).setLngLat(lngLat);
|
|
30609
32260
|
this._popup = popup;
|
|
30610
32261
|
var pending = isThenable(content);
|
|
30611
32262
|
var loading = o.loading !== undefined ? o.loading : (this._popupLoading !== undefined ? this._popupLoading : POPUP_LOADING);
|
|
@@ -30657,12 +32308,14 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30657
32308
|
|
|
30658
32309
|
SMap.prototype._reportError = function (error) {
|
|
30659
32310
|
var m = this.engine;
|
|
30660
|
-
if (m &&
|
|
32311
|
+
if (m && this._lib.ErrorEvent && m.fire) { m.fire(new this._lib.ErrorEvent(error)); } else if (typeof console !== 'undefined') { console.error(error); }
|
|
30661
32312
|
};
|
|
30662
32313
|
|
|
30663
32314
|
// A click on the map: the top feature of the layers with a popup, if any, opens its layer's popup.
|
|
30664
32315
|
SMap.prototype._popupClick = function (e) {
|
|
30665
32316
|
var m = this.engine;
|
|
32317
|
+
// The release of a drag is not a click.
|
|
32318
|
+
if (this._dragEndedAt && Date.now() - this._dragEndedAt < 300) { return; }
|
|
30666
32319
|
var ids = [];
|
|
30667
32320
|
var byEngineLayer = {};
|
|
30668
32321
|
for (var i = 0; i < this._order.length; i++) {
|
|
@@ -30789,7 +32442,7 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30789
32442
|
SMap.prototype.setLanguage = function (language) {
|
|
30790
32443
|
this._language = language;
|
|
30791
32444
|
// A scheme or a custom style of sMap is served per language; a style of your own keeps its labels.
|
|
30792
|
-
if (!this._customStyle) { this._styleReady = false; this.engine.setStyle(this.
|
|
32445
|
+
if (!this._customStyle) { this._styleReady = false; this.engine.setStyle(this._engineStyle()); }
|
|
30793
32446
|
return this;
|
|
30794
32447
|
};
|
|
30795
32448
|
|
|
@@ -30904,6 +32557,8 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30904
32557
|
var def = this._layers[id];
|
|
30905
32558
|
if (!def) { throw new Error('sMap: no layer ' + id); }
|
|
30906
32559
|
def.data = toFeatureCollection(data);
|
|
32560
|
+
if (def.kind === 'custom') { this._customData(id, def); return this; }
|
|
32561
|
+
if (def.kind === 'models') { if (this.engine) { this.engine.triggerRepaint(); } return this; }
|
|
30907
32562
|
if (def.hovered) { this._setHover(id, def, null); }
|
|
30908
32563
|
var src = this.engine.getSource('smap-' + id);
|
|
30909
32564
|
if (src) { src.setData(def.data); }
|
|
@@ -30915,6 +32570,8 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30915
32570
|
if (!def) { return this; }
|
|
30916
32571
|
def.hidden = !visible;
|
|
30917
32572
|
var m = this.engine;
|
|
32573
|
+
if (def.kind === 'custom') { if (!visible && this._tooltipFor && this._tooltipFor.id === id) { this._hideTooltip(); } this._layoutMarkers(); return this; }
|
|
32574
|
+
if (def.clusterSet) { this._layoutMarkers(); }
|
|
30918
32575
|
if (!m) { return this; }
|
|
30919
32576
|
def.engineLayers.forEach(function (l) { if (m.getLayer(l)) { m.setLayoutProperty(l, 'visibility', visible ? 'visible' : 'none'); } });
|
|
30920
32577
|
return this;
|
|
@@ -30947,7 +32604,7 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30947
32604
|
SMap.prototype.fitLayer = function (id, options) {
|
|
30948
32605
|
var def = this._layers[id];
|
|
30949
32606
|
if (!def || !def.data.features.length) { return this; }
|
|
30950
|
-
var b = new
|
|
32607
|
+
var b = new this._lib.LngLatBounds();
|
|
30951
32608
|
def.data.features.forEach(function extend(f) {
|
|
30952
32609
|
(function walk(c) { if (typeof c[0] === 'number') { b.extend(c); } else { c.forEach(walk); } })(f.geometry.coordinates);
|
|
30953
32610
|
});
|
|
@@ -30955,12 +32612,617 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30955
32612
|
return this;
|
|
30956
32613
|
};
|
|
30957
32614
|
|
|
30958
|
-
SMap.prototype.on = function () { this.engine.on.apply(this.engine, arguments); return this; };
|
|
30959
|
-
SMap.prototype.off = function () { this.engine.off.apply(this.engine, arguments); return this; };
|
|
30960
|
-
SMap.prototype.once = function () { return this.engine.once.apply(this.engine, arguments); };
|
|
30961
|
-
SMap.prototype.flyTo = function (o) { this.engine.flyTo(o); return this; };
|
|
30962
|
-
SMap.prototype.remove = function () { clearTimeout(this._refreshTimer); if (this.engine) { this.engine.remove(); } };
|
|
30963
|
-
|
|
32615
|
+
SMap.prototype.on = function () { this.engine.on.apply(this.engine, arguments); return this; };
|
|
32616
|
+
SMap.prototype.off = function () { this.engine.off.apply(this.engine, arguments); return this; };
|
|
32617
|
+
SMap.prototype.once = function () { return this.engine.once.apply(this.engine, arguments); };
|
|
32618
|
+
SMap.prototype.flyTo = function (o) { this.engine.flyTo(o); return this; };
|
|
32619
|
+
SMap.prototype.remove = function () { clearTimeout(this._refreshTimer); if (this.engine) { this.engine.remove(); } };
|
|
32620
|
+
|
|
32621
|
+
// ------------------------------------------------------------------ custom markers (G1), dragging (G2), tooltips (G5)
|
|
32622
|
+
|
|
32623
|
+
// A value that may differ per feature, read in JavaScript (custom markers, tooltips): a constant, a function of the
|
|
32624
|
+
// properties, or { property, default }. Engine expressions (arrays) are not read here and give the fallback.
|
|
32625
|
+
function plainValue(v, props, feature, fallback) {
|
|
32626
|
+
if (v === undefined || v === null) { return fallback; }
|
|
32627
|
+
if (typeof v === 'function') { var r = v(props || {}, feature); return r === undefined ? fallback : r; }
|
|
32628
|
+
if (Array.isArray(v)) { return fallback; }
|
|
32629
|
+
if (typeof v === 'object' && v.property) {
|
|
32630
|
+
var x = props ? props[v.property] : undefined;
|
|
32631
|
+
return x === undefined || x === null ? (v['default'] !== undefined ? v['default'] : fallback) : x;
|
|
32632
|
+
}
|
|
32633
|
+
return v;
|
|
32634
|
+
}
|
|
32635
|
+
|
|
32636
|
+
// Where an element's anchor sits, as the share of its own width and height to move it back by (CSS translate).
|
|
32637
|
+
var ANCHORS = {
|
|
32638
|
+
center: [-50, -50], top: [-50, 0], bottom: [-50, -100], left: [0, -50], right: [-100, -50],
|
|
32639
|
+
'top-left': [0, 0], 'top-right': [-100, 0], 'bottom-left': [0, -100], 'bottom-right': [-100, -100],
|
|
32640
|
+
};
|
|
32641
|
+
// Pixels outside the map an element is still laid out in, so one entering the view is already there.
|
|
32642
|
+
var MARKER_MARGIN = 64;
|
|
32643
|
+
var DRAG_THRESHOLD = 3;
|
|
32644
|
+
|
|
32645
|
+
function featureKey(def, f, i) {
|
|
32646
|
+
var idp = def.o.idProperty;
|
|
32647
|
+
if (idp && f.properties && f.properties[idp] !== undefined && f.properties[idp] !== null) { return String(f.properties[idp]); }
|
|
32648
|
+
if (f.id !== undefined && f.id !== null) { return String(f.id); }
|
|
32649
|
+
return '#' + i;
|
|
32650
|
+
}
|
|
32651
|
+
|
|
32652
|
+
function pointOf(f) {
|
|
32653
|
+
var g = f && f.geometry;
|
|
32654
|
+
return g && g.type === 'Point' && Array.isArray(g.coordinates) && typeof g.coordinates[0] === 'number' ? g.coordinates : null;
|
|
32655
|
+
}
|
|
32656
|
+
|
|
32657
|
+
function offsetOf(v) {
|
|
32658
|
+
if (Array.isArray(v)) { return [+v[0] || 0, +v[1] || 0]; }
|
|
32659
|
+
if (typeof v === 'number') { return [0, -v]; }
|
|
32660
|
+
return [0, 0];
|
|
32661
|
+
}
|
|
32662
|
+
|
|
32663
|
+
// A feature's properties without its position (data given as {lng, lat, …} carries the position in them too), so a
|
|
32664
|
+
// marker that only moved is not rendered again.
|
|
32665
|
+
var POSITION_KEYS = { lng: 1, lat: 1, lon: 1, longitude: 1, latitude: 1, coordinates: 1 };
|
|
32666
|
+
function propsSignature(f) {
|
|
32667
|
+
var p = (f && f.properties) || {}, out = {};
|
|
32668
|
+
Object.keys(p).forEach(function (k) { if (!POSITION_KEYS[k]) { out[k] = p[k]; } });
|
|
32669
|
+
return JSON.stringify(out);
|
|
32670
|
+
}
|
|
32671
|
+
|
|
32672
|
+
/** A copy of a feature collection with one feature's point moved (the caller's own objects are never changed). */
|
|
32673
|
+
function movedCollection(fc, index, lngLat) {
|
|
32674
|
+
var features = fc.features.slice();
|
|
32675
|
+
var f = features[index];
|
|
32676
|
+
features[index] = Object.assign({}, f, { geometry: { type: 'Point', coordinates: [lngLat[0], lngLat[1]] } });
|
|
32677
|
+
return { type: 'FeatureCollection', features: features };
|
|
32678
|
+
}
|
|
32679
|
+
|
|
32680
|
+
/**
|
|
32681
|
+
* Custom markers: the caller's own element at each point — any HTML, laid out above the map's drawn layers. One
|
|
32682
|
+
* element per feature, made the first time the feature comes into view and moved while the map moves; features out
|
|
32683
|
+
* of view are not laid out, so thousands stay smooth.
|
|
32684
|
+
* @param {string} id
|
|
32685
|
+
* @param {Object|Array} data GeoJSON points, or an array of {lng, lat, …properties}
|
|
32686
|
+
* @param {Object} o
|
|
32687
|
+
* render(properties, feature) → an element or trusted HTML (inserted as is, never sanitized); required
|
|
32688
|
+
* anchor: 'center' (default), 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'
|
|
32689
|
+
* offset: [x, y] pixels; className; opacity, zIndex (constants, functions of the properties or { property });
|
|
32690
|
+
* idProperty (the property naming each feature, else the feature's id, else its place in the data);
|
|
32691
|
+
* riseOnHover (the element under the pointer comes to the front); minzoom; maxzoom;
|
|
32692
|
+
* onClick(feature, event); popup (as on addPoints); tooltip, tooltipOptions (see addPoints);
|
|
32693
|
+
* draggable (true, or per feature), onDragStart / onDrag / onDragEnd(feature, { lngLat, point, originalEvent })
|
|
32694
|
+
*/
|
|
32695
|
+
SMap.prototype.addCustomMarkers = function (id, data, o) {
|
|
32696
|
+
o = o || {};
|
|
32697
|
+
if (typeof o.render !== 'function') { throw new Error('sMap: custom markers need render(properties, feature)'); }
|
|
32698
|
+
if (o.anchor !== undefined && !ANCHORS[o.anchor]) { throw new Error('sMap: unknown anchor "' + o.anchor + '"'); }
|
|
32699
|
+
this._define(id, { kind: 'custom', data: toFeatureCollection(data), o: o });
|
|
32700
|
+
return this;
|
|
32701
|
+
};
|
|
32702
|
+
|
|
32703
|
+
/**
|
|
32704
|
+
* The element of one custom marker (made now if it was not in view yet), or null.
|
|
32705
|
+
* @param {string} id the layer
|
|
32706
|
+
* @param {string|number} key the feature's idProperty value, its id, or '#<index>'
|
|
32707
|
+
*/
|
|
32708
|
+
SMap.prototype.getMarker = function (id, key) {
|
|
32709
|
+
var def = this._layers[id];
|
|
32710
|
+
if (!def || def.kind !== 'custom' || !def.custom) { return null; }
|
|
32711
|
+
var item = def.custom.byKey[String(key)];
|
|
32712
|
+
if (!item) { return null; }
|
|
32713
|
+
if (!item.el) { this._renderMarker(id, def, item); }
|
|
32714
|
+
return item.el;
|
|
32715
|
+
};
|
|
32716
|
+
|
|
32717
|
+
SMap.prototype._markerOverlay = function () {
|
|
32718
|
+
if (this._overlay && this._overlay.parentNode) { return this._overlay; }
|
|
32719
|
+
var m = this.engine;
|
|
32720
|
+
var host = m.getCanvasContainer ? m.getCanvasContainer() : m.getContainer();
|
|
32721
|
+
var doc = host.ownerDocument;
|
|
32722
|
+
var overlay = doc.createElement('div');
|
|
32723
|
+
overlay.className = 'smap-markers';
|
|
32724
|
+
// First in the canvas container after the canvas, so the engine's popups (added after it) stay on top.
|
|
32725
|
+
var canvas = m.getCanvas ? m.getCanvas() : null;
|
|
32726
|
+
if (canvas && canvas.parentNode === host) { host.insertBefore(overlay, canvas.nextSibling); } else { host.appendChild(overlay); }
|
|
32727
|
+
this._overlay = overlay;
|
|
32728
|
+
var self = this;
|
|
32729
|
+
if (!this._overlayBound) {
|
|
32730
|
+
this._overlayBound = true;
|
|
32731
|
+
var frame = null;
|
|
32732
|
+
var schedule = function () {
|
|
32733
|
+
if (frame !== null) { return; }
|
|
32734
|
+
var raf = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : function (fn) { return setTimeout(fn, 16); };
|
|
32735
|
+
frame = raf(function () { frame = null; self._layoutMarkers(); });
|
|
32736
|
+
};
|
|
32737
|
+
this._scheduleLayout = schedule;
|
|
32738
|
+
m.on('move', function () { self._layoutMarkers(); });
|
|
32739
|
+
m.on('resize', schedule);
|
|
32740
|
+
m.on('zoomend', schedule);
|
|
32741
|
+
}
|
|
32742
|
+
return overlay;
|
|
32743
|
+
};
|
|
32744
|
+
|
|
32745
|
+
SMap.prototype._addCustom = function (id, def) {
|
|
32746
|
+
this._dropCustom(id, def);
|
|
32747
|
+
var items = [];
|
|
32748
|
+
var byKey = {};
|
|
32749
|
+
def.data.features.forEach(function (f, i) {
|
|
32750
|
+
var at = pointOf(f);
|
|
32751
|
+
if (!at) { return; }
|
|
32752
|
+
var item = { key: featureKey(def, f, i), index: i, f: f, el: null, shown: false, x: NaN, y: NaN, sig: null };
|
|
32753
|
+
items.push(item);
|
|
32754
|
+
byKey[item.key] = item;
|
|
32755
|
+
});
|
|
32756
|
+
def.custom = { items: items, byKey: byKey };
|
|
32757
|
+
this._markerOverlay();
|
|
32758
|
+
this._layoutMarkers();
|
|
32759
|
+
};
|
|
32760
|
+
|
|
32761
|
+
SMap.prototype._dropCustom = function (id, def) {
|
|
32762
|
+
var c = def.custom;
|
|
32763
|
+
if (!c) { return; }
|
|
32764
|
+
c.items.forEach(function (item) { if (item.el && item.el.parentNode) { item.el.parentNode.removeChild(item.el); } });
|
|
32765
|
+
def.custom = null;
|
|
32766
|
+
if (this._tooltipFor && this._tooltipFor.id === id) { this._hideTooltip(); }
|
|
32767
|
+
};
|
|
32768
|
+
|
|
32769
|
+
// New data for a custom marker layer: an element whose feature (by key) is still there is kept — moved when its point
|
|
32770
|
+
// moved, made again when its properties changed — so a marker being dragged or with its popup open stays the same.
|
|
32771
|
+
SMap.prototype._customData = function (id, def) {
|
|
32772
|
+
var old = def.custom;
|
|
32773
|
+
if (!old || !this.engine) { if (this.engine && this._styleReady) { this._addCustom(id, def); } return; }
|
|
32774
|
+
var items = [];
|
|
32775
|
+
var byKey = {};
|
|
32776
|
+
def.data.features.forEach(function (f, i) {
|
|
32777
|
+
if (!pointOf(f)) { return; }
|
|
32778
|
+
var key = featureKey(def, f, i);
|
|
32779
|
+
var item = old.byKey[key];
|
|
32780
|
+
if (item) {
|
|
32781
|
+
delete old.byKey[key];
|
|
32782
|
+
if (item.el && propsSignature(item.f) !== propsSignature(f)) { item.sig = null; }
|
|
32783
|
+
item.f = f;
|
|
32784
|
+
item.index = i;
|
|
32785
|
+
item.x = NaN;
|
|
32786
|
+
} else {
|
|
32787
|
+
item = { key: key, index: i, f: f, el: null, shown: false, x: NaN, y: NaN, sig: null };
|
|
32788
|
+
}
|
|
32789
|
+
items.push(item);
|
|
32790
|
+
byKey[key] = item;
|
|
32791
|
+
});
|
|
32792
|
+
Object.keys(old.byKey).forEach(function (k) { var gone = old.byKey[k]; if (gone.el && gone.el.parentNode) { gone.el.parentNode.removeChild(gone.el); } });
|
|
32793
|
+
def.custom = { items: items, byKey: byKey };
|
|
32794
|
+
this._layoutMarkers();
|
|
32795
|
+
};
|
|
32796
|
+
|
|
32797
|
+
SMap.prototype._renderMarker = function (id, def, item) {
|
|
32798
|
+
var self = this, o = def.o, doc = this._markerOverlay().ownerDocument;
|
|
32799
|
+
var el = item.el;
|
|
32800
|
+
if (!el) {
|
|
32801
|
+
el = doc.createElement('div');
|
|
32802
|
+
el.setAttribute('data-smap-layer', id);
|
|
32803
|
+
el.setAttribute('data-smap-key', item.key);
|
|
32804
|
+
item.el = el;
|
|
32805
|
+
this._bindMarker(id, def, item);
|
|
32806
|
+
}
|
|
32807
|
+
var props = item.f.properties || {};
|
|
32808
|
+
var content = o.render(props, item.f);
|
|
32809
|
+
while (el.firstChild) { el.removeChild(el.firstChild); }
|
|
32810
|
+
if (isElement(content)) { el.appendChild(content); } else if (content !== undefined && content !== null) { el.innerHTML = String(content); }
|
|
32811
|
+
el.className = 'smap-marker' + (o.className ? ' ' + o.className : '') + (o.onClick || o.popup || o.draggable || o.tooltip || o.riseOnHover ? ' smap-marker-interactive' : '');
|
|
32812
|
+
var opacity = plainValue(o.opacity, props, item.f, 1);
|
|
32813
|
+
el.style.opacity = opacity === 1 ? '' : String(opacity);
|
|
32814
|
+
item.z = +plainValue(o.zIndex, props, item.f, 0) || 0;
|
|
32815
|
+
item.sig = true;
|
|
32816
|
+
return el;
|
|
32817
|
+
};
|
|
32818
|
+
|
|
32819
|
+
// The anchor and offset of a layer's markers as a CSS transform tail.
|
|
32820
|
+
function anchorTransform(o) {
|
|
32821
|
+
var a = ANCHORS[o.anchor || 'center'], off = offsetOf(o.offset);
|
|
32822
|
+
return ' translate(' + a[0] + '%, ' + a[1] + '%)' + (off[0] || off[1] ? ' translate(' + off[0] + 'px, ' + off[1] + 'px)' : '');
|
|
32823
|
+
}
|
|
32824
|
+
|
|
32825
|
+
// Places every custom marker in view (and takes the ones that left the view out of the page).
|
|
32826
|
+
SMap.prototype._layoutMarkers = function () {
|
|
32827
|
+
var m = this.engine;
|
|
32828
|
+
if (!m || !this._overlay) { return; }
|
|
32829
|
+
var host = this._overlay;
|
|
32830
|
+
var w = host.clientWidth || (m.getContainer && m.getContainer().clientWidth) || 0;
|
|
32831
|
+
var h = host.clientHeight || (m.getContainer && m.getContainer().clientHeight) || 0;
|
|
32832
|
+
var zoom = m.getZoom ? m.getZoom() : 0;
|
|
32833
|
+
var rank = 0;
|
|
32834
|
+
for (var n = 0; n < this._order.length; n++) {
|
|
32835
|
+
var id = this._order[n], layer = this._layers[id];
|
|
32836
|
+
// A custom marker layer, or the HTML clusters of a point layer (cluster.html / cluster.ring).
|
|
32837
|
+
var def = layer.kind === 'custom' ? layer : layer.clusterSet;
|
|
32838
|
+
if (!def || !def.custom) { continue; }
|
|
32839
|
+
rank += 1;
|
|
32840
|
+
var o = def.o;
|
|
32841
|
+
var inRange = !layer.hidden && (o.minzoom === undefined || zoom >= o.minzoom) && (o.maxzoom === undefined || zoom < o.maxzoom);
|
|
32842
|
+
var tail = anchorTransform(o);
|
|
32843
|
+
var items = def.custom.items;
|
|
32844
|
+
for (var i = 0; i < items.length; i++) {
|
|
32845
|
+
var item = items[i];
|
|
32846
|
+
var at = item.dragAt || pointOf(item.f);
|
|
32847
|
+
var p = inRange && at ? m.project(at) : null;
|
|
32848
|
+
var visible = !!p && p.x >= -MARKER_MARGIN && p.y >= -MARKER_MARGIN && p.x <= w + MARKER_MARGIN && p.y <= h + MARKER_MARGIN;
|
|
32849
|
+
if (!visible) {
|
|
32850
|
+
if (item.shown && item.el && item.el.parentNode) { item.el.parentNode.removeChild(item.el); }
|
|
32851
|
+
item.shown = false;
|
|
32852
|
+
continue;
|
|
32853
|
+
}
|
|
32854
|
+
if (!item.el || !item.sig) { this._renderMarker(id, def, item); }
|
|
32855
|
+
var x = Math.round(p.x * 10) / 10, y = Math.round(p.y * 10) / 10;
|
|
32856
|
+
if (x !== item.x || y !== item.y) {
|
|
32857
|
+
item.el.style.transform = 'translate(' + x + 'px, ' + y + 'px)' + tail;
|
|
32858
|
+
item.x = x; item.y = y;
|
|
32859
|
+
}
|
|
32860
|
+
var z = rank * 100000 + (item.raised ? 99999 : Math.max(-99999, Math.min(99998, item.z)));
|
|
32861
|
+
if (item.zSet !== z) { item.el.style.zIndex = String(z); item.zSet = z; }
|
|
32862
|
+
if (!item.shown || item.el.parentNode !== host) { host.appendChild(item.el); item.shown = true; }
|
|
32863
|
+
}
|
|
32864
|
+
}
|
|
32865
|
+
};
|
|
32866
|
+
|
|
32867
|
+
// Click, popup, hover raise, tooltip and dragging of one custom marker.
|
|
32868
|
+
SMap.prototype._bindMarker = function (id, def, item) {
|
|
32869
|
+
var self = this, el = item.el;
|
|
32870
|
+
var o = function () { return def.o; };
|
|
32871
|
+
// The click a drag's release makes is no tap: stopped on the way down, before anything inside the marker (the
|
|
32872
|
+
// app's own buttons, a framework listening on its own element) sees it.
|
|
32873
|
+
el.addEventListener('click', function (e) {
|
|
32874
|
+
if (self._dragEndedAt && Date.now() - self._dragEndedAt < 300) { e.stopPropagation(); if (e.preventDefault) { e.preventDefault(); } }
|
|
32875
|
+
}, true);
|
|
32876
|
+
el.addEventListener('click', function (e) {
|
|
32877
|
+
if (self._dragEndedAt && Date.now() - self._dragEndedAt < 300) { e.stopPropagation(); return; }
|
|
32878
|
+
var lo = o();
|
|
32879
|
+
if (!lo.onClick && !lo.popup) { return; }
|
|
32880
|
+
e.stopPropagation();
|
|
32881
|
+
if (lo.onClick) { lo.onClick(item.f, { originalEvent: e, lngLat: pointOf(item.f) }); }
|
|
32882
|
+
if (lo.popup) { self._markerPopup(id, def, item); }
|
|
32883
|
+
});
|
|
32884
|
+
el.addEventListener('mouseenter', function (e) {
|
|
32885
|
+
var lo = o();
|
|
32886
|
+
if (lo.riseOnHover) { item.raised = true; self._layoutMarkers(); }
|
|
32887
|
+
if (lo.tooltip) { self._showTooltip(id, def, item.f, { element: el }); }
|
|
32888
|
+
});
|
|
32889
|
+
el.addEventListener('mouseleave', function () {
|
|
32890
|
+
if (item.raised) { item.raised = false; self._layoutMarkers(); }
|
|
32891
|
+
if (self._tooltipFor && self._tooltipFor.f === item.f) { self._hideTooltip(); }
|
|
32892
|
+
});
|
|
32893
|
+
var start = function (e) {
|
|
32894
|
+
var lo = o();
|
|
32895
|
+
if (!plainValue(lo.draggable, item.f.properties || {}, item.f, false)) { return; }
|
|
32896
|
+
if (e.button !== undefined && e.button !== 0) { return; }
|
|
32897
|
+
self._startMarkerDrag(id, item, e);
|
|
32898
|
+
};
|
|
32899
|
+
if (typeof el.addEventListener === 'function') {
|
|
32900
|
+
el.addEventListener(typeof window !== 'undefined' && window.PointerEvent ? 'pointerdown' : 'mousedown', start);
|
|
32901
|
+
}
|
|
32902
|
+
};
|
|
32903
|
+
|
|
32904
|
+
SMap.prototype._markerPopup = function (id, def, item) {
|
|
32905
|
+
var o = def.o, fn = def.popupFn || o.popup, content;
|
|
32906
|
+
try { content = fn(item.f.properties || {}, item.f); } catch (error) { this._reportError(error); return; }
|
|
32907
|
+
var options = Object.assign({}, o.popupOptions || {});
|
|
32908
|
+
if (options.offset === undefined && this._popupDefaults.offset === undefined && item.el) {
|
|
32909
|
+
// Above the element as it is drawn: from its anchor to its top edge.
|
|
32910
|
+
var hgt = item.el.offsetHeight || 0, a = ANCHORS[o.anchor || 'center'];
|
|
32911
|
+
options.offset = Math.round(hgt * (-a[1] / 100) - offsetOf(o.offset)[1] + 4);
|
|
32912
|
+
}
|
|
32913
|
+
if (options.anchor === undefined && this._popupDefaults.anchor === undefined) { options.anchor = 'bottom'; }
|
|
32914
|
+
this.openPopup(pointOf(item.f), content, options);
|
|
32915
|
+
};
|
|
32916
|
+
|
|
32917
|
+
// Dragging a custom marker: it follows the pointer from the first move of DRAG_THRESHOLD pixels; the map does not
|
|
32918
|
+
// pan meanwhile. Its layer's data then holds the new position (a copy: the caller's objects are not changed).
|
|
32919
|
+
SMap.prototype._startMarkerDrag = function (id, item, e) {
|
|
32920
|
+
var self = this, m = this.engine, el = item.el;
|
|
32921
|
+
var doc = el.ownerDocument, win = doc.defaultView || {};
|
|
32922
|
+
var box = m.getContainer().getBoundingClientRect();
|
|
32923
|
+
var p0 = m.project(pointOf(item.f));
|
|
32924
|
+
var grab = { x: e.clientX - box.left - p0.x, y: e.clientY - box.top - p0.y };
|
|
32925
|
+
var sx = e.clientX, sy = e.clientY, dragging = false;
|
|
32926
|
+
// The map must not pan under a marker that can be dragged: off from the press, back on at the release.
|
|
32927
|
+
var panWasOn = !!(m.dragPan && m.dragPan.isEnabled && m.dragPan.isEnabled());
|
|
32928
|
+
if (panWasOn) { m.dragPan.disable(); }
|
|
32929
|
+
var lngLatAt = function (ev) {
|
|
32930
|
+
var r = m.getContainer().getBoundingClientRect();
|
|
32931
|
+
var pt = { x: ev.clientX - r.left - grab.x, y: ev.clientY - r.top - grab.y };
|
|
32932
|
+
var ll = m.unproject([pt.x, pt.y]);
|
|
32933
|
+
return { lngLat: [ll.lng, ll.lat], point: pt };
|
|
32934
|
+
};
|
|
32935
|
+
var evt = function (ev, at) { return { lngLat: at.lngLat, point: at.point, originalEvent: ev }; };
|
|
32936
|
+
var move = function (ev) {
|
|
32937
|
+
if (!dragging) {
|
|
32938
|
+
if (Math.abs(ev.clientX - sx) < DRAG_THRESHOLD && Math.abs(ev.clientY - sy) < DRAG_THRESHOLD) { return; }
|
|
32939
|
+
dragging = true;
|
|
32940
|
+
el.classList.add('smap-marker-dragging');
|
|
32941
|
+
item.raised = true;
|
|
32942
|
+
self._hideTooltip();
|
|
32943
|
+
var o0 = self._layers[id].o;
|
|
32944
|
+
if (o0.onDragStart) { o0.onDragStart(item.f, evt(ev, lngLatAt(ev))); }
|
|
32945
|
+
}
|
|
32946
|
+
if (ev.preventDefault) { ev.preventDefault(); }
|
|
32947
|
+
var at = lngLatAt(ev);
|
|
32948
|
+
item.dragAt = at.lngLat;
|
|
32949
|
+
self._layoutMarkers();
|
|
32950
|
+
var o1 = self._layers[id] && self._layers[id].o;
|
|
32951
|
+
if (o1 && o1.onDrag) { o1.onDrag(item.f, evt(ev, at)); }
|
|
32952
|
+
};
|
|
32953
|
+
var up = function (ev) {
|
|
32954
|
+
doc.removeEventListener(upType, up);
|
|
32955
|
+
doc.removeEventListener(moveType, move);
|
|
32956
|
+
if (panWasOn) { m.dragPan.enable(); }
|
|
32957
|
+
if (!dragging) { return; }
|
|
32958
|
+
var at = lngLatAt(ev);
|
|
32959
|
+
item.dragAt = null;
|
|
32960
|
+
item.raised = false;
|
|
32961
|
+
el.classList.remove('smap-marker-dragging');
|
|
32962
|
+
self._dragEndedAt = Date.now();
|
|
32963
|
+
var def = self._layers[id];
|
|
32964
|
+
if (!def) { return; }
|
|
32965
|
+
def.data = movedCollection(def.data, item.index, at.lngLat);
|
|
32966
|
+
item.f = def.data.features[item.index];
|
|
32967
|
+
self._layoutMarkers();
|
|
32968
|
+
if (def.o.onDragEnd) { def.o.onDragEnd(item.f, evt(ev, at)); }
|
|
32969
|
+
};
|
|
32970
|
+
var pointer = !!win.PointerEvent && e.type === 'pointerdown';
|
|
32971
|
+
var moveType = pointer ? 'pointermove' : 'mousemove';
|
|
32972
|
+
var upType = pointer ? 'pointerup' : 'mouseup';
|
|
32973
|
+
doc.addEventListener(moveType, move);
|
|
32974
|
+
doc.addEventListener(upType, up);
|
|
32975
|
+
if (e.stopPropagation) { e.stopPropagation(); }
|
|
32976
|
+
};
|
|
32977
|
+
|
|
32978
|
+
// Dragging a drawn point (addPoints with draggable): the point is taken out of its layer and drawn in a copy of the
|
|
32979
|
+
// layer's own look that follows the pointer, then put back at the new position. Two data updates per drag, whatever
|
|
32980
|
+
// the size of the layer.
|
|
32981
|
+
SMap.prototype._dragPoints = function (id, def, main) {
|
|
32982
|
+
var self = this, m = this.engine;
|
|
32983
|
+
var begin = function (e) {
|
|
32984
|
+
var f = e.features && e.features[0];
|
|
32985
|
+
if (!f || def.hidden) { return; }
|
|
32986
|
+
var index = self._featureIndex(def, f);
|
|
32987
|
+
if (index < 0) { return; }
|
|
32988
|
+
var original = def.data.features[index];
|
|
32989
|
+
if (!plainValue(def.o.draggable, original.properties || {}, original, false)) { return; }
|
|
32990
|
+
if (e.originalEvent && e.originalEvent.button !== undefined && e.originalEvent.button !== 0) { return; }
|
|
32991
|
+
e.preventDefault();
|
|
32992
|
+
var start = e.point, dragging = false, ghost = null;
|
|
32993
|
+
var at = function (ev) { return { lngLat: [ev.lngLat.lng, ev.lngLat.lat], point: { x: ev.point.x, y: ev.point.y }, originalEvent: ev.originalEvent }; };
|
|
32994
|
+
var move = function (ev) {
|
|
32995
|
+
if (!dragging) {
|
|
32996
|
+
if (Math.abs(ev.point.x - start.x) < DRAG_THRESHOLD && Math.abs(ev.point.y - start.y) < DRAG_THRESHOLD) { return; }
|
|
32997
|
+
dragging = true;
|
|
32998
|
+
if (m.dragPan && m.dragPan.disable) { m.dragPan.disable(); }
|
|
32999
|
+
ghost = self._dragGhost(id, def);
|
|
33000
|
+
var without = def.data.features.slice();
|
|
33001
|
+
without.splice(index, 1);
|
|
33002
|
+
var src = m.getSource('smap-' + id);
|
|
33003
|
+
if (src) { src.setData({ type: 'FeatureCollection', features: without }); }
|
|
33004
|
+
if (def.hovered) { self._setHover(id, def, null); }
|
|
33005
|
+
self._hideTooltip();
|
|
33006
|
+
if (def.o.onDragStart) { def.o.onDragStart(original, at(ev)); }
|
|
33007
|
+
}
|
|
33008
|
+
var a = at(ev);
|
|
33009
|
+
if (ghost) { ghost.setData({ type: 'FeatureCollection', features: [Object.assign({}, original, { geometry: { type: 'Point', coordinates: a.lngLat } })] }); }
|
|
33010
|
+
if (def.o.onDrag) { def.o.onDrag(original, a); }
|
|
33011
|
+
};
|
|
33012
|
+
var end = function (ev) {
|
|
33013
|
+
m.off('mousemove', move); m.off('touchmove', move);
|
|
33014
|
+
m.off('mouseup', end); m.off('touchend', end);
|
|
33015
|
+
if (!dragging) { return; }
|
|
33016
|
+
if (m.dragPan && m.dragPan.enable) { m.dragPan.enable(); }
|
|
33017
|
+
self._dropGhost(id);
|
|
33018
|
+
self._dragEndedAt = Date.now();
|
|
33019
|
+
var a = at(ev);
|
|
33020
|
+
def.data = movedCollection(def.data, index, a.lngLat);
|
|
33021
|
+
var src = m.getSource('smap-' + id);
|
|
33022
|
+
if (src) { src.setData(def.data); }
|
|
33023
|
+
if (def.o.onDragEnd) { def.o.onDragEnd(def.data.features[index], a); }
|
|
33024
|
+
};
|
|
33025
|
+
m.on('mousemove', move); m.on('touchmove', move);
|
|
33026
|
+
m.on('mouseup', end); m.on('touchend', end);
|
|
33027
|
+
};
|
|
33028
|
+
this._listen(id, 'mousedown', main, begin);
|
|
33029
|
+
this._listen(id, 'touchstart', main, begin);
|
|
33030
|
+
};
|
|
33031
|
+
|
|
33032
|
+
// The index in a layer's data of a feature the engine answered (by idProperty, else by the generated id).
|
|
33033
|
+
SMap.prototype._featureIndex = function (def, f) {
|
|
33034
|
+
var feats = def.data.features, idp = def.o.idProperty;
|
|
33035
|
+
if (idp) {
|
|
33036
|
+
var want = f.properties ? f.properties[idp] : undefined;
|
|
33037
|
+
for (var i = 0; i < feats.length; i++) { if (feats[i].properties && feats[i].properties[idp] === want) { return i; } }
|
|
33038
|
+
return -1;
|
|
33039
|
+
}
|
|
33040
|
+
return typeof f.id === 'number' && f.id >= 0 && f.id < feats.length ? f.id : -1;
|
|
33041
|
+
};
|
|
33042
|
+
|
|
33043
|
+
// A copy of a layer's engine layers drawing from a one-feature source on top of the map: the point being dragged.
|
|
33044
|
+
SMap.prototype._dragGhost = function (id, def) {
|
|
33045
|
+
var m = this.engine, src = 'smap-' + id, ghostSrc = src + '-drag';
|
|
33046
|
+
this._dropGhost(id);
|
|
33047
|
+
m.addSource(ghostSrc, { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
|
|
33048
|
+
var st = m.getStyle ? m.getStyle() : null;
|
|
33049
|
+
var ids = [];
|
|
33050
|
+
(st && st.layers ? st.layers : []).forEach(function (l) {
|
|
33051
|
+
if (l.source !== src || def.engineLayers.indexOf(l.id) < 0 || /-(clusters|count)$/.test(l.id)) { return; }
|
|
33052
|
+
var copy = JSON.parse(JSON.stringify(l));
|
|
33053
|
+
copy.id = l.id + '-drag';
|
|
33054
|
+
copy.source = ghostSrc;
|
|
33055
|
+
delete copy.minzoom; delete copy.maxzoom;
|
|
33056
|
+
copy.layout = Object.assign({}, copy.layout || {}, { visibility: 'visible' });
|
|
33057
|
+
m.addLayer(copy);
|
|
33058
|
+
ids.push(copy.id);
|
|
33059
|
+
});
|
|
33060
|
+
this._ghost = { id: id, source: ghostSrc, layers: ids };
|
|
33061
|
+
return m.getSource(ghostSrc);
|
|
33062
|
+
};
|
|
33063
|
+
|
|
33064
|
+
SMap.prototype._dropGhost = function (id) {
|
|
33065
|
+
var g = this._ghost, m = this.engine;
|
|
33066
|
+
if (!g || (id && g.id !== id)) { return; }
|
|
33067
|
+
g.layers.forEach(function (l) { if (m.getLayer(l)) { m.removeLayer(l); } });
|
|
33068
|
+
if (m.getSource(g.source)) { m.removeSource(g.source); }
|
|
33069
|
+
this._ghost = null;
|
|
33070
|
+
};
|
|
33071
|
+
|
|
33072
|
+
// ---- tooltips (G5)
|
|
33073
|
+
|
|
33074
|
+
/**
|
|
33075
|
+
* The map's one tooltip: shown while the pointer is over a feature of a layer with `tooltip`, beside the point (or
|
|
33076
|
+
* the custom marker), or at the pointer on lines and areas. `tooltipOptions`: direction ('top' default, 'bottom',
|
|
33077
|
+
* 'left', 'right'), offset ([x, y] pixels), className.
|
|
33078
|
+
*/
|
|
33079
|
+
SMap.prototype._showTooltip = function (id, def, f, at) {
|
|
33080
|
+
var o = def.o, content;
|
|
33081
|
+
var fn = typeof o.tooltip === 'string' ? (def.tooltipFn = def.tooltipFn || popupTemplate(o.tooltip)) : o.tooltip;
|
|
33082
|
+
try { content = fn(f.properties || {}, f); } catch (error) { this._reportError(error); return; }
|
|
33083
|
+
if (content === undefined || content === null || content === '') { this._hideTooltip(); return; }
|
|
33084
|
+
var m = this.engine, overlay = this._markerOverlay(), doc = overlay.ownerDocument;
|
|
33085
|
+
var tip = this._tooltip;
|
|
33086
|
+
if (!tip || tip.parentNode !== overlay) {
|
|
33087
|
+
tip = this._tooltip = doc.createElement('div');
|
|
33088
|
+
overlay.appendChild(tip);
|
|
33089
|
+
}
|
|
33090
|
+
var to = o.tooltipOptions || {};
|
|
33091
|
+
var dir = ['top', 'bottom', 'left', 'right'].indexOf(to.direction) >= 0 ? to.direction : 'top';
|
|
33092
|
+
tip.className = 'smap-tooltip smap-tooltip-' + dir + (to.className ? ' ' + to.className : '');
|
|
33093
|
+
while (tip.firstChild) { tip.removeChild(tip.firstChild); }
|
|
33094
|
+
if (isElement(content)) { tip.appendChild(content); } else { tip.innerHTML = String(content); }
|
|
33095
|
+
var x, y, gap = 6;
|
|
33096
|
+
if (at.element) {
|
|
33097
|
+
var hb = overlay.getBoundingClientRect(), eb = at.element.getBoundingClientRect();
|
|
33098
|
+
x = eb.left - hb.left + eb.width / 2; y = eb.top - hb.top + eb.height / 2;
|
|
33099
|
+
if (dir === 'top') { y = eb.top - hb.top; } else if (dir === 'bottom') { y = eb.bottom - hb.top; }
|
|
33100
|
+
else if (dir === 'left') { x = eb.left - hb.left; } else { x = eb.right - hb.left; }
|
|
33101
|
+
} else {
|
|
33102
|
+
var p = at.point || m.project(at.lngLat);
|
|
33103
|
+
x = p.x; y = p.y;
|
|
33104
|
+
gap = at.gap !== undefined ? at.gap : 10;
|
|
33105
|
+
}
|
|
33106
|
+
var off = offsetOf(to.offset);
|
|
33107
|
+
var shift = { top: [-50, -100, 0, -gap], bottom: [-50, 0, 0, gap], left: [-100, -50, -gap, 0], right: [0, -50, gap, 0] }[dir];
|
|
33108
|
+
tip.style.transform = 'translate(' + Math.round(x + shift[2] + off[0]) + 'px, ' + Math.round(y + shift[3] + off[1]) + 'px) translate(' + shift[0] + '%, ' + shift[1] + '%)';
|
|
33109
|
+
this._tooltipFor = { id: id, f: f };
|
|
33110
|
+
};
|
|
33111
|
+
|
|
33112
|
+
SMap.prototype._hideTooltip = function () {
|
|
33113
|
+
if (this._tooltip && this._tooltip.parentNode) { this._tooltip.parentNode.removeChild(this._tooltip); }
|
|
33114
|
+
this._tooltipFor = null;
|
|
33115
|
+
};
|
|
33116
|
+
|
|
33117
|
+
// Tooltips of a drawn layer: points show theirs beside the point, lines and areas at the pointer.
|
|
33118
|
+
SMap.prototype._tooltips = function (id, def, main) {
|
|
33119
|
+
var self = this, m = this.engine;
|
|
33120
|
+
this._listen(id, 'mousemove', main, function (e) {
|
|
33121
|
+
var f = e.features && e.features[0];
|
|
33122
|
+
if (!f || def.hidden) { return; }
|
|
33123
|
+
if (def.kind === 'points' && pointOf(f) && !(f.properties && f.properties.point_count)) {
|
|
33124
|
+
var mk = def.o.marker, k = mk ? scaleAt(mk.scale, m.getZoom()) : 1;
|
|
33125
|
+
var size = mk ? (mk.size || 32) * k : (def.o.radius || 6) * 2;
|
|
33126
|
+
var gap = mk && (mk.shape === undefined || mk.shape === 'pin') ? Math.round(size * 1.25) : Math.round(size / 2 + 4);
|
|
33127
|
+
self._showTooltip(id, def, f, { lngLat: pointOf(f), gap: gap });
|
|
33128
|
+
} else {
|
|
33129
|
+
self._showTooltip(id, def, f, { point: e.point, gap: 12 });
|
|
33130
|
+
}
|
|
33131
|
+
});
|
|
33132
|
+
this._listen(id, 'mouseleave', main, function () { if (self._tooltipFor && self._tooltipFor.id === id) { self._hideTooltip(); } });
|
|
33133
|
+
};
|
|
33134
|
+
|
|
33135
|
+
// ---- clusters drawn as HTML (G6)
|
|
33136
|
+
|
|
33137
|
+
// Colours of a ring: each value's share of the points, largest first; up to six slices, the rest in the last.
|
|
33138
|
+
function ringBackground(counts, fallback) {
|
|
33139
|
+
var keys = Object.keys(counts).sort(function (a, b) { return counts[b] - counts[a]; });
|
|
33140
|
+
var total = keys.reduce(function (t, k) { return t + counts[k]; }, 0);
|
|
33141
|
+
if (!total) { return fallback; }
|
|
33142
|
+
if (keys.length === 1) { return keys[0]; }
|
|
33143
|
+
var shown = keys.slice(0, 6), at = 0;
|
|
33144
|
+
return 'conic-gradient(' + shown.map(function (c, i) {
|
|
33145
|
+
var from = at;
|
|
33146
|
+
at = i === shown.length - 1 ? 100 : Math.min(100, at + Math.round(counts[c] / total * 100));
|
|
33147
|
+
return c + ' ' + from + '% ' + at + '%';
|
|
33148
|
+
}).join(', ') + ')';
|
|
33149
|
+
}
|
|
33150
|
+
|
|
33151
|
+
// The cluster as the caller's html() sees it.
|
|
33152
|
+
SMap.prototype._clusterInfo = function (src, f) {
|
|
33153
|
+
var m = this.engine, p = f.properties || {};
|
|
33154
|
+
return {
|
|
33155
|
+
id: p.cluster_id, count: p.point_count, properties: p, lngLat: pointOf(f),
|
|
33156
|
+
leaves: function (limit) {
|
|
33157
|
+
var source = m.getSource(src);
|
|
33158
|
+
return Promise.resolve(source.getClusterLeaves(p.cluster_id, limit === undefined ? Infinity : limit, 0));
|
|
33159
|
+
},
|
|
33160
|
+
};
|
|
33161
|
+
};
|
|
33162
|
+
|
|
33163
|
+
// HTML clusters of a point layer: the clusters the engine made are read after each change of view or data and laid
|
|
33164
|
+
// out as custom markers (kept by cluster id, so an unchanged cluster keeps its element).
|
|
33165
|
+
SMap.prototype._htmlClusters = function (id, def, src) {
|
|
33166
|
+
var self = this, m = this.engine, co = def.o.cluster;
|
|
33167
|
+
var ring = co.ring ? (typeof co.ring === 'string' ? { property: co.ring } : co.ring) : null;
|
|
33168
|
+
var render = function (props, f) {
|
|
33169
|
+
var info = self._clusterInfo(src, f);
|
|
33170
|
+
if (co.html) { return co.html(info); }
|
|
33171
|
+
var size = info.count >= 100 ? 44 : (info.count >= 10 ? 38 : 32);
|
|
33172
|
+
var doc = self._markerOverlay().ownerDocument;
|
|
33173
|
+
var el = doc.createElement('div');
|
|
33174
|
+
el.className = 'smap-cluster';
|
|
33175
|
+
el.style.width = el.style.height = size + 'px';
|
|
33176
|
+
el.style.background = ring.color || def.o.clusterColor || def.o.color || '#4f46e5';
|
|
33177
|
+
var b = doc.createElement('b');
|
|
33178
|
+
b.textContent = String(info.count);
|
|
33179
|
+
el.appendChild(b);
|
|
33180
|
+
// The ring: the share of each value among the cluster's points (read from at most ring.limit of them).
|
|
33181
|
+
info.leaves(ring.limit || 500).then(function (leaves) {
|
|
33182
|
+
var counts = {};
|
|
33183
|
+
(leaves || []).forEach(function (leaf) {
|
|
33184
|
+
var c = leaf.properties ? leaf.properties[ring.property] : undefined;
|
|
33185
|
+
if (typeof c === 'string' && c) { counts[c] = (counts[c] || 0) + 1; }
|
|
33186
|
+
});
|
|
33187
|
+
el.style.background = ringBackground(counts, el.style.background);
|
|
33188
|
+
}, function () {});
|
|
33189
|
+
return el;
|
|
33190
|
+
};
|
|
33191
|
+
def.clusterSet = {
|
|
33192
|
+
o: {
|
|
33193
|
+
render: render, anchor: 'center', className: co.className, riseOnHover: true,
|
|
33194
|
+
// A click zooms in to where the cluster splits (cluster.zoomOnClick: false turns it off).
|
|
33195
|
+
onClick: co.zoomOnClick === false ? co.onClick : function (f, e) {
|
|
33196
|
+
if (co.onClick) { co.onClick(f, e); }
|
|
33197
|
+
var source = m.getSource(src);
|
|
33198
|
+
Promise.resolve(source.getClusterExpansionZoom(f.properties.cluster_id)).then(function (zoom) {
|
|
33199
|
+
m.easeTo({ center: pointOf(f), zoom: zoom });
|
|
33200
|
+
}, function () {});
|
|
33201
|
+
},
|
|
33202
|
+
tooltip: co.tooltip ? function (p, f) { return co.tooltip(self._clusterInfo(src, f)); } : undefined,
|
|
33203
|
+
},
|
|
33204
|
+
data: { type: 'FeatureCollection', features: [] },
|
|
33205
|
+
custom: null,
|
|
33206
|
+
};
|
|
33207
|
+
var refresh = function () {
|
|
33208
|
+
var set = def.clusterSet;
|
|
33209
|
+
if (!set || !m.getSource(src)) { return; }
|
|
33210
|
+
var seen = {}, features = [];
|
|
33211
|
+
(m.querySourceFeatures(src, { filter: ['has', 'point_count'] }) || []).forEach(function (f) {
|
|
33212
|
+
var cid = f.properties && f.properties.cluster_id;
|
|
33213
|
+
if (cid === undefined || seen[cid]) { return; }
|
|
33214
|
+
seen[cid] = true;
|
|
33215
|
+
features.push({ type: 'Feature', id: 'c' + cid, properties: f.properties, geometry: { type: 'Point', coordinates: pointOf(f) } });
|
|
33216
|
+
});
|
|
33217
|
+
set.data = { type: 'FeatureCollection', features: features };
|
|
33218
|
+
self._markerOverlay();
|
|
33219
|
+
self._customData(id + ':clusters', set);
|
|
33220
|
+
};
|
|
33221
|
+
this._listen(id, 'moveend', null, refresh);
|
|
33222
|
+
this._listen(id, 'sourcedata', null, function (e) { if (e && e.sourceId === src && e.isSourceLoaded) { refresh(); } });
|
|
33223
|
+
refresh();
|
|
33224
|
+
};
|
|
33225
|
+
|
|
30964
33226
|
// ------------------------------------------------------------------ map tools (M49-M57)
|
|
30965
33227
|
|
|
30966
33228
|
var TOOL_ID = /^[A-Za-z0-9._-]{1,64}$/;
|
|
@@ -30975,6 +33237,8 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
30975
33237
|
zoom: { control: 'NavigationControl', label: 'Zoom', position: 'top-left', options: { showCompass: false } },
|
|
30976
33238
|
compass: { control: 'NavigationControl', label: 'Compass', position: 'top-left', options: { showZoom: false } },
|
|
30977
33239
|
locate: { control: 'GeolocateControl', label: 'Locate', position: 'top-left' },
|
|
33240
|
+
// Made by the SDK itself: the base map and overlay switcher (G8).
|
|
33241
|
+
layers: { sdk: 'layers', label: 'Layers', position: 'top-right' },
|
|
30978
33242
|
};
|
|
30979
33243
|
var TOOLS_ICON = '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false" fill="none" ' +
|
|
30980
33244
|
'stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 7h10M18 7h2M4 17h2M10 17h10"/>' +
|
|
@@ -31045,7 +33309,7 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
31045
33309
|
}
|
|
31046
33310
|
if (typeof target === 'string') {
|
|
31047
33311
|
if (!kind) { throw new Error('sMap: unknown tool kind "' + target + '"'); }
|
|
31048
|
-
if (!
|
|
33312
|
+
if (!kind.sdk && !this._lib[kind.control]) { throw new Error('sMap: the rendering engine has no ' + kind.control); }
|
|
31049
33313
|
} else if (!isElement(target) && !(target && typeof target.onAdd === 'function')) {
|
|
31050
33314
|
throw new Error('sMap: a tool is an element, an engine control or a tool kind');
|
|
31051
33315
|
}
|
|
@@ -31054,7 +33318,8 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
31054
33318
|
if (position === 'none' && !isElement(target)) { throw new Error('sMap: only an element may stay out of the map\'s corners'); }
|
|
31055
33319
|
if (this._toolIndex(id) >= 0) { this.removeTool(id); }
|
|
31056
33320
|
var tool = { id: id, label: String(o.label || (kind ? kind.label : id)), position: position, el: null, control: null };
|
|
31057
|
-
if (kind) { tool.control = toolControl(tool,
|
|
33321
|
+
if (kind && kind.sdk === 'layers') { tool.control = toolControl(tool, layersControl(this, o.options || {})); }
|
|
33322
|
+
else if (kind) { tool.control = toolControl(tool, new this._lib[kind.control](o.options || kind.options)); }
|
|
31058
33323
|
else if (!isElement(target)) { tool.control = toolControl(tool, target); }
|
|
31059
33324
|
else if (position !== 'none') { tool.control = elementControl(tool, target); }
|
|
31060
33325
|
else { tool.el = target; }
|
|
@@ -31307,6 +33572,1119 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
31307
33572
|
return control;
|
|
31308
33573
|
}
|
|
31309
33574
|
|
|
33575
|
+
// ------------------------------------------------------------------ heat maps (G7), terrain (G11), synced maps (G13), drawn tiles (G14)
|
|
33576
|
+
|
|
33577
|
+
/**
|
|
33578
|
+
* A heat map of points: weight (a number, or per feature: { property } or an expression; default 1), radius
|
|
33579
|
+
* (pixels, default 20, or an expression of the zoom), intensity (default 1), opacity (default 0.8), colors (from low
|
|
33580
|
+
* to high, or [[density 0–1, colour], …]), minzoom, maxzoom, slot, before, zIndex.
|
|
33581
|
+
*/
|
|
33582
|
+
SMap.prototype.addHeatmap = function (id, data, o) {
|
|
33583
|
+
this._define(id, { kind: 'heatmap', data: toFeatureCollection(data), o: o || {} });
|
|
33584
|
+
return this;
|
|
33585
|
+
};
|
|
33586
|
+
|
|
33587
|
+
var TERRAIN_SOURCE = 'smap-terrain';
|
|
33588
|
+
var HILLSHADE_LAYER = 'smap-hillshade';
|
|
33589
|
+
|
|
33590
|
+
/**
|
|
33591
|
+
* Relief: the map drawn over elevation tiles (seen when the map is tilted), with optional hillshading.
|
|
33592
|
+
* @param {Object|null} terrain { tiles: [url with {z}/{x}/{y}], encoding: 'terrarium' | 'mapbox' (default terrarium),
|
|
33593
|
+
* tileSize (256), maxzoom (15), attribution, exaggeration (1), hillshade (true, or { exaggeration, shadow,
|
|
33594
|
+
* highlight }) }; null takes it off
|
|
33595
|
+
*/
|
|
33596
|
+
SMap.prototype.setTerrain = function (terrain) {
|
|
33597
|
+
if (terrain && (!Array.isArray(terrain.tiles) || !terrain.tiles.length)) { throw new Error('sMap: terrain needs tiles'); }
|
|
33598
|
+
this._terrain = terrain ? Object.assign({}, terrain) : null;
|
|
33599
|
+
if (this.engine && this._styleReady) { this._applyTerrain(); }
|
|
33600
|
+
return this;
|
|
33601
|
+
};
|
|
33602
|
+
|
|
33603
|
+
SMap.prototype._applyTerrain = function () {
|
|
33604
|
+
var m = this.engine, t = this._terrain;
|
|
33605
|
+
if (m.setTerrain) { m.setTerrain(null); }
|
|
33606
|
+
if (m.getLayer(HILLSHADE_LAYER)) { m.removeLayer(HILLSHADE_LAYER); }
|
|
33607
|
+
if (m.getSource(TERRAIN_SOURCE)) { m.removeSource(TERRAIN_SOURCE); }
|
|
33608
|
+
if (m.getSource(TERRAIN_SOURCE + '-shade')) { m.removeSource(TERRAIN_SOURCE + '-shade'); }
|
|
33609
|
+
if (!t) { return; }
|
|
33610
|
+
var source = { type: 'raster-dem', tiles: t.tiles.slice(), tileSize: t.tileSize || 256, encoding: t.encoding || 'terrarium', maxzoom: t.maxzoom || 15 };
|
|
33611
|
+
if (t.attribution) { source.attribution = t.attribution; }
|
|
33612
|
+
m.addSource(TERRAIN_SOURCE, source);
|
|
33613
|
+
if (t.hillshade !== false) {
|
|
33614
|
+
var hs = t.hillshade && typeof t.hillshade === 'object' ? t.hillshade : {};
|
|
33615
|
+
var paint = { 'hillshade-exaggeration': hs.exaggeration !== undefined ? hs.exaggeration : 0.35 };
|
|
33616
|
+
if (hs.shadow) { paint['hillshade-shadow-color'] = hs.shadow; }
|
|
33617
|
+
if (hs.highlight) { paint['hillshade-highlight-color'] = hs.highlight; }
|
|
33618
|
+
// Over land and water, under the roads; from a source of its own (the engine draws the relief and the shading
|
|
33619
|
+
// better from two sources than from one).
|
|
33620
|
+
m.addSource(TERRAIN_SOURCE + '-shade', Object.assign({}, source, { tiles: source.tiles.slice() }));
|
|
33621
|
+
m.addLayer({ id: HILLSHADE_LAYER, type: 'hillshade', source: TERRAIN_SOURCE + '-shade', paint: paint }, this.slots().roads || undefined);
|
|
33622
|
+
}
|
|
33623
|
+
if (m.setTerrain) { m.setTerrain({ source: TERRAIN_SOURCE, exaggeration: t.exaggeration || 1 }); }
|
|
33624
|
+
};
|
|
33625
|
+
|
|
33626
|
+
/**
|
|
33627
|
+
* Keeps another sMap map on this map's view, both ways (a picture in picture, a before / after pair). `offsetZoom`
|
|
33628
|
+
* is added to this map's zoom on the other (e.g. -3 for an overview). Returns a function that ends it.
|
|
33629
|
+
*/
|
|
33630
|
+
SMap.prototype.syncWith = function (other, options) {
|
|
33631
|
+
var self = this, offset = (options && options.offsetZoom) || 0, busy = false;
|
|
33632
|
+
if (!other || !(other instanceof SMap)) { throw new Error('sMap: syncWith takes another sMap map'); }
|
|
33633
|
+
var copy = function (from, to, dz) {
|
|
33634
|
+
return function () {
|
|
33635
|
+
if (busy || !from.engine || !to.engine) { return; }
|
|
33636
|
+
busy = true;
|
|
33637
|
+
try {
|
|
33638
|
+
var c = from.engine.getCenter();
|
|
33639
|
+
to.engine.jumpTo({ center: [c.lng, c.lat], zoom: from.engine.getZoom() + dz, bearing: from.engine.getBearing(), pitch: from.engine.getPitch() });
|
|
33640
|
+
} finally { busy = false; }
|
|
33641
|
+
};
|
|
33642
|
+
};
|
|
33643
|
+
var a = copy(this, other, offset), b = copy(other, this, -offset);
|
|
33644
|
+
Promise.all([this.ready, other.ready]).then(function () {
|
|
33645
|
+
if (!a) { return; }
|
|
33646
|
+
self.engine.on('move', a);
|
|
33647
|
+
other.engine.on('move', b);
|
|
33648
|
+
a();
|
|
33649
|
+
});
|
|
33650
|
+
return function () {
|
|
33651
|
+
if (self.engine) { self.engine.off('move', a); }
|
|
33652
|
+
if (other.engine) { other.engine.off('move', b); }
|
|
33653
|
+
a = null;
|
|
33654
|
+
};
|
|
33655
|
+
};
|
|
33656
|
+
|
|
33657
|
+
// Tiles drawn by the page: one protocol for every map, each layer its own key.
|
|
33658
|
+
var canvasTiles = {};
|
|
33659
|
+
var canvasSeq = 0;
|
|
33660
|
+
var canvasRegistered = false;
|
|
33661
|
+
function registerCanvasProtocol(doc) {
|
|
33662
|
+
if (canvasRegistered) { return; }
|
|
33663
|
+
canvasRegistered = true;
|
|
33664
|
+
var handler = function (params) {
|
|
33665
|
+
var m = /^smapcanvas:\/\/([^/]+)\/(\d+)\/(\d+)\/(\d+)/.exec(params.url);
|
|
33666
|
+
var entry = m && canvasTiles[m[1]];
|
|
33667
|
+
if (!entry) { return Promise.reject(new Error('sMap: no drawn tiles for ' + params.url)); }
|
|
33668
|
+
var size = entry.tileSize * entry.ratio;
|
|
33669
|
+
var canvas = entry.doc.createElement('canvas');
|
|
33670
|
+
canvas.width = size; canvas.height = size;
|
|
33671
|
+
var ctx = canvas.getContext('2d');
|
|
33672
|
+
ctx.scale(entry.ratio, entry.ratio);
|
|
33673
|
+
return Promise.resolve(entry.draw(ctx, +m[2], +m[3], +m[4], { size: entry.tileSize, canvas: canvas })).then(function () {
|
|
33674
|
+
return new Promise(function (resolve, reject) {
|
|
33675
|
+
canvas.toBlob(function (blob) {
|
|
33676
|
+
if (!blob) { reject(new Error('sMap: a drawn tile could not be encoded')); return; }
|
|
33677
|
+
blob.arrayBuffer().then(function (data) { resolve({ data: data }); }, reject);
|
|
33678
|
+
}, 'image/png');
|
|
33679
|
+
});
|
|
33680
|
+
});
|
|
33681
|
+
};
|
|
33682
|
+
if (engine && engine.addProtocol) { engine.addProtocol('smapcanvas', handler); }
|
|
33683
|
+
if (canvasLib) { canvasLib.addProtocol('smapcanvas', handler); }
|
|
33684
|
+
}
|
|
33685
|
+
|
|
33686
|
+
/**
|
|
33687
|
+
* Raster tiles drawn by the page: draw(ctx, z, x, y, { size, canvas }) paints one tile (a 2D context already scaled
|
|
33688
|
+
* to `size` × `size` CSS pixels; may return a promise). Options as addRasterLayer (tileSize 256, minzoom, maxzoom,
|
|
33689
|
+
* opacity, attribution, slot, before, zIndex).
|
|
33690
|
+
*/
|
|
33691
|
+
SMap.prototype.addCanvasTiles = function (id, draw, o) {
|
|
33692
|
+
if (typeof draw !== 'function') { throw new Error('sMap: addCanvasTiles needs draw(ctx, z, x, y)'); }
|
|
33693
|
+
o = o || {};
|
|
33694
|
+
var doc = (this.engine && this.engine.getContainer().ownerDocument) || (typeof document !== 'undefined' ? document : null);
|
|
33695
|
+
if (!doc) { throw new Error('sMap: addCanvasTiles needs a page'); }
|
|
33696
|
+
registerCanvasProtocol(doc);
|
|
33697
|
+
var key = 'c' + (++canvasSeq);
|
|
33698
|
+
var win = doc.defaultView || {};
|
|
33699
|
+
canvasTiles[key] = { draw: draw, tileSize: o.tileSize || 256, ratio: Math.min(2, Math.max(1, Math.ceil(win.devicePixelRatio || 1))), doc: doc };
|
|
33700
|
+
var source = { type: 'raster', tiles: ['smapcanvas://' + key + '/{z}/{x}/{y}'], tileSize: o.tileSize || 256 };
|
|
33701
|
+
copyKeys(o, source, ['attribution', 'minzoom', 'maxzoom', 'bounds']);
|
|
33702
|
+
return this._addRaster(id, source, o);
|
|
33703
|
+
};
|
|
33704
|
+
|
|
33705
|
+
// ------------------------------------------------------------------ drawing and editing shapes (G3), area selection (G4)
|
|
33706
|
+
|
|
33707
|
+
var DRAW_TEXT = {
|
|
33708
|
+
polygon: 'Polygon', rectangle: 'Rectangle', circle: 'Circle', line: 'Line', point: 'Point', hole: 'Hole',
|
|
33709
|
+
delete: 'Delete', undo: 'Undo', redo: 'Redo', finish: 'Finish', cancel: 'Cancel',
|
|
33710
|
+
};
|
|
33711
|
+
var DRAW_MODES = ['polygon', 'rectangle', 'circle', 'line', 'point'];
|
|
33712
|
+
var SNAP_PX = 10;
|
|
33713
|
+
var drawSeq = 0;
|
|
33714
|
+
|
|
33715
|
+
function closeRing(ring) {
|
|
33716
|
+
var r = ring.slice();
|
|
33717
|
+
if (r.length && (r[0][0] !== r[r.length - 1][0] || r[0][1] !== r[r.length - 1][1])) { r.push(r[0].slice()); }
|
|
33718
|
+
return r;
|
|
33719
|
+
}
|
|
33720
|
+
function openRing(ring) { return ring.length > 1 && ring[0][0] === ring[ring.length - 1][0] && ring[0][1] === ring[ring.length - 1][1] ? ring.slice(0, -1) : ring.slice(); }
|
|
33721
|
+
|
|
33722
|
+
// A circle of `radius` metres as a ring of 64 points.
|
|
33723
|
+
function circleRing(center, radius) {
|
|
33724
|
+
// The same Earth radius as sMap.geo.distance, so the radius handle measures what was drawn.
|
|
33725
|
+
var ring = [], R = 6371008.8, lat = center[1] * Math.PI / 180;
|
|
33726
|
+
for (var i = 0; i <= 64; i++) {
|
|
33727
|
+
var a = (i % 64) / 64 * 2 * Math.PI;
|
|
33728
|
+
ring.push([center[0] + (radius * Math.sin(a)) / (R * Math.cos(lat)) * 180 / Math.PI, center[1] + (radius * Math.cos(a)) / R * 180 / Math.PI]);
|
|
33729
|
+
}
|
|
33730
|
+
return ring;
|
|
33731
|
+
}
|
|
33732
|
+
|
|
33733
|
+
// The editable rings of a geometry: [{ path: [polygon, ring] or [ring], coords (open), closed }].
|
|
33734
|
+
function ringsOf(g) {
|
|
33735
|
+
if (!g) { return []; }
|
|
33736
|
+
if (g.type === 'LineString') { return [{ path: [], coords: g.coordinates, closed: false }]; }
|
|
33737
|
+
if (g.type === 'Polygon') { return g.coordinates.map(function (r, i) { return { path: [i], coords: openRing(r), closed: true }; }); }
|
|
33738
|
+
if (g.type === 'MultiPolygon') {
|
|
33739
|
+
var out = [];
|
|
33740
|
+
g.coordinates.forEach(function (poly, p) { poly.forEach(function (r, i) { out.push({ path: [p, i], coords: openRing(r), closed: true }); }); });
|
|
33741
|
+
return out;
|
|
33742
|
+
}
|
|
33743
|
+
return [];
|
|
33744
|
+
}
|
|
33745
|
+
|
|
33746
|
+
function setRing(g, path, coords) {
|
|
33747
|
+
if (g.type === 'LineString') { g.coordinates = coords; return; }
|
|
33748
|
+
if (g.type === 'Polygon') { g.coordinates[path[0]] = closeRing(coords); return; }
|
|
33749
|
+
if (g.type === 'MultiPolygon') { g.coordinates[path[0]][path[1]] = closeRing(coords); }
|
|
33750
|
+
}
|
|
33751
|
+
|
|
33752
|
+
function moveGeometry(g, dx, dy) {
|
|
33753
|
+
(function walk(c) { if (typeof c[0] === 'number') { c[0] += dx; c[1] += dy; } else { c.forEach(walk); } })(g.coordinates);
|
|
33754
|
+
}
|
|
33755
|
+
|
|
33756
|
+
/**
|
|
33757
|
+
* A drawing and editing tool on this map. Shapes are kept by the tool as GeoJSON features (each with an id) and drawn
|
|
33758
|
+
* in its own layers; your page reads them with getAll() or from the events.
|
|
33759
|
+
* @param {Object} [o]
|
|
33760
|
+
* features (GeoJSON to start from), modes (the shapes offered, default all: polygon, rectangle, circle, line, point),
|
|
33761
|
+
* snap (true: a new or moved vertex within 10 px of another shape's vertex takes its place; default true),
|
|
33762
|
+
* toolbar (false, or a corner; default 'top-left'), labels (the toolbar's texts), color (default '#2563eb'),
|
|
33763
|
+
* onChange({ type: 'created' | 'edited' | 'deleted', feature, features }) after every change
|
|
33764
|
+
*/
|
|
33765
|
+
SMap.prototype.drawTool = function (o) {
|
|
33766
|
+
return new DrawTool(this, o || {});
|
|
33767
|
+
};
|
|
33768
|
+
|
|
33769
|
+
function DrawTool(map, o) {
|
|
33770
|
+
this.map = map;
|
|
33771
|
+
this.o = o;
|
|
33772
|
+
this.id = 'smap-draw-' + (++drawSeq);
|
|
33773
|
+
this.color = o.color || '#2563eb';
|
|
33774
|
+
this.modes = (o.modes || DRAW_MODES).filter(function (k) { return DRAW_MODES.indexOf(k) >= 0; });
|
|
33775
|
+
this.snap = o.snap !== false;
|
|
33776
|
+
this.text = Object.assign({}, DRAW_TEXT, o.labels || {});
|
|
33777
|
+
this.features = [];
|
|
33778
|
+
this.seq = 0;
|
|
33779
|
+
this.selected = null; // feature id
|
|
33780
|
+
this.vertex = null; // { ring, index } of the selected vertex
|
|
33781
|
+
this.mode = null; // the shape being drawn
|
|
33782
|
+
this.sketch = null; // coordinates of the shape being drawn
|
|
33783
|
+
this.hover = null;
|
|
33784
|
+
this.undoStack = [];
|
|
33785
|
+
this.redoStack = [];
|
|
33786
|
+
this.handlers = {};
|
|
33787
|
+
var self = this;
|
|
33788
|
+
(o.features ? toFeatureCollection(o.features).features : []).forEach(function (f) { self.features.push(self._own(f)); });
|
|
33789
|
+
map.ready.then(function () { if (!self.destroyed) { self._mount(); } });
|
|
33790
|
+
}
|
|
33791
|
+
|
|
33792
|
+
DrawTool.prototype._own = function (f) {
|
|
33793
|
+
var copy = JSON.parse(JSON.stringify(f));
|
|
33794
|
+
if (copy.id === undefined || copy.id === null) { copy.id = 'f' + (++this.seq); }
|
|
33795
|
+
copy.properties = copy.properties || {};
|
|
33796
|
+
return copy;
|
|
33797
|
+
};
|
|
33798
|
+
|
|
33799
|
+
DrawTool.prototype.on = function (type, fn) { (this.handlers[type] = this.handlers[type] || []).push(fn); return this; };
|
|
33800
|
+
DrawTool.prototype.off = function (type, fn) { this.handlers[type] = (this.handlers[type] || []).filter(function (h) { return h !== fn; }); return this; };
|
|
33801
|
+
DrawTool.prototype._emit = function (type, feature) {
|
|
33802
|
+
var e = { type: type, feature: feature ? JSON.parse(JSON.stringify(feature)) : null, features: this.getAll() };
|
|
33803
|
+
(this.handlers[type] || []).slice().forEach(function (h) { h(e); });
|
|
33804
|
+
(this.handlers.change || []).slice().forEach(function (h) { h(e); });
|
|
33805
|
+
if (this.o.onChange) { this.o.onChange(e); }
|
|
33806
|
+
};
|
|
33807
|
+
|
|
33808
|
+
/** Every shape as a FeatureCollection (a copy). */
|
|
33809
|
+
DrawTool.prototype.getAll = function () { return JSON.parse(JSON.stringify({ type: 'FeatureCollection', features: this.features })); };
|
|
33810
|
+
DrawTool.prototype.get = function (id) { var f = this._find(id); return f ? JSON.parse(JSON.stringify(f)) : null; };
|
|
33811
|
+
/** Replaces every shape (not recorded for undo). */
|
|
33812
|
+
DrawTool.prototype.set = function (data) {
|
|
33813
|
+
var self = this;
|
|
33814
|
+
this.features = toFeatureCollection(data).features.map(function (f) { return self._own(f); });
|
|
33815
|
+
this.selected = null; this.vertex = null;
|
|
33816
|
+
this.undoStack = []; this.redoStack = [];
|
|
33817
|
+
this._render();
|
|
33818
|
+
return this;
|
|
33819
|
+
};
|
|
33820
|
+
DrawTool.prototype._find = function (id) { for (var i = 0; i < this.features.length; i++) { if (this.features[i].id === id) { return this.features[i]; } } return null; };
|
|
33821
|
+
DrawTool.prototype._record = function () {
|
|
33822
|
+
this.undoStack.push(JSON.stringify(this.features));
|
|
33823
|
+
if (this.undoStack.length > 100) { this.undoStack.shift(); }
|
|
33824
|
+
this.redoStack = [];
|
|
33825
|
+
};
|
|
33826
|
+
DrawTool.prototype.undo = function () {
|
|
33827
|
+
if (!this.undoStack.length) { return this; }
|
|
33828
|
+
this.redoStack.push(JSON.stringify(this.features));
|
|
33829
|
+
this.features = JSON.parse(this.undoStack.pop());
|
|
33830
|
+
if (this.selected && !this._find(this.selected)) { this.selected = null; }
|
|
33831
|
+
this.vertex = null;
|
|
33832
|
+
this._render();
|
|
33833
|
+
this._emit('edited', this.selected ? this._find(this.selected) : null);
|
|
33834
|
+
return this;
|
|
33835
|
+
};
|
|
33836
|
+
DrawTool.prototype.redo = function () {
|
|
33837
|
+
if (!this.redoStack.length) { return this; }
|
|
33838
|
+
this.undoStack.push(JSON.stringify(this.features));
|
|
33839
|
+
this.features = JSON.parse(this.redoStack.pop());
|
|
33840
|
+
if (this.selected && !this._find(this.selected)) { this.selected = null; }
|
|
33841
|
+
this.vertex = null;
|
|
33842
|
+
this._render();
|
|
33843
|
+
this._emit('edited', this.selected ? this._find(this.selected) : null);
|
|
33844
|
+
return this;
|
|
33845
|
+
};
|
|
33846
|
+
|
|
33847
|
+
/** Starts drawing a shape: 'polygon', 'rectangle', 'circle', 'line', 'point', or 'hole' (in the selected polygon). */
|
|
33848
|
+
DrawTool.prototype.start = function (mode) {
|
|
33849
|
+
if (mode === 'hole') {
|
|
33850
|
+
var f = this.selected && this._find(this.selected);
|
|
33851
|
+
if (!f || !f.geometry || f.geometry.type !== 'Polygon') { throw new Error('sMap: select a polygon to draw a hole in'); }
|
|
33852
|
+
} else if (DRAW_MODES.indexOf(mode) < 0) { throw new Error('sMap: unknown shape "' + mode + '"'); }
|
|
33853
|
+
this.mode = mode;
|
|
33854
|
+
this.sketch = [];
|
|
33855
|
+
this.vertex = null;
|
|
33856
|
+
if (mode !== 'hole') { this.selected = null; }
|
|
33857
|
+
this._render();
|
|
33858
|
+
this._renderToolbar();
|
|
33859
|
+
return this;
|
|
33860
|
+
};
|
|
33861
|
+
|
|
33862
|
+
/** Stops drawing without keeping the shape being drawn. */
|
|
33863
|
+
DrawTool.prototype.cancel = function () { this.mode = null; this.sketch = null; this._render(); this._renderToolbar(); return this; };
|
|
33864
|
+
|
|
33865
|
+
/** Selects a shape for editing (null to deselect). */
|
|
33866
|
+
DrawTool.prototype.select = function (id) { this.selected = id && this._find(id) ? id : null; this.vertex = null; this._render(); this._renderToolbar(); return this; };
|
|
33867
|
+
DrawTool.prototype.selection = function () { return this.selected; };
|
|
33868
|
+
|
|
33869
|
+
/** Deletes a shape (default: the selected one); with a vertex selected, deletes that vertex instead. */
|
|
33870
|
+
DrawTool.prototype.delete = function (id) {
|
|
33871
|
+
if (id === undefined && this.vertex && this.selected) { return this._deleteVertex(); }
|
|
33872
|
+
id = id === undefined ? this.selected : id;
|
|
33873
|
+
var f = this._find(id);
|
|
33874
|
+
if (!f) { return this; }
|
|
33875
|
+
this._record();
|
|
33876
|
+
this.features = this.features.filter(function (x) { return x !== f; });
|
|
33877
|
+
if (this.selected === id) { this.selected = null; this.vertex = null; }
|
|
33878
|
+
this._render();
|
|
33879
|
+
this._renderToolbar();
|
|
33880
|
+
this._emit('deleted', f);
|
|
33881
|
+
return this;
|
|
33882
|
+
};
|
|
33883
|
+
|
|
33884
|
+
DrawTool.prototype._deleteVertex = function () {
|
|
33885
|
+
var f = this._find(this.selected), v = this.vertex;
|
|
33886
|
+
var rings = ringsOf(f.geometry), r = rings[v.ring];
|
|
33887
|
+
if (!r || r.coords.length <= (r.closed ? 3 : 2)) { return this; }
|
|
33888
|
+
this._record();
|
|
33889
|
+
var coords = r.coords.slice();
|
|
33890
|
+
coords.splice(v.index, 1);
|
|
33891
|
+
setRing(f.geometry, r.path, coords);
|
|
33892
|
+
this.vertex = null;
|
|
33893
|
+
this._render();
|
|
33894
|
+
this._emit('edited', f);
|
|
33895
|
+
return this;
|
|
33896
|
+
};
|
|
33897
|
+
|
|
33898
|
+
DrawTool.prototype.destroy = function () {
|
|
33899
|
+
this.destroyed = true;
|
|
33900
|
+
var m = this.map.engine;
|
|
33901
|
+
if (!m) { return; }
|
|
33902
|
+
var self = this;
|
|
33903
|
+
(this.listeners || []).forEach(function (l) { if (l[1]) { m.off(l[0], l[1], l[2]); } else { m.off(l[0], l[2]); } });
|
|
33904
|
+
[this.id + '-fill', this.id + '-line', this.id + '-sketch', this.id + '-vertex', this.id + '-mid'].forEach(function (l) { if (m.getLayer(l)) { m.removeLayer(l); } });
|
|
33905
|
+
if (m.getSource(this.id)) { m.removeSource(this.id); }
|
|
33906
|
+
if (this.toolbar) { m.removeControl(this.toolbar); }
|
|
33907
|
+
if (this.keyListener) { m.getContainer().removeEventListener('keydown', this.keyListener); }
|
|
33908
|
+
if (this.panOff && m.dragPan) { m.dragPan.enable(); }
|
|
33909
|
+
if (m.doubleClickZoom && this.dblWasOn) { m.doubleClickZoom.enable(); }
|
|
33910
|
+
self.map._drawTools = (self.map._drawTools || []).filter(function (t) { return t !== self; });
|
|
33911
|
+
};
|
|
33912
|
+
|
|
33913
|
+
DrawTool.prototype._listen = function (type, layer, fn) {
|
|
33914
|
+
var m = this.map.engine;
|
|
33915
|
+
if (layer) { m.on(type, layer, fn); } else { m.on(type, fn); }
|
|
33916
|
+
(this.listeners = this.listeners || []).push([type, layer, fn]);
|
|
33917
|
+
};
|
|
33918
|
+
|
|
33919
|
+
DrawTool.prototype._mount = function () {
|
|
33920
|
+
var self = this, m = this.map.engine;
|
|
33921
|
+
this.map._drawTools = (this.map._drawTools || []).concat([this]);
|
|
33922
|
+
this._addLayers();
|
|
33923
|
+
// The map's style changes drop every layer: the tool's come back with the map's own.
|
|
33924
|
+
this._listen('style.load', null, function () { self._addLayers(); });
|
|
33925
|
+
this._listen('mousedown', null, function (e) { self._down(e); });
|
|
33926
|
+
this._listen('touchstart', null, function (e) { if (e.points && e.points.length === 1) { self._down(e); } });
|
|
33927
|
+
this._listen('mousemove', null, function (e) { self._move(e); });
|
|
33928
|
+
this._listen('touchmove', null, function (e) { self._move(e); });
|
|
33929
|
+
this._listen('mouseup', null, function (e) { self._up(e); });
|
|
33930
|
+
this._listen('touchend', null, function (e) { self._up(e); });
|
|
33931
|
+
this._listen('click', null, function (e) { self._click(e); });
|
|
33932
|
+
this._listen('dblclick', null, function (e) { if (self.mode === 'polygon' || self.mode === 'line' || self.mode === 'hole') { e.preventDefault(); self._finish(); } });
|
|
33933
|
+
if (m.doubleClickZoom && m.doubleClickZoom.isEnabled && m.doubleClickZoom.isEnabled()) { this.dblWasOn = true; }
|
|
33934
|
+
var el = m.getContainer();
|
|
33935
|
+
this.keyListener = function (ev) {
|
|
33936
|
+
if (ev.key === 'Escape') { if (self.mode) { self.cancel(); } else { self.select(null); } }
|
|
33937
|
+
else if (ev.key === 'Enter' && self.mode) { self._finish(); }
|
|
33938
|
+
else if ((ev.key === 'Backspace' || ev.key === 'Delete') && self.mode && self.sketch && self.sketch.length) { self.sketch.pop(); self._render(); ev.preventDefault(); }
|
|
33939
|
+
else if ((ev.key === 'Backspace' || ev.key === 'Delete') && self.selected) { self.delete(); ev.preventDefault(); }
|
|
33940
|
+
else if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'z') { if (ev.shiftKey) { self.redo(); } else { self.undo(); } ev.preventDefault(); }
|
|
33941
|
+
};
|
|
33942
|
+
el.addEventListener('keydown', this.keyListener);
|
|
33943
|
+
if (this.o.toolbar !== false) {
|
|
33944
|
+
this.toolbar = this._toolbarControl();
|
|
33945
|
+
m.addControl(this.toolbar, typeof this.o.toolbar === 'string' ? this.o.toolbar : 'top-left');
|
|
33946
|
+
}
|
|
33947
|
+
this._render();
|
|
33948
|
+
};
|
|
33949
|
+
|
|
33950
|
+
DrawTool.prototype._addLayers = function () {
|
|
33951
|
+
var m = this.map.engine, id = this.id, c = this.color;
|
|
33952
|
+
if (m.getSource(id)) { return; }
|
|
33953
|
+
m.addSource(id, { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
|
|
33954
|
+
var role = function (r) { return ['==', ['get', '_role'], r]; };
|
|
33955
|
+
m.addLayer({ id: id + '-fill', type: 'fill', source: id, filter: ['all', ['==', ['geometry-type'], 'Polygon'], ['!', role('sketch')]],
|
|
33956
|
+
paint: { 'fill-color': ['coalesce', ['get', '_fill'], c], 'fill-opacity': ['case', ['==', ['get', '_selected'], true], 0.3, 0.18] } });
|
|
33957
|
+
m.addLayer({ id: id + '-line', type: 'line', source: id, filter: ['all', ['!=', ['geometry-type'], 'Point'], ['!', role('sketch')]],
|
|
33958
|
+
layout: { 'line-join': 'round', 'line-cap': 'round' },
|
|
33959
|
+
paint: { 'line-color': ['coalesce', ['get', '_stroke'], c], 'line-width': ['case', ['==', ['get', '_selected'], true], 3, 2] } });
|
|
33960
|
+
m.addLayer({ id: id + '-sketch', type: 'line', source: id, filter: role('sketch'),
|
|
33961
|
+
layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': c, 'line-width': 2, 'line-dasharray': [2, 1.5] } });
|
|
33962
|
+
m.addLayer({ id: id + '-mid', type: 'circle', source: id, filter: role('mid'),
|
|
33963
|
+
paint: { 'circle-radius': 4, 'circle-color': '#ffffff', 'circle-opacity': 0.9, 'circle-stroke-width': 1.5, 'circle-stroke-color': c } });
|
|
33964
|
+
m.addLayer({ id: id + '-vertex', type: 'circle', source: id, filter: ['any', role('vertex'), role('point'), role('sketch-vertex')],
|
|
33965
|
+
paint: { 'circle-radius': ['case', ['==', ['get', '_active'], true], 7, ['==', ['get', '_role'], 'point'], 7, 5],
|
|
33966
|
+
'circle-color': ['case', ['==', ['get', '_active'], true], c, '#ffffff'], 'circle-stroke-width': 2, 'circle-stroke-color': c } });
|
|
33967
|
+
};
|
|
33968
|
+
|
|
33969
|
+
// What is drawn: every shape, the selected one's vertices and midpoints, and the shape being drawn.
|
|
33970
|
+
DrawTool.prototype._render = function () {
|
|
33971
|
+
var m = this.map.engine;
|
|
33972
|
+
if (!m || this.destroyed) { return; }
|
|
33973
|
+
var out = [], self = this;
|
|
33974
|
+
this.features.forEach(function (f) {
|
|
33975
|
+
var sel = f.id === self.selected;
|
|
33976
|
+
var props = { _id: f.id, _selected: sel };
|
|
33977
|
+
if (f.properties && f.properties.fill) { props._fill = f.properties.fill; }
|
|
33978
|
+
if (f.properties && f.properties.stroke) { props._stroke = f.properties.stroke; }
|
|
33979
|
+
if (f.geometry.type === 'Point') { props._role = 'point'; props._active = sel; }
|
|
33980
|
+
out.push({ type: 'Feature', properties: props, geometry: f.geometry });
|
|
33981
|
+
if (!sel || f.geometry.type === 'Point') { return; }
|
|
33982
|
+
if (f.properties && f.properties.shape === 'circle' && f.properties.center) {
|
|
33983
|
+
// A circle: one handle on its edge (east) sets the radius.
|
|
33984
|
+
out.push({ type: 'Feature', properties: { _role: 'vertex', _id: f.id, _radius: true, _active: false }, geometry: { type: 'Point', coordinates: circleRing(f.properties.center, f.properties.radius)[16] } });
|
|
33985
|
+
return;
|
|
33986
|
+
}
|
|
33987
|
+
ringsOf(f.geometry).forEach(function (r, ri) {
|
|
33988
|
+
r.coords.forEach(function (c, i) {
|
|
33989
|
+
var active = !!self.vertex && self.vertex.ring === ri && self.vertex.index === i;
|
|
33990
|
+
out.push({ type: 'Feature', properties: { _role: 'vertex', _id: f.id, _ring: ri, _index: i, _active: active }, geometry: { type: 'Point', coordinates: c } });
|
|
33991
|
+
var next = r.coords[i + 1] || (r.closed ? r.coords[0] : null);
|
|
33992
|
+
if (next && !(f.properties && f.properties.shape === 'rectangle')) {
|
|
33993
|
+
out.push({ type: 'Feature', properties: { _role: 'mid', _id: f.id, _ring: ri, _index: i + 1 }, geometry: { type: 'Point', coordinates: [(c[0] + next[0]) / 2, (c[1] + next[1]) / 2] } });
|
|
33994
|
+
}
|
|
33995
|
+
});
|
|
33996
|
+
});
|
|
33997
|
+
});
|
|
33998
|
+
if (this.mode && this.sketch) {
|
|
33999
|
+
var pts = this.sketch.concat(this.hover ? [this.hover] : []);
|
|
34000
|
+
if (pts.length >= 2) {
|
|
34001
|
+
var closed = this.mode !== 'line' && pts.length >= 3;
|
|
34002
|
+
out.push({ type: 'Feature', properties: { _role: 'sketch' }, geometry: { type: 'LineString', coordinates: closed ? pts.concat([pts[0]]) : pts } });
|
|
34003
|
+
}
|
|
34004
|
+
this.sketch.forEach(function (c) { out.push({ type: 'Feature', properties: { _role: 'sketch-vertex' }, geometry: { type: 'Point', coordinates: c } }); });
|
|
34005
|
+
}
|
|
34006
|
+
var src = m.getSource(this.id);
|
|
34007
|
+
if (src) { src.setData({ type: 'FeatureCollection', features: out }); }
|
|
34008
|
+
};
|
|
34009
|
+
|
|
34010
|
+
DrawTool.prototype._lngLat = function (e) { return [e.lngLat.lng, e.lngLat.lat]; };
|
|
34011
|
+
|
|
34012
|
+
// A position taken over by a vertex of another shape within SNAP_PX pixels (when snapping is on).
|
|
34013
|
+
DrawTool.prototype._snapped = function (e, except) {
|
|
34014
|
+
if (!this.snap) { return this._lngLat(e); }
|
|
34015
|
+
var m = this.map.engine, best = null, bestD = SNAP_PX;
|
|
34016
|
+
this.features.forEach(function (f) {
|
|
34017
|
+
if (f.id === except) { return; }
|
|
34018
|
+
var pts = f.geometry.type === 'Point' ? [f.geometry.coordinates] : [];
|
|
34019
|
+
ringsOf(f.geometry).forEach(function (r) { pts = pts.concat(r.coords); });
|
|
34020
|
+
pts.forEach(function (c) {
|
|
34021
|
+
var p = m.project(c), d = Math.sqrt(Math.pow(p.x - e.point.x, 2) + Math.pow(p.y - e.point.y, 2));
|
|
34022
|
+
if (d <= bestD) { bestD = d; best = c; }
|
|
34023
|
+
});
|
|
34024
|
+
});
|
|
34025
|
+
return best ? best.slice() : this._lngLat(e);
|
|
34026
|
+
};
|
|
34027
|
+
|
|
34028
|
+
DrawTool.prototype._hit = function (e, layers) {
|
|
34029
|
+
var m = this.map.engine, self = this;
|
|
34030
|
+
var ids = layers.map(function (l) { return self.id + '-' + l; }).filter(function (l) { return m.getLayer(l); });
|
|
34031
|
+
if (!ids.length) { return null; }
|
|
34032
|
+
var r = 4, box = [[e.point.x - r, e.point.y - r], [e.point.x + r, e.point.y + r]];
|
|
34033
|
+
var hits = m.queryRenderedFeatures(box, { layers: ids }) || [];
|
|
34034
|
+
return hits[0] || null;
|
|
34035
|
+
};
|
|
34036
|
+
|
|
34037
|
+
DrawTool.prototype._pan = function (on) {
|
|
34038
|
+
var m = this.map.engine;
|
|
34039
|
+
if (!m.dragPan) { return; }
|
|
34040
|
+
if (on && this.panOff) { m.dragPan.enable(); this.panOff = false; }
|
|
34041
|
+
else if (!on && !this.panOff && (!m.dragPan.isEnabled || m.dragPan.isEnabled())) { m.dragPan.disable(); this.panOff = true; }
|
|
34042
|
+
};
|
|
34043
|
+
|
|
34044
|
+
DrawTool.prototype._down = function (e) {
|
|
34045
|
+
if (e.originalEvent && e.originalEvent.button !== undefined && e.originalEvent.button !== 0) { return; }
|
|
34046
|
+
var self = this;
|
|
34047
|
+
if (this.mode === 'rectangle' || this.mode === 'circle') {
|
|
34048
|
+
e.preventDefault();
|
|
34049
|
+
this._pan(false);
|
|
34050
|
+
this.drag = { kind: this.mode, start: this._snapped(e) };
|
|
34051
|
+
return;
|
|
34052
|
+
}
|
|
34053
|
+
if (this.mode) { return; }
|
|
34054
|
+
if (!this.selected) { return; }
|
|
34055
|
+
var f = this._find(this.selected);
|
|
34056
|
+
var hit = this._hit(e, ['vertex', 'mid']);
|
|
34057
|
+
if (hit && hit.properties._id === this.selected) {
|
|
34058
|
+
e.preventDefault();
|
|
34059
|
+
this._pan(false);
|
|
34060
|
+
var p = hit.properties;
|
|
34061
|
+
if (p._radius) {
|
|
34062
|
+
this._record();
|
|
34063
|
+
this.drag = { kind: 'radius', moved: false };
|
|
34064
|
+
return;
|
|
34065
|
+
}
|
|
34066
|
+
if (p._role === 'mid') {
|
|
34067
|
+
// A midpoint dragged becomes a new vertex.
|
|
34068
|
+
this._record();
|
|
34069
|
+
var rings = ringsOf(f.geometry), r = rings[p._ring], coords = r.coords.slice();
|
|
34070
|
+
coords.splice(p._index, 0, hit.geometry.coordinates.slice());
|
|
34071
|
+
setRing(f.geometry, r.path, coords);
|
|
34072
|
+
this.vertex = { ring: p._ring, index: p._index };
|
|
34073
|
+
} else {
|
|
34074
|
+
this._record();
|
|
34075
|
+
this.vertex = { ring: p._ring, index: p._index };
|
|
34076
|
+
}
|
|
34077
|
+
this.drag = { kind: 'vertex', moved: false };
|
|
34078
|
+
this._render();
|
|
34079
|
+
return;
|
|
34080
|
+
}
|
|
34081
|
+
var body = this._hit(e, ['fill', 'line', 'vertex']);
|
|
34082
|
+
if (body && body.properties._id === this.selected) {
|
|
34083
|
+
e.preventDefault();
|
|
34084
|
+
this._pan(false);
|
|
34085
|
+
this._record();
|
|
34086
|
+
this.drag = { kind: 'move', last: this._lngLat(e), moved: false };
|
|
34087
|
+
self.vertex = null;
|
|
34088
|
+
}
|
|
34089
|
+
};
|
|
34090
|
+
|
|
34091
|
+
DrawTool.prototype._move = function (e) {
|
|
34092
|
+
if (this.mode && this.sketch && !this.drag) { this.hover = this._snapped(e); this._render(); }
|
|
34093
|
+
var d = this.drag;
|
|
34094
|
+
if (!d) { return; }
|
|
34095
|
+
var f = this.selected && this._find(this.selected);
|
|
34096
|
+
if (d.kind === 'rectangle' || d.kind === 'circle') {
|
|
34097
|
+
d.end = this._lngLat(e);
|
|
34098
|
+
var shape = this._dragShape(d);
|
|
34099
|
+
this.sketch = shape ? openRing(shape) : [];
|
|
34100
|
+
this.hover = null;
|
|
34101
|
+
this._render();
|
|
34102
|
+
return;
|
|
34103
|
+
}
|
|
34104
|
+
if (!f) { return; }
|
|
34105
|
+
d.moved = true;
|
|
34106
|
+
if (d.kind === 'radius') {
|
|
34107
|
+
f.properties.radius = interop.geo.distance(f.properties.center, this._lngLat(e));
|
|
34108
|
+
f.geometry.coordinates = [circleRing(f.properties.center, f.properties.radius)];
|
|
34109
|
+
} else if (d.kind === 'vertex' && f.properties && f.properties.shape === 'rectangle') {
|
|
34110
|
+
// A rectangle stays a rectangle: the opposite corner stays, the dragged one follows the pointer.
|
|
34111
|
+
var cs = ringsOf(f.geometry)[0].coords, opp = cs[(this.vertex.index + 2) % 4], at = this._snapped(e, f.id);
|
|
34112
|
+
var rect = [[opp[0], opp[1]], [at[0], opp[1]], [at[0], at[1]], [opp[0], at[1]]];
|
|
34113
|
+
// Keep the dragged corner at its index.
|
|
34114
|
+
var shift = (this.vertex.index + 2) % 4, ordered = [];
|
|
34115
|
+
for (var q = 0; q < 4; q++) { ordered[(shift + q) % 4] = rect[q]; }
|
|
34116
|
+
f.geometry.coordinates = [closeRing(ordered)];
|
|
34117
|
+
} else if (d.kind === 'vertex') {
|
|
34118
|
+
var rings = ringsOf(f.geometry), r = rings[this.vertex.ring], coords = r.coords.slice();
|
|
34119
|
+
coords[this.vertex.index] = this._snapped(e, f.id);
|
|
34120
|
+
setRing(f.geometry, r.path, coords);
|
|
34121
|
+
} else if (d.kind === 'move') {
|
|
34122
|
+
var now = this._lngLat(e);
|
|
34123
|
+
if (f.properties && f.properties.shape === 'circle' && f.properties.center) {
|
|
34124
|
+
f.properties.center = [f.properties.center[0] + now[0] - d.last[0], f.properties.center[1] + now[1] - d.last[1]];
|
|
34125
|
+
}
|
|
34126
|
+
moveGeometry(f.geometry, now[0] - d.last[0], now[1] - d.last[1]);
|
|
34127
|
+
d.last = now;
|
|
34128
|
+
}
|
|
34129
|
+
this._render();
|
|
34130
|
+
};
|
|
34131
|
+
|
|
34132
|
+
DrawTool.prototype._dragShape = function (d) {
|
|
34133
|
+
if (!d.end) { return null; }
|
|
34134
|
+
if (d.kind === 'rectangle') {
|
|
34135
|
+
var a = d.start, b = d.end;
|
|
34136
|
+
return [[a[0], a[1]], [b[0], a[1]], [b[0], b[1]], [a[0], b[1]], [a[0], a[1]]];
|
|
34137
|
+
}
|
|
34138
|
+
var radius = interop.geo.distance(d.start, d.end);
|
|
34139
|
+
return radius > 0 ? circleRing(d.start, radius) : null;
|
|
34140
|
+
};
|
|
34141
|
+
|
|
34142
|
+
DrawTool.prototype._up = function (e) {
|
|
34143
|
+
var d = this.drag;
|
|
34144
|
+
if (!d) { return; }
|
|
34145
|
+
this.drag = null;
|
|
34146
|
+
this._pan(true);
|
|
34147
|
+
this._suppressClick = true;
|
|
34148
|
+
var self = this;
|
|
34149
|
+
setTimeout(function () { self._suppressClick = false; }, 0);
|
|
34150
|
+
if (d.kind === 'rectangle' || d.kind === 'circle') {
|
|
34151
|
+
d.end = this._lngLat(e);
|
|
34152
|
+
var ring = this._dragShape(d);
|
|
34153
|
+
var mode = this.mode;
|
|
34154
|
+
this.mode = null; this.sketch = null; this.hover = null;
|
|
34155
|
+
if (ring && Math.abs(ring[0][0] - ring[2][0]) + Math.abs(ring[0][1] - ring[2][1]) > 0) {
|
|
34156
|
+
var props = mode === 'circle' ? { shape: 'circle', center: d.start, radius: interop.geo.distance(d.start, d.end) } : { shape: 'rectangle' };
|
|
34157
|
+
this._add({ type: 'Feature', properties: props, geometry: { type: 'Polygon', coordinates: [ring] } });
|
|
34158
|
+
} else { this._render(); this._renderToolbar(); }
|
|
34159
|
+
return;
|
|
34160
|
+
}
|
|
34161
|
+
if (d.moved) { this._emit('edited', this._find(this.selected)); } else { this.undoStack.pop(); }
|
|
34162
|
+
this._render();
|
|
34163
|
+
};
|
|
34164
|
+
|
|
34165
|
+
DrawTool.prototype._click = function (e) {
|
|
34166
|
+
if (this._suppressClick) { return; }
|
|
34167
|
+
var self = this;
|
|
34168
|
+
if (this.mode === 'point') { this.mode = null; this.sketch = null; this._add({ type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: this._snapped(e) } }); return; }
|
|
34169
|
+
if (this.mode === 'polygon' || this.mode === 'line' || this.mode === 'hole') {
|
|
34170
|
+
var at = this._snapped(e);
|
|
34171
|
+
// A click on the first vertex closes a polygon.
|
|
34172
|
+
if (this.mode !== 'line' && this.sketch.length >= 3) {
|
|
34173
|
+
var first = this.map.engine.project(this.sketch[0]);
|
|
34174
|
+
if (Math.abs(first.x - e.point.x) <= SNAP_PX && Math.abs(first.y - e.point.y) <= SNAP_PX) { this._finish(); return; }
|
|
34175
|
+
}
|
|
34176
|
+
var last = this.sketch[this.sketch.length - 1];
|
|
34177
|
+
if (!last || last[0] !== at[0] || last[1] !== at[1]) { this.sketch.push(at); }
|
|
34178
|
+
this._render();
|
|
34179
|
+
return;
|
|
34180
|
+
}
|
|
34181
|
+
if (this.mode) { return; }
|
|
34182
|
+
// Not drawing: a click selects the shape under it (or a vertex of the selected one), elsewhere deselects.
|
|
34183
|
+
var v = this.selected && this._hit(e, ['vertex']);
|
|
34184
|
+
if (v && v.properties._id === this.selected && v.properties._role === 'vertex') {
|
|
34185
|
+
this.vertex = { ring: v.properties._ring, index: v.properties._index };
|
|
34186
|
+
this._render();
|
|
34187
|
+
return;
|
|
34188
|
+
}
|
|
34189
|
+
var hit = this._hit(e, ['vertex', 'line', 'fill']);
|
|
34190
|
+
self.select(hit ? hit.properties._id : null);
|
|
34191
|
+
};
|
|
34192
|
+
|
|
34193
|
+
DrawTool.prototype._finish = function () {
|
|
34194
|
+
var mode = this.mode, pts = (this.sketch || []).slice();
|
|
34195
|
+
this.mode = null; this.sketch = null; this.hover = null;
|
|
34196
|
+
if (mode === 'line' && pts.length >= 2) { this._add({ type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: pts } }); return; }
|
|
34197
|
+
if (mode === 'polygon' && pts.length >= 3) { this._add({ type: 'Feature', properties: {}, geometry: { type: 'Polygon', coordinates: [closeRing(pts)] } }); return; }
|
|
34198
|
+
if (mode === 'hole' && pts.length >= 3) {
|
|
34199
|
+
var f = this._find(this.selected);
|
|
34200
|
+
if (f && f.geometry.type === 'Polygon') {
|
|
34201
|
+
this._record();
|
|
34202
|
+
f.geometry.coordinates.push(closeRing(pts));
|
|
34203
|
+
this._render(); this._renderToolbar();
|
|
34204
|
+
this._emit('edited', f);
|
|
34205
|
+
return;
|
|
34206
|
+
}
|
|
34207
|
+
}
|
|
34208
|
+
this._render();
|
|
34209
|
+
this._renderToolbar();
|
|
34210
|
+
};
|
|
34211
|
+
|
|
34212
|
+
DrawTool.prototype._add = function (feature) {
|
|
34213
|
+
this._record();
|
|
34214
|
+
var f = this._own(feature);
|
|
34215
|
+
this.features.push(f);
|
|
34216
|
+
this.selected = f.geometry.type === 'Point' ? null : f.id;
|
|
34217
|
+
this.vertex = null;
|
|
34218
|
+
this._render();
|
|
34219
|
+
this._renderToolbar();
|
|
34220
|
+
this._emit('created', f);
|
|
34221
|
+
};
|
|
34222
|
+
|
|
34223
|
+
DrawTool.prototype._toolbarControl = function () {
|
|
34224
|
+
var self = this;
|
|
34225
|
+
return {
|
|
34226
|
+
onAdd: function (m) {
|
|
34227
|
+
var doc = m.getContainer().ownerDocument;
|
|
34228
|
+
var el = doc.createElement('div');
|
|
34229
|
+
el.className = 'maplibregl-ctrl maplibregl-ctrl-group smap-draw';
|
|
34230
|
+
self.toolbarEl = el;
|
|
34231
|
+
self._renderToolbar();
|
|
34232
|
+
return el;
|
|
34233
|
+
},
|
|
34234
|
+
onRemove: function () { if (self.toolbarEl && self.toolbarEl.parentNode) { self.toolbarEl.parentNode.removeChild(self.toolbarEl); } self.toolbarEl = null; },
|
|
34235
|
+
};
|
|
34236
|
+
};
|
|
34237
|
+
|
|
34238
|
+
var DRAW_ICONS = {
|
|
34239
|
+
polygon: '<path d="M5 7l7-4 7 5-2 10H7z"/>', rectangle: '<rect x="4" y="6" width="16" height="12"/>',
|
|
34240
|
+
circle: '<circle cx="12" cy="12" r="8"/>', line: '<path d="M4 18 10 8l4 6 6-10"/>', point: '<circle cx="12" cy="12" r="3"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/>',
|
|
34241
|
+
hole: '<path d="M5 7l7-4 7 5-2 10H7z"/><circle cx="12" cy="11" r="2.5"/>', delete: '<path d="M5 7h14M9 7V4h6v3M7 7l1 13h8l1-13"/>',
|
|
34242
|
+
undo: '<path d="M9 14 4 9l5-5"/><path d="M4 9h10a6 6 0 0 1 0 12h-3"/>', redo: '<path d="m15 14 5-5-5-5"/><path d="M20 9H10a6 6 0 0 0 0 12h3"/>',
|
|
34243
|
+
};
|
|
34244
|
+
|
|
34245
|
+
DrawTool.prototype._renderToolbar = function () {
|
|
34246
|
+
var el = this.toolbarEl;
|
|
34247
|
+
if (!el) { return; }
|
|
34248
|
+
var self = this, doc = el.ownerDocument;
|
|
34249
|
+
while (el.firstChild) { el.removeChild(el.firstChild); }
|
|
34250
|
+
var selectedPolygon = this.selected && this._find(this.selected) && this._find(this.selected).geometry.type === 'Polygon' && !(this._find(this.selected).properties.shape);
|
|
34251
|
+
var buttons = this.modes.slice();
|
|
34252
|
+
if (selectedPolygon) { buttons.push('hole'); }
|
|
34253
|
+
buttons = buttons.concat(['delete', 'undo', 'redo']);
|
|
34254
|
+
buttons.forEach(function (k) {
|
|
34255
|
+
var b = doc.createElement('button');
|
|
34256
|
+
b.type = 'button';
|
|
34257
|
+
b.className = 'smap-draw-' + k;
|
|
34258
|
+
b.title = self.text[k];
|
|
34259
|
+
b.setAttribute('aria-label', self.text[k]);
|
|
34260
|
+
b.innerHTML = '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + DRAW_ICONS[k] + '</svg>';
|
|
34261
|
+
if (DRAW_MODES.indexOf(k) >= 0 || k === 'hole') {
|
|
34262
|
+
b.setAttribute('aria-pressed', self.mode === k ? 'true' : 'false');
|
|
34263
|
+
b.addEventListener('click', function () { if (self.mode === k) { self.cancel(); } else { self.start(k); } });
|
|
34264
|
+
} else if (k === 'delete') {
|
|
34265
|
+
b.disabled = !self.selected;
|
|
34266
|
+
b.addEventListener('click', function () { self.delete(); });
|
|
34267
|
+
} else {
|
|
34268
|
+
b.disabled = k === 'undo' ? !self.undoStack.length : !self.redoStack.length;
|
|
34269
|
+
b.addEventListener('click', function () { self[k](); });
|
|
34270
|
+
}
|
|
34271
|
+
el.appendChild(b);
|
|
34272
|
+
});
|
|
34273
|
+
};
|
|
34274
|
+
|
|
34275
|
+
/**
|
|
34276
|
+
* Selects features by drawing on the map: 'box' (press and drag) or 'lasso' (press and draw a free shape). Resolves
|
|
34277
|
+
* with { features: { layerId: [features] }, geometry } once the shape is drawn, or null when Escape cancels it.
|
|
34278
|
+
* A point (or custom marker) counts when it is inside; a line or area when any of its vertices is.
|
|
34279
|
+
* @param {Object} [o] mode ('box' default, 'lasso'), layers (default every point, custom marker, line and area layer)
|
|
34280
|
+
*/
|
|
34281
|
+
SMap.prototype.selectArea = function (o) {
|
|
34282
|
+
o = o || {};
|
|
34283
|
+
var self = this;
|
|
34284
|
+
var mode = o.mode === 'lasso' ? 'lasso' : 'box';
|
|
34285
|
+
return this.ready.then(function () {
|
|
34286
|
+
return new Promise(function (resolve) {
|
|
34287
|
+
var m = self.engine;
|
|
34288
|
+
var overlay = self._markerOverlay(), doc = overlay.ownerDocument;
|
|
34289
|
+
var svgNs = 'http://www.w3.org/2000/svg';
|
|
34290
|
+
var svg = doc.createElementNS(svgNs, 'svg');
|
|
34291
|
+
svg.setAttribute('class', 'smap-select');
|
|
34292
|
+
var shape = doc.createElementNS(svgNs, mode === 'box' ? 'rect' : 'polyline');
|
|
34293
|
+
svg.appendChild(shape);
|
|
34294
|
+
var points = null, panWasOn = !!(m.dragPan && (!m.dragPan.isEnabled || m.dragPan.isEnabled()));
|
|
34295
|
+
if (panWasOn) { m.dragPan.disable(); }
|
|
34296
|
+
if (m.boxZoom && m.boxZoom.disable) { m.boxZoom.disable(); }
|
|
34297
|
+
m.getCanvas().style.cursor = 'crosshair';
|
|
34298
|
+
var finish = function (result) {
|
|
34299
|
+
m.off('mousedown', down); m.off('mousemove', move); m.off('mouseup', up);
|
|
34300
|
+
m.off('touchstart', down); m.off('touchmove', move); m.off('touchend', up);
|
|
34301
|
+
m.getContainer().removeEventListener('keydown', key);
|
|
34302
|
+
if (svg.parentNode) { svg.parentNode.removeChild(svg); }
|
|
34303
|
+
if (panWasOn) { m.dragPan.enable(); }
|
|
34304
|
+
if (m.boxZoom && m.boxZoom.enable) { m.boxZoom.enable(); }
|
|
34305
|
+
m.getCanvas().style.cursor = '';
|
|
34306
|
+
resolve(result);
|
|
34307
|
+
};
|
|
34308
|
+
var draw = function () {
|
|
34309
|
+
if (mode === 'box') {
|
|
34310
|
+
var a = points[0], b = points[points.length - 1];
|
|
34311
|
+
shape.setAttribute('x', Math.min(a.x, b.x)); shape.setAttribute('y', Math.min(a.y, b.y));
|
|
34312
|
+
shape.setAttribute('width', Math.abs(a.x - b.x)); shape.setAttribute('height', Math.abs(a.y - b.y));
|
|
34313
|
+
} else {
|
|
34314
|
+
shape.setAttribute('points', points.map(function (p) { return p.x + ',' + p.y; }).join(' '));
|
|
34315
|
+
}
|
|
34316
|
+
};
|
|
34317
|
+
var down = function (e) { e.preventDefault(); points = [e.point]; overlay.appendChild(svg); draw(); };
|
|
34318
|
+
var move = function (e) { if (!points) { return; } if (mode === 'box') { points = [points[0], e.point]; } else { points.push(e.point); } draw(); };
|
|
34319
|
+
var up = function (e) {
|
|
34320
|
+
if (!points) { return; }
|
|
34321
|
+
if (mode === 'box') { points = [points[0], e.point]; }
|
|
34322
|
+
var ring;
|
|
34323
|
+
if (mode === 'box') {
|
|
34324
|
+
var a = points[0], b = points[1];
|
|
34325
|
+
ring = [[a.x, a.y], [b.x, a.y], [b.x, b.y], [a.x, b.y]];
|
|
34326
|
+
} else {
|
|
34327
|
+
ring = points.map(function (p) { return [p.x, p.y]; });
|
|
34328
|
+
}
|
|
34329
|
+
var geoRing = ring.map(function (p) { var ll = m.unproject(p); return [ll.lng, ll.lat]; });
|
|
34330
|
+
var geometry = { type: 'Polygon', coordinates: [closeRing(geoRing)] };
|
|
34331
|
+
finish({ features: self._featuresIn(ring, o.layers), geometry: geometry });
|
|
34332
|
+
};
|
|
34333
|
+
var key = function (ev) { if (ev.key === 'Escape') { finish(null); } };
|
|
34334
|
+
m.on('mousedown', down); m.on('mousemove', move); m.on('mouseup', up);
|
|
34335
|
+
m.on('touchstart', down); m.on('touchmove', move); m.on('touchend', up);
|
|
34336
|
+
m.getContainer().addEventListener('keydown', key);
|
|
34337
|
+
});
|
|
34338
|
+
});
|
|
34339
|
+
};
|
|
34340
|
+
|
|
34341
|
+
// The features of the map's layers with a vertex inside a ring of screen points (projected now).
|
|
34342
|
+
SMap.prototype._featuresIn = function (ring, layers) {
|
|
34343
|
+
var self = this, m = this.engine, out = {};
|
|
34344
|
+
var xs = ring.map(function (p) { return p[0]; }), ys = ring.map(function (p) { return p[1]; });
|
|
34345
|
+
var minX = Math.min.apply(null, xs), maxX = Math.max.apply(null, xs), minY = Math.min.apply(null, ys), maxY = Math.max.apply(null, ys);
|
|
34346
|
+
var inside = function (c) {
|
|
34347
|
+
var p = m.project(c);
|
|
34348
|
+
if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) { return false; }
|
|
34349
|
+
return interop.geo.pointInPolygon([p.x, p.y], ring);
|
|
34350
|
+
};
|
|
34351
|
+
(layers || this._order).forEach(function (id) {
|
|
34352
|
+
var def = self._layers[id];
|
|
34353
|
+
if (!def || def.hidden || !def.data || ['points', 'custom', 'lines', 'polygons', 'heatmap'].indexOf(def.kind) < 0) { return; }
|
|
34354
|
+
var hits = def.data.features.filter(function (f) {
|
|
34355
|
+
var found = false;
|
|
34356
|
+
(function walk(c) { if (found || !c) { return; } if (typeof c[0] === 'number') { found = inside(c); } else { c.forEach(walk); } })(f.geometry && f.geometry.coordinates);
|
|
34357
|
+
return found;
|
|
34358
|
+
});
|
|
34359
|
+
if (hits.length) { out[id] = hits; }
|
|
34360
|
+
});
|
|
34361
|
+
return out;
|
|
34362
|
+
};
|
|
34363
|
+
|
|
34364
|
+
// ------------------------------------------------------------------ controls (G8): own controls, base imagery, layer switcher
|
|
34365
|
+
|
|
34366
|
+
/**
|
|
34367
|
+
* Puts an element or an engine control in a corner of the map without making it a tool (it is never hidden by the
|
|
34368
|
+
* tools control). An element returns where the page had it when removed.
|
|
34369
|
+
* @param {Element|Object} target
|
|
34370
|
+
* @param {string} [position] 'top-left', 'top-right' (default), 'bottom-left', 'bottom-right'
|
|
34371
|
+
*/
|
|
34372
|
+
SMap.prototype.addControl = function (target, position) {
|
|
34373
|
+
position = position || 'top-right';
|
|
34374
|
+
if (TOOL_CORNERS.indexOf(position) < 0) { throw new Error('sMap: unknown control position "' + position + '"'); }
|
|
34375
|
+
if (!isElement(target) && !(target && typeof target.onAdd === 'function')) { throw new Error('sMap: a control is an element or an engine control'); }
|
|
34376
|
+
this._controls = this._controls || [];
|
|
34377
|
+
var holder = { position: position, el: null };
|
|
34378
|
+
var control = isElement(target) ? elementControl(holder, target) : target;
|
|
34379
|
+
this._controls.push({ target: target, control: control, position: position });
|
|
34380
|
+
var self = this;
|
|
34381
|
+
if (this.engine) { this.engine.addControl(control, position); } else { this.ready.then(function () { self.engine.addControl(control, position); }); }
|
|
34382
|
+
return this;
|
|
34383
|
+
};
|
|
34384
|
+
|
|
34385
|
+
SMap.prototype.removeControl = function (target) {
|
|
34386
|
+
var list = this._controls || [];
|
|
34387
|
+
for (var i = 0; i < list.length; i++) {
|
|
34388
|
+
if (list[i].target !== target) { continue; }
|
|
34389
|
+
if (this.engine) { this.engine.removeControl(list[i].control); }
|
|
34390
|
+
list.splice(i, 1);
|
|
34391
|
+
break;
|
|
34392
|
+
}
|
|
34393
|
+
return this;
|
|
34394
|
+
};
|
|
34395
|
+
|
|
34396
|
+
var BASE_RASTER = 'smap-base-imagery';
|
|
34397
|
+
|
|
34398
|
+
/**
|
|
34399
|
+
* Imagery (satellite, aerial, a scanned map, …) in place of the street map: raster tiles drawn over the basemap's
|
|
34400
|
+
* land, water and roads, under its labels — which stay with `labels: true` (a hybrid map) and are hidden otherwise.
|
|
34401
|
+
* Your layers stay on top, in their slots. null goes back to the street map.
|
|
34402
|
+
* @param {Object|null} imagery { tiles: [url with {z}/{x}/{y}], tileSize (256), minzoom, maxzoom, attribution,
|
|
34403
|
+
* scheme ('xyz' | 'tms'), labels (default false) }
|
|
34404
|
+
*/
|
|
34405
|
+
SMap.prototype.setBaseImagery = function (imagery) {
|
|
34406
|
+
if (imagery && (!Array.isArray(imagery.tiles) || !imagery.tiles.length)) { throw new Error('sMap: base imagery needs tiles'); }
|
|
34407
|
+
// Google's and Bing's tiles only through their own terms: Google's with setGoogleBase (M31, M66).
|
|
34408
|
+
if (imagery) { interop.refuseForbidden(imagery.tiles); }
|
|
34409
|
+
return this._setBaseImagery(imagery);
|
|
34410
|
+
};
|
|
34411
|
+
|
|
34412
|
+
SMap.prototype._setBaseImagery = function (imagery) {
|
|
34413
|
+
this._baseImagery = imagery ? Object.assign({}, imagery) : null;
|
|
34414
|
+
if (this.engine && this._styleReady) { this._applyBaseImagery(); }
|
|
34415
|
+
return this;
|
|
34416
|
+
};
|
|
34417
|
+
|
|
34418
|
+
// Whether a style (a scheme, 'custom:<id>', a URL or an object) is the one shown now.
|
|
34419
|
+
SMap.prototype._isStyle = function (style) {
|
|
34420
|
+
if (typeof style === 'string' && FLAVORS.indexOf(style) >= 0) { return !this._customStyle && !this._serverStyle && this._flavor === style; }
|
|
34421
|
+
if (typeof style === 'string' && style.indexOf(CUSTOM_PREFIX) === 0) { return this._serverStyle === style; }
|
|
34422
|
+
return this._customStyle === style;
|
|
34423
|
+
};
|
|
34424
|
+
|
|
34425
|
+
SMap.prototype.baseImagery = function () { return this._baseImagery ? Object.assign({}, this._baseImagery) : null; };
|
|
34426
|
+
|
|
34427
|
+
SMap.prototype._applyBaseImagery = function () {
|
|
34428
|
+
var m = this.engine, b = this._baseImagery;
|
|
34429
|
+
if (m.getLayer(BASE_RASTER)) { m.removeLayer(BASE_RASTER); }
|
|
34430
|
+
if (m.getSource(BASE_RASTER)) { m.removeSource(BASE_RASTER); }
|
|
34431
|
+
var anchors = this.slots();
|
|
34432
|
+
var labelsAt = anchors.labels;
|
|
34433
|
+
// The basemap's labels: every basemap layer from where the labels slot begins.
|
|
34434
|
+
var ids = this._basemapIds.filter(function (x) { return m.getLayer(x); });
|
|
34435
|
+
var from = labelsAt ? ids.indexOf(labelsAt) : -1;
|
|
34436
|
+
var labelIds = from >= 0 ? ids.slice(from) : [];
|
|
34437
|
+
var hidden = this._imageryHid || [];
|
|
34438
|
+
hidden.forEach(function (x) { if (m.getLayer(x)) { m.setLayoutProperty(x, 'visibility', 'visible'); } });
|
|
34439
|
+
this._imageryHid = [];
|
|
34440
|
+
if (!b) { return; }
|
|
34441
|
+
var source = { type: 'raster', tiles: b.tiles.slice(), tileSize: b.tileSize || 256 };
|
|
34442
|
+
if (b.minzoom !== undefined) { source.minzoom = b.minzoom; }
|
|
34443
|
+
if (b.maxzoom !== undefined) { source.maxzoom = b.maxzoom; }
|
|
34444
|
+
if (b.attribution) { source.attribution = b.attribution; }
|
|
34445
|
+
if (b.scheme) { source.scheme = b.scheme; }
|
|
34446
|
+
m.addSource(BASE_RASTER, source);
|
|
34447
|
+
m.addLayer({ id: BASE_RASTER, type: 'raster', source: BASE_RASTER }, labelsAt || undefined);
|
|
34448
|
+
if (!b.labels) {
|
|
34449
|
+
var self = this;
|
|
34450
|
+
labelIds.forEach(function (x) {
|
|
34451
|
+
if (m.getLayoutProperty && m.getLayoutProperty(x, 'visibility') === 'none') { return; }
|
|
34452
|
+
m.setLayoutProperty(x, 'visibility', 'none');
|
|
34453
|
+
self._imageryHid.push(x);
|
|
34454
|
+
});
|
|
34455
|
+
}
|
|
34456
|
+
};
|
|
34457
|
+
|
|
34458
|
+
// ---- 3D models (G12): glTF models at points, drawn with three.js (handed over once with sMap.useThree)
|
|
34459
|
+
|
|
34460
|
+
|
|
34461
|
+
/**
|
|
34462
|
+
* glTF models at points (vehicles, buildings, equipment), drawn in the map's 3D view and turned to each feature's
|
|
34463
|
+
* heading. Needs three.js: call sMap.useThree(THREE, GLTFLoader) once first.
|
|
34464
|
+
* @param {Object} o model (a .glb / .gltf URL), size (metres per model unit, default 1; or per feature),
|
|
34465
|
+
* heading (degrees clockwise from north, default 0; or per feature, e.g. { property: 'bearing' }),
|
|
34466
|
+
* altitude (metres above the ground, default 0), rotate ([x, y, z] degrees turning the model upright and to
|
|
34467
|
+
* face north before the heading; default [90, 0, 180], right for a model whose up is +Y and front +Z),
|
|
34468
|
+
* idProperty, minzoom, slot, before, zIndex
|
|
34469
|
+
*/
|
|
34470
|
+
SMap.prototype.addModels = function (id, data, o) {
|
|
34471
|
+
if (!interop.getThree()) { throw new Error('sMap: call sMap.useThree(THREE, GLTFLoader) before addModels'); }
|
|
34472
|
+
if (!o || !o.model) { throw new Error('sMap: addModels needs a model URL'); }
|
|
34473
|
+
this._define(id, { kind: 'models', data: toFeatureCollection(data), o: o });
|
|
34474
|
+
return this;
|
|
34475
|
+
};
|
|
34476
|
+
|
|
34477
|
+
SMap.prototype._modelLayer = function (id, def) {
|
|
34478
|
+
var self = this, three = interop.getThree(), T = three.THREE, o = def.o;
|
|
34479
|
+
var deg = Math.PI / 180;
|
|
34480
|
+
var base = o.rotate || [90, 0, 180];
|
|
34481
|
+
var baseMatrix = new T.Matrix4().makeRotationFromEuler(new T.Euler(base[0] * deg, base[1] * deg, base[2] * deg, 'ZYX'));
|
|
34482
|
+
var layer = {
|
|
34483
|
+
id: 'smap-' + id + '-models', type: 'custom', renderingMode: '3d',
|
|
34484
|
+
onAdd: function (map, gl) {
|
|
34485
|
+
this.map = map;
|
|
34486
|
+
this.scene = new T.Scene();
|
|
34487
|
+
this.camera = new T.Camera();
|
|
34488
|
+
this.renderer = new T.WebGLRenderer({ canvas: map.getCanvas(), context: gl, antialias: true });
|
|
34489
|
+
this.renderer.autoClear = false;
|
|
34490
|
+
this.scene.add(new T.AmbientLight(0xffffff, 0.8));
|
|
34491
|
+
var sun = new T.DirectionalLight(0xffffff, 0.6);
|
|
34492
|
+
sun.position.set(0.3, -0.5, 1);
|
|
34493
|
+
this.scene.add(sun);
|
|
34494
|
+
this.objects = {};
|
|
34495
|
+
var me = this;
|
|
34496
|
+
new three.GLTFLoader().load(o.model, function (gltf) {
|
|
34497
|
+
me.prototypeObject = gltf.scene;
|
|
34498
|
+
map.triggerRepaint();
|
|
34499
|
+
}, undefined, function (error) { self._reportError(error); });
|
|
34500
|
+
},
|
|
34501
|
+
render: function (gl, args) {
|
|
34502
|
+
if (!this.prototypeObject) { return; }
|
|
34503
|
+
var matrix = args && args.defaultProjectionData ? args.defaultProjectionData.mainMatrix : (args && args.modelViewProjectionMatrix) || args;
|
|
34504
|
+
var seen = {}, me = this, cur = self._layers[id];
|
|
34505
|
+
if (!cur) { return; }
|
|
34506
|
+
cur.data.features.forEach(function (f, i) {
|
|
34507
|
+
var at = pointOf(f);
|
|
34508
|
+
if (!at) { return; }
|
|
34509
|
+
var key = featureKey(cur, f, i);
|
|
34510
|
+
seen[key] = true;
|
|
34511
|
+
var obj = me.objects[key];
|
|
34512
|
+
if (!obj) { obj = me.objects[key] = me.prototypeObject.clone(true); obj.matrixAutoUpdate = false; me.scene.add(obj); }
|
|
34513
|
+
var props = f.properties || {};
|
|
34514
|
+
var mc = engine.MercatorCoordinate.fromLngLat(at, +plainValue(o.altitude, props, f, 0) || 0);
|
|
34515
|
+
var k = mc.meterInMercatorCoordinateUnits() * (+plainValue(o.size, props, f, 1) || 1);
|
|
34516
|
+
var heading = (+plainValue(o.heading, props, f, 0) || 0) * deg;
|
|
34517
|
+
obj.matrix.copy(new T.Matrix4().makeTranslation(mc.x, mc.y, mc.z)
|
|
34518
|
+
.multiply(new T.Matrix4().makeScale(k, -k, k))
|
|
34519
|
+
.multiply(new T.Matrix4().makeRotationZ(heading))
|
|
34520
|
+
.multiply(baseMatrix));
|
|
34521
|
+
obj.matrixWorldNeedsUpdate = true;
|
|
34522
|
+
});
|
|
34523
|
+
Object.keys(this.objects).forEach(function (key) { if (!seen[key]) { me.scene.remove(me.objects[key]); delete me.objects[key]; } });
|
|
34524
|
+
this.camera.projectionMatrix = new T.Matrix4().fromArray(matrix);
|
|
34525
|
+
this.renderer.resetState();
|
|
34526
|
+
this.renderer.render(this.scene, this.camera);
|
|
34527
|
+
},
|
|
34528
|
+
onRemove: function () { if (this.renderer && this.renderer.dispose) { this.renderer.dispose(); } },
|
|
34529
|
+
};
|
|
34530
|
+
return layer;
|
|
34531
|
+
};
|
|
34532
|
+
|
|
34533
|
+
var GOOGLE_LOGO = 'https://maps.gstatic.com/mapfiles/api-3/images/google_white5.png';
|
|
34534
|
+
|
|
34535
|
+
/**
|
|
34536
|
+
* Google's base maps from the Map Tiles API (G10), read straight from Google with a session your server made with
|
|
34537
|
+
* your own Google key; shown in place of the street map like setBaseImagery. As Google's terms ask, the Google logo
|
|
34538
|
+
* is shown and the copyright of the area in view is asked of Google after each move. null takes it off.
|
|
34539
|
+
* @param {Object|null} g { session, key } (or `tiles`: the tile URL template, and `viewport`: the viewport address up
|
|
34540
|
+
* to its query, both with session and key), type ('roadmap' | 'satellite', for the attribution only),
|
|
34541
|
+
* maxzoom (22), labels (keep the street map's labels over Google's imagery; default false),
|
|
34542
|
+
* onFail() (called when Google's first tiles fail and none loads: show your own map instead)
|
|
34543
|
+
*/
|
|
34544
|
+
SMap.prototype.setGoogleBase = function (g) {
|
|
34545
|
+
var self = this;
|
|
34546
|
+
var run = function () {
|
|
34547
|
+
var m = self.engine;
|
|
34548
|
+
if (self._google) {
|
|
34549
|
+
if (self._google.control) { m.removeControl(self._google.control); }
|
|
34550
|
+
self._google.off.forEach(function (x) { m.off(x[0], x[1]); });
|
|
34551
|
+
self._google = null;
|
|
34552
|
+
}
|
|
34553
|
+
if (!g) { self._setBaseImagery(null); return; }
|
|
34554
|
+
if (!g.tiles && !(g.session && g.key)) { throw new Error('sMap: a Google layer needs session and key, or tiles'); }
|
|
34555
|
+
var q = g.session ? 'session=' + encodeURIComponent(g.session) + '&key=' + encodeURIComponent(g.key) : '';
|
|
34556
|
+
var tiles = g.tiles || 'https://tile.googleapis.com/v1/2dtiles/{z}/{x}/{y}?' + q;
|
|
34557
|
+
var viewport = g.viewport || (q ? 'https://tile.googleapis.com/tile/v1/viewport?' + q : null);
|
|
34558
|
+
var state = { off: [], control: null, text: null, errors: 0, loaded: 0, failed: false };
|
|
34559
|
+
self._google = state;
|
|
34560
|
+
self._setBaseImagery({ tiles: [tiles], tileSize: 256, maxzoom: g.maxzoom || 22, labels: !!g.labels });
|
|
34561
|
+
// The logo and the copyright of the area in view, bottom left.
|
|
34562
|
+
var doc = m.getContainer().ownerDocument;
|
|
34563
|
+
var box = doc.createElement('div');
|
|
34564
|
+
box.className = 'smap-google';
|
|
34565
|
+
var logo = doc.createElement('img');
|
|
34566
|
+
logo.src = GOOGLE_LOGO;
|
|
34567
|
+
logo.alt = 'Google';
|
|
34568
|
+
var text = doc.createElement('span');
|
|
34569
|
+
text.className = 'smap-google-copyright';
|
|
34570
|
+
text.textContent = 'Google';
|
|
34571
|
+
box.appendChild(logo);
|
|
34572
|
+
box.appendChild(text);
|
|
34573
|
+
state.text = text;
|
|
34574
|
+
state.control = { onAdd: function () { return box; }, onRemove: function () { if (box.parentNode) { box.parentNode.removeChild(box); } } };
|
|
34575
|
+
m.addControl(state.control, 'bottom-left');
|
|
34576
|
+
var seq = 0;
|
|
34577
|
+
var copyright = function () {
|
|
34578
|
+
if (!viewport || self._google !== state || typeof fetch !== 'function') { return; }
|
|
34579
|
+
var b = m.getBounds(), mine = ++seq;
|
|
34580
|
+
var url = viewport + (viewport.indexOf('?') >= 0 ? '&' : '?') + 'zoom=' + Math.round(m.getZoom() + 1) +
|
|
34581
|
+
'&north=' + b.getNorth() + '&south=' + b.getSouth() + '&east=' + b.getEast() + '&west=' + b.getWest();
|
|
34582
|
+
fetch(url).then(function (r) { return r.ok ? r.json() : null; }).then(function (v) {
|
|
34583
|
+
if (mine === seq && self._google === state) { text.textContent = (v && v.copyright) || 'Google'; }
|
|
34584
|
+
}, function () {});
|
|
34585
|
+
};
|
|
34586
|
+
var watch = function (e) {
|
|
34587
|
+
if (!e || e.sourceId !== BASE_RASTER || state.failed) { return; }
|
|
34588
|
+
if (e.type === 'error') { state.errors += 1; } else if (e.tile) { state.loaded += 1; }
|
|
34589
|
+
if (state.errors >= 4 && state.loaded === 0) {
|
|
34590
|
+
state.failed = true;
|
|
34591
|
+
if (g.onFail) { g.onFail(); }
|
|
34592
|
+
}
|
|
34593
|
+
};
|
|
34594
|
+
m.on('moveend', copyright); state.off.push(['moveend', copyright]);
|
|
34595
|
+
m.on('error', watch); state.off.push(['error', watch]);
|
|
34596
|
+
m.on('sourcedata', watch); state.off.push(['sourcedata', watch]);
|
|
34597
|
+
copyright();
|
|
34598
|
+
};
|
|
34599
|
+
if (this.engine) { run(); } else { this.ready.then(run); }
|
|
34600
|
+
return this;
|
|
34601
|
+
};
|
|
34602
|
+
|
|
34603
|
+
var LAYERS_ICON = '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false" fill="none" ' +
|
|
34604
|
+
'stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="M12 3 2 8l10 5 10-5-10-5z"/>' +
|
|
34605
|
+
'<path d="m2 13 10 5 10-5"/></svg>';
|
|
34606
|
+
|
|
34607
|
+
/**
|
|
34608
|
+
* The layer switcher: one button opening a list of base maps (one at a time) and overlays (each on or off).
|
|
34609
|
+
* base: [{ id, label, style? (a scheme, 'custom:…' or a style URL), imagery? (as setBaseImagery) }]
|
|
34610
|
+
* overlays: [{ id, label, layers: [layer ids], visible? }]
|
|
34611
|
+
* value (the base shown at first), title (the button's label), onChange({ base, overlays: [ids shown] })
|
|
34612
|
+
*/
|
|
34613
|
+
function layersControl(map, opts) {
|
|
34614
|
+
var base = opts.base || [];
|
|
34615
|
+
var overlays = opts.overlays || [];
|
|
34616
|
+
var current = opts.value || (base[0] && base[0].id) || null;
|
|
34617
|
+
var shown = {};
|
|
34618
|
+
overlays.forEach(function (ov) { shown[ov.id] = ov.visible !== false; });
|
|
34619
|
+
var el = null, button = null, panel = null;
|
|
34620
|
+
function report() {
|
|
34621
|
+
if (opts.onChange) { opts.onChange({ base: current, overlays: overlays.filter(function (ov) { return shown[ov.id]; }).map(function (ov) { return ov.id; }) }); }
|
|
34622
|
+
}
|
|
34623
|
+
function applyBase(entry) {
|
|
34624
|
+
if (!entry) { return; }
|
|
34625
|
+
// The scheme already shown is not loaded again (no flicker when only the imagery changes).
|
|
34626
|
+
if (entry.style !== undefined && entry.style !== null && !map._isStyle(entry.style)) { map.setStyle(entry.style); }
|
|
34627
|
+
map.setBaseImagery(entry.imagery || null);
|
|
34628
|
+
}
|
|
34629
|
+
function applyOverlay(ov) { (ov.layers || []).forEach(function (id) { map.setVisible(id, !!shown[ov.id]); }); }
|
|
34630
|
+
function render() {
|
|
34631
|
+
var doc = el.ownerDocument;
|
|
34632
|
+
while (panel.firstChild) { panel.removeChild(panel.firstChild); }
|
|
34633
|
+
var name = 'smap-base-' + Math.random().toString(36).slice(2);
|
|
34634
|
+
base.forEach(function (entry) {
|
|
34635
|
+
var row = doc.createElement('label');
|
|
34636
|
+
row.className = 'smap-layers-row';
|
|
34637
|
+
var input = doc.createElement('input');
|
|
34638
|
+
input.type = 'radio'; input.name = name; input.value = entry.id; input.checked = entry.id === current;
|
|
34639
|
+
input.addEventListener('change', function () { current = entry.id; applyBase(entry); report(); });
|
|
34640
|
+
row.appendChild(input);
|
|
34641
|
+
row.appendChild(doc.createTextNode(' ' + (entry.label || entry.id)));
|
|
34642
|
+
panel.appendChild(row);
|
|
34643
|
+
});
|
|
34644
|
+
if (base.length && overlays.length) { var hr = doc.createElement('div'); hr.className = 'smap-layers-sep'; panel.appendChild(hr); }
|
|
34645
|
+
overlays.forEach(function (ov) {
|
|
34646
|
+
var row = doc.createElement('label');
|
|
34647
|
+
row.className = 'smap-layers-row';
|
|
34648
|
+
var input = doc.createElement('input');
|
|
34649
|
+
input.type = 'checkbox'; input.value = ov.id; input.checked = !!shown[ov.id];
|
|
34650
|
+
input.addEventListener('change', function () { shown[ov.id] = input.checked; applyOverlay(ov); report(); });
|
|
34651
|
+
row.appendChild(input);
|
|
34652
|
+
row.appendChild(doc.createTextNode(' ' + (ov.label || ov.id)));
|
|
34653
|
+
panel.appendChild(row);
|
|
34654
|
+
});
|
|
34655
|
+
}
|
|
34656
|
+
function open(on) { panel.hidden = !on; button.setAttribute('aria-expanded', on ? 'true' : 'false'); }
|
|
34657
|
+
function close() { if (panel && !panel.hidden) { open(false); } }
|
|
34658
|
+
return {
|
|
34659
|
+
onAdd: function (m) {
|
|
34660
|
+
var doc = m.getContainer().ownerDocument;
|
|
34661
|
+
el = doc.createElement('div');
|
|
34662
|
+
el.className = 'maplibregl-ctrl maplibregl-ctrl-group smap-layers';
|
|
34663
|
+
button = doc.createElement('button');
|
|
34664
|
+
button.type = 'button';
|
|
34665
|
+
button.className = 'smap-layers-button';
|
|
34666
|
+
button.innerHTML = LAYERS_ICON;
|
|
34667
|
+
button.title = opts.title || 'Layers';
|
|
34668
|
+
button.setAttribute('aria-label', opts.title || 'Layers');
|
|
34669
|
+
button.setAttribute('aria-haspopup', 'true');
|
|
34670
|
+
button.addEventListener('click', function () { open(panel.hidden); });
|
|
34671
|
+
panel = doc.createElement('div');
|
|
34672
|
+
panel.className = 'smap-layers-panel';
|
|
34673
|
+
el.appendChild(button);
|
|
34674
|
+
el.appendChild(panel);
|
|
34675
|
+
render();
|
|
34676
|
+
open(false);
|
|
34677
|
+
m.on('click', close);
|
|
34678
|
+
// The starting state: the chosen base and every overlay as given.
|
|
34679
|
+
var entry = base.filter(function (b) { return b.id === current; })[0];
|
|
34680
|
+
if (entry && entry.imagery) { map.setBaseImagery(entry.imagery); }
|
|
34681
|
+
overlays.forEach(function (ov) { if (!shown[ov.id]) { applyOverlay(ov); } });
|
|
34682
|
+
return el;
|
|
34683
|
+
},
|
|
34684
|
+
onRemove: function (m) { m.off('click', close); if (el && el.parentNode) { el.parentNode.removeChild(el); } el = null; },
|
|
34685
|
+
};
|
|
34686
|
+
}
|
|
34687
|
+
|
|
31310
34688
|
// ------------------------------------------------------------------ plan editor (OPEN-ROUTE-EDIT E10)
|
|
31311
34689
|
|
|
31312
34690
|
/**
|
|
@@ -31826,7 +35204,9 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
31826
35204
|
var out = { type: 'smap.layers', version: 1 };
|
|
31827
35205
|
if (!this._customStyle) { out.basemap = { style: this._serverStyle || this._flavor, language: this._language, overrides: clone(this._basemapOverrides) }; }
|
|
31828
35206
|
if (this._sourceOrder.length) { out.sources = this.sources(); }
|
|
31829
|
-
|
|
35207
|
+
// The shared description has points, lines, areas and style layers; custom markers, heat maps and 3D models are
|
|
35208
|
+
// not part of it and are left out.
|
|
35209
|
+
out.layers = this._order.filter(function (id) { return ['points', 'lines', 'polygons', 'style'].indexOf(self._layers[id].kind) >= 0; }).map(function (id) {
|
|
31830
35210
|
var def = self._layers[id];
|
|
31831
35211
|
var e = { id: id, kind: def.kind };
|
|
31832
35212
|
if (def.slot) { e.slot = def.slot; }
|
|
@@ -31885,7 +35265,8 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
31885
35265
|
def.slot = o.slot || null;
|
|
31886
35266
|
def.before = o.before || null;
|
|
31887
35267
|
def.popupFn = typeof o.popup === 'string' ? popupTemplate(o.popup) : null;
|
|
31888
|
-
|
|
35268
|
+
// A layer's place among layers; a per-feature zIndex (custom markers) orders within the layer only.
|
|
35269
|
+
def.zIndex = typeof def.o.zIndex === 'number' ? def.o.zIndex : 0;
|
|
31889
35270
|
def.engineLayers = [];
|
|
31890
35271
|
if (this._layerToolHides(id)) { def.hidden = true; }
|
|
31891
35272
|
this._layers[id] = def;
|
|
@@ -31944,6 +35325,10 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
31944
35325
|
|
|
31945
35326
|
SMap.prototype._drop = function (id, def) {
|
|
31946
35327
|
var m = this.engine;
|
|
35328
|
+
if (def.kind === 'custom') { this._dropCustom(id, def); return; }
|
|
35329
|
+
if (this._ghost && this._ghost.id === id && m) { this._dropGhost(id); }
|
|
35330
|
+
if (this._tooltipFor && this._tooltipFor.id === id) { this._hideTooltip(); }
|
|
35331
|
+
if (def.clusterSet) { this._dropCustom(id + ':clusters', def.clusterSet); def.clusterSet = null; }
|
|
31947
35332
|
if (!m) { return; }
|
|
31948
35333
|
def.engineLayers.forEach(function (l) { if (m.getLayer(l)) { m.removeLayer(l); } });
|
|
31949
35334
|
if (def.kind !== 'style' && m.getSource('smap-' + id)) { m.removeSource('smap-' + id); }
|
|
@@ -31963,13 +35348,24 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
31963
35348
|
Object.keys(this._basemapEdits).forEach(function (id) { self._applyEdits(id, self._basemapEdits[id]); });
|
|
31964
35349
|
this._sourceOrder.forEach(function (id) { if (!m.getSource(id)) { self._engineSource(id); } });
|
|
31965
35350
|
this._applyBuildings();
|
|
35351
|
+
this._imageryHid = [];
|
|
35352
|
+
if (this._baseImagery) { this._applyBaseImagery(); }
|
|
35353
|
+
if (this._terrain) { this._applyTerrain(); }
|
|
31966
35354
|
this._order.forEach(function (id) { self._add(id, self._layers[id]); });
|
|
31967
35355
|
};
|
|
31968
35356
|
|
|
31969
35357
|
SMap.prototype._add = function (id, def) {
|
|
31970
35358
|
var m = this.engine;
|
|
31971
35359
|
var o = def.o;
|
|
35360
|
+
if (def.kind === 'custom') { this._addCustom(id, def); return; }
|
|
31972
35361
|
var before = this._beforeId(id);
|
|
35362
|
+
if (def.kind === 'models') {
|
|
35363
|
+
var modelLayer = this._modelLayer(id, def);
|
|
35364
|
+
if (o.minzoom !== undefined) { modelLayer.minzoom = o.minzoom; }
|
|
35365
|
+
m.addLayer(modelLayer, before);
|
|
35366
|
+
def.engineLayers.push(modelLayer.id);
|
|
35367
|
+
return;
|
|
35368
|
+
}
|
|
31973
35369
|
if (def.kind === 'style') {
|
|
31974
35370
|
// Passed to the engine as it is (a copy, so the engine never holds the object you gave).
|
|
31975
35371
|
var styleLayer = clone(def.layer);
|
|
@@ -31985,6 +35381,8 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
31985
35381
|
if (o.maxzoom !== undefined) { range.maxzoom = o.maxzoom; }
|
|
31986
35382
|
var visibility = def.hidden ? 'none' : 'visible';
|
|
31987
35383
|
var source = { type: 'geojson', data: def.data, promoteId: o.idProperty };
|
|
35384
|
+
// A dragged point is found again by its place in the data (unless idProperty names it).
|
|
35385
|
+
if (def.kind === 'points' && o.draggable && !o.idProperty) { source.generateId = true; }
|
|
31988
35386
|
if (def.kind === 'points' && o.cluster) {
|
|
31989
35387
|
source.cluster = true;
|
|
31990
35388
|
source.clusterRadius = (o.cluster && o.cluster.radius) || 50;
|
|
@@ -32007,7 +35405,8 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
32007
35405
|
var color = o.color || '#4f46e5';
|
|
32008
35406
|
var main;
|
|
32009
35407
|
if (def.kind === 'points') {
|
|
32010
|
-
|
|
35408
|
+
var htmlClusters = o.cluster && (o.cluster.html || o.cluster.ring);
|
|
35409
|
+
if (o.cluster && !htmlClusters) {
|
|
32011
35410
|
add({
|
|
32012
35411
|
id: src + '-clusters', type: 'circle', filter: ['has', 'point_count'],
|
|
32013
35412
|
paint: {
|
|
@@ -32028,6 +35427,7 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
32028
35427
|
});
|
|
32029
35428
|
});
|
|
32030
35429
|
}
|
|
35430
|
+
if (htmlClusters) { this._htmlClusters(id, def, src); }
|
|
32031
35431
|
var only = o.cluster ? ['!', ['has', 'point_count']] : undefined;
|
|
32032
35432
|
var both = function (extra) { return only ? ['all', only, extra] : extra; };
|
|
32033
35433
|
if (o.marker) {
|
|
@@ -32122,6 +35522,30 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
32122
35522
|
paint: { 'text-color': '#1f2937', 'text-halo-color': '#ffffff', 'text-halo-width': 1.4 },
|
|
32123
35523
|
});
|
|
32124
35524
|
}
|
|
35525
|
+
} else if (def.kind === 'heatmap') {
|
|
35526
|
+
main = src + '-heat';
|
|
35527
|
+
var hc = o.colors;
|
|
35528
|
+
var ramp = Array.isArray(hc) && hc.length && Array.isArray(hc[0]) ? hc : null;
|
|
35529
|
+
var colorExpr = ['interpolate', ['linear'], ['heatmap-density']];
|
|
35530
|
+
if (ramp) { ramp.forEach(function (st) { colorExpr.push(st[0], st[1]); }); }
|
|
35531
|
+
else if (Array.isArray(hc) && hc.length) {
|
|
35532
|
+
// Colours from low to high: transparent at no density, then evenly spaced.
|
|
35533
|
+
colorExpr.push(0, 'rgba(0, 0, 0, 0)');
|
|
35534
|
+
hc.forEach(function (c, i) { colorExpr.push((i + 1) / hc.length, c); });
|
|
35535
|
+
} else {
|
|
35536
|
+
colorExpr.push(0, 'rgba(33, 102, 172, 0)', 0.2, 'rgb(103, 169, 207)', 0.4, 'rgb(209, 229, 240)', 0.6, 'rgb(253, 219, 199)', 0.8, 'rgb(239, 138, 98)', 1, 'rgb(178, 24, 43)');
|
|
35537
|
+
}
|
|
35538
|
+
var weight = o.weight === undefined ? 1 : (typeof o.weight === 'number' ? o.weight : ['to-number', valueExpr(o.weight, 1), 1]);
|
|
35539
|
+
add({
|
|
35540
|
+
id: main, type: 'heatmap',
|
|
35541
|
+
paint: {
|
|
35542
|
+
'heatmap-weight': weight,
|
|
35543
|
+
'heatmap-radius': o.radius === undefined ? 20 : o.radius,
|
|
35544
|
+
'heatmap-intensity': o.intensity === undefined ? 1 : o.intensity,
|
|
35545
|
+
'heatmap-opacity': o.opacity === undefined ? 0.8 : o.opacity,
|
|
35546
|
+
'heatmap-color': colorExpr,
|
|
35547
|
+
},
|
|
35548
|
+
});
|
|
32125
35549
|
} else if (def.kind === 'lines') {
|
|
32126
35550
|
main = src + '-lines';
|
|
32127
35551
|
// simplestyle (stroke, stroke-width, stroke-opacity) when the layer gives no colour of its own
|
|
@@ -32146,6 +35570,15 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
32146
35570
|
};
|
|
32147
35571
|
if (o.dash) { paint['line-dasharray'] = o.dash; }
|
|
32148
35572
|
add({ id: main, type: 'line', layout: round, paint: paint });
|
|
35573
|
+
if (o.label) {
|
|
35574
|
+
// A permanent label along the line (a route's name), repeated along long lines.
|
|
35575
|
+
add({
|
|
35576
|
+
id: src + '-labels', type: 'symbol',
|
|
35577
|
+
layout: { 'symbol-placement': 'line', 'symbol-spacing': o.labelSpacing || 250, 'text-field': ['to-string', ['get', o.label]],
|
|
35578
|
+
'text-font': ['Noto Sans Medium'], 'text-size': 12, 'text-keep-upright': true },
|
|
35579
|
+
paint: { 'text-color': valueExpr(o.labelColor, '#1f2937'), 'text-halo-color': '#ffffff', 'text-halo-width': 1.6 },
|
|
35580
|
+
});
|
|
35581
|
+
}
|
|
32149
35582
|
if (o.arrows) {
|
|
32150
35583
|
var ar = o.arrows === true ? {} : o.arrows;
|
|
32151
35584
|
var arrowSize = ar.size || 14;
|
|
@@ -32188,7 +35621,9 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
32188
35621
|
}
|
|
32189
35622
|
// The popup is opened by the map's one click handler (_popupClick), from this layer's own hit test.
|
|
32190
35623
|
def.popupLayer = o.popup ? main : null;
|
|
32191
|
-
if (main &&
|
|
35624
|
+
if (main && o.tooltip) { this._tooltips(id, def, main); }
|
|
35625
|
+
if (main && def.kind === 'points' && o.draggable) { this._dragPoints(id, def, main); }
|
|
35626
|
+
if (main && (o.popup || o.onClick || o.draggable)) {
|
|
32192
35627
|
if (o.onClick) {
|
|
32193
35628
|
this._listen(id, 'click', main, function (e) { o.onClick(e.features[0], e); });
|
|
32194
35629
|
}
|
|
@@ -32259,6 +35694,8 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
32259
35694
|
slots: SLOTS.slice(),
|
|
32260
35695
|
formats: interop.formats,
|
|
32261
35696
|
convert: interop.convert,
|
|
35697
|
+
geo: interop.geo,
|
|
35698
|
+
useThree: interop.useThree,
|
|
32262
35699
|
rasterUrl: interop.rasterUrl,
|
|
32263
35700
|
rasterTileJsonUrl: interop.rasterTileJsonUrl,
|
|
32264
35701
|
styleUrl: interop.styleUrl,
|
|
@@ -32277,9 +35714,10 @@ function smapCreateSdk(engine, interop, plan) {
|
|
|
32277
35714
|
return module.exports;
|
|
32278
35715
|
}
|
|
32279
35716
|
var smapInterop = smapCreateInterop();
|
|
35717
|
+
var smapCanvasLib = smapCreateCanvas();
|
|
32280
35718
|
var smapPlan = smapCreatePlan();
|
|
32281
35719
|
function smapLoad() {
|
|
32282
|
-
if (!smapCore) { smapCore = smapCreateSdk(smapCreateEngine(), smapInterop, smapPlan); }
|
|
35720
|
+
if (!smapCore) { smapCore = smapCreateSdk(smapCreateEngine(), smapInterop, smapCanvasLib, smapPlan); }
|
|
32283
35721
|
return smapCore;
|
|
32284
35722
|
}
|
|
32285
35723
|
// Every top-level name starts with smap: the engine and the SDK run inside this scope and must keep seeing the
|
|
@@ -32293,19 +35731,20 @@ function SMapMap(options) {
|
|
|
32293
35731
|
SMapMap.prototype = core.Map.prototype;
|
|
32294
35732
|
return new core.Map(options);
|
|
32295
35733
|
}
|
|
32296
|
-
var smapVersion = "1.
|
|
35734
|
+
var smapVersion = "1.2.0";
|
|
32297
35735
|
var smapStyles = ['light', 'white', 'grayscale', 'dark', 'black'];
|
|
32298
35736
|
/** The bundled rendering engine, for anything the SDK does not wrap (loaded on the first call). */
|
|
32299
35737
|
function smapGetEngine() { return smapLoad().engine; }
|
|
32300
35738
|
// Templates need no engine: before the first map they come from an SDK made without one.
|
|
32301
35739
|
var smapNoEngine = null;
|
|
32302
35740
|
function smapPopupTemplate(template) {
|
|
32303
|
-
if (!smapCore && !smapNoEngine) { smapNoEngine = smapCreateSdk(null, smapInterop, smapPlan); }
|
|
35741
|
+
if (!smapCore && !smapNoEngine) { smapNoEngine = smapCreateSdk(null, smapInterop, smapCanvasLib, smapPlan); }
|
|
32304
35742
|
return (smapCore || smapNoEngine).popupTemplate(template);
|
|
32305
35743
|
}
|
|
32306
35744
|
var smapSlots = ['bottom', 'roads', 'labels', 'top'];
|
|
32307
35745
|
var smap_formats = smapInterop.formats;
|
|
32308
35746
|
var smap_convert = smapInterop.convert;
|
|
35747
|
+
var smap_geo = smapInterop.geo;
|
|
32309
35748
|
var smap_rasterUrl = smapInterop.rasterUrl;
|
|
32310
35749
|
var smap_rasterTileJsonUrl = smapInterop.rasterTileJsonUrl;
|
|
32311
35750
|
var smap_styleUrl = smapInterop.styleUrl;
|
|
@@ -32315,11 +35754,12 @@ var smap_slotAnchors = smapInterop.slotAnchors;
|
|
|
32315
35754
|
var smap_parseWmtsCapabilities = smapInterop.parseWmtsCapabilities;
|
|
32316
35755
|
var smap_PMTilesArchive = smapInterop.PMTilesArchive;
|
|
32317
35756
|
var smap_useDeck = smapInterop.useDeck;
|
|
35757
|
+
var smap_useThree = smapInterop.useThree;
|
|
32318
35758
|
var smapPlanEditor = smapPlan.PlanEditor;
|
|
32319
35759
|
var smapPlanTexts = smapPlan.texts;
|
|
32320
35760
|
var smapPlanEditorParts = smapPlan.parts;
|
|
32321
35761
|
var smapDefault = { Map: SMapMap, version: smapVersion, styles: smapStyles, getEngine: smapGetEngine, popupTemplate: smapPopupTemplate,
|
|
32322
|
-
slots: smapSlots, formats: smap_formats, convert: smap_convert, rasterUrl: smap_rasterUrl, rasterTileJsonUrl: smap_rasterTileJsonUrl, styleUrl: smap_styleUrl, applyBasemapOverrides: smap_applyBasemapOverrides, basemapClasses: smap_basemapClasses, slotAnchors: smap_slotAnchors, parseWmtsCapabilities: smap_parseWmtsCapabilities, PMTilesArchive: smap_PMTilesArchive, useDeck: smap_useDeck, PlanEditor: smapPlanEditor, planTexts: smapPlanTexts,
|
|
35762
|
+
slots: smapSlots, formats: smap_formats, convert: smap_convert, geo: smap_geo, rasterUrl: smap_rasterUrl, rasterTileJsonUrl: smap_rasterTileJsonUrl, styleUrl: smap_styleUrl, applyBasemapOverrides: smap_applyBasemapOverrides, basemapClasses: smap_basemapClasses, slotAnchors: smap_slotAnchors, parseWmtsCapabilities: smap_parseWmtsCapabilities, PMTilesArchive: smap_PMTilesArchive, useDeck: smap_useDeck, useThree: smap_useThree, PlanEditor: smapPlanEditor, planTexts: smapPlanTexts,
|
|
32323
35763
|
planEditorParts: smapPlanEditorParts };
|
|
32324
|
-
export { SMapMap as Map, smapVersion as version, smapStyles as styles, smapGetEngine as getEngine, smapPopupTemplate as popupTemplate, smapSlots as slots, smap_formats as formats, smap_convert as convert, smap_rasterUrl as rasterUrl, smap_rasterTileJsonUrl as rasterTileJsonUrl, smap_styleUrl as styleUrl, smap_applyBasemapOverrides as applyBasemapOverrides, smap_basemapClasses as basemapClasses, smap_slotAnchors as slotAnchors, smap_parseWmtsCapabilities as parseWmtsCapabilities, smap_PMTilesArchive as PMTilesArchive, smap_useDeck as useDeck, smapPlanEditor as PlanEditor, smapPlanTexts as planTexts, smapPlanEditorParts as planEditorParts };
|
|
35764
|
+
export { SMapMap as Map, smapVersion as version, smapStyles as styles, smapGetEngine as getEngine, smapPopupTemplate as popupTemplate, smapSlots as slots, smap_formats as formats, smap_convert as convert, smap_geo as geo, smap_rasterUrl as rasterUrl, smap_rasterTileJsonUrl as rasterTileJsonUrl, smap_styleUrl as styleUrl, smap_applyBasemapOverrides as applyBasemapOverrides, smap_basemapClasses as basemapClasses, smap_slotAnchors as slotAnchors, smap_parseWmtsCapabilities as parseWmtsCapabilities, smap_PMTilesArchive as PMTilesArchive, smap_useDeck as useDeck, smap_useThree as useThree, smapPlanEditor as PlanEditor, smapPlanTexts as planTexts, smapPlanEditorParts as planEditorParts };
|
|
32325
35765
|
export default smapDefault;
|