@superrouteca/smap 1.1.7 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.1.7. Bundles maplibre-gl 6.16.0 (BSD-3-Clause), see vendor-licenses.txt. */
2
+ /*! sMap Web SDK 1.2.1. 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 (M67): 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 = '&#215;';
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;
@@ -30098,12 +31562,12 @@ function smapCreatePlan() {
30098
31562
 
30099
31563
  return module.exports;
30100
31564
  }
30101
- function smapCreateSdk(engine, interop, plan) {
31565
+ function smapCreateSdk(engine, interop, canvas, plan) {
30102
31566
  var module = { exports: {} };
30103
31567
  var exports = module.exports;
30104
31568
  var define;
30105
31569
  var require = function (name) { throw new Error('sMap: unexpected require of ' + name); };
30106
- var self = { maplibregl: engine, smapInterop: interop, smapPlan: plan };
31570
+ var self = { maplibregl: engine, smapInterop: interop, smapCanvas: canvas, smapPlan: plan };
30107
31571
  /*!
30108
31572
  * sMap Web SDK — maps from Superroute's sMap service.
30109
31573
  *
@@ -30117,14 +31581,15 @@ function smapCreateSdk(engine, interop, plan) {
30117
31581
  if (typeof module === 'object' && module.exports) {
30118
31582
  module.exports = factory(root.maplibregl !== undefined ? root.maplibregl : require('maplibre-gl'),
30119
31583
  root.smapInterop !== undefined ? root.smapInterop : require('./smap-interop.js'),
31584
+ root.smapCanvas !== undefined ? root.smapCanvas : (typeof require === 'function' ? require('./smap-canvas.js') : null),
30120
31585
  root.smapPlan !== undefined ? root.smapPlan : (typeof require === 'function' ? require('./smap-plan.js') : null));
30121
31586
  } else {
30122
- root.sMap = factory(root.maplibregl, root.smapInterop, root.smapPlan);
31587
+ root.sMap = factory(root.maplibregl, root.smapInterop, root.smapCanvas, root.smapPlan);
30123
31588
  }
30124
- })(typeof self !== 'undefined' ? self : this, function (engine, interop, plan) {
31589
+ })(typeof self !== 'undefined' ? self : this, function (engine, interop, canvasLib, plan) {
30125
31590
  'use strict';
30126
31591
 
30127
- var VERSION = '1.1.7';
31592
+ var VERSION = '1.2.1';
30128
31593
  var FLAVORS = ['light', 'white', 'grayscale', 'dark', 'black'];
30129
31594
  var BASEMAP_SOURCE = 'basemap';
30130
31595
  var BUILDINGS_LAYER = 'smap-buildings-3d';
@@ -30349,28 +31814,51 @@ function smapCreateSdk(engine, interop, plan) {
30349
31814
  },
30350
31815
  };
30351
31816
  },
30352
- // pinnum|shape|fill|border|borderWidth|size|textColor|text: a pin with its number drawn in, so a pin above covers a
30353
- // pin below completely (used close up, where few pins are in view; further out the number is its own layer).
31817
+ // pinnum|shape|fill|border|borderWidth|size|textColor|badge|text: a pin with its number and its count badge drawn
31818
+ // in, so a pin above covers a pin below completely, number and badge included, as one element of the route map's
31819
+ // pins does. An empty number or badge is left out; the text comes last because it may hold '|'.
30354
31820
  pinnum: function (args, measure) {
30355
31821
  var pin = IMAGE_KINDS.pin(args.slice(0, 5));
30356
- var size = Number(args[4]), color = args[5], text = args.slice(6).join('|');
31822
+ var size = Number(args[4]), color = args[5], badgeText = args[6] || '', text = args.slice(7).join('|');
30357
31823
  var px = 14 * size / 32, font = 'bold ' + px + 'px monospace';
31824
+ var badge = badgeText !== '' ? IMAGE_KINDS.badge([badgeText], measure) : null;
31825
+ var g = markerGeometry(args[0], size);
31826
+ // The badge's centre is where its own layer puts it: g.badge from the tip, i.e. from the head's centre for a
31827
+ // pin (the image's centre), so the image is widened on both sides when the badge reaches past it.
31828
+ var w = pin.width, h = pin.height;
31829
+ if (badge) {
31830
+ var reach = Math.abs(g.badge[0]) + badge.width / 2;
31831
+ w = Math.max(w, Math.ceil(2 * reach));
31832
+ }
30358
31833
  return {
30359
- width: pin.width, height: pin.height,
31834
+ width: w, height: h,
30360
31835
  draw: function (ctx, ratio) {
30361
- pin.draw(ctx, ratio);
30362
- if (text === '') { return; }
30363
31836
  ctx.save();
30364
- ctx.font = font;
30365
- ctx.textAlign = 'center';
30366
- ctx.textBaseline = 'middle';
30367
- ctx.shadowColor = 'rgba(0,0,0,0.5)';
30368
- ctx.shadowOffsetX = ratio;
30369
- ctx.shadowOffsetY = ratio;
30370
- ctx.shadowBlur = 2 * ratio;
30371
- ctx.fillStyle = color;
30372
- ctx.fillText(text, pin.width / 2, pin.height / 2 + 0.5);
31837
+ ctx.translate((w - pin.width) / 2, 0);
31838
+ pin.draw(ctx, ratio);
30373
31839
  ctx.restore();
31840
+ if (text !== '') {
31841
+ ctx.save();
31842
+ ctx.font = font;
31843
+ ctx.textAlign = 'center';
31844
+ ctx.textBaseline = 'middle';
31845
+ ctx.shadowColor = 'rgba(0,0,0,0.5)';
31846
+ ctx.shadowOffsetX = ratio;
31847
+ ctx.shadowOffsetY = ratio;
31848
+ ctx.shadowBlur = 2 * ratio;
31849
+ ctx.fillStyle = color;
31850
+ ctx.fillText(text, w / 2, h / 2 + 0.5);
31851
+ ctx.restore();
31852
+ }
31853
+ if (badge) {
31854
+ // A pin's head is the image's centre and its tip half the image height lower; the badge sits g.badge from
31855
+ // the tip (a circle's centre is the image's centre).
31856
+ var tipY = args[0] === 'pin' ? h / 2 + g.tipToHead : h / 2;
31857
+ ctx.save();
31858
+ ctx.translate(w / 2 + g.badge[0] - badge.width / 2, tipY + g.badge[1] - badge.height / 2);
31859
+ badge.draw(ctx, ratio);
31860
+ ctx.restore();
31861
+ }
30374
31862
  },
30375
31863
  };
30376
31864
  },
@@ -30535,12 +32023,14 @@ function smapCreateSdk(engine, interop, plan) {
30535
32023
  * @param {Object} [options.popup] default popup options of every layer and openPopup (className, maxWidth,
30536
32024
  * minWidth, offset, anchor, closeButton, closeOnClick, focusAfterOpen)
30537
32025
  * @param {string|Node} [options.popupLoading] shown while a popup's content is a pending promise (default: dots)
32026
+ * @param {'auto'|'webgl'|'canvas'} [options.renderer] 'auto' (default): the WebGL map, or the canvas map (raster
32027
+ * tiles, layers drawn in 2D) where the browser has no WebGL 2 (M67)
30538
32028
  * Every other option (center, zoom, pitch, bearing, minZoom, maxZoom, maxBounds, hash, …) goes to the engine.
30539
32029
  */
30540
32030
  function SMap(options) {
30541
32031
  if (!(this instanceof SMap)) { return new SMap(options); }
30542
32032
  options = options || {};
30543
- if (!engine) { throw new Error('sMap: the rendering engine is not loaded'); }
32033
+ if (!engine && !canvasLib) { throw new Error('sMap: the rendering engine is not loaded'); }
30544
32034
  this._base = (options.baseUrl || DEFAULT_BASE).replace(/\/$/, '');
30545
32035
  this._token = options.token || null;
30546
32036
  this._getToken = options.getToken || null;
@@ -30590,9 +32080,16 @@ function smapCreateSdk(engine, interop, plan) {
30590
32080
  this._toolHandlers = [];
30591
32081
 
30592
32082
  var self = this;
32083
+ // The renderer (M67): the WebGL engine, or the canvas map where there is no WebGL 2 (or when asked for).
32084
+ var want = options.renderer || 'auto';
32085
+ // Asked of the page itself; without a page (a server, a test) the WebGL engine is kept.
32086
+ var hasPage = typeof document !== 'undefined' && !!document && typeof document.createElement === 'function';
32087
+ var useCanvas = !!canvasLib && (want === 'canvas' || (want === 'auto' && (!engine || (hasPage && !canvasLib.webgl2Available(document)))));
32088
+ this._lib = useCanvas ? canvasLib : engine;
32089
+ this.renderer = useCanvas ? 'canvas' : 'webgl';
30593
32090
  var engineOptions = {};
30594
32091
  for (var k in options) {
30595
- if (Object.prototype.hasOwnProperty.call(options, k) && ['token', 'getToken', 'apiKey', 'style', 'language', 'baseUrl', 'projection', 'nearest', 'nodes', 'popup', 'popupLoading', 'autoZoom', 'keyboard3D', 'tools'].indexOf(k) < 0) {
32092
+ 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) {
30596
32093
  engineOptions[k] = options[k];
30597
32094
  }
30598
32095
  }
@@ -30600,6 +32097,11 @@ function smapCreateSdk(engine, interop, plan) {
30600
32097
  // The page's origin is its identity: no app header is sent from a browser. The key travels in the style URL
30601
32098
  // (the service carries it into the tile URLs) and, for a style given as a URL or an object, as a header.
30602
32099
  engineOptions.transformRequest = function (url) {
32100
+ // Tiles of the canvas map are images: the token rides in the query (sMap reads `token` there too).
32101
+ if (self.renderer === 'canvas') {
32102
+ if (self._token && url.indexOf(self._base) === 0 && !/[?&]token=/.test(url)) { url += (url.indexOf('?') >= 0 ? '&' : '?') + 'token=' + encodeURIComponent(self._token); }
32103
+ return { url: url };
32104
+ }
30603
32105
  if ((self._token || self._apiKey) && url.indexOf(self._base) === 0) {
30604
32106
  var headers = {};
30605
32107
  if (self._token) { headers.Authorization = 'Bearer ' + self._token; }
@@ -30617,8 +32119,17 @@ function smapCreateSdk(engine, interop, plan) {
30617
32119
  options.nearest === false ? Promise.resolve() : nearestNode(this._base, options.nodes).then(function (base) { self._base = base; })
30618
32120
  ]);
30619
32121
  this.ready = start.then(function () {
30620
- engineOptions.style = self._styleUrl();
30621
- self.engine = new engine.Map(engineOptions);
32122
+ engineOptions.style = self._engineStyle();
32123
+ try {
32124
+ self.engine = new self._lib.Map(engineOptions);
32125
+ } catch (error) {
32126
+ // A WebGL context the browser refuses (a blocked GPU, too many maps): the canvas map instead, when allowed.
32127
+ if (self.renderer !== 'webgl' || want !== 'auto' || !canvasLib) { throw error; }
32128
+ self._lib = canvasLib;
32129
+ self.renderer = 'canvas';
32130
+ engineOptions.style = self._engineStyle();
32131
+ self.engine = new canvasLib.Map(engineOptions);
32132
+ }
30622
32133
  deckCompat(self.engine);
30623
32134
  if (self.engine.addControl) {
30624
32135
  var badge = badgeControl();
@@ -30647,6 +32158,20 @@ function smapCreateSdk(engine, interop, plan) {
30647
32158
  else if (style) { this._customStyle = style; this._serverStyle = null; }
30648
32159
  };
30649
32160
 
32161
+ // The style handed to the renderer: the vector style, or for the canvas map sMap's raster tiles of the same scheme.
32162
+ SMap.prototype._engineStyle = function () {
32163
+ if (this.renderer !== 'canvas') { return this._styleUrl(); }
32164
+ if (this._customStyle && typeof this._customStyle === 'object') { return this._customStyle; }
32165
+ var name = this._serverStyle || this._flavor;
32166
+ var win = typeof window !== 'undefined' ? window : {};
32167
+ var tiles = interop.rasterUrl(name, { baseUrl: this._base, lang: this._language, apiKey: this._apiKey, retina: (win.devicePixelRatio || 1) > 1 });
32168
+ return {
32169
+ version: 8,
32170
+ sources: { 'smap-raster': { type: 'raster', tiles: [tiles], tileSize: 256, maxzoom: 19, attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' } },
32171
+ layers: [{ id: 'smap-raster', type: 'raster', source: 'smap-raster' }],
32172
+ };
32173
+ };
32174
+
30650
32175
  SMap.prototype._styleUrl = function () {
30651
32176
  if (this._customStyle) { return this._customStyle; }
30652
32177
  if (this._serverStyle) { return interop.styleUrl(this._serverStyle, { baseUrl: this._base, lang: this._language, apiKey: this._apiKey }); }
@@ -30677,7 +32202,7 @@ function smapCreateSdk(engine, interop, plan) {
30677
32202
  SMap.prototype.setStyle = function (style) {
30678
32203
  this._takeStyle(style);
30679
32204
  this._styleReady = false;
30680
- this.engine.setStyle(this._styleUrl());
32205
+ this.engine.setStyle(this._engineStyle());
30681
32206
  this._styleClass();
30682
32207
  return this;
30683
32208
  };
@@ -30754,7 +32279,7 @@ function smapCreateSdk(engine, interop, plan) {
30754
32279
  var k = POPUP_OPTIONS[i];
30755
32280
  if (k !== 'className' && k !== 'minWidth' && k !== 'autoPan' && o[k] !== undefined) { engineOptions[k] = k === 'maxWidth' ? cssSize(o[k]) : o[k]; }
30756
32281
  }
30757
- var popup = new engine.Popup(engineOptions).setLngLat(lngLat);
32282
+ var popup = new this._lib.Popup(engineOptions).setLngLat(lngLat);
30758
32283
  this._popup = popup;
30759
32284
  var pending = isThenable(content);
30760
32285
  var loading = o.loading !== undefined ? o.loading : (this._popupLoading !== undefined ? this._popupLoading : POPUP_LOADING);
@@ -30806,12 +32331,14 @@ function smapCreateSdk(engine, interop, plan) {
30806
32331
 
30807
32332
  SMap.prototype._reportError = function (error) {
30808
32333
  var m = this.engine;
30809
- if (m && engine.ErrorEvent && m.fire) { m.fire(new engine.ErrorEvent(error)); } else if (typeof console !== 'undefined') { console.error(error); }
32334
+ if (m && this._lib.ErrorEvent && m.fire) { m.fire(new this._lib.ErrorEvent(error)); } else if (typeof console !== 'undefined') { console.error(error); }
30810
32335
  };
30811
32336
 
30812
32337
  // A click on the map: the top feature of the layers with a popup, if any, opens its layer's popup.
30813
32338
  SMap.prototype._popupClick = function (e) {
30814
32339
  var m = this.engine;
32340
+ // The release of a drag is not a click.
32341
+ if (this._dragEndedAt && Date.now() - this._dragEndedAt < 300) { return; }
30815
32342
  var ids = [];
30816
32343
  var byEngineLayer = {};
30817
32344
  for (var i = 0; i < this._order.length; i++) {
@@ -30938,7 +32465,7 @@ function smapCreateSdk(engine, interop, plan) {
30938
32465
  SMap.prototype.setLanguage = function (language) {
30939
32466
  this._language = language;
30940
32467
  // A scheme or a custom style of sMap is served per language; a style of your own keeps its labels.
30941
- if (!this._customStyle) { this._styleReady = false; this.engine.setStyle(this._styleUrl()); }
32468
+ if (!this._customStyle) { this._styleReady = false; this.engine.setStyle(this._engineStyle()); }
30942
32469
  return this;
30943
32470
  };
30944
32471
 
@@ -31053,6 +32580,8 @@ function smapCreateSdk(engine, interop, plan) {
31053
32580
  var def = this._layers[id];
31054
32581
  if (!def) { throw new Error('sMap: no layer ' + id); }
31055
32582
  def.data = toFeatureCollection(data);
32583
+ if (def.kind === 'custom') { this._customData(id, def); return this; }
32584
+ if (def.kind === 'models') { if (this.engine) { this.engine.triggerRepaint(); } return this; }
31056
32585
  if (def.hovered) { this._setHover(id, def, null); }
31057
32586
  var src = this.engine.getSource('smap-' + id);
31058
32587
  if (src) { src.setData(def.data); }
@@ -31064,6 +32593,8 @@ function smapCreateSdk(engine, interop, plan) {
31064
32593
  if (!def) { return this; }
31065
32594
  def.hidden = !visible;
31066
32595
  var m = this.engine;
32596
+ if (def.kind === 'custom') { if (!visible && this._tooltipFor && this._tooltipFor.id === id) { this._hideTooltip(); } this._layoutMarkers(); return this; }
32597
+ if (def.clusterSet) { this._layoutMarkers(); }
31067
32598
  if (!m) { return this; }
31068
32599
  def.engineLayers.forEach(function (l) { if (m.getLayer(l)) { m.setLayoutProperty(l, 'visibility', visible ? 'visible' : 'none'); } });
31069
32600
  return this;
@@ -31096,7 +32627,7 @@ function smapCreateSdk(engine, interop, plan) {
31096
32627
  SMap.prototype.fitLayer = function (id, options) {
31097
32628
  var def = this._layers[id];
31098
32629
  if (!def || !def.data.features.length) { return this; }
31099
- var b = new engine.LngLatBounds();
32630
+ var b = new this._lib.LngLatBounds();
31100
32631
  def.data.features.forEach(function extend(f) {
31101
32632
  (function walk(c) { if (typeof c[0] === 'number') { b.extend(c); } else { c.forEach(walk); } })(f.geometry.coordinates);
31102
32633
  });
@@ -31110,6 +32641,611 @@ function smapCreateSdk(engine, interop, plan) {
31110
32641
  SMap.prototype.flyTo = function (o) { this.engine.flyTo(o); return this; };
31111
32642
  SMap.prototype.remove = function () { clearTimeout(this._refreshTimer); if (this.engine) { this.engine.remove(); } };
31112
32643
 
32644
+ // ------------------------------------------------------------------ custom markers (G1), dragging (G2), tooltips (G5)
32645
+
32646
+ // A value that may differ per feature, read in JavaScript (custom markers, tooltips): a constant, a function of the
32647
+ // properties, or { property, default }. Engine expressions (arrays) are not read here and give the fallback.
32648
+ function plainValue(v, props, feature, fallback) {
32649
+ if (v === undefined || v === null) { return fallback; }
32650
+ if (typeof v === 'function') { var r = v(props || {}, feature); return r === undefined ? fallback : r; }
32651
+ if (Array.isArray(v)) { return fallback; }
32652
+ if (typeof v === 'object' && v.property) {
32653
+ var x = props ? props[v.property] : undefined;
32654
+ return x === undefined || x === null ? (v['default'] !== undefined ? v['default'] : fallback) : x;
32655
+ }
32656
+ return v;
32657
+ }
32658
+
32659
+ // Where an element's anchor sits, as the share of its own width and height to move it back by (CSS translate).
32660
+ var ANCHORS = {
32661
+ center: [-50, -50], top: [-50, 0], bottom: [-50, -100], left: [0, -50], right: [-100, -50],
32662
+ 'top-left': [0, 0], 'top-right': [-100, 0], 'bottom-left': [0, -100], 'bottom-right': [-100, -100],
32663
+ };
32664
+ // Pixels outside the map an element is still laid out in, so one entering the view is already there.
32665
+ var MARKER_MARGIN = 64;
32666
+ var DRAG_THRESHOLD = 3;
32667
+
32668
+ function featureKey(def, f, i) {
32669
+ var idp = def.o.idProperty;
32670
+ if (idp && f.properties && f.properties[idp] !== undefined && f.properties[idp] !== null) { return String(f.properties[idp]); }
32671
+ if (f.id !== undefined && f.id !== null) { return String(f.id); }
32672
+ return '#' + i;
32673
+ }
32674
+
32675
+ function pointOf(f) {
32676
+ var g = f && f.geometry;
32677
+ return g && g.type === 'Point' && Array.isArray(g.coordinates) && typeof g.coordinates[0] === 'number' ? g.coordinates : null;
32678
+ }
32679
+
32680
+ function offsetOf(v) {
32681
+ if (Array.isArray(v)) { return [+v[0] || 0, +v[1] || 0]; }
32682
+ if (typeof v === 'number') { return [0, -v]; }
32683
+ return [0, 0];
32684
+ }
32685
+
32686
+ // A feature's properties without its position (data given as {lng, lat, …} carries the position in them too), so a
32687
+ // marker that only moved is not rendered again.
32688
+ var POSITION_KEYS = { lng: 1, lat: 1, lon: 1, longitude: 1, latitude: 1, coordinates: 1 };
32689
+ function propsSignature(f) {
32690
+ var p = (f && f.properties) || {}, out = {};
32691
+ Object.keys(p).forEach(function (k) { if (!POSITION_KEYS[k]) { out[k] = p[k]; } });
32692
+ return JSON.stringify(out);
32693
+ }
32694
+
32695
+ /** A copy of a feature collection with one feature's point moved (the caller's own objects are never changed). */
32696
+ function movedCollection(fc, index, lngLat) {
32697
+ var features = fc.features.slice();
32698
+ var f = features[index];
32699
+ features[index] = Object.assign({}, f, { geometry: { type: 'Point', coordinates: [lngLat[0], lngLat[1]] } });
32700
+ return { type: 'FeatureCollection', features: features };
32701
+ }
32702
+
32703
+ /**
32704
+ * Custom markers: the caller's own element at each point — any HTML, laid out above the map's drawn layers. One
32705
+ * element per feature, made the first time the feature comes into view and moved while the map moves; features out
32706
+ * of view are not laid out, so thousands stay smooth.
32707
+ * @param {string} id
32708
+ * @param {Object|Array} data GeoJSON points, or an array of {lng, lat, …properties}
32709
+ * @param {Object} o
32710
+ * render(properties, feature) → an element or trusted HTML (inserted as is, never sanitized); required
32711
+ * anchor: 'center' (default), 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'
32712
+ * offset: [x, y] pixels; className; opacity, zIndex (constants, functions of the properties or { property });
32713
+ * idProperty (the property naming each feature, else the feature's id, else its place in the data);
32714
+ * riseOnHover (the element under the pointer comes to the front); minzoom; maxzoom;
32715
+ * onClick(feature, event); popup (as on addPoints); tooltip, tooltipOptions (see addPoints);
32716
+ * draggable (true, or per feature), onDragStart / onDrag / onDragEnd(feature, { lngLat, point, originalEvent })
32717
+ */
32718
+ SMap.prototype.addCustomMarkers = function (id, data, o) {
32719
+ o = o || {};
32720
+ if (typeof o.render !== 'function') { throw new Error('sMap: custom markers need render(properties, feature)'); }
32721
+ if (o.anchor !== undefined && !ANCHORS[o.anchor]) { throw new Error('sMap: unknown anchor "' + o.anchor + '"'); }
32722
+ this._define(id, { kind: 'custom', data: toFeatureCollection(data), o: o });
32723
+ return this;
32724
+ };
32725
+
32726
+ /**
32727
+ * The element of one custom marker (made now if it was not in view yet), or null.
32728
+ * @param {string} id the layer
32729
+ * @param {string|number} key the feature's idProperty value, its id, or '#<index>'
32730
+ */
32731
+ SMap.prototype.getMarker = function (id, key) {
32732
+ var def = this._layers[id];
32733
+ if (!def || def.kind !== 'custom' || !def.custom) { return null; }
32734
+ var item = def.custom.byKey[String(key)];
32735
+ if (!item) { return null; }
32736
+ if (!item.el) { this._renderMarker(id, def, item); }
32737
+ return item.el;
32738
+ };
32739
+
32740
+ SMap.prototype._markerOverlay = function () {
32741
+ if (this._overlay && this._overlay.parentNode) { return this._overlay; }
32742
+ var m = this.engine;
32743
+ var host = m.getCanvasContainer ? m.getCanvasContainer() : m.getContainer();
32744
+ var doc = host.ownerDocument;
32745
+ var overlay = doc.createElement('div');
32746
+ overlay.className = 'smap-markers';
32747
+ // First in the canvas container after the canvas, so the engine's popups (added after it) stay on top.
32748
+ var canvas = m.getCanvas ? m.getCanvas() : null;
32749
+ if (canvas && canvas.parentNode === host) { host.insertBefore(overlay, canvas.nextSibling); } else { host.appendChild(overlay); }
32750
+ this._overlay = overlay;
32751
+ var self = this;
32752
+ if (!this._overlayBound) {
32753
+ this._overlayBound = true;
32754
+ var frame = null;
32755
+ var schedule = function () {
32756
+ if (frame !== null) { return; }
32757
+ var raf = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : function (fn) { return setTimeout(fn, 16); };
32758
+ frame = raf(function () { frame = null; self._layoutMarkers(); });
32759
+ };
32760
+ this._scheduleLayout = schedule;
32761
+ m.on('move', function () { self._layoutMarkers(); });
32762
+ m.on('resize', schedule);
32763
+ m.on('zoomend', schedule);
32764
+ }
32765
+ return overlay;
32766
+ };
32767
+
32768
+ SMap.prototype._addCustom = function (id, def) {
32769
+ this._dropCustom(id, def);
32770
+ var items = [];
32771
+ var byKey = {};
32772
+ def.data.features.forEach(function (f, i) {
32773
+ var at = pointOf(f);
32774
+ if (!at) { return; }
32775
+ var item = { key: featureKey(def, f, i), index: i, f: f, el: null, shown: false, x: NaN, y: NaN, sig: null };
32776
+ items.push(item);
32777
+ byKey[item.key] = item;
32778
+ });
32779
+ def.custom = { items: items, byKey: byKey };
32780
+ this._markerOverlay();
32781
+ this._layoutMarkers();
32782
+ };
32783
+
32784
+ SMap.prototype._dropCustom = function (id, def) {
32785
+ var c = def.custom;
32786
+ if (!c) { return; }
32787
+ c.items.forEach(function (item) { if (item.el && item.el.parentNode) { item.el.parentNode.removeChild(item.el); } });
32788
+ def.custom = null;
32789
+ if (this._tooltipFor && this._tooltipFor.id === id) { this._hideTooltip(); }
32790
+ };
32791
+
32792
+ // New data for a custom marker layer: an element whose feature (by key) is still there is kept — moved when its point
32793
+ // moved, made again when its properties changed — so a marker being dragged or with its popup open stays the same.
32794
+ SMap.prototype._customData = function (id, def) {
32795
+ var old = def.custom;
32796
+ if (!old || !this.engine) { if (this.engine && this._styleReady) { this._addCustom(id, def); } return; }
32797
+ var items = [];
32798
+ var byKey = {};
32799
+ def.data.features.forEach(function (f, i) {
32800
+ if (!pointOf(f)) { return; }
32801
+ var key = featureKey(def, f, i);
32802
+ var item = old.byKey[key];
32803
+ if (item) {
32804
+ delete old.byKey[key];
32805
+ if (item.el && propsSignature(item.f) !== propsSignature(f)) { item.sig = null; }
32806
+ item.f = f;
32807
+ item.index = i;
32808
+ item.x = NaN;
32809
+ } else {
32810
+ item = { key: key, index: i, f: f, el: null, shown: false, x: NaN, y: NaN, sig: null };
32811
+ }
32812
+ items.push(item);
32813
+ byKey[key] = item;
32814
+ });
32815
+ Object.keys(old.byKey).forEach(function (k) { var gone = old.byKey[k]; if (gone.el && gone.el.parentNode) { gone.el.parentNode.removeChild(gone.el); } });
32816
+ def.custom = { items: items, byKey: byKey };
32817
+ this._layoutMarkers();
32818
+ };
32819
+
32820
+ SMap.prototype._renderMarker = function (id, def, item) {
32821
+ var self = this, o = def.o, doc = this._markerOverlay().ownerDocument;
32822
+ var el = item.el;
32823
+ if (!el) {
32824
+ el = doc.createElement('div');
32825
+ el.setAttribute('data-smap-layer', id);
32826
+ el.setAttribute('data-smap-key', item.key);
32827
+ item.el = el;
32828
+ this._bindMarker(id, def, item);
32829
+ }
32830
+ var props = item.f.properties || {};
32831
+ var content = o.render(props, item.f);
32832
+ while (el.firstChild) { el.removeChild(el.firstChild); }
32833
+ if (isElement(content)) { el.appendChild(content); } else if (content !== undefined && content !== null) { el.innerHTML = String(content); }
32834
+ el.className = 'smap-marker' + (o.className ? ' ' + o.className : '') + (o.onClick || o.popup || o.draggable || o.tooltip || o.riseOnHover ? ' smap-marker-interactive' : '');
32835
+ var opacity = plainValue(o.opacity, props, item.f, 1);
32836
+ el.style.opacity = opacity === 1 ? '' : String(opacity);
32837
+ item.z = +plainValue(o.zIndex, props, item.f, 0) || 0;
32838
+ item.sig = true;
32839
+ return el;
32840
+ };
32841
+
32842
+ // The anchor and offset of a layer's markers as a CSS transform tail.
32843
+ function anchorTransform(o) {
32844
+ var a = ANCHORS[o.anchor || 'center'], off = offsetOf(o.offset);
32845
+ return ' translate(' + a[0] + '%, ' + a[1] + '%)' + (off[0] || off[1] ? ' translate(' + off[0] + 'px, ' + off[1] + 'px)' : '');
32846
+ }
32847
+
32848
+ // Places every custom marker in view (and takes the ones that left the view out of the page).
32849
+ SMap.prototype._layoutMarkers = function () {
32850
+ var m = this.engine;
32851
+ if (!m || !this._overlay) { return; }
32852
+ var host = this._overlay;
32853
+ var w = host.clientWidth || (m.getContainer && m.getContainer().clientWidth) || 0;
32854
+ var h = host.clientHeight || (m.getContainer && m.getContainer().clientHeight) || 0;
32855
+ var zoom = m.getZoom ? m.getZoom() : 0;
32856
+ var rank = 0;
32857
+ for (var n = 0; n < this._order.length; n++) {
32858
+ var id = this._order[n], layer = this._layers[id];
32859
+ // A custom marker layer, or the HTML clusters of a point layer (cluster.html / cluster.ring).
32860
+ var def = layer.kind === 'custom' ? layer : layer.clusterSet;
32861
+ if (!def || !def.custom) { continue; }
32862
+ rank += 1;
32863
+ var o = def.o;
32864
+ var inRange = !layer.hidden && (o.minzoom === undefined || zoom >= o.minzoom) && (o.maxzoom === undefined || zoom < o.maxzoom);
32865
+ var tail = anchorTransform(o);
32866
+ var items = def.custom.items;
32867
+ for (var i = 0; i < items.length; i++) {
32868
+ var item = items[i];
32869
+ var at = item.dragAt || pointOf(item.f);
32870
+ var p = inRange && at ? m.project(at) : null;
32871
+ var visible = !!p && p.x >= -MARKER_MARGIN && p.y >= -MARKER_MARGIN && p.x <= w + MARKER_MARGIN && p.y <= h + MARKER_MARGIN;
32872
+ if (!visible) {
32873
+ if (item.shown && item.el && item.el.parentNode) { item.el.parentNode.removeChild(item.el); }
32874
+ item.shown = false;
32875
+ continue;
32876
+ }
32877
+ if (!item.el || !item.sig) { this._renderMarker(id, def, item); }
32878
+ var x = Math.round(p.x * 10) / 10, y = Math.round(p.y * 10) / 10;
32879
+ if (x !== item.x || y !== item.y) {
32880
+ item.el.style.transform = 'translate(' + x + 'px, ' + y + 'px)' + tail;
32881
+ item.x = x; item.y = y;
32882
+ }
32883
+ var z = rank * 100000 + (item.raised ? 99999 : Math.max(-99999, Math.min(99998, item.z)));
32884
+ if (item.zSet !== z) { item.el.style.zIndex = String(z); item.zSet = z; }
32885
+ if (!item.shown || item.el.parentNode !== host) { host.appendChild(item.el); item.shown = true; }
32886
+ }
32887
+ }
32888
+ };
32889
+
32890
+ // Click, popup, hover raise, tooltip and dragging of one custom marker.
32891
+ SMap.prototype._bindMarker = function (id, def, item) {
32892
+ var self = this, el = item.el;
32893
+ var o = function () { return def.o; };
32894
+ // The click a drag's release makes is no tap: stopped on the way down, before anything inside the marker (the
32895
+ // app's own buttons, a framework listening on its own element) sees it.
32896
+ el.addEventListener('click', function (e) {
32897
+ if (self._dragEndedAt && Date.now() - self._dragEndedAt < 300) { e.stopPropagation(); if (e.preventDefault) { e.preventDefault(); } }
32898
+ }, true);
32899
+ el.addEventListener('click', function (e) {
32900
+ if (self._dragEndedAt && Date.now() - self._dragEndedAt < 300) { e.stopPropagation(); return; }
32901
+ var lo = o();
32902
+ if (!lo.onClick && !lo.popup) { return; }
32903
+ e.stopPropagation();
32904
+ if (lo.onClick) { lo.onClick(item.f, { originalEvent: e, lngLat: pointOf(item.f) }); }
32905
+ if (lo.popup) { self._markerPopup(id, def, item); }
32906
+ });
32907
+ el.addEventListener('mouseenter', function (e) {
32908
+ var lo = o();
32909
+ if (lo.riseOnHover) { item.raised = true; self._layoutMarkers(); }
32910
+ if (lo.tooltip) { self._showTooltip(id, def, item.f, { element: el }); }
32911
+ });
32912
+ el.addEventListener('mouseleave', function () {
32913
+ if (item.raised) { item.raised = false; self._layoutMarkers(); }
32914
+ if (self._tooltipFor && self._tooltipFor.f === item.f) { self._hideTooltip(); }
32915
+ });
32916
+ var start = function (e) {
32917
+ var lo = o();
32918
+ if (!plainValue(lo.draggable, item.f.properties || {}, item.f, false)) { return; }
32919
+ if (e.button !== undefined && e.button !== 0) { return; }
32920
+ self._startMarkerDrag(id, item, e);
32921
+ };
32922
+ if (typeof el.addEventListener === 'function') {
32923
+ el.addEventListener(typeof window !== 'undefined' && window.PointerEvent ? 'pointerdown' : 'mousedown', start);
32924
+ }
32925
+ };
32926
+
32927
+ SMap.prototype._markerPopup = function (id, def, item) {
32928
+ var o = def.o, fn = def.popupFn || o.popup, content;
32929
+ try { content = fn(item.f.properties || {}, item.f); } catch (error) { this._reportError(error); return; }
32930
+ var options = Object.assign({}, o.popupOptions || {});
32931
+ if (options.offset === undefined && this._popupDefaults.offset === undefined && item.el) {
32932
+ // Above the element as it is drawn: from its anchor to its top edge.
32933
+ var hgt = item.el.offsetHeight || 0, a = ANCHORS[o.anchor || 'center'];
32934
+ options.offset = Math.round(hgt * (-a[1] / 100) - offsetOf(o.offset)[1] + 4);
32935
+ }
32936
+ if (options.anchor === undefined && this._popupDefaults.anchor === undefined) { options.anchor = 'bottom'; }
32937
+ this.openPopup(pointOf(item.f), content, options);
32938
+ };
32939
+
32940
+ // Dragging a custom marker: it follows the pointer from the first move of DRAG_THRESHOLD pixels; the map does not
32941
+ // pan meanwhile. Its layer's data then holds the new position (a copy: the caller's objects are not changed).
32942
+ SMap.prototype._startMarkerDrag = function (id, item, e) {
32943
+ var self = this, m = this.engine, el = item.el;
32944
+ var doc = el.ownerDocument, win = doc.defaultView || {};
32945
+ var box = m.getContainer().getBoundingClientRect();
32946
+ var p0 = m.project(pointOf(item.f));
32947
+ var grab = { x: e.clientX - box.left - p0.x, y: e.clientY - box.top - p0.y };
32948
+ var sx = e.clientX, sy = e.clientY, dragging = false;
32949
+ // The map must not pan under a marker that can be dragged: off from the press, back on at the release.
32950
+ var panWasOn = !!(m.dragPan && m.dragPan.isEnabled && m.dragPan.isEnabled());
32951
+ if (panWasOn) { m.dragPan.disable(); }
32952
+ var lngLatAt = function (ev) {
32953
+ var r = m.getContainer().getBoundingClientRect();
32954
+ var pt = { x: ev.clientX - r.left - grab.x, y: ev.clientY - r.top - grab.y };
32955
+ var ll = m.unproject([pt.x, pt.y]);
32956
+ return { lngLat: [ll.lng, ll.lat], point: pt };
32957
+ };
32958
+ var evt = function (ev, at) { return { lngLat: at.lngLat, point: at.point, originalEvent: ev }; };
32959
+ var move = function (ev) {
32960
+ if (!dragging) {
32961
+ if (Math.abs(ev.clientX - sx) < DRAG_THRESHOLD && Math.abs(ev.clientY - sy) < DRAG_THRESHOLD) { return; }
32962
+ dragging = true;
32963
+ el.classList.add('smap-marker-dragging');
32964
+ item.raised = true;
32965
+ self._hideTooltip();
32966
+ var o0 = self._layers[id].o;
32967
+ if (o0.onDragStart) { o0.onDragStart(item.f, evt(ev, lngLatAt(ev))); }
32968
+ }
32969
+ if (ev.preventDefault) { ev.preventDefault(); }
32970
+ var at = lngLatAt(ev);
32971
+ item.dragAt = at.lngLat;
32972
+ self._layoutMarkers();
32973
+ var o1 = self._layers[id] && self._layers[id].o;
32974
+ if (o1 && o1.onDrag) { o1.onDrag(item.f, evt(ev, at)); }
32975
+ };
32976
+ var up = function (ev) {
32977
+ doc.removeEventListener(upType, up);
32978
+ doc.removeEventListener(moveType, move);
32979
+ if (panWasOn) { m.dragPan.enable(); }
32980
+ if (!dragging) { return; }
32981
+ var at = lngLatAt(ev);
32982
+ item.dragAt = null;
32983
+ item.raised = false;
32984
+ el.classList.remove('smap-marker-dragging');
32985
+ self._dragEndedAt = Date.now();
32986
+ var def = self._layers[id];
32987
+ if (!def) { return; }
32988
+ def.data = movedCollection(def.data, item.index, at.lngLat);
32989
+ item.f = def.data.features[item.index];
32990
+ self._layoutMarkers();
32991
+ if (def.o.onDragEnd) { def.o.onDragEnd(item.f, evt(ev, at)); }
32992
+ };
32993
+ var pointer = !!win.PointerEvent && e.type === 'pointerdown';
32994
+ var moveType = pointer ? 'pointermove' : 'mousemove';
32995
+ var upType = pointer ? 'pointerup' : 'mouseup';
32996
+ doc.addEventListener(moveType, move);
32997
+ doc.addEventListener(upType, up);
32998
+ if (e.stopPropagation) { e.stopPropagation(); }
32999
+ };
33000
+
33001
+ // Dragging a drawn point (addPoints with draggable): the point is taken out of its layer and drawn in a copy of the
33002
+ // layer's own look that follows the pointer, then put back at the new position. Two data updates per drag, whatever
33003
+ // the size of the layer.
33004
+ SMap.prototype._dragPoints = function (id, def, main) {
33005
+ var self = this, m = this.engine;
33006
+ var begin = function (e) {
33007
+ var f = e.features && e.features[0];
33008
+ if (!f || def.hidden) { return; }
33009
+ var index = self._featureIndex(def, f);
33010
+ if (index < 0) { return; }
33011
+ var original = def.data.features[index];
33012
+ if (!plainValue(def.o.draggable, original.properties || {}, original, false)) { return; }
33013
+ if (e.originalEvent && e.originalEvent.button !== undefined && e.originalEvent.button !== 0) { return; }
33014
+ e.preventDefault();
33015
+ var start = e.point, dragging = false, ghost = null;
33016
+ var at = function (ev) { return { lngLat: [ev.lngLat.lng, ev.lngLat.lat], point: { x: ev.point.x, y: ev.point.y }, originalEvent: ev.originalEvent }; };
33017
+ var move = function (ev) {
33018
+ if (!dragging) {
33019
+ if (Math.abs(ev.point.x - start.x) < DRAG_THRESHOLD && Math.abs(ev.point.y - start.y) < DRAG_THRESHOLD) { return; }
33020
+ dragging = true;
33021
+ if (m.dragPan && m.dragPan.disable) { m.dragPan.disable(); }
33022
+ ghost = self._dragGhost(id, def);
33023
+ var without = def.data.features.slice();
33024
+ without.splice(index, 1);
33025
+ var src = m.getSource('smap-' + id);
33026
+ if (src) { src.setData({ type: 'FeatureCollection', features: without }); }
33027
+ if (def.hovered) { self._setHover(id, def, null); }
33028
+ self._hideTooltip();
33029
+ if (def.o.onDragStart) { def.o.onDragStart(original, at(ev)); }
33030
+ }
33031
+ var a = at(ev);
33032
+ if (ghost) { ghost.setData({ type: 'FeatureCollection', features: [Object.assign({}, original, { geometry: { type: 'Point', coordinates: a.lngLat } })] }); }
33033
+ if (def.o.onDrag) { def.o.onDrag(original, a); }
33034
+ };
33035
+ var end = function (ev) {
33036
+ m.off('mousemove', move); m.off('touchmove', move);
33037
+ m.off('mouseup', end); m.off('touchend', end);
33038
+ if (!dragging) { return; }
33039
+ if (m.dragPan && m.dragPan.enable) { m.dragPan.enable(); }
33040
+ self._dropGhost(id);
33041
+ self._dragEndedAt = Date.now();
33042
+ var a = at(ev);
33043
+ def.data = movedCollection(def.data, index, a.lngLat);
33044
+ var src = m.getSource('smap-' + id);
33045
+ if (src) { src.setData(def.data); }
33046
+ if (def.o.onDragEnd) { def.o.onDragEnd(def.data.features[index], a); }
33047
+ };
33048
+ m.on('mousemove', move); m.on('touchmove', move);
33049
+ m.on('mouseup', end); m.on('touchend', end);
33050
+ };
33051
+ this._listen(id, 'mousedown', main, begin);
33052
+ this._listen(id, 'touchstart', main, begin);
33053
+ };
33054
+
33055
+ // The index in a layer's data of a feature the engine answered (by idProperty, else by the generated id).
33056
+ SMap.prototype._featureIndex = function (def, f) {
33057
+ var feats = def.data.features, idp = def.o.idProperty;
33058
+ if (idp) {
33059
+ var want = f.properties ? f.properties[idp] : undefined;
33060
+ for (var i = 0; i < feats.length; i++) { if (feats[i].properties && feats[i].properties[idp] === want) { return i; } }
33061
+ return -1;
33062
+ }
33063
+ return typeof f.id === 'number' && f.id >= 0 && f.id < feats.length ? f.id : -1;
33064
+ };
33065
+
33066
+ // A copy of a layer's engine layers drawing from a one-feature source on top of the map: the point being dragged.
33067
+ SMap.prototype._dragGhost = function (id, def) {
33068
+ var m = this.engine, src = 'smap-' + id, ghostSrc = src + '-drag';
33069
+ this._dropGhost(id);
33070
+ m.addSource(ghostSrc, { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
33071
+ var st = m.getStyle ? m.getStyle() : null;
33072
+ var ids = [];
33073
+ (st && st.layers ? st.layers : []).forEach(function (l) {
33074
+ if (l.source !== src || def.engineLayers.indexOf(l.id) < 0 || /-(clusters|count)$/.test(l.id)) { return; }
33075
+ var copy = JSON.parse(JSON.stringify(l));
33076
+ copy.id = l.id + '-drag';
33077
+ copy.source = ghostSrc;
33078
+ delete copy.minzoom; delete copy.maxzoom;
33079
+ copy.layout = Object.assign({}, copy.layout || {}, { visibility: 'visible' });
33080
+ m.addLayer(copy);
33081
+ ids.push(copy.id);
33082
+ });
33083
+ this._ghost = { id: id, source: ghostSrc, layers: ids };
33084
+ return m.getSource(ghostSrc);
33085
+ };
33086
+
33087
+ SMap.prototype._dropGhost = function (id) {
33088
+ var g = this._ghost, m = this.engine;
33089
+ if (!g || (id && g.id !== id)) { return; }
33090
+ g.layers.forEach(function (l) { if (m.getLayer(l)) { m.removeLayer(l); } });
33091
+ if (m.getSource(g.source)) { m.removeSource(g.source); }
33092
+ this._ghost = null;
33093
+ };
33094
+
33095
+ // ---- tooltips (G5)
33096
+
33097
+ /**
33098
+ * The map's one tooltip: shown while the pointer is over a feature of a layer with `tooltip`, beside the point (or
33099
+ * the custom marker), or at the pointer on lines and areas. `tooltipOptions`: direction ('top' default, 'bottom',
33100
+ * 'left', 'right'), offset ([x, y] pixels), className.
33101
+ */
33102
+ SMap.prototype._showTooltip = function (id, def, f, at) {
33103
+ var o = def.o, content;
33104
+ var fn = typeof o.tooltip === 'string' ? (def.tooltipFn = def.tooltipFn || popupTemplate(o.tooltip)) : o.tooltip;
33105
+ try { content = fn(f.properties || {}, f); } catch (error) { this._reportError(error); return; }
33106
+ if (content === undefined || content === null || content === '') { this._hideTooltip(); return; }
33107
+ var m = this.engine, overlay = this._markerOverlay(), doc = overlay.ownerDocument;
33108
+ var tip = this._tooltip;
33109
+ if (!tip || tip.parentNode !== overlay) {
33110
+ tip = this._tooltip = doc.createElement('div');
33111
+ overlay.appendChild(tip);
33112
+ }
33113
+ var to = o.tooltipOptions || {};
33114
+ var dir = ['top', 'bottom', 'left', 'right'].indexOf(to.direction) >= 0 ? to.direction : 'top';
33115
+ tip.className = 'smap-tooltip smap-tooltip-' + dir + (to.className ? ' ' + to.className : '');
33116
+ while (tip.firstChild) { tip.removeChild(tip.firstChild); }
33117
+ if (isElement(content)) { tip.appendChild(content); } else { tip.innerHTML = String(content); }
33118
+ var x, y, gap = 6;
33119
+ if (at.element) {
33120
+ var hb = overlay.getBoundingClientRect(), eb = at.element.getBoundingClientRect();
33121
+ x = eb.left - hb.left + eb.width / 2; y = eb.top - hb.top + eb.height / 2;
33122
+ if (dir === 'top') { y = eb.top - hb.top; } else if (dir === 'bottom') { y = eb.bottom - hb.top; }
33123
+ else if (dir === 'left') { x = eb.left - hb.left; } else { x = eb.right - hb.left; }
33124
+ } else {
33125
+ var p = at.point || m.project(at.lngLat);
33126
+ x = p.x; y = p.y;
33127
+ gap = at.gap !== undefined ? at.gap : 10;
33128
+ }
33129
+ var off = offsetOf(to.offset);
33130
+ var shift = { top: [-50, -100, 0, -gap], bottom: [-50, 0, 0, gap], left: [-100, -50, -gap, 0], right: [0, -50, gap, 0] }[dir];
33131
+ 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] + '%)';
33132
+ this._tooltipFor = { id: id, f: f };
33133
+ };
33134
+
33135
+ SMap.prototype._hideTooltip = function () {
33136
+ if (this._tooltip && this._tooltip.parentNode) { this._tooltip.parentNode.removeChild(this._tooltip); }
33137
+ this._tooltipFor = null;
33138
+ };
33139
+
33140
+ // Tooltips of a drawn layer: points show theirs beside the point, lines and areas at the pointer.
33141
+ SMap.prototype._tooltips = function (id, def, main) {
33142
+ var self = this, m = this.engine;
33143
+ this._listen(id, 'mousemove', main, function (e) {
33144
+ var f = e.features && e.features[0];
33145
+ if (!f || def.hidden) { return; }
33146
+ if (def.kind === 'points' && pointOf(f) && !(f.properties && f.properties.point_count)) {
33147
+ var mk = def.o.marker, k = mk ? scaleAt(mk.scale, m.getZoom()) : 1;
33148
+ var size = mk ? (mk.size || 32) * k : (def.o.radius || 6) * 2;
33149
+ var gap = mk && (mk.shape === undefined || mk.shape === 'pin') ? Math.round(size * 1.25) : Math.round(size / 2 + 4);
33150
+ self._showTooltip(id, def, f, { lngLat: pointOf(f), gap: gap });
33151
+ } else {
33152
+ self._showTooltip(id, def, f, { point: e.point, gap: 12 });
33153
+ }
33154
+ });
33155
+ this._listen(id, 'mouseleave', main, function () { if (self._tooltipFor && self._tooltipFor.id === id) { self._hideTooltip(); } });
33156
+ };
33157
+
33158
+ // ---- clusters drawn as HTML (G6)
33159
+
33160
+ // Colours of a ring: each value's share of the points, largest first; up to six slices, the rest in the last.
33161
+ function ringBackground(counts, fallback) {
33162
+ var keys = Object.keys(counts).sort(function (a, b) { return counts[b] - counts[a]; });
33163
+ var total = keys.reduce(function (t, k) { return t + counts[k]; }, 0);
33164
+ if (!total) { return fallback; }
33165
+ if (keys.length === 1) { return keys[0]; }
33166
+ var shown = keys.slice(0, 6), at = 0;
33167
+ return 'conic-gradient(' + shown.map(function (c, i) {
33168
+ var from = at;
33169
+ at = i === shown.length - 1 ? 100 : Math.min(100, at + Math.round(counts[c] / total * 100));
33170
+ return c + ' ' + from + '% ' + at + '%';
33171
+ }).join(', ') + ')';
33172
+ }
33173
+
33174
+ // The cluster as the caller's html() sees it.
33175
+ SMap.prototype._clusterInfo = function (src, f) {
33176
+ var m = this.engine, p = f.properties || {};
33177
+ return {
33178
+ id: p.cluster_id, count: p.point_count, properties: p, lngLat: pointOf(f),
33179
+ leaves: function (limit) {
33180
+ var source = m.getSource(src);
33181
+ return Promise.resolve(source.getClusterLeaves(p.cluster_id, limit === undefined ? Infinity : limit, 0));
33182
+ },
33183
+ };
33184
+ };
33185
+
33186
+ // HTML clusters of a point layer: the clusters the engine made are read after each change of view or data and laid
33187
+ // out as custom markers (kept by cluster id, so an unchanged cluster keeps its element).
33188
+ SMap.prototype._htmlClusters = function (id, def, src) {
33189
+ var self = this, m = this.engine, co = def.o.cluster;
33190
+ var ring = co.ring ? (typeof co.ring === 'string' ? { property: co.ring } : co.ring) : null;
33191
+ var render = function (props, f) {
33192
+ var info = self._clusterInfo(src, f);
33193
+ if (co.html) { return co.html(info); }
33194
+ var size = info.count >= 100 ? 44 : (info.count >= 10 ? 38 : 32);
33195
+ var doc = self._markerOverlay().ownerDocument;
33196
+ var el = doc.createElement('div');
33197
+ el.className = 'smap-cluster';
33198
+ el.style.width = el.style.height = size + 'px';
33199
+ el.style.background = ring.color || def.o.clusterColor || def.o.color || '#4f46e5';
33200
+ var b = doc.createElement('b');
33201
+ b.textContent = String(info.count);
33202
+ el.appendChild(b);
33203
+ // The ring: the share of each value among the cluster's points (read from at most ring.limit of them).
33204
+ info.leaves(ring.limit || 500).then(function (leaves) {
33205
+ var counts = {};
33206
+ (leaves || []).forEach(function (leaf) {
33207
+ var c = leaf.properties ? leaf.properties[ring.property] : undefined;
33208
+ if (typeof c === 'string' && c) { counts[c] = (counts[c] || 0) + 1; }
33209
+ });
33210
+ el.style.background = ringBackground(counts, el.style.background);
33211
+ }, function () {});
33212
+ return el;
33213
+ };
33214
+ def.clusterSet = {
33215
+ o: {
33216
+ render: render, anchor: 'center', className: co.className, riseOnHover: true,
33217
+ // A click zooms in to where the cluster splits (cluster.zoomOnClick: false turns it off).
33218
+ onClick: co.zoomOnClick === false ? co.onClick : function (f, e) {
33219
+ if (co.onClick) { co.onClick(f, e); }
33220
+ var source = m.getSource(src);
33221
+ Promise.resolve(source.getClusterExpansionZoom(f.properties.cluster_id)).then(function (zoom) {
33222
+ m.easeTo({ center: pointOf(f), zoom: zoom });
33223
+ }, function () {});
33224
+ },
33225
+ tooltip: co.tooltip ? function (p, f) { return co.tooltip(self._clusterInfo(src, f)); } : undefined,
33226
+ },
33227
+ data: { type: 'FeatureCollection', features: [] },
33228
+ custom: null,
33229
+ };
33230
+ var refresh = function () {
33231
+ var set = def.clusterSet;
33232
+ if (!set || !m.getSource(src)) { return; }
33233
+ var seen = {}, features = [];
33234
+ (m.querySourceFeatures(src, { filter: ['has', 'point_count'] }) || []).forEach(function (f) {
33235
+ var cid = f.properties && f.properties.cluster_id;
33236
+ if (cid === undefined || seen[cid]) { return; }
33237
+ seen[cid] = true;
33238
+ features.push({ type: 'Feature', id: 'c' + cid, properties: f.properties, geometry: { type: 'Point', coordinates: pointOf(f) } });
33239
+ });
33240
+ set.data = { type: 'FeatureCollection', features: features };
33241
+ self._markerOverlay();
33242
+ self._customData(id + ':clusters', set);
33243
+ };
33244
+ this._listen(id, 'moveend', null, refresh);
33245
+ this._listen(id, 'sourcedata', null, function (e) { if (e && e.sourceId === src && e.isSourceLoaded) { refresh(); } });
33246
+ refresh();
33247
+ };
33248
+
31113
33249
  // ------------------------------------------------------------------ map tools (M49-M57)
31114
33250
 
31115
33251
  var TOOL_ID = /^[A-Za-z0-9._-]{1,64}$/;
@@ -31124,6 +33260,8 @@ function smapCreateSdk(engine, interop, plan) {
31124
33260
  zoom: { control: 'NavigationControl', label: 'Zoom', position: 'top-left', options: { showCompass: false } },
31125
33261
  compass: { control: 'NavigationControl', label: 'Compass', position: 'top-left', options: { showZoom: false } },
31126
33262
  locate: { control: 'GeolocateControl', label: 'Locate', position: 'top-left' },
33263
+ // Made by the SDK itself: the base map and overlay switcher (G8).
33264
+ layers: { sdk: 'layers', label: 'Layers', position: 'top-right' },
31127
33265
  };
31128
33266
  var TOOLS_ICON = '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false" fill="none" ' +
31129
33267
  'stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 7h10M18 7h2M4 17h2M10 17h10"/>' +
@@ -31194,7 +33332,7 @@ function smapCreateSdk(engine, interop, plan) {
31194
33332
  }
31195
33333
  if (typeof target === 'string') {
31196
33334
  if (!kind) { throw new Error('sMap: unknown tool kind "' + target + '"'); }
31197
- if (!engine[kind.control]) { throw new Error('sMap: the rendering engine has no ' + kind.control); }
33335
+ if (!kind.sdk && !this._lib[kind.control]) { throw new Error('sMap: the rendering engine has no ' + kind.control); }
31198
33336
  } else if (!isElement(target) && !(target && typeof target.onAdd === 'function')) {
31199
33337
  throw new Error('sMap: a tool is an element, an engine control or a tool kind');
31200
33338
  }
@@ -31203,7 +33341,8 @@ function smapCreateSdk(engine, interop, plan) {
31203
33341
  if (position === 'none' && !isElement(target)) { throw new Error('sMap: only an element may stay out of the map\'s corners'); }
31204
33342
  if (this._toolIndex(id) >= 0) { this.removeTool(id); }
31205
33343
  var tool = { id: id, label: String(o.label || (kind ? kind.label : id)), position: position, el: null, control: null };
31206
- if (kind) { tool.control = toolControl(tool, new engine[kind.control](o.options || kind.options)); }
33344
+ if (kind && kind.sdk === 'layers') { tool.control = toolControl(tool, layersControl(this, o.options || {})); }
33345
+ else if (kind) { tool.control = toolControl(tool, new this._lib[kind.control](o.options || kind.options)); }
31207
33346
  else if (!isElement(target)) { tool.control = toolControl(tool, target); }
31208
33347
  else if (position !== 'none') { tool.control = elementControl(tool, target); }
31209
33348
  else { tool.el = target; }
@@ -31456,6 +33595,1119 @@ function smapCreateSdk(engine, interop, plan) {
31456
33595
  return control;
31457
33596
  }
31458
33597
 
33598
+ // ------------------------------------------------------------------ heat maps (G7), terrain (G11), synced maps (G13), drawn tiles (G14)
33599
+
33600
+ /**
33601
+ * A heat map of points: weight (a number, or per feature: { property } or an expression; default 1), radius
33602
+ * (pixels, default 20, or an expression of the zoom), intensity (default 1), opacity (default 0.8), colors (from low
33603
+ * to high, or [[density 0–1, colour], …]), minzoom, maxzoom, slot, before, zIndex.
33604
+ */
33605
+ SMap.prototype.addHeatmap = function (id, data, o) {
33606
+ this._define(id, { kind: 'heatmap', data: toFeatureCollection(data), o: o || {} });
33607
+ return this;
33608
+ };
33609
+
33610
+ var TERRAIN_SOURCE = 'smap-terrain';
33611
+ var HILLSHADE_LAYER = 'smap-hillshade';
33612
+
33613
+ /**
33614
+ * Relief: the map drawn over elevation tiles (seen when the map is tilted), with optional hillshading.
33615
+ * @param {Object|null} terrain { tiles: [url with {z}/{x}/{y}], encoding: 'terrarium' | 'mapbox' (default terrarium),
33616
+ * tileSize (256), maxzoom (15), attribution, exaggeration (1), hillshade (true, or { exaggeration, shadow,
33617
+ * highlight }) }; null takes it off
33618
+ */
33619
+ SMap.prototype.setTerrain = function (terrain) {
33620
+ if (terrain && (!Array.isArray(terrain.tiles) || !terrain.tiles.length)) { throw new Error('sMap: terrain needs tiles'); }
33621
+ this._terrain = terrain ? Object.assign({}, terrain) : null;
33622
+ if (this.engine && this._styleReady) { this._applyTerrain(); }
33623
+ return this;
33624
+ };
33625
+
33626
+ SMap.prototype._applyTerrain = function () {
33627
+ var m = this.engine, t = this._terrain;
33628
+ if (m.setTerrain) { m.setTerrain(null); }
33629
+ if (m.getLayer(HILLSHADE_LAYER)) { m.removeLayer(HILLSHADE_LAYER); }
33630
+ if (m.getSource(TERRAIN_SOURCE)) { m.removeSource(TERRAIN_SOURCE); }
33631
+ if (m.getSource(TERRAIN_SOURCE + '-shade')) { m.removeSource(TERRAIN_SOURCE + '-shade'); }
33632
+ if (!t) { return; }
33633
+ var source = { type: 'raster-dem', tiles: t.tiles.slice(), tileSize: t.tileSize || 256, encoding: t.encoding || 'terrarium', maxzoom: t.maxzoom || 15 };
33634
+ if (t.attribution) { source.attribution = t.attribution; }
33635
+ m.addSource(TERRAIN_SOURCE, source);
33636
+ if (t.hillshade !== false) {
33637
+ var hs = t.hillshade && typeof t.hillshade === 'object' ? t.hillshade : {};
33638
+ var paint = { 'hillshade-exaggeration': hs.exaggeration !== undefined ? hs.exaggeration : 0.35 };
33639
+ if (hs.shadow) { paint['hillshade-shadow-color'] = hs.shadow; }
33640
+ if (hs.highlight) { paint['hillshade-highlight-color'] = hs.highlight; }
33641
+ // Over land and water, under the roads; from a source of its own (the engine draws the relief and the shading
33642
+ // better from two sources than from one).
33643
+ m.addSource(TERRAIN_SOURCE + '-shade', Object.assign({}, source, { tiles: source.tiles.slice() }));
33644
+ m.addLayer({ id: HILLSHADE_LAYER, type: 'hillshade', source: TERRAIN_SOURCE + '-shade', paint: paint }, this.slots().roads || undefined);
33645
+ }
33646
+ if (m.setTerrain) { m.setTerrain({ source: TERRAIN_SOURCE, exaggeration: t.exaggeration || 1 }); }
33647
+ };
33648
+
33649
+ /**
33650
+ * Keeps another sMap map on this map's view, both ways (a picture in picture, a before / after pair). `offsetZoom`
33651
+ * is added to this map's zoom on the other (e.g. -3 for an overview). Returns a function that ends it.
33652
+ */
33653
+ SMap.prototype.syncWith = function (other, options) {
33654
+ var self = this, offset = (options && options.offsetZoom) || 0, busy = false;
33655
+ if (!other || !(other instanceof SMap)) { throw new Error('sMap: syncWith takes another sMap map'); }
33656
+ var copy = function (from, to, dz) {
33657
+ return function () {
33658
+ if (busy || !from.engine || !to.engine) { return; }
33659
+ busy = true;
33660
+ try {
33661
+ var c = from.engine.getCenter();
33662
+ to.engine.jumpTo({ center: [c.lng, c.lat], zoom: from.engine.getZoom() + dz, bearing: from.engine.getBearing(), pitch: from.engine.getPitch() });
33663
+ } finally { busy = false; }
33664
+ };
33665
+ };
33666
+ var a = copy(this, other, offset), b = copy(other, this, -offset);
33667
+ Promise.all([this.ready, other.ready]).then(function () {
33668
+ if (!a) { return; }
33669
+ self.engine.on('move', a);
33670
+ other.engine.on('move', b);
33671
+ a();
33672
+ });
33673
+ return function () {
33674
+ if (self.engine) { self.engine.off('move', a); }
33675
+ if (other.engine) { other.engine.off('move', b); }
33676
+ a = null;
33677
+ };
33678
+ };
33679
+
33680
+ // Tiles drawn by the page: one protocol for every map, each layer its own key.
33681
+ var canvasTiles = {};
33682
+ var canvasSeq = 0;
33683
+ var canvasRegistered = false;
33684
+ function registerCanvasProtocol(doc) {
33685
+ if (canvasRegistered) { return; }
33686
+ canvasRegistered = true;
33687
+ var handler = function (params) {
33688
+ var m = /^smapcanvas:\/\/([^/]+)\/(\d+)\/(\d+)\/(\d+)/.exec(params.url);
33689
+ var entry = m && canvasTiles[m[1]];
33690
+ if (!entry) { return Promise.reject(new Error('sMap: no drawn tiles for ' + params.url)); }
33691
+ var size = entry.tileSize * entry.ratio;
33692
+ var canvas = entry.doc.createElement('canvas');
33693
+ canvas.width = size; canvas.height = size;
33694
+ var ctx = canvas.getContext('2d');
33695
+ ctx.scale(entry.ratio, entry.ratio);
33696
+ return Promise.resolve(entry.draw(ctx, +m[2], +m[3], +m[4], { size: entry.tileSize, canvas: canvas })).then(function () {
33697
+ return new Promise(function (resolve, reject) {
33698
+ canvas.toBlob(function (blob) {
33699
+ if (!blob) { reject(new Error('sMap: a drawn tile could not be encoded')); return; }
33700
+ blob.arrayBuffer().then(function (data) { resolve({ data: data }); }, reject);
33701
+ }, 'image/png');
33702
+ });
33703
+ });
33704
+ };
33705
+ if (engine && engine.addProtocol) { engine.addProtocol('smapcanvas', handler); }
33706
+ if (canvasLib) { canvasLib.addProtocol('smapcanvas', handler); }
33707
+ }
33708
+
33709
+ /**
33710
+ * Raster tiles drawn by the page: draw(ctx, z, x, y, { size, canvas }) paints one tile (a 2D context already scaled
33711
+ * to `size` × `size` CSS pixels; may return a promise). Options as addRasterLayer (tileSize 256, minzoom, maxzoom,
33712
+ * opacity, attribution, slot, before, zIndex).
33713
+ */
33714
+ SMap.prototype.addCanvasTiles = function (id, draw, o) {
33715
+ if (typeof draw !== 'function') { throw new Error('sMap: addCanvasTiles needs draw(ctx, z, x, y)'); }
33716
+ o = o || {};
33717
+ var doc = (this.engine && this.engine.getContainer().ownerDocument) || (typeof document !== 'undefined' ? document : null);
33718
+ if (!doc) { throw new Error('sMap: addCanvasTiles needs a page'); }
33719
+ registerCanvasProtocol(doc);
33720
+ var key = 'c' + (++canvasSeq);
33721
+ var win = doc.defaultView || {};
33722
+ canvasTiles[key] = { draw: draw, tileSize: o.tileSize || 256, ratio: Math.min(2, Math.max(1, Math.ceil(win.devicePixelRatio || 1))), doc: doc };
33723
+ var source = { type: 'raster', tiles: ['smapcanvas://' + key + '/{z}/{x}/{y}'], tileSize: o.tileSize || 256 };
33724
+ copyKeys(o, source, ['attribution', 'minzoom', 'maxzoom', 'bounds']);
33725
+ return this._addRaster(id, source, o);
33726
+ };
33727
+
33728
+ // ------------------------------------------------------------------ drawing and editing shapes (G3), area selection (G4)
33729
+
33730
+ var DRAW_TEXT = {
33731
+ polygon: 'Polygon', rectangle: 'Rectangle', circle: 'Circle', line: 'Line', point: 'Point', hole: 'Hole',
33732
+ delete: 'Delete', undo: 'Undo', redo: 'Redo', finish: 'Finish', cancel: 'Cancel',
33733
+ };
33734
+ var DRAW_MODES = ['polygon', 'rectangle', 'circle', 'line', 'point'];
33735
+ var SNAP_PX = 10;
33736
+ var drawSeq = 0;
33737
+
33738
+ function closeRing(ring) {
33739
+ var r = ring.slice();
33740
+ if (r.length && (r[0][0] !== r[r.length - 1][0] || r[0][1] !== r[r.length - 1][1])) { r.push(r[0].slice()); }
33741
+ return r;
33742
+ }
33743
+ 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(); }
33744
+
33745
+ // A circle of `radius` metres as a ring of 64 points.
33746
+ function circleRing(center, radius) {
33747
+ // The same Earth radius as sMap.geo.distance, so the radius handle measures what was drawn.
33748
+ var ring = [], R = 6371008.8, lat = center[1] * Math.PI / 180;
33749
+ for (var i = 0; i <= 64; i++) {
33750
+ var a = (i % 64) / 64 * 2 * Math.PI;
33751
+ 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]);
33752
+ }
33753
+ return ring;
33754
+ }
33755
+
33756
+ // The editable rings of a geometry: [{ path: [polygon, ring] or [ring], coords (open), closed }].
33757
+ function ringsOf(g) {
33758
+ if (!g) { return []; }
33759
+ if (g.type === 'LineString') { return [{ path: [], coords: g.coordinates, closed: false }]; }
33760
+ if (g.type === 'Polygon') { return g.coordinates.map(function (r, i) { return { path: [i], coords: openRing(r), closed: true }; }); }
33761
+ if (g.type === 'MultiPolygon') {
33762
+ var out = [];
33763
+ g.coordinates.forEach(function (poly, p) { poly.forEach(function (r, i) { out.push({ path: [p, i], coords: openRing(r), closed: true }); }); });
33764
+ return out;
33765
+ }
33766
+ return [];
33767
+ }
33768
+
33769
+ function setRing(g, path, coords) {
33770
+ if (g.type === 'LineString') { g.coordinates = coords; return; }
33771
+ if (g.type === 'Polygon') { g.coordinates[path[0]] = closeRing(coords); return; }
33772
+ if (g.type === 'MultiPolygon') { g.coordinates[path[0]][path[1]] = closeRing(coords); }
33773
+ }
33774
+
33775
+ function moveGeometry(g, dx, dy) {
33776
+ (function walk(c) { if (typeof c[0] === 'number') { c[0] += dx; c[1] += dy; } else { c.forEach(walk); } })(g.coordinates);
33777
+ }
33778
+
33779
+ /**
33780
+ * A drawing and editing tool on this map. Shapes are kept by the tool as GeoJSON features (each with an id) and drawn
33781
+ * in its own layers; your page reads them with getAll() or from the events.
33782
+ * @param {Object} [o]
33783
+ * features (GeoJSON to start from), modes (the shapes offered, default all: polygon, rectangle, circle, line, point),
33784
+ * snap (true: a new or moved vertex within 10 px of another shape's vertex takes its place; default true),
33785
+ * toolbar (false, or a corner; default 'top-left'), labels (the toolbar's texts), color (default '#2563eb'),
33786
+ * onChange({ type: 'created' | 'edited' | 'deleted', feature, features }) after every change
33787
+ */
33788
+ SMap.prototype.drawTool = function (o) {
33789
+ return new DrawTool(this, o || {});
33790
+ };
33791
+
33792
+ function DrawTool(map, o) {
33793
+ this.map = map;
33794
+ this.o = o;
33795
+ this.id = 'smap-draw-' + (++drawSeq);
33796
+ this.color = o.color || '#2563eb';
33797
+ this.modes = (o.modes || DRAW_MODES).filter(function (k) { return DRAW_MODES.indexOf(k) >= 0; });
33798
+ this.snap = o.snap !== false;
33799
+ this.text = Object.assign({}, DRAW_TEXT, o.labels || {});
33800
+ this.features = [];
33801
+ this.seq = 0;
33802
+ this.selected = null; // feature id
33803
+ this.vertex = null; // { ring, index } of the selected vertex
33804
+ this.mode = null; // the shape being drawn
33805
+ this.sketch = null; // coordinates of the shape being drawn
33806
+ this.hover = null;
33807
+ this.undoStack = [];
33808
+ this.redoStack = [];
33809
+ this.handlers = {};
33810
+ var self = this;
33811
+ (o.features ? toFeatureCollection(o.features).features : []).forEach(function (f) { self.features.push(self._own(f)); });
33812
+ map.ready.then(function () { if (!self.destroyed) { self._mount(); } });
33813
+ }
33814
+
33815
+ DrawTool.prototype._own = function (f) {
33816
+ var copy = JSON.parse(JSON.stringify(f));
33817
+ if (copy.id === undefined || copy.id === null) { copy.id = 'f' + (++this.seq); }
33818
+ copy.properties = copy.properties || {};
33819
+ return copy;
33820
+ };
33821
+
33822
+ DrawTool.prototype.on = function (type, fn) { (this.handlers[type] = this.handlers[type] || []).push(fn); return this; };
33823
+ DrawTool.prototype.off = function (type, fn) { this.handlers[type] = (this.handlers[type] || []).filter(function (h) { return h !== fn; }); return this; };
33824
+ DrawTool.prototype._emit = function (type, feature) {
33825
+ var e = { type: type, feature: feature ? JSON.parse(JSON.stringify(feature)) : null, features: this.getAll() };
33826
+ (this.handlers[type] || []).slice().forEach(function (h) { h(e); });
33827
+ (this.handlers.change || []).slice().forEach(function (h) { h(e); });
33828
+ if (this.o.onChange) { this.o.onChange(e); }
33829
+ };
33830
+
33831
+ /** Every shape as a FeatureCollection (a copy). */
33832
+ DrawTool.prototype.getAll = function () { return JSON.parse(JSON.stringify({ type: 'FeatureCollection', features: this.features })); };
33833
+ DrawTool.prototype.get = function (id) { var f = this._find(id); return f ? JSON.parse(JSON.stringify(f)) : null; };
33834
+ /** Replaces every shape (not recorded for undo). */
33835
+ DrawTool.prototype.set = function (data) {
33836
+ var self = this;
33837
+ this.features = toFeatureCollection(data).features.map(function (f) { return self._own(f); });
33838
+ this.selected = null; this.vertex = null;
33839
+ this.undoStack = []; this.redoStack = [];
33840
+ this._render();
33841
+ return this;
33842
+ };
33843
+ 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; };
33844
+ DrawTool.prototype._record = function () {
33845
+ this.undoStack.push(JSON.stringify(this.features));
33846
+ if (this.undoStack.length > 100) { this.undoStack.shift(); }
33847
+ this.redoStack = [];
33848
+ };
33849
+ DrawTool.prototype.undo = function () {
33850
+ if (!this.undoStack.length) { return this; }
33851
+ this.redoStack.push(JSON.stringify(this.features));
33852
+ this.features = JSON.parse(this.undoStack.pop());
33853
+ if (this.selected && !this._find(this.selected)) { this.selected = null; }
33854
+ this.vertex = null;
33855
+ this._render();
33856
+ this._emit('edited', this.selected ? this._find(this.selected) : null);
33857
+ return this;
33858
+ };
33859
+ DrawTool.prototype.redo = function () {
33860
+ if (!this.redoStack.length) { return this; }
33861
+ this.undoStack.push(JSON.stringify(this.features));
33862
+ this.features = JSON.parse(this.redoStack.pop());
33863
+ if (this.selected && !this._find(this.selected)) { this.selected = null; }
33864
+ this.vertex = null;
33865
+ this._render();
33866
+ this._emit('edited', this.selected ? this._find(this.selected) : null);
33867
+ return this;
33868
+ };
33869
+
33870
+ /** Starts drawing a shape: 'polygon', 'rectangle', 'circle', 'line', 'point', or 'hole' (in the selected polygon). */
33871
+ DrawTool.prototype.start = function (mode) {
33872
+ if (mode === 'hole') {
33873
+ var f = this.selected && this._find(this.selected);
33874
+ if (!f || !f.geometry || f.geometry.type !== 'Polygon') { throw new Error('sMap: select a polygon to draw a hole in'); }
33875
+ } else if (DRAW_MODES.indexOf(mode) < 0) { throw new Error('sMap: unknown shape "' + mode + '"'); }
33876
+ this.mode = mode;
33877
+ this.sketch = [];
33878
+ this.vertex = null;
33879
+ if (mode !== 'hole') { this.selected = null; }
33880
+ this._render();
33881
+ this._renderToolbar();
33882
+ return this;
33883
+ };
33884
+
33885
+ /** Stops drawing without keeping the shape being drawn. */
33886
+ DrawTool.prototype.cancel = function () { this.mode = null; this.sketch = null; this._render(); this._renderToolbar(); return this; };
33887
+
33888
+ /** Selects a shape for editing (null to deselect). */
33889
+ DrawTool.prototype.select = function (id) { this.selected = id && this._find(id) ? id : null; this.vertex = null; this._render(); this._renderToolbar(); return this; };
33890
+ DrawTool.prototype.selection = function () { return this.selected; };
33891
+
33892
+ /** Deletes a shape (default: the selected one); with a vertex selected, deletes that vertex instead. */
33893
+ DrawTool.prototype.delete = function (id) {
33894
+ if (id === undefined && this.vertex && this.selected) { return this._deleteVertex(); }
33895
+ id = id === undefined ? this.selected : id;
33896
+ var f = this._find(id);
33897
+ if (!f) { return this; }
33898
+ this._record();
33899
+ this.features = this.features.filter(function (x) { return x !== f; });
33900
+ if (this.selected === id) { this.selected = null; this.vertex = null; }
33901
+ this._render();
33902
+ this._renderToolbar();
33903
+ this._emit('deleted', f);
33904
+ return this;
33905
+ };
33906
+
33907
+ DrawTool.prototype._deleteVertex = function () {
33908
+ var f = this._find(this.selected), v = this.vertex;
33909
+ var rings = ringsOf(f.geometry), r = rings[v.ring];
33910
+ if (!r || r.coords.length <= (r.closed ? 3 : 2)) { return this; }
33911
+ this._record();
33912
+ var coords = r.coords.slice();
33913
+ coords.splice(v.index, 1);
33914
+ setRing(f.geometry, r.path, coords);
33915
+ this.vertex = null;
33916
+ this._render();
33917
+ this._emit('edited', f);
33918
+ return this;
33919
+ };
33920
+
33921
+ DrawTool.prototype.destroy = function () {
33922
+ this.destroyed = true;
33923
+ var m = this.map.engine;
33924
+ if (!m) { return; }
33925
+ var self = this;
33926
+ (this.listeners || []).forEach(function (l) { if (l[1]) { m.off(l[0], l[1], l[2]); } else { m.off(l[0], l[2]); } });
33927
+ [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); } });
33928
+ if (m.getSource(this.id)) { m.removeSource(this.id); }
33929
+ if (this.toolbar) { m.removeControl(this.toolbar); }
33930
+ if (this.keyListener) { m.getContainer().removeEventListener('keydown', this.keyListener); }
33931
+ if (this.panOff && m.dragPan) { m.dragPan.enable(); }
33932
+ if (m.doubleClickZoom && this.dblWasOn) { m.doubleClickZoom.enable(); }
33933
+ self.map._drawTools = (self.map._drawTools || []).filter(function (t) { return t !== self; });
33934
+ };
33935
+
33936
+ DrawTool.prototype._listen = function (type, layer, fn) {
33937
+ var m = this.map.engine;
33938
+ if (layer) { m.on(type, layer, fn); } else { m.on(type, fn); }
33939
+ (this.listeners = this.listeners || []).push([type, layer, fn]);
33940
+ };
33941
+
33942
+ DrawTool.prototype._mount = function () {
33943
+ var self = this, m = this.map.engine;
33944
+ this.map._drawTools = (this.map._drawTools || []).concat([this]);
33945
+ this._addLayers();
33946
+ // The map's style changes drop every layer: the tool's come back with the map's own.
33947
+ this._listen('style.load', null, function () { self._addLayers(); });
33948
+ this._listen('mousedown', null, function (e) { self._down(e); });
33949
+ this._listen('touchstart', null, function (e) { if (e.points && e.points.length === 1) { self._down(e); } });
33950
+ this._listen('mousemove', null, function (e) { self._move(e); });
33951
+ this._listen('touchmove', null, function (e) { self._move(e); });
33952
+ this._listen('mouseup', null, function (e) { self._up(e); });
33953
+ this._listen('touchend', null, function (e) { self._up(e); });
33954
+ this._listen('click', null, function (e) { self._click(e); });
33955
+ this._listen('dblclick', null, function (e) { if (self.mode === 'polygon' || self.mode === 'line' || self.mode === 'hole') { e.preventDefault(); self._finish(); } });
33956
+ if (m.doubleClickZoom && m.doubleClickZoom.isEnabled && m.doubleClickZoom.isEnabled()) { this.dblWasOn = true; }
33957
+ var el = m.getContainer();
33958
+ this.keyListener = function (ev) {
33959
+ if (ev.key === 'Escape') { if (self.mode) { self.cancel(); } else { self.select(null); } }
33960
+ else if (ev.key === 'Enter' && self.mode) { self._finish(); }
33961
+ else if ((ev.key === 'Backspace' || ev.key === 'Delete') && self.mode && self.sketch && self.sketch.length) { self.sketch.pop(); self._render(); ev.preventDefault(); }
33962
+ else if ((ev.key === 'Backspace' || ev.key === 'Delete') && self.selected) { self.delete(); ev.preventDefault(); }
33963
+ else if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'z') { if (ev.shiftKey) { self.redo(); } else { self.undo(); } ev.preventDefault(); }
33964
+ };
33965
+ el.addEventListener('keydown', this.keyListener);
33966
+ if (this.o.toolbar !== false) {
33967
+ this.toolbar = this._toolbarControl();
33968
+ m.addControl(this.toolbar, typeof this.o.toolbar === 'string' ? this.o.toolbar : 'top-left');
33969
+ }
33970
+ this._render();
33971
+ };
33972
+
33973
+ DrawTool.prototype._addLayers = function () {
33974
+ var m = this.map.engine, id = this.id, c = this.color;
33975
+ if (m.getSource(id)) { return; }
33976
+ m.addSource(id, { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
33977
+ var role = function (r) { return ['==', ['get', '_role'], r]; };
33978
+ m.addLayer({ id: id + '-fill', type: 'fill', source: id, filter: ['all', ['==', ['geometry-type'], 'Polygon'], ['!', role('sketch')]],
33979
+ paint: { 'fill-color': ['coalesce', ['get', '_fill'], c], 'fill-opacity': ['case', ['==', ['get', '_selected'], true], 0.3, 0.18] } });
33980
+ m.addLayer({ id: id + '-line', type: 'line', source: id, filter: ['all', ['!=', ['geometry-type'], 'Point'], ['!', role('sketch')]],
33981
+ layout: { 'line-join': 'round', 'line-cap': 'round' },
33982
+ paint: { 'line-color': ['coalesce', ['get', '_stroke'], c], 'line-width': ['case', ['==', ['get', '_selected'], true], 3, 2] } });
33983
+ m.addLayer({ id: id + '-sketch', type: 'line', source: id, filter: role('sketch'),
33984
+ layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': c, 'line-width': 2, 'line-dasharray': [2, 1.5] } });
33985
+ m.addLayer({ id: id + '-mid', type: 'circle', source: id, filter: role('mid'),
33986
+ paint: { 'circle-radius': 4, 'circle-color': '#ffffff', 'circle-opacity': 0.9, 'circle-stroke-width': 1.5, 'circle-stroke-color': c } });
33987
+ m.addLayer({ id: id + '-vertex', type: 'circle', source: id, filter: ['any', role('vertex'), role('point'), role('sketch-vertex')],
33988
+ paint: { 'circle-radius': ['case', ['==', ['get', '_active'], true], 7, ['==', ['get', '_role'], 'point'], 7, 5],
33989
+ 'circle-color': ['case', ['==', ['get', '_active'], true], c, '#ffffff'], 'circle-stroke-width': 2, 'circle-stroke-color': c } });
33990
+ };
33991
+
33992
+ // What is drawn: every shape, the selected one's vertices and midpoints, and the shape being drawn.
33993
+ DrawTool.prototype._render = function () {
33994
+ var m = this.map.engine;
33995
+ if (!m || this.destroyed) { return; }
33996
+ var out = [], self = this;
33997
+ this.features.forEach(function (f) {
33998
+ var sel = f.id === self.selected;
33999
+ var props = { _id: f.id, _selected: sel };
34000
+ if (f.properties && f.properties.fill) { props._fill = f.properties.fill; }
34001
+ if (f.properties && f.properties.stroke) { props._stroke = f.properties.stroke; }
34002
+ if (f.geometry.type === 'Point') { props._role = 'point'; props._active = sel; }
34003
+ out.push({ type: 'Feature', properties: props, geometry: f.geometry });
34004
+ if (!sel || f.geometry.type === 'Point') { return; }
34005
+ if (f.properties && f.properties.shape === 'circle' && f.properties.center) {
34006
+ // A circle: one handle on its edge (east) sets the radius.
34007
+ 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] } });
34008
+ return;
34009
+ }
34010
+ ringsOf(f.geometry).forEach(function (r, ri) {
34011
+ r.coords.forEach(function (c, i) {
34012
+ var active = !!self.vertex && self.vertex.ring === ri && self.vertex.index === i;
34013
+ out.push({ type: 'Feature', properties: { _role: 'vertex', _id: f.id, _ring: ri, _index: i, _active: active }, geometry: { type: 'Point', coordinates: c } });
34014
+ var next = r.coords[i + 1] || (r.closed ? r.coords[0] : null);
34015
+ if (next && !(f.properties && f.properties.shape === 'rectangle')) {
34016
+ 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] } });
34017
+ }
34018
+ });
34019
+ });
34020
+ });
34021
+ if (this.mode && this.sketch) {
34022
+ var pts = this.sketch.concat(this.hover ? [this.hover] : []);
34023
+ if (pts.length >= 2) {
34024
+ var closed = this.mode !== 'line' && pts.length >= 3;
34025
+ out.push({ type: 'Feature', properties: { _role: 'sketch' }, geometry: { type: 'LineString', coordinates: closed ? pts.concat([pts[0]]) : pts } });
34026
+ }
34027
+ this.sketch.forEach(function (c) { out.push({ type: 'Feature', properties: { _role: 'sketch-vertex' }, geometry: { type: 'Point', coordinates: c } }); });
34028
+ }
34029
+ var src = m.getSource(this.id);
34030
+ if (src) { src.setData({ type: 'FeatureCollection', features: out }); }
34031
+ };
34032
+
34033
+ DrawTool.prototype._lngLat = function (e) { return [e.lngLat.lng, e.lngLat.lat]; };
34034
+
34035
+ // A position taken over by a vertex of another shape within SNAP_PX pixels (when snapping is on).
34036
+ DrawTool.prototype._snapped = function (e, except) {
34037
+ if (!this.snap) { return this._lngLat(e); }
34038
+ var m = this.map.engine, best = null, bestD = SNAP_PX;
34039
+ this.features.forEach(function (f) {
34040
+ if (f.id === except) { return; }
34041
+ var pts = f.geometry.type === 'Point' ? [f.geometry.coordinates] : [];
34042
+ ringsOf(f.geometry).forEach(function (r) { pts = pts.concat(r.coords); });
34043
+ pts.forEach(function (c) {
34044
+ var p = m.project(c), d = Math.sqrt(Math.pow(p.x - e.point.x, 2) + Math.pow(p.y - e.point.y, 2));
34045
+ if (d <= bestD) { bestD = d; best = c; }
34046
+ });
34047
+ });
34048
+ return best ? best.slice() : this._lngLat(e);
34049
+ };
34050
+
34051
+ DrawTool.prototype._hit = function (e, layers) {
34052
+ var m = this.map.engine, self = this;
34053
+ var ids = layers.map(function (l) { return self.id + '-' + l; }).filter(function (l) { return m.getLayer(l); });
34054
+ if (!ids.length) { return null; }
34055
+ var r = 4, box = [[e.point.x - r, e.point.y - r], [e.point.x + r, e.point.y + r]];
34056
+ var hits = m.queryRenderedFeatures(box, { layers: ids }) || [];
34057
+ return hits[0] || null;
34058
+ };
34059
+
34060
+ DrawTool.prototype._pan = function (on) {
34061
+ var m = this.map.engine;
34062
+ if (!m.dragPan) { return; }
34063
+ if (on && this.panOff) { m.dragPan.enable(); this.panOff = false; }
34064
+ else if (!on && !this.panOff && (!m.dragPan.isEnabled || m.dragPan.isEnabled())) { m.dragPan.disable(); this.panOff = true; }
34065
+ };
34066
+
34067
+ DrawTool.prototype._down = function (e) {
34068
+ if (e.originalEvent && e.originalEvent.button !== undefined && e.originalEvent.button !== 0) { return; }
34069
+ var self = this;
34070
+ if (this.mode === 'rectangle' || this.mode === 'circle') {
34071
+ e.preventDefault();
34072
+ this._pan(false);
34073
+ this.drag = { kind: this.mode, start: this._snapped(e) };
34074
+ return;
34075
+ }
34076
+ if (this.mode) { return; }
34077
+ if (!this.selected) { return; }
34078
+ var f = this._find(this.selected);
34079
+ var hit = this._hit(e, ['vertex', 'mid']);
34080
+ if (hit && hit.properties._id === this.selected) {
34081
+ e.preventDefault();
34082
+ this._pan(false);
34083
+ var p = hit.properties;
34084
+ if (p._radius) {
34085
+ this._record();
34086
+ this.drag = { kind: 'radius', moved: false };
34087
+ return;
34088
+ }
34089
+ if (p._role === 'mid') {
34090
+ // A midpoint dragged becomes a new vertex.
34091
+ this._record();
34092
+ var rings = ringsOf(f.geometry), r = rings[p._ring], coords = r.coords.slice();
34093
+ coords.splice(p._index, 0, hit.geometry.coordinates.slice());
34094
+ setRing(f.geometry, r.path, coords);
34095
+ this.vertex = { ring: p._ring, index: p._index };
34096
+ } else {
34097
+ this._record();
34098
+ this.vertex = { ring: p._ring, index: p._index };
34099
+ }
34100
+ this.drag = { kind: 'vertex', moved: false };
34101
+ this._render();
34102
+ return;
34103
+ }
34104
+ var body = this._hit(e, ['fill', 'line', 'vertex']);
34105
+ if (body && body.properties._id === this.selected) {
34106
+ e.preventDefault();
34107
+ this._pan(false);
34108
+ this._record();
34109
+ this.drag = { kind: 'move', last: this._lngLat(e), moved: false };
34110
+ self.vertex = null;
34111
+ }
34112
+ };
34113
+
34114
+ DrawTool.prototype._move = function (e) {
34115
+ if (this.mode && this.sketch && !this.drag) { this.hover = this._snapped(e); this._render(); }
34116
+ var d = this.drag;
34117
+ if (!d) { return; }
34118
+ var f = this.selected && this._find(this.selected);
34119
+ if (d.kind === 'rectangle' || d.kind === 'circle') {
34120
+ d.end = this._lngLat(e);
34121
+ var shape = this._dragShape(d);
34122
+ this.sketch = shape ? openRing(shape) : [];
34123
+ this.hover = null;
34124
+ this._render();
34125
+ return;
34126
+ }
34127
+ if (!f) { return; }
34128
+ d.moved = true;
34129
+ if (d.kind === 'radius') {
34130
+ f.properties.radius = interop.geo.distance(f.properties.center, this._lngLat(e));
34131
+ f.geometry.coordinates = [circleRing(f.properties.center, f.properties.radius)];
34132
+ } else if (d.kind === 'vertex' && f.properties && f.properties.shape === 'rectangle') {
34133
+ // A rectangle stays a rectangle: the opposite corner stays, the dragged one follows the pointer.
34134
+ var cs = ringsOf(f.geometry)[0].coords, opp = cs[(this.vertex.index + 2) % 4], at = this._snapped(e, f.id);
34135
+ var rect = [[opp[0], opp[1]], [at[0], opp[1]], [at[0], at[1]], [opp[0], at[1]]];
34136
+ // Keep the dragged corner at its index.
34137
+ var shift = (this.vertex.index + 2) % 4, ordered = [];
34138
+ for (var q = 0; q < 4; q++) { ordered[(shift + q) % 4] = rect[q]; }
34139
+ f.geometry.coordinates = [closeRing(ordered)];
34140
+ } else if (d.kind === 'vertex') {
34141
+ var rings = ringsOf(f.geometry), r = rings[this.vertex.ring], coords = r.coords.slice();
34142
+ coords[this.vertex.index] = this._snapped(e, f.id);
34143
+ setRing(f.geometry, r.path, coords);
34144
+ } else if (d.kind === 'move') {
34145
+ var now = this._lngLat(e);
34146
+ if (f.properties && f.properties.shape === 'circle' && f.properties.center) {
34147
+ f.properties.center = [f.properties.center[0] + now[0] - d.last[0], f.properties.center[1] + now[1] - d.last[1]];
34148
+ }
34149
+ moveGeometry(f.geometry, now[0] - d.last[0], now[1] - d.last[1]);
34150
+ d.last = now;
34151
+ }
34152
+ this._render();
34153
+ };
34154
+
34155
+ DrawTool.prototype._dragShape = function (d) {
34156
+ if (!d.end) { return null; }
34157
+ if (d.kind === 'rectangle') {
34158
+ var a = d.start, b = d.end;
34159
+ return [[a[0], a[1]], [b[0], a[1]], [b[0], b[1]], [a[0], b[1]], [a[0], a[1]]];
34160
+ }
34161
+ var radius = interop.geo.distance(d.start, d.end);
34162
+ return radius > 0 ? circleRing(d.start, radius) : null;
34163
+ };
34164
+
34165
+ DrawTool.prototype._up = function (e) {
34166
+ var d = this.drag;
34167
+ if (!d) { return; }
34168
+ this.drag = null;
34169
+ this._pan(true);
34170
+ this._suppressClick = true;
34171
+ var self = this;
34172
+ setTimeout(function () { self._suppressClick = false; }, 0);
34173
+ if (d.kind === 'rectangle' || d.kind === 'circle') {
34174
+ d.end = this._lngLat(e);
34175
+ var ring = this._dragShape(d);
34176
+ var mode = this.mode;
34177
+ this.mode = null; this.sketch = null; this.hover = null;
34178
+ if (ring && Math.abs(ring[0][0] - ring[2][0]) + Math.abs(ring[0][1] - ring[2][1]) > 0) {
34179
+ var props = mode === 'circle' ? { shape: 'circle', center: d.start, radius: interop.geo.distance(d.start, d.end) } : { shape: 'rectangle' };
34180
+ this._add({ type: 'Feature', properties: props, geometry: { type: 'Polygon', coordinates: [ring] } });
34181
+ } else { this._render(); this._renderToolbar(); }
34182
+ return;
34183
+ }
34184
+ if (d.moved) { this._emit('edited', this._find(this.selected)); } else { this.undoStack.pop(); }
34185
+ this._render();
34186
+ };
34187
+
34188
+ DrawTool.prototype._click = function (e) {
34189
+ if (this._suppressClick) { return; }
34190
+ var self = this;
34191
+ if (this.mode === 'point') { this.mode = null; this.sketch = null; this._add({ type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: this._snapped(e) } }); return; }
34192
+ if (this.mode === 'polygon' || this.mode === 'line' || this.mode === 'hole') {
34193
+ var at = this._snapped(e);
34194
+ // A click on the first vertex closes a polygon.
34195
+ if (this.mode !== 'line' && this.sketch.length >= 3) {
34196
+ var first = this.map.engine.project(this.sketch[0]);
34197
+ if (Math.abs(first.x - e.point.x) <= SNAP_PX && Math.abs(first.y - e.point.y) <= SNAP_PX) { this._finish(); return; }
34198
+ }
34199
+ var last = this.sketch[this.sketch.length - 1];
34200
+ if (!last || last[0] !== at[0] || last[1] !== at[1]) { this.sketch.push(at); }
34201
+ this._render();
34202
+ return;
34203
+ }
34204
+ if (this.mode) { return; }
34205
+ // Not drawing: a click selects the shape under it (or a vertex of the selected one), elsewhere deselects.
34206
+ var v = this.selected && this._hit(e, ['vertex']);
34207
+ if (v && v.properties._id === this.selected && v.properties._role === 'vertex') {
34208
+ this.vertex = { ring: v.properties._ring, index: v.properties._index };
34209
+ this._render();
34210
+ return;
34211
+ }
34212
+ var hit = this._hit(e, ['vertex', 'line', 'fill']);
34213
+ self.select(hit ? hit.properties._id : null);
34214
+ };
34215
+
34216
+ DrawTool.prototype._finish = function () {
34217
+ var mode = this.mode, pts = (this.sketch || []).slice();
34218
+ this.mode = null; this.sketch = null; this.hover = null;
34219
+ if (mode === 'line' && pts.length >= 2) { this._add({ type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: pts } }); return; }
34220
+ if (mode === 'polygon' && pts.length >= 3) { this._add({ type: 'Feature', properties: {}, geometry: { type: 'Polygon', coordinates: [closeRing(pts)] } }); return; }
34221
+ if (mode === 'hole' && pts.length >= 3) {
34222
+ var f = this._find(this.selected);
34223
+ if (f && f.geometry.type === 'Polygon') {
34224
+ this._record();
34225
+ f.geometry.coordinates.push(closeRing(pts));
34226
+ this._render(); this._renderToolbar();
34227
+ this._emit('edited', f);
34228
+ return;
34229
+ }
34230
+ }
34231
+ this._render();
34232
+ this._renderToolbar();
34233
+ };
34234
+
34235
+ DrawTool.prototype._add = function (feature) {
34236
+ this._record();
34237
+ var f = this._own(feature);
34238
+ this.features.push(f);
34239
+ this.selected = f.geometry.type === 'Point' ? null : f.id;
34240
+ this.vertex = null;
34241
+ this._render();
34242
+ this._renderToolbar();
34243
+ this._emit('created', f);
34244
+ };
34245
+
34246
+ DrawTool.prototype._toolbarControl = function () {
34247
+ var self = this;
34248
+ return {
34249
+ onAdd: function (m) {
34250
+ var doc = m.getContainer().ownerDocument;
34251
+ var el = doc.createElement('div');
34252
+ el.className = 'maplibregl-ctrl maplibregl-ctrl-group smap-draw';
34253
+ self.toolbarEl = el;
34254
+ self._renderToolbar();
34255
+ return el;
34256
+ },
34257
+ onRemove: function () { if (self.toolbarEl && self.toolbarEl.parentNode) { self.toolbarEl.parentNode.removeChild(self.toolbarEl); } self.toolbarEl = null; },
34258
+ };
34259
+ };
34260
+
34261
+ var DRAW_ICONS = {
34262
+ polygon: '<path d="M5 7l7-4 7 5-2 10H7z"/>', rectangle: '<rect x="4" y="6" width="16" height="12"/>',
34263
+ 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"/>',
34264
+ 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"/>',
34265
+ 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"/>',
34266
+ };
34267
+
34268
+ DrawTool.prototype._renderToolbar = function () {
34269
+ var el = this.toolbarEl;
34270
+ if (!el) { return; }
34271
+ var self = this, doc = el.ownerDocument;
34272
+ while (el.firstChild) { el.removeChild(el.firstChild); }
34273
+ var selectedPolygon = this.selected && this._find(this.selected) && this._find(this.selected).geometry.type === 'Polygon' && !(this._find(this.selected).properties.shape);
34274
+ var buttons = this.modes.slice();
34275
+ if (selectedPolygon) { buttons.push('hole'); }
34276
+ buttons = buttons.concat(['delete', 'undo', 'redo']);
34277
+ buttons.forEach(function (k) {
34278
+ var b = doc.createElement('button');
34279
+ b.type = 'button';
34280
+ b.className = 'smap-draw-' + k;
34281
+ b.title = self.text[k];
34282
+ b.setAttribute('aria-label', self.text[k]);
34283
+ 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>';
34284
+ if (DRAW_MODES.indexOf(k) >= 0 || k === 'hole') {
34285
+ b.setAttribute('aria-pressed', self.mode === k ? 'true' : 'false');
34286
+ b.addEventListener('click', function () { if (self.mode === k) { self.cancel(); } else { self.start(k); } });
34287
+ } else if (k === 'delete') {
34288
+ b.disabled = !self.selected;
34289
+ b.addEventListener('click', function () { self.delete(); });
34290
+ } else {
34291
+ b.disabled = k === 'undo' ? !self.undoStack.length : !self.redoStack.length;
34292
+ b.addEventListener('click', function () { self[k](); });
34293
+ }
34294
+ el.appendChild(b);
34295
+ });
34296
+ };
34297
+
34298
+ /**
34299
+ * Selects features by drawing on the map: 'box' (press and drag) or 'lasso' (press and draw a free shape). Resolves
34300
+ * with { features: { layerId: [features] }, geometry } once the shape is drawn, or null when Escape cancels it.
34301
+ * A point (or custom marker) counts when it is inside; a line or area when any of its vertices is.
34302
+ * @param {Object} [o] mode ('box' default, 'lasso'), layers (default every point, custom marker, line and area layer)
34303
+ */
34304
+ SMap.prototype.selectArea = function (o) {
34305
+ o = o || {};
34306
+ var self = this;
34307
+ var mode = o.mode === 'lasso' ? 'lasso' : 'box';
34308
+ return this.ready.then(function () {
34309
+ return new Promise(function (resolve) {
34310
+ var m = self.engine;
34311
+ var overlay = self._markerOverlay(), doc = overlay.ownerDocument;
34312
+ var svgNs = 'http://www.w3.org/2000/svg';
34313
+ var svg = doc.createElementNS(svgNs, 'svg');
34314
+ svg.setAttribute('class', 'smap-select');
34315
+ var shape = doc.createElementNS(svgNs, mode === 'box' ? 'rect' : 'polyline');
34316
+ svg.appendChild(shape);
34317
+ var points = null, panWasOn = !!(m.dragPan && (!m.dragPan.isEnabled || m.dragPan.isEnabled()));
34318
+ if (panWasOn) { m.dragPan.disable(); }
34319
+ if (m.boxZoom && m.boxZoom.disable) { m.boxZoom.disable(); }
34320
+ m.getCanvas().style.cursor = 'crosshair';
34321
+ var finish = function (result) {
34322
+ m.off('mousedown', down); m.off('mousemove', move); m.off('mouseup', up);
34323
+ m.off('touchstart', down); m.off('touchmove', move); m.off('touchend', up);
34324
+ m.getContainer().removeEventListener('keydown', key);
34325
+ if (svg.parentNode) { svg.parentNode.removeChild(svg); }
34326
+ if (panWasOn) { m.dragPan.enable(); }
34327
+ if (m.boxZoom && m.boxZoom.enable) { m.boxZoom.enable(); }
34328
+ m.getCanvas().style.cursor = '';
34329
+ resolve(result);
34330
+ };
34331
+ var draw = function () {
34332
+ if (mode === 'box') {
34333
+ var a = points[0], b = points[points.length - 1];
34334
+ shape.setAttribute('x', Math.min(a.x, b.x)); shape.setAttribute('y', Math.min(a.y, b.y));
34335
+ shape.setAttribute('width', Math.abs(a.x - b.x)); shape.setAttribute('height', Math.abs(a.y - b.y));
34336
+ } else {
34337
+ shape.setAttribute('points', points.map(function (p) { return p.x + ',' + p.y; }).join(' '));
34338
+ }
34339
+ };
34340
+ var down = function (e) { e.preventDefault(); points = [e.point]; overlay.appendChild(svg); draw(); };
34341
+ var move = function (e) { if (!points) { return; } if (mode === 'box') { points = [points[0], e.point]; } else { points.push(e.point); } draw(); };
34342
+ var up = function (e) {
34343
+ if (!points) { return; }
34344
+ if (mode === 'box') { points = [points[0], e.point]; }
34345
+ var ring;
34346
+ if (mode === 'box') {
34347
+ var a = points[0], b = points[1];
34348
+ ring = [[a.x, a.y], [b.x, a.y], [b.x, b.y], [a.x, b.y]];
34349
+ } else {
34350
+ ring = points.map(function (p) { return [p.x, p.y]; });
34351
+ }
34352
+ var geoRing = ring.map(function (p) { var ll = m.unproject(p); return [ll.lng, ll.lat]; });
34353
+ var geometry = { type: 'Polygon', coordinates: [closeRing(geoRing)] };
34354
+ finish({ features: self._featuresIn(ring, o.layers), geometry: geometry });
34355
+ };
34356
+ var key = function (ev) { if (ev.key === 'Escape') { finish(null); } };
34357
+ m.on('mousedown', down); m.on('mousemove', move); m.on('mouseup', up);
34358
+ m.on('touchstart', down); m.on('touchmove', move); m.on('touchend', up);
34359
+ m.getContainer().addEventListener('keydown', key);
34360
+ });
34361
+ });
34362
+ };
34363
+
34364
+ // The features of the map's layers with a vertex inside a ring of screen points (projected now).
34365
+ SMap.prototype._featuresIn = function (ring, layers) {
34366
+ var self = this, m = this.engine, out = {};
34367
+ var xs = ring.map(function (p) { return p[0]; }), ys = ring.map(function (p) { return p[1]; });
34368
+ 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);
34369
+ var inside = function (c) {
34370
+ var p = m.project(c);
34371
+ if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) { return false; }
34372
+ return interop.geo.pointInPolygon([p.x, p.y], ring);
34373
+ };
34374
+ (layers || this._order).forEach(function (id) {
34375
+ var def = self._layers[id];
34376
+ if (!def || def.hidden || !def.data || ['points', 'custom', 'lines', 'polygons', 'heatmap'].indexOf(def.kind) < 0) { return; }
34377
+ var hits = def.data.features.filter(function (f) {
34378
+ var found = false;
34379
+ (function walk(c) { if (found || !c) { return; } if (typeof c[0] === 'number') { found = inside(c); } else { c.forEach(walk); } })(f.geometry && f.geometry.coordinates);
34380
+ return found;
34381
+ });
34382
+ if (hits.length) { out[id] = hits; }
34383
+ });
34384
+ return out;
34385
+ };
34386
+
34387
+ // ------------------------------------------------------------------ controls (G8): own controls, base imagery, layer switcher
34388
+
34389
+ /**
34390
+ * Puts an element or an engine control in a corner of the map without making it a tool (it is never hidden by the
34391
+ * tools control). An element returns where the page had it when removed.
34392
+ * @param {Element|Object} target
34393
+ * @param {string} [position] 'top-left', 'top-right' (default), 'bottom-left', 'bottom-right'
34394
+ */
34395
+ SMap.prototype.addControl = function (target, position) {
34396
+ position = position || 'top-right';
34397
+ if (TOOL_CORNERS.indexOf(position) < 0) { throw new Error('sMap: unknown control position "' + position + '"'); }
34398
+ if (!isElement(target) && !(target && typeof target.onAdd === 'function')) { throw new Error('sMap: a control is an element or an engine control'); }
34399
+ this._controls = this._controls || [];
34400
+ var holder = { position: position, el: null };
34401
+ var control = isElement(target) ? elementControl(holder, target) : target;
34402
+ this._controls.push({ target: target, control: control, position: position });
34403
+ var self = this;
34404
+ if (this.engine) { this.engine.addControl(control, position); } else { this.ready.then(function () { self.engine.addControl(control, position); }); }
34405
+ return this;
34406
+ };
34407
+
34408
+ SMap.prototype.removeControl = function (target) {
34409
+ var list = this._controls || [];
34410
+ for (var i = 0; i < list.length; i++) {
34411
+ if (list[i].target !== target) { continue; }
34412
+ if (this.engine) { this.engine.removeControl(list[i].control); }
34413
+ list.splice(i, 1);
34414
+ break;
34415
+ }
34416
+ return this;
34417
+ };
34418
+
34419
+ var BASE_RASTER = 'smap-base-imagery';
34420
+
34421
+ /**
34422
+ * Imagery (satellite, aerial, a scanned map, …) in place of the street map: raster tiles drawn over the basemap's
34423
+ * land, water and roads, under its labels — which stay with `labels: true` (a hybrid map) and are hidden otherwise.
34424
+ * Your layers stay on top, in their slots. null goes back to the street map.
34425
+ * @param {Object|null} imagery { tiles: [url with {z}/{x}/{y}], tileSize (256), minzoom, maxzoom, attribution,
34426
+ * scheme ('xyz' | 'tms'), labels (default false) }
34427
+ */
34428
+ SMap.prototype.setBaseImagery = function (imagery) {
34429
+ if (imagery && (!Array.isArray(imagery.tiles) || !imagery.tiles.length)) { throw new Error('sMap: base imagery needs tiles'); }
34430
+ // Google's and Bing's tiles only through their own terms: Google's with setGoogleBase (M31, M72).
34431
+ if (imagery) { interop.refuseForbidden(imagery.tiles); }
34432
+ return this._setBaseImagery(imagery);
34433
+ };
34434
+
34435
+ SMap.prototype._setBaseImagery = function (imagery) {
34436
+ this._baseImagery = imagery ? Object.assign({}, imagery) : null;
34437
+ if (this.engine && this._styleReady) { this._applyBaseImagery(); }
34438
+ return this;
34439
+ };
34440
+
34441
+ // Whether a style (a scheme, 'custom:<id>', a URL or an object) is the one shown now.
34442
+ SMap.prototype._isStyle = function (style) {
34443
+ if (typeof style === 'string' && FLAVORS.indexOf(style) >= 0) { return !this._customStyle && !this._serverStyle && this._flavor === style; }
34444
+ if (typeof style === 'string' && style.indexOf(CUSTOM_PREFIX) === 0) { return this._serverStyle === style; }
34445
+ return this._customStyle === style;
34446
+ };
34447
+
34448
+ SMap.prototype.baseImagery = function () { return this._baseImagery ? Object.assign({}, this._baseImagery) : null; };
34449
+
34450
+ SMap.prototype._applyBaseImagery = function () {
34451
+ var m = this.engine, b = this._baseImagery;
34452
+ if (m.getLayer(BASE_RASTER)) { m.removeLayer(BASE_RASTER); }
34453
+ if (m.getSource(BASE_RASTER)) { m.removeSource(BASE_RASTER); }
34454
+ var anchors = this.slots();
34455
+ var labelsAt = anchors.labels;
34456
+ // The basemap's labels: every basemap layer from where the labels slot begins.
34457
+ var ids = this._basemapIds.filter(function (x) { return m.getLayer(x); });
34458
+ var from = labelsAt ? ids.indexOf(labelsAt) : -1;
34459
+ var labelIds = from >= 0 ? ids.slice(from) : [];
34460
+ var hidden = this._imageryHid || [];
34461
+ hidden.forEach(function (x) { if (m.getLayer(x)) { m.setLayoutProperty(x, 'visibility', 'visible'); } });
34462
+ this._imageryHid = [];
34463
+ if (!b) { return; }
34464
+ var source = { type: 'raster', tiles: b.tiles.slice(), tileSize: b.tileSize || 256 };
34465
+ if (b.minzoom !== undefined) { source.minzoom = b.minzoom; }
34466
+ if (b.maxzoom !== undefined) { source.maxzoom = b.maxzoom; }
34467
+ if (b.attribution) { source.attribution = b.attribution; }
34468
+ if (b.scheme) { source.scheme = b.scheme; }
34469
+ m.addSource(BASE_RASTER, source);
34470
+ m.addLayer({ id: BASE_RASTER, type: 'raster', source: BASE_RASTER }, labelsAt || undefined);
34471
+ if (!b.labels) {
34472
+ var self = this;
34473
+ labelIds.forEach(function (x) {
34474
+ if (m.getLayoutProperty && m.getLayoutProperty(x, 'visibility') === 'none') { return; }
34475
+ m.setLayoutProperty(x, 'visibility', 'none');
34476
+ self._imageryHid.push(x);
34477
+ });
34478
+ }
34479
+ };
34480
+
34481
+ // ---- 3D models (G12): glTF models at points, drawn with three.js (handed over once with sMap.useThree)
34482
+
34483
+
34484
+ /**
34485
+ * glTF models at points (vehicles, buildings, equipment), drawn in the map's 3D view and turned to each feature's
34486
+ * heading. Needs three.js: call sMap.useThree(THREE, GLTFLoader) once first.
34487
+ * @param {Object} o model (a .glb / .gltf URL), size (metres per model unit, default 1; or per feature),
34488
+ * heading (degrees clockwise from north, default 0; or per feature, e.g. { property: 'bearing' }),
34489
+ * altitude (metres above the ground, default 0), rotate ([x, y, z] degrees turning the model upright and to
34490
+ * face north before the heading; default [90, 0, 180], right for a model whose up is +Y and front +Z),
34491
+ * idProperty, minzoom, slot, before, zIndex
34492
+ */
34493
+ SMap.prototype.addModels = function (id, data, o) {
34494
+ if (!interop.getThree()) { throw new Error('sMap: call sMap.useThree(THREE, GLTFLoader) before addModels'); }
34495
+ if (!o || !o.model) { throw new Error('sMap: addModels needs a model URL'); }
34496
+ this._define(id, { kind: 'models', data: toFeatureCollection(data), o: o });
34497
+ return this;
34498
+ };
34499
+
34500
+ SMap.prototype._modelLayer = function (id, def) {
34501
+ var self = this, three = interop.getThree(), T = three.THREE, o = def.o;
34502
+ var deg = Math.PI / 180;
34503
+ var base = o.rotate || [90, 0, 180];
34504
+ var baseMatrix = new T.Matrix4().makeRotationFromEuler(new T.Euler(base[0] * deg, base[1] * deg, base[2] * deg, 'ZYX'));
34505
+ var layer = {
34506
+ id: 'smap-' + id + '-models', type: 'custom', renderingMode: '3d',
34507
+ onAdd: function (map, gl) {
34508
+ this.map = map;
34509
+ this.scene = new T.Scene();
34510
+ this.camera = new T.Camera();
34511
+ this.renderer = new T.WebGLRenderer({ canvas: map.getCanvas(), context: gl, antialias: true });
34512
+ this.renderer.autoClear = false;
34513
+ this.scene.add(new T.AmbientLight(0xffffff, 0.8));
34514
+ var sun = new T.DirectionalLight(0xffffff, 0.6);
34515
+ sun.position.set(0.3, -0.5, 1);
34516
+ this.scene.add(sun);
34517
+ this.objects = {};
34518
+ var me = this;
34519
+ new three.GLTFLoader().load(o.model, function (gltf) {
34520
+ me.prototypeObject = gltf.scene;
34521
+ map.triggerRepaint();
34522
+ }, undefined, function (error) { self._reportError(error); });
34523
+ },
34524
+ render: function (gl, args) {
34525
+ if (!this.prototypeObject) { return; }
34526
+ var matrix = args && args.defaultProjectionData ? args.defaultProjectionData.mainMatrix : (args && args.modelViewProjectionMatrix) || args;
34527
+ var seen = {}, me = this, cur = self._layers[id];
34528
+ if (!cur) { return; }
34529
+ cur.data.features.forEach(function (f, i) {
34530
+ var at = pointOf(f);
34531
+ if (!at) { return; }
34532
+ var key = featureKey(cur, f, i);
34533
+ seen[key] = true;
34534
+ var obj = me.objects[key];
34535
+ if (!obj) { obj = me.objects[key] = me.prototypeObject.clone(true); obj.matrixAutoUpdate = false; me.scene.add(obj); }
34536
+ var props = f.properties || {};
34537
+ var mc = engine.MercatorCoordinate.fromLngLat(at, +plainValue(o.altitude, props, f, 0) || 0);
34538
+ var k = mc.meterInMercatorCoordinateUnits() * (+plainValue(o.size, props, f, 1) || 1);
34539
+ var heading = (+plainValue(o.heading, props, f, 0) || 0) * deg;
34540
+ obj.matrix.copy(new T.Matrix4().makeTranslation(mc.x, mc.y, mc.z)
34541
+ .multiply(new T.Matrix4().makeScale(k, -k, k))
34542
+ .multiply(new T.Matrix4().makeRotationZ(heading))
34543
+ .multiply(baseMatrix));
34544
+ obj.matrixWorldNeedsUpdate = true;
34545
+ });
34546
+ Object.keys(this.objects).forEach(function (key) { if (!seen[key]) { me.scene.remove(me.objects[key]); delete me.objects[key]; } });
34547
+ this.camera.projectionMatrix = new T.Matrix4().fromArray(matrix);
34548
+ this.renderer.resetState();
34549
+ this.renderer.render(this.scene, this.camera);
34550
+ },
34551
+ onRemove: function () { if (this.renderer && this.renderer.dispose) { this.renderer.dispose(); } },
34552
+ };
34553
+ return layer;
34554
+ };
34555
+
34556
+ var GOOGLE_LOGO = 'https://maps.gstatic.com/mapfiles/api-3/images/google_white5.png';
34557
+
34558
+ /**
34559
+ * Google's base maps from the Map Tiles API (G10), read straight from Google with a session your server made with
34560
+ * your own Google key; shown in place of the street map like setBaseImagery. As Google's terms ask, the Google logo
34561
+ * is shown and the copyright of the area in view is asked of Google after each move. null takes it off.
34562
+ * @param {Object|null} g { session, key } (or `tiles`: the tile URL template, and `viewport`: the viewport address up
34563
+ * to its query, both with session and key), type ('roadmap' | 'satellite', for the attribution only),
34564
+ * maxzoom (22), labels (keep the street map's labels over Google's imagery; default false),
34565
+ * onFail() (called when Google's first tiles fail and none loads: show your own map instead)
34566
+ */
34567
+ SMap.prototype.setGoogleBase = function (g) {
34568
+ var self = this;
34569
+ var run = function () {
34570
+ var m = self.engine;
34571
+ if (self._google) {
34572
+ if (self._google.control) { m.removeControl(self._google.control); }
34573
+ self._google.off.forEach(function (x) { m.off(x[0], x[1]); });
34574
+ self._google = null;
34575
+ }
34576
+ if (!g) { self._setBaseImagery(null); return; }
34577
+ if (!g.tiles && !(g.session && g.key)) { throw new Error('sMap: a Google layer needs session and key, or tiles'); }
34578
+ var q = g.session ? 'session=' + encodeURIComponent(g.session) + '&key=' + encodeURIComponent(g.key) : '';
34579
+ var tiles = g.tiles || 'https://tile.googleapis.com/v1/2dtiles/{z}/{x}/{y}?' + q;
34580
+ var viewport = g.viewport || (q ? 'https://tile.googleapis.com/tile/v1/viewport?' + q : null);
34581
+ var state = { off: [], control: null, text: null, errors: 0, loaded: 0, failed: false };
34582
+ self._google = state;
34583
+ self._setBaseImagery({ tiles: [tiles], tileSize: 256, maxzoom: g.maxzoom || 22, labels: !!g.labels });
34584
+ // The logo and the copyright of the area in view, bottom left.
34585
+ var doc = m.getContainer().ownerDocument;
34586
+ var box = doc.createElement('div');
34587
+ box.className = 'smap-google';
34588
+ var logo = doc.createElement('img');
34589
+ logo.src = GOOGLE_LOGO;
34590
+ logo.alt = 'Google';
34591
+ var text = doc.createElement('span');
34592
+ text.className = 'smap-google-copyright';
34593
+ text.textContent = 'Google';
34594
+ box.appendChild(logo);
34595
+ box.appendChild(text);
34596
+ state.text = text;
34597
+ state.control = { onAdd: function () { return box; }, onRemove: function () { if (box.parentNode) { box.parentNode.removeChild(box); } } };
34598
+ m.addControl(state.control, 'bottom-left');
34599
+ var seq = 0;
34600
+ var copyright = function () {
34601
+ if (!viewport || self._google !== state || typeof fetch !== 'function') { return; }
34602
+ var b = m.getBounds(), mine = ++seq;
34603
+ var url = viewport + (viewport.indexOf('?') >= 0 ? '&' : '?') + 'zoom=' + Math.round(m.getZoom() + 1) +
34604
+ '&north=' + b.getNorth() + '&south=' + b.getSouth() + '&east=' + b.getEast() + '&west=' + b.getWest();
34605
+ fetch(url).then(function (r) { return r.ok ? r.json() : null; }).then(function (v) {
34606
+ if (mine === seq && self._google === state) { text.textContent = (v && v.copyright) || 'Google'; }
34607
+ }, function () {});
34608
+ };
34609
+ var watch = function (e) {
34610
+ if (!e || e.sourceId !== BASE_RASTER || state.failed) { return; }
34611
+ if (e.type === 'error') { state.errors += 1; } else if (e.tile) { state.loaded += 1; }
34612
+ if (state.errors >= 4 && state.loaded === 0) {
34613
+ state.failed = true;
34614
+ if (g.onFail) { g.onFail(); }
34615
+ }
34616
+ };
34617
+ m.on('moveend', copyright); state.off.push(['moveend', copyright]);
34618
+ m.on('error', watch); state.off.push(['error', watch]);
34619
+ m.on('sourcedata', watch); state.off.push(['sourcedata', watch]);
34620
+ copyright();
34621
+ };
34622
+ if (this.engine) { run(); } else { this.ready.then(run); }
34623
+ return this;
34624
+ };
34625
+
34626
+ var LAYERS_ICON = '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false" fill="none" ' +
34627
+ 'stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="M12 3 2 8l10 5 10-5-10-5z"/>' +
34628
+ '<path d="m2 13 10 5 10-5"/></svg>';
34629
+
34630
+ /**
34631
+ * The layer switcher: one button opening a list of base maps (one at a time) and overlays (each on or off).
34632
+ * base: [{ id, label, style? (a scheme, 'custom:…' or a style URL), imagery? (as setBaseImagery) }]
34633
+ * overlays: [{ id, label, layers: [layer ids], visible? }]
34634
+ * value (the base shown at first), title (the button's label), onChange({ base, overlays: [ids shown] })
34635
+ */
34636
+ function layersControl(map, opts) {
34637
+ var base = opts.base || [];
34638
+ var overlays = opts.overlays || [];
34639
+ var current = opts.value || (base[0] && base[0].id) || null;
34640
+ var shown = {};
34641
+ overlays.forEach(function (ov) { shown[ov.id] = ov.visible !== false; });
34642
+ var el = null, button = null, panel = null;
34643
+ function report() {
34644
+ if (opts.onChange) { opts.onChange({ base: current, overlays: overlays.filter(function (ov) { return shown[ov.id]; }).map(function (ov) { return ov.id; }) }); }
34645
+ }
34646
+ function applyBase(entry) {
34647
+ if (!entry) { return; }
34648
+ // The scheme already shown is not loaded again (no flicker when only the imagery changes).
34649
+ if (entry.style !== undefined && entry.style !== null && !map._isStyle(entry.style)) { map.setStyle(entry.style); }
34650
+ map.setBaseImagery(entry.imagery || null);
34651
+ }
34652
+ function applyOverlay(ov) { (ov.layers || []).forEach(function (id) { map.setVisible(id, !!shown[ov.id]); }); }
34653
+ function render() {
34654
+ var doc = el.ownerDocument;
34655
+ while (panel.firstChild) { panel.removeChild(panel.firstChild); }
34656
+ var name = 'smap-base-' + Math.random().toString(36).slice(2);
34657
+ base.forEach(function (entry) {
34658
+ var row = doc.createElement('label');
34659
+ row.className = 'smap-layers-row';
34660
+ var input = doc.createElement('input');
34661
+ input.type = 'radio'; input.name = name; input.value = entry.id; input.checked = entry.id === current;
34662
+ input.addEventListener('change', function () { current = entry.id; applyBase(entry); report(); });
34663
+ row.appendChild(input);
34664
+ row.appendChild(doc.createTextNode(' ' + (entry.label || entry.id)));
34665
+ panel.appendChild(row);
34666
+ });
34667
+ if (base.length && overlays.length) { var hr = doc.createElement('div'); hr.className = 'smap-layers-sep'; panel.appendChild(hr); }
34668
+ overlays.forEach(function (ov) {
34669
+ var row = doc.createElement('label');
34670
+ row.className = 'smap-layers-row';
34671
+ var input = doc.createElement('input');
34672
+ input.type = 'checkbox'; input.value = ov.id; input.checked = !!shown[ov.id];
34673
+ input.addEventListener('change', function () { shown[ov.id] = input.checked; applyOverlay(ov); report(); });
34674
+ row.appendChild(input);
34675
+ row.appendChild(doc.createTextNode(' ' + (ov.label || ov.id)));
34676
+ panel.appendChild(row);
34677
+ });
34678
+ }
34679
+ function open(on) { panel.hidden = !on; button.setAttribute('aria-expanded', on ? 'true' : 'false'); }
34680
+ function close() { if (panel && !panel.hidden) { open(false); } }
34681
+ return {
34682
+ onAdd: function (m) {
34683
+ var doc = m.getContainer().ownerDocument;
34684
+ el = doc.createElement('div');
34685
+ el.className = 'maplibregl-ctrl maplibregl-ctrl-group smap-layers';
34686
+ button = doc.createElement('button');
34687
+ button.type = 'button';
34688
+ button.className = 'smap-layers-button';
34689
+ button.innerHTML = LAYERS_ICON;
34690
+ button.title = opts.title || 'Layers';
34691
+ button.setAttribute('aria-label', opts.title || 'Layers');
34692
+ button.setAttribute('aria-haspopup', 'true');
34693
+ button.addEventListener('click', function () { open(panel.hidden); });
34694
+ panel = doc.createElement('div');
34695
+ panel.className = 'smap-layers-panel';
34696
+ el.appendChild(button);
34697
+ el.appendChild(panel);
34698
+ render();
34699
+ open(false);
34700
+ m.on('click', close);
34701
+ // The starting state: the chosen base and every overlay as given.
34702
+ var entry = base.filter(function (b) { return b.id === current; })[0];
34703
+ if (entry && entry.imagery) { map.setBaseImagery(entry.imagery); }
34704
+ overlays.forEach(function (ov) { if (!shown[ov.id]) { applyOverlay(ov); } });
34705
+ return el;
34706
+ },
34707
+ onRemove: function (m) { m.off('click', close); if (el && el.parentNode) { el.parentNode.removeChild(el); } el = null; },
34708
+ };
34709
+ }
34710
+
31459
34711
  // ------------------------------------------------------------------ plan editor (OPEN-ROUTE-EDIT E10)
31460
34712
 
31461
34713
  /**
@@ -31975,7 +35227,9 @@ function smapCreateSdk(engine, interop, plan) {
31975
35227
  var out = { type: 'smap.layers', version: 1 };
31976
35228
  if (!this._customStyle) { out.basemap = { style: this._serverStyle || this._flavor, language: this._language, overrides: clone(this._basemapOverrides) }; }
31977
35229
  if (this._sourceOrder.length) { out.sources = this.sources(); }
31978
- out.layers = this._order.map(function (id) {
35230
+ // The shared description has points, lines, areas and style layers; custom markers, heat maps and 3D models are
35231
+ // not part of it and are left out.
35232
+ out.layers = this._order.filter(function (id) { return ['points', 'lines', 'polygons', 'style'].indexOf(self._layers[id].kind) >= 0; }).map(function (id) {
31979
35233
  var def = self._layers[id];
31980
35234
  var e = { id: id, kind: def.kind };
31981
35235
  if (def.slot) { e.slot = def.slot; }
@@ -32034,7 +35288,8 @@ function smapCreateSdk(engine, interop, plan) {
32034
35288
  def.slot = o.slot || null;
32035
35289
  def.before = o.before || null;
32036
35290
  def.popupFn = typeof o.popup === 'string' ? popupTemplate(o.popup) : null;
32037
- def.zIndex = def.o.zIndex || 0;
35291
+ // A layer's place among layers; a per-feature zIndex (custom markers) orders within the layer only.
35292
+ def.zIndex = typeof def.o.zIndex === 'number' ? def.o.zIndex : 0;
32038
35293
  def.engineLayers = [];
32039
35294
  if (this._layerToolHides(id)) { def.hidden = true; }
32040
35295
  this._layers[id] = def;
@@ -32093,6 +35348,10 @@ function smapCreateSdk(engine, interop, plan) {
32093
35348
 
32094
35349
  SMap.prototype._drop = function (id, def) {
32095
35350
  var m = this.engine;
35351
+ if (def.kind === 'custom') { this._dropCustom(id, def); return; }
35352
+ if (this._ghost && this._ghost.id === id && m) { this._dropGhost(id); }
35353
+ if (this._tooltipFor && this._tooltipFor.id === id) { this._hideTooltip(); }
35354
+ if (def.clusterSet) { this._dropCustom(id + ':clusters', def.clusterSet); def.clusterSet = null; }
32096
35355
  if (!m) { return; }
32097
35356
  def.engineLayers.forEach(function (l) { if (m.getLayer(l)) { m.removeLayer(l); } });
32098
35357
  if (def.kind !== 'style' && m.getSource('smap-' + id)) { m.removeSource('smap-' + id); }
@@ -32112,13 +35371,24 @@ function smapCreateSdk(engine, interop, plan) {
32112
35371
  Object.keys(this._basemapEdits).forEach(function (id) { self._applyEdits(id, self._basemapEdits[id]); });
32113
35372
  this._sourceOrder.forEach(function (id) { if (!m.getSource(id)) { self._engineSource(id); } });
32114
35373
  this._applyBuildings();
35374
+ this._imageryHid = [];
35375
+ if (this._baseImagery) { this._applyBaseImagery(); }
35376
+ if (this._terrain) { this._applyTerrain(); }
32115
35377
  this._order.forEach(function (id) { self._add(id, self._layers[id]); });
32116
35378
  };
32117
35379
 
32118
35380
  SMap.prototype._add = function (id, def) {
32119
35381
  var m = this.engine;
32120
35382
  var o = def.o;
35383
+ if (def.kind === 'custom') { this._addCustom(id, def); return; }
32121
35384
  var before = this._beforeId(id);
35385
+ if (def.kind === 'models') {
35386
+ var modelLayer = this._modelLayer(id, def);
35387
+ if (o.minzoom !== undefined) { modelLayer.minzoom = o.minzoom; }
35388
+ m.addLayer(modelLayer, before);
35389
+ def.engineLayers.push(modelLayer.id);
35390
+ return;
35391
+ }
32122
35392
  if (def.kind === 'style') {
32123
35393
  // Passed to the engine as it is (a copy, so the engine never holds the object you gave).
32124
35394
  var styleLayer = clone(def.layer);
@@ -32134,6 +35404,8 @@ function smapCreateSdk(engine, interop, plan) {
32134
35404
  if (o.maxzoom !== undefined) { range.maxzoom = o.maxzoom; }
32135
35405
  var visibility = def.hidden ? 'none' : 'visible';
32136
35406
  var source = { type: 'geojson', data: def.data, promoteId: o.idProperty };
35407
+ // A dragged point is found again by its place in the data (unless idProperty names it).
35408
+ if (def.kind === 'points' && o.draggable && !o.idProperty) { source.generateId = true; }
32137
35409
  if (def.kind === 'points' && o.cluster) {
32138
35410
  source.cluster = true;
32139
35411
  source.clusterRadius = (o.cluster && o.cluster.radius) || 50;
@@ -32156,7 +35428,8 @@ function smapCreateSdk(engine, interop, plan) {
32156
35428
  var color = o.color || '#4f46e5';
32157
35429
  var main;
32158
35430
  if (def.kind === 'points') {
32159
- if (o.cluster) {
35431
+ var htmlClusters = o.cluster && (o.cluster.html || o.cluster.ring);
35432
+ if (o.cluster && !htmlClusters) {
32160
35433
  add({
32161
35434
  id: src + '-clusters', type: 'circle', filter: ['has', 'point_count'],
32162
35435
  paint: {
@@ -32177,6 +35450,7 @@ function smapCreateSdk(engine, interop, plan) {
32177
35450
  });
32178
35451
  });
32179
35452
  }
35453
+ if (htmlClusters) { this._htmlClusters(id, def, src); }
32180
35454
  var only = o.cluster ? ['!', ['has', 'point_count']] : undefined;
32181
35455
  var both = function (extra) { return only ? ['all', only, extra] : extra; };
32182
35456
  if (o.marker) {
@@ -32194,13 +35468,31 @@ function smapCreateSdk(engine, interop, plan) {
32194
35468
  // Opacity of the pin, its number and its badge together (the route map draws settled stops at .72).
32195
35469
  var markerPaint = mk.opacity !== undefined ? { 'icon-opacity': valueExpr(mk.opacity, 1) } : undefined;
32196
35470
  main = src + '-points';
32197
- // From bakeZoom on (default 13) a pin's number is drawn into its own image, so an upper pin covers a lower
32198
- // one completely. Further out, where thousands of pins can be in view, the numbers stay one layer above the
32199
- // pins (an image per colour pair instead of per pin and number). bakeZoom: false keeps them separate.
35471
+ // A pin's number and count badge are drawn into its own image (as one element of the route map's pins), so an
35472
+ // upper pin covers a lower one completely, number and badge included. bakeZoom (a zoom) keeps them their own
35473
+ // layers above the pins further out than it — fewer images where thousands of pins are in view, but numbers
35474
+ // over every pin there — and bakeZoom: false keeps them apart at every zoom. textMinzoom / badgeMinzoom start
35475
+ // them at their zoom either way.
32200
35476
  var plainPin = drawnImageId('pin', [shape, str(mk.fill, '#ffffff'), str(mk.border, color), String(bw), String(size)]);
32201
- var bakeZoom = mk.text !== undefined && mk.bakeZoom !== false ? (mk.bakeZoom !== undefined ? mk.bakeZoom : 13) : null;
32202
- var pinImage = bakeZoom === null ? plainPin : ['step', ['zoom'], plainPin, bakeZoom,
32203
- drawnImageId('pinnum', [shape, str(mk.fill, '#ffffff'), str(mk.border, color), String(bw), String(size), str(mk.textColor, '#ffffff'), str(mk.text, '')])];
35477
+ var hasText = mk.text !== undefined, hasBadge = mk.badge !== undefined;
35478
+ var bakeZoom = (hasText || hasBadge) && mk.bakeZoom !== false ? (typeof mk.bakeZoom === 'number' ? mk.bakeZoom : 0) : null;
35479
+ var badgeNumber = ['to-number', valueExpr(mk.badge, 0), 0];
35480
+ var textMin = typeof mk.textMinzoom === 'number' ? mk.textMinzoom : 0;
35481
+ var badgeMin = typeof mk.badgeMinzoom === 'number' ? mk.badgeMinzoom : 0;
35482
+ var imageAt = function (z) {
35483
+ if (bakeZoom === null || z < bakeZoom) { return plainPin; }
35484
+ var withText = hasText && z >= textMin, withBadge = hasBadge && z >= badgeMin;
35485
+ if (!withText && !withBadge) { return plainPin; }
35486
+ return drawnImageId('pinnum', [shape, str(mk.fill, '#ffffff'), str(mk.border, color), String(bw), String(size), str(mk.textColor, '#ffffff'),
35487
+ withBadge ? ['case', ['>', badgeNumber, 1], ['to-string', badgeNumber], ''] : '', withText ? str(mk.text, '') : '']);
35488
+ };
35489
+ var breaks = [bakeZoom, textMin, badgeMin].filter(function (z, i, all) { return typeof z === 'number' && z > 0 && all.indexOf(z) === i; })
35490
+ .sort(function (x, y) { return x - y; });
35491
+ var pinImage = imageAt(0);
35492
+ if (breaks.length) {
35493
+ pinImage = ['step', ['zoom'], imageAt(0)];
35494
+ breaks.forEach(function (z) { pinImage.push(z, imageAt(z)); });
35495
+ }
32204
35496
  var markerSpecs = [];
32205
35497
  var addMarker = function (layer) { markerSpecs.push(JSON.parse(JSON.stringify(layer))); add(layer); };
32206
35498
  addMarker({
@@ -32213,7 +35505,7 @@ function smapCreateSdk(engine, interop, plan) {
32213
35505
  'icon-allow-overlap': true, 'icon-ignore-placement': true,
32214
35506
  },
32215
35507
  });
32216
- if (mk.text !== undefined) {
35508
+ if (hasText && bakeZoom !== 0) {
32217
35509
  var text = str(mk.text, '');
32218
35510
  addMarker({
32219
35511
  id: src + '-numbers', type: 'symbol', filter: both(['!=', text, '']), minzoom: mk.textMinzoom, maxzoom: bakeZoom === null ? undefined : bakeZoom, paint: markerPaint,
@@ -32223,10 +35515,10 @@ function smapCreateSdk(engine, interop, plan) {
32223
35515
  },
32224
35516
  });
32225
35517
  }
32226
- if (mk.badge !== undefined) {
32227
- var badge = ['to-number', valueExpr(mk.badge, 0), 0];
35518
+ if (hasBadge && bakeZoom !== 0) {
35519
+ var badge = badgeNumber;
32228
35520
  addMarker({
32229
- id: src + '-badges', type: 'symbol', filter: both(['>', badge, 1]), minzoom: mk.badgeMinzoom, paint: markerPaint,
35521
+ id: src + '-badges', type: 'symbol', filter: both(['>', badge, 1]), minzoom: mk.badgeMinzoom, maxzoom: bakeZoom === null ? undefined : bakeZoom, paint: markerPaint,
32230
35522
  layout: {
32231
35523
  'icon-image': drawnImageId('badge', [['to-string', badge]]),
32232
35524
  'icon-offset': g.badge, 'icon-size': scale,
@@ -32271,6 +35563,30 @@ function smapCreateSdk(engine, interop, plan) {
32271
35563
  paint: { 'text-color': '#1f2937', 'text-halo-color': '#ffffff', 'text-halo-width': 1.4 },
32272
35564
  });
32273
35565
  }
35566
+ } else if (def.kind === 'heatmap') {
35567
+ main = src + '-heat';
35568
+ var hc = o.colors;
35569
+ var ramp = Array.isArray(hc) && hc.length && Array.isArray(hc[0]) ? hc : null;
35570
+ var colorExpr = ['interpolate', ['linear'], ['heatmap-density']];
35571
+ if (ramp) { ramp.forEach(function (st) { colorExpr.push(st[0], st[1]); }); }
35572
+ else if (Array.isArray(hc) && hc.length) {
35573
+ // Colours from low to high: transparent at no density, then evenly spaced.
35574
+ colorExpr.push(0, 'rgba(0, 0, 0, 0)');
35575
+ hc.forEach(function (c, i) { colorExpr.push((i + 1) / hc.length, c); });
35576
+ } else {
35577
+ 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)');
35578
+ }
35579
+ var weight = o.weight === undefined ? 1 : (typeof o.weight === 'number' ? o.weight : ['to-number', valueExpr(o.weight, 1), 1]);
35580
+ add({
35581
+ id: main, type: 'heatmap',
35582
+ paint: {
35583
+ 'heatmap-weight': weight,
35584
+ 'heatmap-radius': o.radius === undefined ? 20 : o.radius,
35585
+ 'heatmap-intensity': o.intensity === undefined ? 1 : o.intensity,
35586
+ 'heatmap-opacity': o.opacity === undefined ? 0.8 : o.opacity,
35587
+ 'heatmap-color': colorExpr,
35588
+ },
35589
+ });
32274
35590
  } else if (def.kind === 'lines') {
32275
35591
  main = src + '-lines';
32276
35592
  // simplestyle (stroke, stroke-width, stroke-opacity) when the layer gives no colour of its own
@@ -32295,6 +35611,15 @@ function smapCreateSdk(engine, interop, plan) {
32295
35611
  };
32296
35612
  if (o.dash) { paint['line-dasharray'] = o.dash; }
32297
35613
  add({ id: main, type: 'line', layout: round, paint: paint });
35614
+ if (o.label) {
35615
+ // A permanent label along the line (a route's name), repeated along long lines.
35616
+ add({
35617
+ id: src + '-labels', type: 'symbol',
35618
+ layout: { 'symbol-placement': 'line', 'symbol-spacing': o.labelSpacing || 250, 'text-field': ['to-string', ['get', o.label]],
35619
+ 'text-font': ['Noto Sans Medium'], 'text-size': 12, 'text-keep-upright': true },
35620
+ paint: { 'text-color': valueExpr(o.labelColor, '#1f2937'), 'text-halo-color': '#ffffff', 'text-halo-width': 1.6 },
35621
+ });
35622
+ }
32298
35623
  if (o.arrows) {
32299
35624
  var ar = o.arrows === true ? {} : o.arrows;
32300
35625
  var arrowSize = ar.size || 14;
@@ -32337,7 +35662,9 @@ function smapCreateSdk(engine, interop, plan) {
32337
35662
  }
32338
35663
  // The popup is opened by the map's one click handler (_popupClick), from this layer's own hit test.
32339
35664
  def.popupLayer = o.popup ? main : null;
32340
- if (main && (o.popup || o.onClick)) {
35665
+ if (main && o.tooltip) { this._tooltips(id, def, main); }
35666
+ if (main && def.kind === 'points' && o.draggable) { this._dragPoints(id, def, main); }
35667
+ if (main && (o.popup || o.onClick || o.draggable)) {
32341
35668
  if (o.onClick) {
32342
35669
  this._listen(id, 'click', main, function (e) { o.onClick(e.features[0], e); });
32343
35670
  }
@@ -32408,6 +35735,8 @@ function smapCreateSdk(engine, interop, plan) {
32408
35735
  slots: SLOTS.slice(),
32409
35736
  formats: interop.formats,
32410
35737
  convert: interop.convert,
35738
+ geo: interop.geo,
35739
+ useThree: interop.useThree,
32411
35740
  rasterUrl: interop.rasterUrl,
32412
35741
  rasterTileJsonUrl: interop.rasterTileJsonUrl,
32413
35742
  styleUrl: interop.styleUrl,
@@ -32426,9 +35755,10 @@ function smapCreateSdk(engine, interop, plan) {
32426
35755
  return module.exports;
32427
35756
  }
32428
35757
  var smapInterop = smapCreateInterop();
35758
+ var smapCanvasLib = smapCreateCanvas();
32429
35759
  var smapPlan = smapCreatePlan();
32430
35760
  function smapLoad() {
32431
- if (!smapCore) { smapCore = smapCreateSdk(smapCreateEngine(), smapInterop, smapPlan); }
35761
+ if (!smapCore) { smapCore = smapCreateSdk(smapCreateEngine(), smapInterop, smapCanvasLib, smapPlan); }
32432
35762
  return smapCore;
32433
35763
  }
32434
35764
  // Every top-level name starts with smap: the engine and the SDK run inside this scope and must keep seeing the
@@ -32442,19 +35772,20 @@ function SMapMap(options) {
32442
35772
  SMapMap.prototype = core.Map.prototype;
32443
35773
  return new core.Map(options);
32444
35774
  }
32445
- var smapVersion = "1.1.7";
35775
+ var smapVersion = "1.2.1";
32446
35776
  var smapStyles = ['light', 'white', 'grayscale', 'dark', 'black'];
32447
35777
  /** The bundled rendering engine, for anything the SDK does not wrap (loaded on the first call). */
32448
35778
  function smapGetEngine() { return smapLoad().engine; }
32449
35779
  // Templates need no engine: before the first map they come from an SDK made without one.
32450
35780
  var smapNoEngine = null;
32451
35781
  function smapPopupTemplate(template) {
32452
- if (!smapCore && !smapNoEngine) { smapNoEngine = smapCreateSdk(null, smapInterop, smapPlan); }
35782
+ if (!smapCore && !smapNoEngine) { smapNoEngine = smapCreateSdk(null, smapInterop, smapCanvasLib, smapPlan); }
32453
35783
  return (smapCore || smapNoEngine).popupTemplate(template);
32454
35784
  }
32455
35785
  var smapSlots = ['bottom', 'roads', 'labels', 'top'];
32456
35786
  var smap_formats = smapInterop.formats;
32457
35787
  var smap_convert = smapInterop.convert;
35788
+ var smap_geo = smapInterop.geo;
32458
35789
  var smap_rasterUrl = smapInterop.rasterUrl;
32459
35790
  var smap_rasterTileJsonUrl = smapInterop.rasterTileJsonUrl;
32460
35791
  var smap_styleUrl = smapInterop.styleUrl;
@@ -32464,11 +35795,12 @@ var smap_slotAnchors = smapInterop.slotAnchors;
32464
35795
  var smap_parseWmtsCapabilities = smapInterop.parseWmtsCapabilities;
32465
35796
  var smap_PMTilesArchive = smapInterop.PMTilesArchive;
32466
35797
  var smap_useDeck = smapInterop.useDeck;
35798
+ var smap_useThree = smapInterop.useThree;
32467
35799
  var smapPlanEditor = smapPlan.PlanEditor;
32468
35800
  var smapPlanTexts = smapPlan.texts;
32469
35801
  var smapPlanEditorParts = smapPlan.parts;
32470
35802
  var smapDefault = { Map: SMapMap, version: smapVersion, styles: smapStyles, getEngine: smapGetEngine, popupTemplate: smapPopupTemplate,
32471
- 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,
35803
+ 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,
32472
35804
  planEditorParts: smapPlanEditorParts };
32473
- 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 };
35805
+ 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 };
32474
35806
  export default smapDefault;