@superrouteca/smap 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/smap.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! sMap Web SDK 1.0.5. Bundles maplibre-gl 6.16.0 (BSD-3-Clause), see vendor-licenses.txt. */
1
+ /*! sMap Web SDK 1.1.0. Bundles maplibre-gl 6.16.0 (BSD-3-Clause), see vendor-licenses.txt. */
2
2
  (function () {
3
3
  function smapCreateEngine() {
4
4
  var SMAP_BASE_URL = (typeof document !== 'undefined' && document.baseURI) || (typeof location !== 'undefined' && location.href) || 'about:blank';
@@ -26846,12 +26846,1491 @@ ${o2.prelude.vertexSource}`, define: o2.define }, defaultProjectionData: d2, get
26846
26846
  smapEngine.setWorkerUrl(smapWorkerUrl);
26847
26847
  return smapEngine;
26848
26848
  }
26849
- function smapCreateSdk(engine) {
26849
+ function smapCreateInterop() {
26850
+ var module = { exports: {} };
26851
+ var exports = module.exports;
26852
+ var define;
26853
+ var self = {};
26854
+ /*!
26855
+ * sMap Web SDK — the parts that need no map: basemap classes and overrides, slots, file formats (read and write),
26856
+ * the converting endpoint, raster tile URLs, PMTiles archives and WMTS capabilities. Pure functions, usable on a
26857
+ * server; the map view (smap.js) is built on them. Rules shared by every SDK: smap-sdk shared/INTEROP-CONTRACT.md,
26858
+ * shared/basemap-classes.json, shared/FORMAT-RULES.md, shared/LAYER-JSON.md.
26859
+ */
26860
+ (function (root, factory) {
26861
+ if (typeof module === 'object' && module.exports) { module.exports = factory(); } else { root.smapInterop = factory(); }
26862
+ })(typeof self !== 'undefined' ? self : this, function () {
26863
+ 'use strict';
26864
+
26865
+ var DEFAULT_BASE = 'https://tile.srsr.ca';
26866
+ var FLAVORS = ['light', 'white', 'grayscale', 'dark', 'black'];
26867
+
26868
+ // ------------------------------------------------------------------ basemap classes (contract §1)
26869
+
26870
+ // shared/basemap-classes.json, `paint` and `classes` as [layer, role] pairs (test/source-interop.test.cjs checks
26871
+ // that it equals the shared file).
26872
+ var BASEMAP = {"paint":{"background":{"color":"background-color","opacity":"background-opacity"},"fill":{"color":"fill-color","opacity":"fill-opacity"},"line":{"color":"line-color","opacity":"line-opacity","width":"line-width"},"casing":{"casing":"line-color","opacity":"line-opacity","width":"line-width"},"label":{"color":"text-color","halo":"text-halo-color","opacity":"text-opacity"}},"classes":{"land":[["background","background"],["earth","fill"]],"landcover":[["landcover","fill"]],"parks":[["landuse_park","fill"],["landuse_urban_green","fill"]],"landuse":[["landuse_hospital","fill"],["landuse_industrial","fill"],["landuse_school","fill"],["landuse_beach","fill"],["landuse_zoo","fill"],["landuse_pedestrian","fill"],["landuse_pier","fill"]],"airports":[["landuse_aerodrome","fill"],["roads_runway","line"],["roads_taxiway","line"],["landuse_runway","fill"]],"water":[["water","fill"],["water_stream","line"],["water_river","line"]],"roads.paths":[["roads_tunnels_other_casing","casing"],["roads_tunnels_other","line"],["roads_pier","line"],["roads_other","line"],["roads_bridges_other_casing","casing"],["roads_bridges_other","line"]],"roads.minor":[["roads_tunnels_minor_casing","casing"],["roads_tunnels_link_casing","casing"],["roads_tunnels_minor","line"],["roads_tunnels_link","line"],["roads_minor_service_casing","casing"],["roads_minor_casing","casing"],["roads_link_casing","casing"],["roads_link","line"],["roads_minor_service","line"],["roads_minor","line"],["roads_bridges_link_casing","casing"],["roads_bridges_minor_casing","casing"],["roads_bridges_minor","line"],["roads_bridges_link","line"]],"roads.major":[["roads_tunnels_major_casing","casing"],["roads_tunnels_major","line"],["roads_major_casing_late","casing"],["roads_major_casing_early","casing"],["roads_major","line"],["roads_bridges_major_casing","casing"],["roads_bridges_major","line"]],"roads.highway":[["roads_tunnels_highway_casing","casing"],["roads_tunnels_highway","line"],["roads_highway_casing_late","casing"],["roads_highway_casing_early","casing"],["roads_highway","line"],["roads_bridges_highway_casing","casing"],["roads_bridges_highway","line"]],"buildings":[["buildings","fill"]],"rail":[["roads_rail","line"]],"boundaries":[["boundaries_country","line"],["boundaries","line"]],"labels.addresses":[["address_label","label"]],"labels.water":[["water_waterway_label","label"],["water_label_ocean","label"],["water_label_lakes","label"]],"labels.roads":[["roads_oneway","label"],["roads_labels_minor","label"],["roads_shields","label"],["roads_labels_major","label"]],"labels.places":[["earth_label_islands","label"],["places_subplace","label"],["places_region","label"],["places_locality","label"],["places_country","label"]],"pois":[["pois","label"]]}};
26873
+ var OVERRIDE_FIELDS = ['visible', 'color', 'casing', 'halo', 'opacity', 'width'];
26874
+ var PARENTS = ['roads', 'labels'];
26875
+
26876
+ /** The class table as the shared file has it: {paint, classes: {name: [{layer, role}]}}. */
26877
+ function basemapClasses() {
26878
+ var classes = {};
26879
+ Object.keys(BASEMAP.classes).forEach(function (name) {
26880
+ classes[name] = BASEMAP.classes[name].map(function (p) { return { layer: p[0], role: p[1] }; });
26881
+ });
26882
+ return { paint: JSON.parse(JSON.stringify(BASEMAP.paint)), classes: classes };
26883
+ }
26884
+
26885
+ // A table given as the shared file ({classes: {name: [{layer, role}]}}) or as the embedded one.
26886
+ function classTable(table) {
26887
+ if (!table) { return BASEMAP; }
26888
+ var classes = {};
26889
+ Object.keys(table.classes || {}).forEach(function (name) {
26890
+ classes[name] = (table.classes[name] || []).map(function (p) { return Array.isArray(p) ? p : [p.layer, p.role]; });
26891
+ });
26892
+ return { paint: table.paint || BASEMAP.paint, classes: classes };
26893
+ }
26894
+
26895
+ function isColor(v) { return typeof v === 'string' && v.length > 0; }
26896
+
26897
+ /**
26898
+ * Checks basemap overrides (contract §1) and returns the list of [class, override] in the order they apply:
26899
+ * parents (`roads`, `labels`) first, then the classes in the order given. An unknown class or field, or a value of
26900
+ * the wrong kind, throws an Error naming it.
26901
+ */
26902
+ function overrideOrder(overrides, table) {
26903
+ var t = classTable(table);
26904
+ if (overrides === null || overrides === undefined) { return []; }
26905
+ if (typeof overrides !== 'object' || Array.isArray(overrides)) { throw new Error('sMap: basemap overrides must be an object'); }
26906
+ var parents = [], children = [];
26907
+ Object.keys(overrides).forEach(function (name) {
26908
+ var isParent = PARENTS.indexOf(name) >= 0;
26909
+ if (!isParent && !t.classes[name]) { throw new Error('sMap: unknown basemap class "' + name + '"'); }
26910
+ var o = overrides[name];
26911
+ if (!o || typeof o !== 'object' || Array.isArray(o)) { throw new Error('sMap: basemap class "' + name + '" needs an object'); }
26912
+ Object.keys(o).forEach(function (field) {
26913
+ var v = o[field];
26914
+ if (OVERRIDE_FIELDS.indexOf(field) < 0) { throw new Error('sMap: unknown field "' + field + '" on basemap class "' + name + '"'); }
26915
+ if (field === 'visible' && typeof v !== 'boolean') { throw new Error('sMap: "' + name + '.visible" must be true or false'); }
26916
+ if ((field === 'color' || field === 'casing' || field === 'halo') && !isColor(v)) { throw new Error('sMap: "' + name + '.' + field + '" must be a colour'); }
26917
+ if (field === 'opacity' && !(typeof v === 'number' && v >= 0 && v <= 1)) { throw new Error('sMap: "' + name + '.opacity" must be between 0 and 1'); }
26918
+ if (field === 'width' && !(typeof v === 'number' && v >= 0.25 && v <= 4)) { throw new Error('sMap: "' + name + '.width" must be between 0.25 and 4'); }
26919
+ });
26920
+ (isParent ? parents : children).push(name);
26921
+ });
26922
+ var out = [];
26923
+ parents.sort(function (a, b) { return PARENTS.indexOf(a) - PARENTS.indexOf(b); });
26924
+ parents.forEach(function (p) {
26925
+ Object.keys(t.classes).forEach(function (name) { if (name.indexOf(p + '.') === 0) { out.push([name, overrides[p]]); } });
26926
+ });
26927
+ children.forEach(function (name) { out.push([name, overrides[name]]); });
26928
+ return out;
26929
+ }
26930
+
26931
+ function round4(n) { return Math.round(n * 10000) / 10000; }
26932
+
26933
+ // line-width × factor (contract §10): a number is multiplied (rounded to 4 decimals); a top-level interpolate /
26934
+ // step (or legacy {stops}) keeps its structure with each output multiplied the same way, since the engine accepts
26935
+ // a zoom expression only at the top of a value; anything else becomes ["*", factor, <original>]. Superroute's
26936
+ // preview (SmapBasemapClasses::scaleWidth) gives the same values.
26937
+ function scaledWidth(width, factor) {
26938
+ if (typeof width === 'number') { return round4(width * factor); }
26939
+ var k;
26940
+ if (width && typeof width === 'object' && !Array.isArray(width) && Array.isArray(width.stops)) {
26941
+ width = JSON.parse(JSON.stringify(width));
26942
+ for (k = 0; k < width.stops.length; k++) { if (Array.isArray(width.stops[k]) && width.stops[k].length === 2) { width.stops[k][1] = scaledWidth(width.stops[k][1], factor); } }
26943
+ return width;
26944
+ }
26945
+ if (Array.isArray(width) && width[0] === 'interpolate') {
26946
+ width = width.slice();
26947
+ for (k = 4; k < width.length; k += 2) { width[k] = scaledWidth(width[k], factor); }
26948
+ return width;
26949
+ }
26950
+ if (Array.isArray(width) && width[0] === 'step') {
26951
+ width = width.slice();
26952
+ for (k = 2; k < width.length; k += 2) { width[k] = scaledWidth(width[k], factor); }
26953
+ return width;
26954
+ }
26955
+ return ['*', factor, width];
26956
+ }
26957
+
26958
+ // Applies one class's fields to one style layer of a role, in place. The width always scales the scheme's own
26959
+ // width: the fields are merged (parent, then the class) before anything is applied.
26960
+ function applyToLayer(layer, role, o, paintKeys) {
26961
+ var keys = paintKeys[role] || {};
26962
+ if (o.visible !== undefined) { layer.layout = layer.layout || {}; layer.layout.visibility = o.visible ? 'visible' : 'none'; }
26963
+ var paint = function (k, v) { if (k) { layer.paint = layer.paint || {}; layer.paint[k] = v; } };
26964
+ if (o.color !== undefined && role !== 'casing') { paint(keys.color, o.color); }
26965
+ if (o.casing !== undefined && role === 'casing') { paint(keys.casing, o.casing); }
26966
+ if (o.halo !== undefined && role === 'label') { paint(keys.halo, o.halo); }
26967
+ if (o.opacity !== undefined) { paint(keys.opacity, o.opacity); }
26968
+ if (o.width !== undefined && keys.width) {
26969
+ var w = layer.paint ? layer.paint[keys.width] : undefined;
26970
+ paint(keys.width, scaledWidth(w === undefined ? 1 : w, o.width));
26971
+ }
26972
+ }
26973
+
26974
+ /** The fields that apply to each class: its parent's (roads, labels), then its own, which win. */
26975
+ function effectiveOverrides(overrides, table) {
26976
+ var t = classTable(table);
26977
+ overrideOrder(overrides, table);
26978
+ var out = {};
26979
+ if (!overrides) { return out; }
26980
+ Object.keys(t.classes).forEach(function (name) {
26981
+ var dot = name.indexOf('.');
26982
+ var merged = {}, any = false;
26983
+ var parent = dot > 0 ? overrides[name.slice(0, dot)] : null;
26984
+ [parent, overrides[name]].forEach(function (o) { if (o) { Object.keys(o).forEach(function (f) { merged[f] = o[f]; any = true; }); } });
26985
+ if (any) { out[name] = merged; }
26986
+ });
26987
+ return out;
26988
+ }
26989
+
26990
+ /**
26991
+ * The style with basemap overrides applied (contract §1 and §10): a copy, the input is never changed. `table` is
26992
+ * the class table (default: the one bundled, equal to shared/basemap-classes.json). Layers of the table the style
26993
+ * lacks are skipped; an unknown class or field throws.
26994
+ */
26995
+ function applyBasemapOverrides(style, overrides, table) {
26996
+ var t = classTable(table);
26997
+ var eff = effectiveOverrides(overrides, table);
26998
+ var out = JSON.parse(JSON.stringify(style || {}));
26999
+ if (!Object.keys(eff).length || !Array.isArray(out.layers)) { return out; }
27000
+ var byId = {};
27001
+ out.layers.forEach(function (l) { byId[l.id] = l; });
27002
+ Object.keys(eff).forEach(function (name) {
27003
+ t.classes[name].forEach(function (lr) {
27004
+ var layer = byId[lr[0]];
27005
+ if (layer) { applyToLayer(layer, lr[1], eff[name], t.paint); }
27006
+ });
27007
+ });
27008
+ return out;
27009
+ }
27010
+
27011
+ /** layer id → {class, role} for every basemap layer of the table. */
27012
+ function basemapLayerIndex(table) {
27013
+ var t = classTable(table), idx = {};
27014
+ Object.keys(t.classes).forEach(function (name) {
27015
+ t.classes[name].forEach(function (lr) { idx[lr[0]] = { 'class': name, role: lr[1] }; });
27016
+ });
27017
+ return idx;
27018
+ }
27019
+
27020
+ // ------------------------------------------------------------------ slots (M25)
27021
+
27022
+ var SLOTS = ['bottom', 'roads', 'labels', 'top'];
27023
+
27024
+ /**
27025
+ * Where each slot begins in a basemap's layer list (ids bottom to top): the id of the basemap layer a layer of that
27026
+ * slot is drawn under, or undefined for the very top.
27027
+ * labels: the first label layer (classes labels.* and pois)
27028
+ * roads: the first road layer (classes roads.* and rail), else labels
27029
+ * bottom: the layer after the last layer of land, landcover and water, else roads (never above roads)
27030
+ * top: undefined
27031
+ * Stable when the basemap's list changes: it is found from the classes, never from a position.
27032
+ */
27033
+ function slotAnchors(layerIds, table) {
27034
+ var idx = basemapLayerIndex(table);
27035
+ var cls = function (id) { return idx[id] && idx[id]['class']; };
27036
+ var first = function (test) { for (var i = 0; i < layerIds.length; i++) { var c = cls(layerIds[i]); if (c && test(c)) { return i; } } return -1; };
27037
+ var labels = first(function (c) { return c.indexOf('labels.') === 0 || c === 'pois'; });
27038
+ var roads = first(function (c) { return c.indexOf('roads.') === 0 || c === 'rail'; });
27039
+ if (roads < 0 || (labels >= 0 && roads > labels)) { roads = labels; }
27040
+ var lastGround = -1;
27041
+ for (var i = 0; i < layerIds.length; i++) { var c = cls(layerIds[i]); if (c === 'land' || c === 'landcover' || c === 'water') { lastGround = i; } }
27042
+ var bottom = lastGround >= 0 && lastGround + 1 < layerIds.length ? lastGround + 1 : roads;
27043
+ if (roads >= 0 && (bottom < 0 || bottom > roads)) { bottom = roads; }
27044
+ var id = function (i) { return i >= 0 ? layerIds[i] : undefined; };
27045
+ return { bottom: id(bottom), roads: id(roads), labels: id(labels), top: undefined };
27046
+ }
27047
+
27048
+ // ------------------------------------------------------------------ bytes, text, inflate
27049
+
27050
+ function toBytes(input) {
27051
+ if (input instanceof Uint8Array) { return input; }
27052
+ if (typeof ArrayBuffer !== 'undefined' && input instanceof ArrayBuffer) { return new Uint8Array(input); }
27053
+ if (input && input.buffer instanceof ArrayBuffer && typeof input.byteLength === 'number') { return new Uint8Array(input.buffer, input.byteOffset || 0, input.byteLength); }
27054
+ if (input && typeof input.length === 'number' && typeof input !== 'string') { return Uint8Array.from(input); }
27055
+ return null;
27056
+ }
27057
+
27058
+ function utf8(bytes) {
27059
+ if (typeof TextDecoder !== 'undefined') { return new TextDecoder('utf-8').decode(bytes); }
27060
+ var s = '';
27061
+ for (var i = 0; i < bytes.length; i++) { s += String.fromCharCode(bytes[i]); }
27062
+ return decodeURIComponent(escape(s));
27063
+ }
27064
+
27065
+ function stripBom(s) { return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s; }
27066
+
27067
+ var LBASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];
27068
+ var LEXT = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
27069
+ var DBASE = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577];
27070
+ var DEXT = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
27071
+ var CLORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
27072
+
27073
+ function huffman(lengths) {
27074
+ var count = new Uint16Array(16), offs = new Uint16Array(16), symbol = new Uint16Array(lengths.length);
27075
+ for (var s = 0; s < lengths.length; s++) { count[lengths[s]]++; }
27076
+ count[0] = 0;
27077
+ for (var len = 1; len < 15; len++) { offs[len + 1] = offs[len] + count[len]; }
27078
+ for (s = 0; s < lengths.length; s++) { if (lengths[s]) { symbol[offs[lengths[s]]++] = s; } }
27079
+ return { count: count, symbol: symbol };
27080
+ }
27081
+
27082
+ /** Raw DEFLATE (RFC 1951) → bytes. Small and synchronous, for KMZ, gzip and PMTiles directories. */
27083
+ function inflateRaw(src) {
27084
+ src = toBytes(src);
27085
+ var pos = 0, bitBuf = 0, bitCnt = 0, out = new Uint8Array(Math.max(1024, src.length * 4)), n = 0;
27086
+ function need(k) { if (n + k > out.length) { var b = new Uint8Array(Math.max(out.length * 2, n + k)); b.set(out.subarray(0, n)); out = b; } }
27087
+ function bits(k) {
27088
+ while (bitCnt < k) { if (pos >= src.length) { throw new Error('sMap: compressed data ends early'); } bitBuf |= src[pos++] << bitCnt; bitCnt += 8; }
27089
+ var v = bitBuf & ((1 << k) - 1);
27090
+ bitBuf >>>= k; bitCnt -= k;
27091
+ return v;
27092
+ }
27093
+ function decode(h) {
27094
+ var code = 0, first = 0, index = 0;
27095
+ for (var len = 1; len < 16; len++) {
27096
+ code |= bits(1);
27097
+ var c = h.count[len];
27098
+ if (code - first < c) { return h.symbol[index + code - first]; }
27099
+ index += c; first += c; first <<= 1; code <<= 1;
27100
+ }
27101
+ throw new Error('sMap: bad compressed data');
27102
+ }
27103
+ var fixedLit = null, fixedDist = null;
27104
+ var last;
27105
+ do {
27106
+ last = bits(1);
27107
+ var type = bits(2);
27108
+ if (type === 0) {
27109
+ bitBuf = 0; bitCnt = 0;
27110
+ if (pos + 4 > src.length) { throw new Error('sMap: compressed data ends early'); }
27111
+ var len = src[pos] | (src[pos + 1] << 8);
27112
+ pos += 4;
27113
+ if (pos + len > src.length) { throw new Error('sMap: compressed data ends early'); }
27114
+ need(len); out.set(src.subarray(pos, pos + len), n); n += len; pos += len;
27115
+ continue;
27116
+ }
27117
+ var lit, dist;
27118
+ if (type === 1) {
27119
+ if (!fixedLit) {
27120
+ var l = new Uint8Array(288);
27121
+ for (var i = 0; i < 288; i++) { l[i] = i < 144 ? 8 : i < 256 ? 9 : i < 280 ? 7 : 8; }
27122
+ fixedLit = huffman(l);
27123
+ var d = new Uint8Array(30); d.fill(5); fixedDist = huffman(d);
27124
+ }
27125
+ lit = fixedLit; dist = fixedDist;
27126
+ } else if (type === 2) {
27127
+ var hlit = bits(5) + 257, hdist = bits(5) + 1, hclen = bits(4) + 4;
27128
+ var cl = new Uint8Array(19);
27129
+ for (i = 0; i < hclen; i++) { cl[CLORDER[i]] = bits(3); }
27130
+ var clh = huffman(cl);
27131
+ var lengths = new Uint8Array(hlit + hdist);
27132
+ for (i = 0; i < hlit + hdist;) {
27133
+ var sym = decode(clh);
27134
+ if (sym < 16) { lengths[i++] = sym; continue; }
27135
+ var rep, val = 0;
27136
+ if (sym === 16) { if (i === 0) { throw new Error('sMap: bad compressed data'); } val = lengths[i - 1]; rep = 3 + bits(2); } else if (sym === 17) { rep = 3 + bits(3); } else { rep = 11 + bits(7); }
27137
+ if (i + rep > hlit + hdist) { throw new Error('sMap: bad compressed data'); }
27138
+ while (rep--) { lengths[i++] = val; }
27139
+ }
27140
+ lit = huffman(lengths.subarray(0, hlit)); dist = huffman(lengths.subarray(hlit));
27141
+ } else { throw new Error('sMap: bad compressed data'); }
27142
+ for (;;) {
27143
+ var s = decode(lit);
27144
+ if (s < 256) { need(1); out[n++] = s; continue; }
27145
+ if (s === 256) { break; }
27146
+ s -= 257;
27147
+ if (s >= 29) { throw new Error('sMap: bad compressed data'); }
27148
+ var length = LBASE[s] + bits(LEXT[s]);
27149
+ var ds = decode(dist);
27150
+ if (ds >= 30) { throw new Error('sMap: bad compressed data'); }
27151
+ var back = DBASE[ds] + bits(DEXT[ds]);
27152
+ if (back > n) { throw new Error('sMap: bad compressed data'); }
27153
+ need(length);
27154
+ for (var k = 0; k < length; k++) { out[n] = out[n - back]; n++; }
27155
+ }
27156
+ } while (!last);
27157
+ return out.slice(0, n);
27158
+ }
27159
+
27160
+ /** gzip (RFC 1952) → bytes; bytes that are not gzip are returned as they are. */
27161
+ function gunzip(bytes) {
27162
+ bytes = toBytes(bytes);
27163
+ if (bytes.length < 18 || bytes[0] !== 0x1f || bytes[1] !== 0x8b) { return bytes; }
27164
+ if (bytes[2] !== 8) { throw new Error('sMap: unsupported gzip method'); }
27165
+ var flags = bytes[3], p = 10;
27166
+ if (flags & 4) { p += 2 + (bytes[p] | (bytes[p + 1] << 8)); }
27167
+ if (flags & 8) { while (bytes[p++] !== 0) { /* name */ } }
27168
+ if (flags & 16) { while (bytes[p++] !== 0) { /* comment */ } }
27169
+ if (flags & 2) { p += 2; }
27170
+ return inflateRaw(bytes.subarray(p));
27171
+ }
27172
+
27173
+ /** The files of a ZIP archive: [{name, bytes()}] (stored and deflated entries). */
27174
+ function unzip(bytes) {
27175
+ bytes = toBytes(bytes);
27176
+ var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
27177
+ var e = -1;
27178
+ for (var i = bytes.length - 22; i >= Math.max(0, bytes.length - 65557); i--) { if (dv.getUint32(i, true) === 0x06054b50) { e = i; break; } }
27179
+ if (e < 0) { throw new Error('sMap: not a ZIP archive'); }
27180
+ var count = dv.getUint16(e + 10, true), p = dv.getUint32(e + 16, true), files = [];
27181
+ for (var k = 0; k < count; k++) {
27182
+ if (dv.getUint32(p, true) !== 0x02014b50) { throw new Error('sMap: bad ZIP directory'); }
27183
+ var method = dv.getUint16(p + 10, true), size = dv.getUint32(p + 20, true);
27184
+ var nameLen = dv.getUint16(p + 28, true), extraLen = dv.getUint16(p + 30, true), commentLen = dv.getUint16(p + 32, true);
27185
+ var local = dv.getUint32(p + 42, true);
27186
+ var name = utf8(bytes.subarray(p + 46, p + 46 + nameLen));
27187
+ p += 46 + nameLen + extraLen + commentLen;
27188
+ (function (method, size, local, name) {
27189
+ files.push({
27190
+ name: name,
27191
+ bytes: function () {
27192
+ var start = local + 30 + dv.getUint16(local + 26, true) + dv.getUint16(local + 28, true);
27193
+ var raw = bytes.subarray(start, start + size);
27194
+ if (method === 0) { return raw; }
27195
+ if (method === 8) { return inflateRaw(raw); }
27196
+ throw new Error('sMap: unsupported ZIP method ' + method + ' for ' + name);
27197
+ },
27198
+ });
27199
+ })(method, size, local, name);
27200
+ }
27201
+ return files;
27202
+ }
27203
+
27204
+ // ------------------------------------------------------------------ XML (enough for KML, GPX, WMTS capabilities)
27205
+
27206
+ var ENTITIES = { lt: '<', gt: '>', amp: '&', quot: '"', apos: "'" };
27207
+ function decodeEntities(s) {
27208
+ return s.indexOf('&') < 0 ? s : s.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, function (m, e) {
27209
+ if (e[0] === '#') { var cp = e[1] === 'x' || e[1] === 'X' ? parseInt(e.slice(2), 16) : parseInt(e.slice(1), 10); return isFinite(cp) ? String.fromCodePoint(cp) : m; }
27210
+ return ENTITIES[e] !== undefined ? ENTITIES[e] : m;
27211
+ });
27212
+ }
27213
+
27214
+ /** A small XML reader: elements {name, local, attrs, children} where children holds elements and strings. */
27215
+ function parseXml(s) {
27216
+ var doc = { name: '#document', local: '#document', attrs: {}, children: [] }, stack = [doc], i = 0, n = s.length;
27217
+ var top = function () { return stack[stack.length - 1]; };
27218
+ while (i < n) {
27219
+ var lt = s.indexOf('<', i);
27220
+ if (lt < 0) { top().children.push(decodeEntities(s.slice(i))); break; }
27221
+ if (lt > i) { top().children.push(decodeEntities(s.slice(i, lt))); }
27222
+ var end;
27223
+ if (s.startsWith('<!--', lt)) { end = s.indexOf('-->', lt + 4); i = end < 0 ? n : end + 3; continue; }
27224
+ if (s.startsWith('<![CDATA[', lt)) { end = s.indexOf(']]>', lt + 9); top().children.push(s.slice(lt + 9, end < 0 ? n : end)); i = end < 0 ? n : end + 3; continue; }
27225
+ if (s[lt + 1] === '?') { end = s.indexOf('?>', lt + 2); i = end < 0 ? n : end + 2; continue; }
27226
+ if (s[lt + 1] === '!') {
27227
+ var depth = 0, j = lt + 2;
27228
+ for (; j < n; j++) { if (s[j] === '[') { depth++; } else if (s[j] === ']') { depth--; } else if (s[j] === '>' && depth <= 0) { break; } }
27229
+ i = j + 1; continue;
27230
+ }
27231
+ if (s[lt + 1] === '/') {
27232
+ end = s.indexOf('>', lt);
27233
+ var closing = s.slice(lt + 2, end < 0 ? n : end).trim();
27234
+ for (var k = stack.length - 1; k > 0; k--) { if (stack[k].name === closing) { stack.length = k; break; } }
27235
+ i = end < 0 ? n : end + 1; continue;
27236
+ }
27237
+ var q = null, p = lt + 1;
27238
+ for (; p < n; p++) { var c = s[p]; if (q) { if (c === q) { q = null; } } else if (c === '"' || c === "'") { q = c; } else if (c === '>') { break; } }
27239
+ var body = s.slice(lt + 1, p), selfClose = /\/\s*$/.test(body);
27240
+ if (selfClose) { body = body.replace(/\/\s*$/, ''); }
27241
+ var m = /^([^\s/>]+)/.exec(body);
27242
+ if (!m) { i = p + 1; continue; }
27243
+ var attrs = {}, re = /([^\s=]+)\s*=\s*("([^"]*)"|'([^']*)')/g, a;
27244
+ var rest = body.slice(m[1].length);
27245
+ while ((a = re.exec(rest))) { attrs[a[1]] = decodeEntities(a[3] !== undefined ? a[3] : a[4]); }
27246
+ var el = { name: m[1], local: m[1].replace(/^.*:/, ''), attrs: attrs, children: [] };
27247
+ top().children.push(el);
27248
+ if (!selfClose) { stack.push(el); }
27249
+ i = p + 1;
27250
+ }
27251
+ return doc;
27252
+ }
27253
+
27254
+ function elems(el, local) { var out = []; if (el) { el.children.forEach(function (c) { if (typeof c !== 'string' && (!local || c.local === local)) { out.push(c); } }); } return out; }
27255
+ function elem(el, local) { return elems(el, local)[0] || null; }
27256
+ function textOf(el) {
27257
+ if (!el) { return ''; }
27258
+ var s = '';
27259
+ el.children.forEach(function (c) { s += typeof c === 'string' ? c : textOf(c); });
27260
+ return s;
27261
+ }
27262
+ function trimmed(el) { return textOf(el).trim(); }
27263
+ function descendants(el, local, out) {
27264
+ out = out || [];
27265
+ elems(el).forEach(function (c) { if (c.local === local) { out.push(c); } descendants(c, local, out); });
27266
+ return out;
27267
+ }
27268
+ function attr(el, local) {
27269
+ if (!el) { return undefined; }
27270
+ for (var k in el.attrs) { if (k === local || k.replace(/^.*:/, '') === local) { return el.attrs[k]; } }
27271
+ return undefined;
27272
+ }
27273
+
27274
+ // ------------------------------------------------------------------ formats: read
27275
+
27276
+ var FORMATS = ['kml', 'kmz', 'gpx', 'polyline', 'polyline6', 'wkt', 'wkb', 'csv', 'topojson', 'geojson'];
27277
+ var GEOMETRY_TYPES = ['Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeometryCollection'];
27278
+
27279
+ function fc(features) { return { type: 'FeatureCollection', features: features }; }
27280
+ function feature(properties, geometry, id) {
27281
+ var f = { type: 'Feature' };
27282
+ if (id !== undefined && id !== null) { f.id = id; }
27283
+ f.properties = properties || {};
27284
+ f.geometry = geometry || null;
27285
+ return f;
27286
+ }
27287
+
27288
+ // KML colour aabbggrr → ['#rrggbb', opacity rounded to 2 decimals]
27289
+ function kmlColor(v) {
27290
+ v = String(v || '').trim().replace(/^#/, '');
27291
+ if (!/^[0-9a-fA-F]{8}$/.test(v)) { return null; }
27292
+ v = v.toLowerCase();
27293
+ return ['#' + v.slice(6, 8) + v.slice(4, 6) + v.slice(2, 4), Math.round(parseInt(v.slice(0, 2), 16) / 255 * 100) / 100];
27294
+ }
27295
+
27296
+ // Tuples are separated by white space that is not next to a comma ("-79.38, 43.65" is one tuple).
27297
+ function kmlCoords(text) {
27298
+ var out = [];
27299
+ String(text).replace(/\s*,\s*/g, ',').trim().split(/\s+/).forEach(function (tuple) {
27300
+ if (!tuple) { return; }
27301
+ var p = tuple.split(',').map(Number);
27302
+ if (p.length < 2 || !isFinite(p[0]) || !isFinite(p[1])) { return; }
27303
+ out.push(p.length >= 3 && isFinite(p[2]) ? [p[0], p[1], p[2]] : [p[0], p[1]]);
27304
+ });
27305
+ return out;
27306
+ }
27307
+
27308
+ function kmlGeometry(el) {
27309
+ switch (el.local) {
27310
+ case 'Point': { var c = kmlCoords(trimmed(elem(el, 'coordinates'))); return c.length ? { type: 'Point', coordinates: c[0] } : null; }
27311
+ case 'LineString': case 'LinearRing': { var l = kmlCoords(trimmed(elem(el, 'coordinates'))); return l.length ? { type: 'LineString', coordinates: l } : null; }
27312
+ case 'Polygon': {
27313
+ var rings = [];
27314
+ var ring = function (b) { var r = elem(b, 'LinearRing'); var cs = r ? kmlCoords(trimmed(elem(r, 'coordinates'))) : []; if (cs.length) { rings.push(cs); } };
27315
+ elems(el, 'outerBoundaryIs').forEach(ring);
27316
+ if (!rings.length) { return null; }
27317
+ elems(el, 'innerBoundaryIs').forEach(ring);
27318
+ return { type: 'Polygon', coordinates: rings };
27319
+ }
27320
+ case 'Track': {
27321
+ var t = elems(el, 'coord').map(function (x) { var p = trimmed(x).split(/\s+/).map(Number); return p.length >= 3 && isFinite(p[2]) ? [p[0], p[1], p[2]] : [p[0], p[1]]; })
27322
+ .filter(function (p) { return isFinite(p[0]) && isFinite(p[1]); });
27323
+ return t.length ? { type: 'LineString', coordinates: t } : null;
27324
+ }
27325
+ case 'MultiTrack': {
27326
+ var lines = elems(el, 'Track').map(kmlGeometry).filter(Boolean).map(function (g) { return g.coordinates; });
27327
+ return lines.length ? { type: 'MultiLineString', coordinates: lines } : null;
27328
+ }
27329
+ case 'MultiGeometry': {
27330
+ var parts = elems(el).map(function (c) { return GEOMETRY_ELEMENTS.indexOf(c.local) >= 0 ? kmlGeometry(c) : null; }).filter(Boolean);
27331
+ return combine(parts);
27332
+ }
27333
+ }
27334
+ return null;
27335
+ }
27336
+ var GEOMETRY_ELEMENTS = ['Point', 'LineString', 'LinearRing', 'Polygon', 'MultiGeometry', 'Track', 'MultiTrack'];
27337
+
27338
+ // Parts of one feature: one part is itself; parts all Point / LineString / Polygon → Multi*; anything else a collection.
27339
+ function combine(parts) {
27340
+ if (!parts.length) { return null; }
27341
+ if (parts.length === 1) { return parts[0]; }
27342
+ var t = parts[0].type;
27343
+ var same = parts.every(function (g) { return g.type === t; });
27344
+ if (same && (t === 'Point' || t === 'LineString' || t === 'Polygon')) {
27345
+ return { type: 'Multi' + t, coordinates: parts.map(function (g) { return g.coordinates; }) };
27346
+ }
27347
+ return { type: 'GeometryCollection', geometries: parts };
27348
+ }
27349
+
27350
+ function kmlStyleProps(style) {
27351
+ var props = {};
27352
+ if (!style) { return props; }
27353
+ var icon = elem(style, 'IconStyle'), line = elem(style, 'LineStyle'), poly = elem(style, 'PolyStyle');
27354
+ var c;
27355
+ if (icon && (c = kmlColor(trimmed(elem(icon, 'color'))))) { props['marker-color'] = c[0]; }
27356
+ if (line) {
27357
+ if ((c = kmlColor(trimmed(elem(line, 'color'))))) { props.stroke = c[0]; props['stroke-opacity'] = c[1]; }
27358
+ var w = parseFloat(trimmed(elem(line, 'width')));
27359
+ if (isFinite(w)) { props['stroke-width'] = w; }
27360
+ }
27361
+ if (poly) {
27362
+ if ((c = kmlColor(trimmed(elem(poly, 'color'))))) { props.fill = c[0]; props['fill-opacity'] = c[1]; }
27363
+ if (trimmed(elem(poly, 'fill')) === '0') { props['fill-opacity'] = 0; }
27364
+ if (trimmed(elem(poly, 'outline')) === '0') { props['stroke-opacity'] = 0; }
27365
+ }
27366
+ return props;
27367
+ }
27368
+
27369
+ function readKml(text) {
27370
+ var doc = parseXml(stripBom(text));
27371
+ var styles = {}, maps = {};
27372
+ descendants(doc, 'Style').forEach(function (s) { if (s.attrs.id) { styles[s.attrs.id] = s; } });
27373
+ descendants(doc, 'StyleMap').forEach(function (s) {
27374
+ if (!s.attrs.id) { return; }
27375
+ elems(s, 'Pair').forEach(function (p) { if (trimmed(elem(p, 'key')) === 'normal') { maps[s.attrs.id] = trimmed(elem(p, 'styleUrl')); } });
27376
+ });
27377
+ var resolve = function (url, depth) {
27378
+ var id = String(url || '').replace(/^[^#]*#/, '');
27379
+ if (styles[id]) { return styles[id]; }
27380
+ if (maps[id] && depth < 4) { return resolve(maps[id], depth + 1); }
27381
+ return null;
27382
+ };
27383
+ var features = descendants(doc, 'Placemark').map(function (pm) {
27384
+ var props = {};
27385
+ var name = elem(pm, 'name'), desc = elem(pm, 'description');
27386
+ if (name) { props.name = trimmed(name); }
27387
+ if (desc) { props.description = trimmed(desc); }
27388
+ var ext = elem(pm, 'ExtendedData');
27389
+ if (ext) {
27390
+ elems(ext, 'Data').forEach(function (d) { if (d.attrs.name !== undefined) { props[d.attrs.name] = trimmed(elem(d, 'value')); } });
27391
+ elems(ext, 'SchemaData').forEach(function (sd) { elems(sd, 'SimpleData').forEach(function (d) { if (d.attrs.name !== undefined) { props[d.attrs.name] = trimmed(d); } }); });
27392
+ }
27393
+ var style = {};
27394
+ var url = elem(pm, 'styleUrl');
27395
+ if (url) { Object.assign(style, kmlStyleProps(resolve(trimmed(url), 0))); }
27396
+ var inline = elem(pm, 'Style');
27397
+ if (inline) { Object.assign(style, kmlStyleProps(inline)); }
27398
+ ['marker-color', 'stroke', 'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity'].forEach(function (k) { if (style[k] !== undefined) { props[k] = style[k]; } });
27399
+ var geomEl = elems(pm).filter(function (c) { return GEOMETRY_ELEMENTS.indexOf(c.local) >= 0; });
27400
+ return feature(props, combine(geomEl.map(kmlGeometry).filter(Boolean)));
27401
+ });
27402
+ return fc(features);
27403
+ }
27404
+
27405
+ function readKmz(bytes) {
27406
+ var files = unzip(bytes).filter(function (f) { return /\.kml$/i.test(f.name); });
27407
+ if (!files.length) { throw new Error('sMap: the KMZ file holds no KML document'); }
27408
+ var main = files.filter(function (f) { return /(^|\/)doc\.kml$/i.test(f.name); })[0]
27409
+ || files.filter(function (f) { return f.name.indexOf('/') < 0; })[0] || files[0];
27410
+ return readKml(utf8(main.bytes()));
27411
+ }
27412
+
27413
+ var GPX_SKIP = ['ele', 'extensions', 'link', 'rtept', 'trkseg', 'trkpt'];
27414
+ function gpxProps(el) {
27415
+ var props = {};
27416
+ elems(el).forEach(function (c) {
27417
+ if (GPX_SKIP.indexOf(c.local) >= 0 || elems(c).length) { return; }
27418
+ props[c.local] = trimmed(c);
27419
+ });
27420
+ return props;
27421
+ }
27422
+ function gpxPoint(pt) {
27423
+ var lon = parseFloat(pt.attrs.lon), lat = parseFloat(pt.attrs.lat);
27424
+ if (!isFinite(lon) || !isFinite(lat)) { return null; }
27425
+ var ele = parseFloat(trimmed(elem(pt, 'ele')));
27426
+ return isFinite(ele) && elem(pt, 'ele') ? [lon, lat, ele] : [lon, lat];
27427
+ }
27428
+
27429
+ function readGpx(text) {
27430
+ var doc = parseXml(stripBom(text));
27431
+ var gpx = descendants(doc, 'gpx')[0];
27432
+ if (!gpx) { throw new Error('sMap: not a GPX document'); }
27433
+ var features = [];
27434
+ elems(gpx).forEach(function (el) {
27435
+ if (el.local === 'wpt') {
27436
+ var p = gpxPoint(el);
27437
+ if (p) { features.push(feature(gpxProps(el), { type: 'Point', coordinates: p })); }
27438
+ } else if (el.local === 'rte') {
27439
+ var r = elems(el, 'rtept').map(gpxPoint).filter(Boolean);
27440
+ if (r.length) { features.push(feature(gpxProps(el), { type: 'LineString', coordinates: r })); }
27441
+ } else if (el.local === 'trk') {
27442
+ var segs = elems(el, 'trkseg').map(function (s) { return elems(s, 'trkpt').map(gpxPoint).filter(Boolean); }).filter(function (s) { return s.length; });
27443
+ if (segs.length === 1) { features.push(feature(gpxProps(el), { type: 'LineString', coordinates: segs[0] })); }
27444
+ else if (segs.length > 1) { features.push(feature(gpxProps(el), { type: 'MultiLineString', coordinates: segs })); }
27445
+ }
27446
+ });
27447
+ return fc(features);
27448
+ }
27449
+
27450
+ /** Decodes an encoded polyline (precision 5: Google, OSRM; 6: Valhalla) to [[lng, lat], …]. */
27451
+ function decodePolyline(str, precision) {
27452
+ var factor = Math.pow(10, precision === undefined ? 5 : precision);
27453
+ str = String(str).trim();
27454
+ var i = 0, lat = 0, lng = 0, out = [];
27455
+ var next = function () {
27456
+ var result = 0, shift = 1, b;
27457
+ do {
27458
+ if (i >= str.length) { throw new Error('sMap: the polyline ends early'); }
27459
+ b = str.charCodeAt(i++) - 63;
27460
+ if (b < 0 || b > 63) { throw new Error('sMap: not an encoded polyline'); }
27461
+ result += (b & 0x1f) * shift;
27462
+ shift *= 32;
27463
+ } while (b >= 0x20);
27464
+ return result % 2 ? -(result + 1) / 2 : result / 2;
27465
+ };
27466
+ while (i < str.length) {
27467
+ lat += next();
27468
+ lng += next();
27469
+ out.push([lng / factor, lat / factor]);
27470
+ }
27471
+ return out;
27472
+ }
27473
+
27474
+ /** Encodes [[lng, lat], …] as a polyline. */
27475
+ function encodePolyline(coords, precision) {
27476
+ var factor = Math.pow(10, precision === undefined ? 5 : precision);
27477
+ var out = '', plat = 0, plng = 0;
27478
+ var enc = function (v) {
27479
+ var n = v < 0 ? -2 * v - 1 : 2 * v;
27480
+ while (n >= 32) { out += String.fromCharCode((32 | (n % 32)) + 63); n = Math.floor(n / 32); }
27481
+ out += String.fromCharCode(n + 63);
27482
+ };
27483
+ coords.forEach(function (c) {
27484
+ var lat = Math.round(c[1] * factor), lng = Math.round(c[0] * factor);
27485
+ enc(lat - plat); enc(lng - plng);
27486
+ plat = lat; plng = lng;
27487
+ });
27488
+ return out;
27489
+ }
27490
+
27491
+ function readPolyline(text, precision) {
27492
+ var c = decodePolyline(text, precision);
27493
+ return fc(c.length ? [feature({}, { type: 'LineString', coordinates: c })] : []);
27494
+ }
27495
+
27496
+ // WKT: one or more geometries, separated by white space, commas, semicolons or new lines.
27497
+ function readWkt(text) {
27498
+ var s = String(text), i = 0, features = [];
27499
+ var ws = function () { while (i < s.length && /[\s,;]/.test(s[i])) { i++; } };
27500
+ var spaces = function () { while (i < s.length && /\s/.test(s[i])) { i++; } };
27501
+ var word = function () { spaces(); var m = /^[A-Za-z]+/.exec(s.slice(i)); if (!m) { return ''; } i += m[0].length; return m[0].toUpperCase(); };
27502
+ var expect = function (c) { spaces(); if (s[i] !== c) { throw new Error('sMap: WKT: "' + c + '" expected at ' + i); } i++; };
27503
+ var peek = function () { spaces(); return s[i]; };
27504
+ var dims = { z: false, m: false };
27505
+ var point = function () {
27506
+ var nums = [];
27507
+ for (;;) {
27508
+ spaces();
27509
+ var m = /^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?/.exec(s.slice(i));
27510
+ if (!m) { break; }
27511
+ nums.push(Number(m[0])); i += m[0].length;
27512
+ }
27513
+ if (nums.length < 2) { throw new Error('sMap: WKT: a position needs two numbers at ' + i); }
27514
+ var hasZ = dims.z || (!dims.m && nums.length >= 3);
27515
+ return hasZ && nums.length >= 3 ? [nums[0], nums[1], nums[2]] : [nums[0], nums[1]];
27516
+ };
27517
+ var list = function (item) {
27518
+ expect('(');
27519
+ var out = [item()];
27520
+ while (peek() === ',') { i++; out.push(item()); }
27521
+ expect(')');
27522
+ return out;
27523
+ };
27524
+ var empty = function () { var save = i; if (word() === 'EMPTY') { return true; } i = save; return false; };
27525
+ var geometry = function () {
27526
+ spaces();
27527
+ var m = /^SRID=\d+;/i.exec(s.slice(i));
27528
+ if (m) { i += m[0].length; }
27529
+ var type = word();
27530
+ if (!type) { throw new Error('sMap: WKT: a geometry type expected at ' + i); }
27531
+ var flag = null;
27532
+ // The flag may be glued to the type: POINTZ, LINESTRINGM, POLYGONZM are POINT Z, LINESTRING M, POLYGON ZM.
27533
+ var glued = /^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)(ZM|Z|M)$/.exec(type);
27534
+ if (glued) { type = glued[1]; flag = glued[2]; } else {
27535
+ var save = i;
27536
+ flag = word();
27537
+ if (flag !== 'Z' && flag !== 'M' && flag !== 'ZM') { i = save; flag = null; }
27538
+ }
27539
+ dims = { z: flag === 'Z' || flag === 'ZM', m: flag === 'M' || flag === 'ZM' };
27540
+ if (empty()) { return null; }
27541
+ var ring = function () { return list(point); };
27542
+ switch (type) {
27543
+ case 'POINT': return { type: 'Point', coordinates: list(point)[0] };
27544
+ case 'LINESTRING': return { type: 'LineString', coordinates: list(point) };
27545
+ case 'POLYGON': return { type: 'Polygon', coordinates: list(ring) };
27546
+ case 'MULTIPOINT': return { type: 'MultiPoint', coordinates: list(function () { if (peek() === '(') { return list(point)[0]; } return point(); }) };
27547
+ case 'MULTILINESTRING': return { type: 'MultiLineString', coordinates: list(ring) };
27548
+ case 'MULTIPOLYGON': return { type: 'MultiPolygon', coordinates: list(function () { return list(ring); }) };
27549
+ case 'GEOMETRYCOLLECTION': {
27550
+ var outer = dims;
27551
+ var g = list(function () { var x = geometry(); dims = outer; return x; }).filter(Boolean);
27552
+ return { type: 'GeometryCollection', geometries: g };
27553
+ }
27554
+ }
27555
+ throw new Error('sMap: WKT: unknown geometry type ' + type);
27556
+ };
27557
+ ws();
27558
+ while (i < s.length) { features.push(feature({}, geometry())); ws(); }
27559
+ return fc(features);
27560
+ }
27561
+
27562
+ function readWkbGeometry(bytes, at) {
27563
+ var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), p = at.p;
27564
+ var little = bytes[p] === 1; p += 1;
27565
+ var raw = dv.getUint32(p, little); p += 4;
27566
+ var z = !!(raw & 0x80000000), m = !!(raw & 0x40000000);
27567
+ if (raw & 0x20000000) { p += 4; }
27568
+ var type = raw & 0x0fffffff;
27569
+ if (type >= 1000) { var k = Math.floor(type / 1000); z = z || k === 1 || k === 3; m = m || k === 2 || k === 3; type %= 1000; }
27570
+ var dims = 2 + (z ? 1 : 0) + (m ? 1 : 0);
27571
+ var pos = function () {
27572
+ var c = [dv.getFloat64(p, little), dv.getFloat64(p + 8, little)];
27573
+ if (z) { c.push(dv.getFloat64(p + 16, little)); }
27574
+ p += 8 * dims;
27575
+ return c;
27576
+ };
27577
+ var u32 = function () { var v = dv.getUint32(p, little); p += 4; return v; };
27578
+ var seq = function () { var n = u32(), out = []; for (var i = 0; i < n; i++) { out.push(pos()); } return out; };
27579
+ var parts = function () { var n = u32(), out = []; at.p = p; for (var i = 0; i < n; i++) { out.push(readWkbGeometry(bytes, at)); } p = at.p; return out; };
27580
+ var g;
27581
+ switch (type) {
27582
+ case 1: { var c = pos(); g = c.every(function (v) { return isNaN(v); }) ? null : { type: 'Point', coordinates: c }; break; }
27583
+ case 2: g = { type: 'LineString', coordinates: seq() }; break;
27584
+ case 3: { var n = u32(), rings = []; for (var r = 0; r < n; r++) { rings.push(seq()); } g = { type: 'Polygon', coordinates: rings }; break; }
27585
+ case 4: g = { type: 'MultiPoint', coordinates: parts().filter(Boolean).map(function (x) { return x.coordinates; }) }; break;
27586
+ case 5: g = { type: 'MultiLineString', coordinates: parts().filter(Boolean).map(function (x) { return x.coordinates; }) }; break;
27587
+ case 6: g = { type: 'MultiPolygon', coordinates: parts().filter(Boolean).map(function (x) { return x.coordinates; }) }; break;
27588
+ case 7: g = { type: 'GeometryCollection', geometries: parts().filter(Boolean) }; break;
27589
+ default: throw new Error('sMap: WKB: unknown geometry type ' + type);
27590
+ }
27591
+ if (g && g.type !== 'Point' && g.type !== 'GeometryCollection' && g.coordinates.length === 0) { g = null; }
27592
+ if (g && g.type === 'GeometryCollection' && g.geometries.length === 0) { g = null; }
27593
+ at.p = p;
27594
+ return g;
27595
+ }
27596
+
27597
+ // WKB or EWKB: bytes, or hex text (one geometry per line; a leading \x is allowed).
27598
+ function readWkb(input) {
27599
+ var chunks = [];
27600
+ if (typeof input === 'string') {
27601
+ input.split(/\s+/).forEach(function (h) {
27602
+ h = h.replace(/^\\x/i, '');
27603
+ if (!h) { return; }
27604
+ if (!/^[0-9a-fA-F]+$/.test(h) || h.length % 2) { throw new Error('sMap: WKB text must be hexadecimal'); }
27605
+ var b = new Uint8Array(h.length / 2);
27606
+ for (var i = 0; i < b.length; i++) { b[i] = parseInt(h.substr(i * 2, 2), 16); }
27607
+ chunks.push(b);
27608
+ });
27609
+ } else { chunks.push(toBytes(input)); }
27610
+ var features = [];
27611
+ chunks.forEach(function (b) {
27612
+ var at = { p: 0 };
27613
+ while (at.p < b.length) { features.push(feature({}, readWkbGeometry(b, at))); }
27614
+ });
27615
+ return fc(features);
27616
+ }
27617
+
27618
+ var LAT_HEADINGS = ['lat', 'latitude', 'y', 'lat_dd', 'latitude_dd', 'ycoord', 'y_coord', 'point_y', '纬度'];
27619
+ var LNG_HEADINGS = ['lng', 'lon', 'long', 'longitude', 'x', 'lng_dd', 'lon_dd', 'longitude_dd', 'xcoord', 'x_coord', 'point_x', '经度'];
27620
+
27621
+ function csvRows(text, delim) {
27622
+ var rows = [], row = [], field = '', q = false, i = 0, s = text;
27623
+ for (; i < s.length; i++) {
27624
+ var c = s[i];
27625
+ if (q) {
27626
+ if (c === '"') { if (s[i + 1] === '"') { field += '"'; i++; } else { q = false; } } else { field += c; }
27627
+ } else if (c === '"' && field === '') { q = true; }
27628
+ else if (c === delim) { row.push(field); field = ''; }
27629
+ else if (c === '\n' || c === '\r') {
27630
+ if (c === '\r' && s[i + 1] === '\n') { i++; }
27631
+ row.push(field); field = ''; rows.push(row); row = [];
27632
+ } else { field += c; }
27633
+ }
27634
+ if (field !== '' || row.length) { row.push(field); rows.push(row); }
27635
+ return rows.filter(function (r) { return !(r.length === 1 && r[0].trim() === ''); });
27636
+ }
27637
+
27638
+ function readCsv(text, o) {
27639
+ text = stripBom(String(text));
27640
+ var firstLine = text.split(/\r?\n/)[0] || '';
27641
+ var delim = [',', ';', '\t', '|'].map(function (d) { return [d, firstLine.split(d).length]; }).sort(function (a, b) { return b[1] - a[1]; })[0][0];
27642
+ var rows = csvRows(text, delim);
27643
+ if (!rows.length) { return fc([]); }
27644
+ var head = rows[0].map(function (h) { return h.trim(); });
27645
+ var norm = head.map(function (h) { return h.toLowerCase().replace(/\s+/g, '_'); });
27646
+ var find = function (given, candidates, what) {
27647
+ if (given !== undefined && given !== null) {
27648
+ var at = head.indexOf(String(given));
27649
+ if (at < 0) { at = norm.indexOf(String(given).toLowerCase()); }
27650
+ if (at < 0) { throw new Error('sMap: CSV has no column "' + given + '"'); }
27651
+ return at;
27652
+ }
27653
+ for (var i = 0; i < candidates.length; i++) { var k = norm.indexOf(candidates[i]); if (k >= 0) { return k; } }
27654
+ throw new Error('sMap: CSV: no ' + what + ' column found; name it with the ' + what + ' option');
27655
+ };
27656
+ var la = find(o.lat, LAT_HEADINGS, 'lat'), ln = find(o.lng, LNG_HEADINGS, 'lng');
27657
+ // A coordinate is a whole number text (never a prefix of one); with ';' or tab delimiters a decimal comma counts.
27658
+ var decimalComma = delim === ';' || delim === '\t';
27659
+ var coord = function (v) {
27660
+ v = (v === undefined ? '' : v).trim();
27661
+ if (decimalComma && /^[-+]?\d*,\d+([eE][-+]?\d+)?$/.test(v)) { v = v.replace(',', '.'); }
27662
+ return /^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(v) ? Number(v) : NaN;
27663
+ };
27664
+ var features = [];
27665
+ for (var r = 1; r < rows.length; r++) {
27666
+ var row = rows[r].map(function (cell) { return cell.trim(); });
27667
+ var lat = coord(row[la]), lng = coord(row[ln]);
27668
+ if (!isFinite(lat) || !isFinite(lng) || Math.abs(lat) > 90 || Math.abs(lng) > 180) { continue; }
27669
+ var props = {};
27670
+ for (var c = 0; c < head.length; c++) { if (c !== la && c !== ln) { props[head[c]] = row[c] !== undefined ? row[c] : ''; } }
27671
+ features.push(feature(props, { type: 'Point', coordinates: [lng, lat] }));
27672
+ }
27673
+ return fc(features);
27674
+ }
27675
+
27676
+ function readTopojson(topo) {
27677
+ if (!topo || topo.type !== 'Topology') { throw new Error('sMap: not a TopoJSON topology'); }
27678
+ var tr = topo.transform;
27679
+ var kx = tr ? tr.scale[0] : 1, ky = tr ? tr.scale[1] : 1, dx = tr ? tr.translate[0] : 0, dy = tr ? tr.translate[1] : 0;
27680
+ var arcs = (topo.arcs || []).map(function (arc) {
27681
+ var x = 0, y = 0;
27682
+ return arc.map(function (p) {
27683
+ if (tr) { x += p[0]; y += p[1]; var q = [x * kx + dx, y * ky + dy]; return p.length > 2 ? q.concat(p.slice(2)) : q; }
27684
+ return p.slice();
27685
+ });
27686
+ });
27687
+ var arc = function (i) { return i < 0 ? arcs[~i].slice().reverse() : arcs[i]; };
27688
+ var line = function (ids) {
27689
+ var out = [];
27690
+ ids.forEach(function (id) { var a = arc(id); out = out.concat(out.length ? a.slice(1) : a); });
27691
+ return out;
27692
+ };
27693
+ var pt = function (p) { if (!tr) { return p.slice(); } var q = [p[0] * kx + dx, p[1] * ky + dy]; return p.length > 2 ? q.concat(p.slice(2)) : q; };
27694
+ var geom = function (o) {
27695
+ switch (o.type) {
27696
+ case 'Point': return { type: 'Point', coordinates: pt(o.coordinates) };
27697
+ case 'MultiPoint': return { type: 'MultiPoint', coordinates: o.coordinates.map(pt) };
27698
+ case 'LineString': return { type: 'LineString', coordinates: line(o.arcs) };
27699
+ case 'MultiLineString': return { type: 'MultiLineString', coordinates: o.arcs.map(line) };
27700
+ case 'Polygon': return { type: 'Polygon', coordinates: o.arcs.map(line) };
27701
+ case 'MultiPolygon': return { type: 'MultiPolygon', coordinates: o.arcs.map(function (p) { return p.map(line); }) };
27702
+ case 'GeometryCollection': return { type: 'GeometryCollection', geometries: (o.geometries || []).map(geom).filter(Boolean) };
27703
+ }
27704
+ return null;
27705
+ };
27706
+ var features = [];
27707
+ Object.keys(topo.objects || {}).forEach(function (name) {
27708
+ var o = topo.objects[name];
27709
+ var list = o.type === 'GeometryCollection' ? o.geometries || [] : [o];
27710
+ list.forEach(function (g) { features.push(feature(g.properties ? JSON.parse(JSON.stringify(g.properties)) : {}, geom(g), g.id)); });
27711
+ });
27712
+ return fc(features);
27713
+ }
27714
+
27715
+ function readGeojson(data) {
27716
+ if (!data) { return fc([]); }
27717
+ var norm = function (f) { return feature(f.properties || {}, f.geometry || null, f.id); };
27718
+ if (data.type === 'FeatureCollection') { return fc((data.features || []).map(norm)); }
27719
+ if (data.type === 'Feature') { return fc([norm(data)]); }
27720
+ if (GEOMETRY_TYPES.indexOf(data.type) >= 0) { return fc([feature({}, data)]); }
27721
+ throw new Error('sMap: not GeoJSON');
27722
+ }
27723
+
27724
+ var EXTENSIONS = { kml: 'kml', kmz: 'kmz', gpx: 'gpx', wkt: 'wkt', wkb: 'wkb', csv: 'csv', tsv: 'csv', txt: null,
27725
+ topojson: 'topojson', geojson: 'geojson', json: null, polyline: 'polyline', polyline6: 'polyline6' };
27726
+
27727
+ /** The format of a file name's extension, or null. */
27728
+ function formatOfName(name) {
27729
+ var m = /\.([A-Za-z0-9]+)$/.exec(String(name || ''));
27730
+ return m && EXTENSIONS[m[1].toLowerCase()] ? EXTENSIONS[m[1].toLowerCase()] : null;
27731
+ }
27732
+
27733
+ /** Guesses the format from the content: zip → kmz, XML root kml / gpx, JSON Topology / GeoJSON, WKT, hex WKB, CSV. */
27734
+ function sniff(input) {
27735
+ var bytes = typeof input === 'string' || (input && typeof input === 'object' && !toBytes(input)) ? null : toBytes(input);
27736
+ if (bytes && bytes[0] === 0x50 && bytes[1] === 0x4b) { return 'kmz'; }
27737
+ if (bytes && (bytes[0] === 0 || bytes[0] === 1) && bytes.length >= 21) { return 'wkb'; }
27738
+ if (input && typeof input === 'object' && !bytes) { return input.type === 'Topology' ? 'topojson' : 'geojson'; }
27739
+ var s = stripBom(bytes ? utf8(bytes) : String(input)).trim();
27740
+ if (s[0] === '<') {
27741
+ if (/<kml[\s>]/i.test(s)) { return 'kml'; }
27742
+ if (/<gpx[\s>]/i.test(s)) { return 'gpx'; }
27743
+ return null;
27744
+ }
27745
+ if (s[0] === '{') { return /"type"\s*:\s*"Topology"/.test(s) ? 'topojson' : 'geojson'; }
27746
+ if (/^(SRID=\d+;)?\s*(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)\b/i.test(s)) { return 'wkt'; }
27747
+ if (/^(\\x)?0[01][0-9a-fA-F]{8}/.test(s) && /^[\s0-9a-fA-Fx\\]+$/.test(s)) { return 'wkb'; }
27748
+ var first = s.split(/\r?\n/)[0].toLowerCase();
27749
+ if (/[,;\t|]/.test(first) && LAT_HEADINGS.concat(LNG_HEADINGS).some(function (h) { return first.split(/[,;\t|]/).map(function (x) { return x.trim().replace(/^"|"$/g, '').replace(/\s+/g, '_'); }).indexOf(h) >= 0; })) { return 'csv'; }
27750
+ return null;
27751
+ }
27752
+
27753
+ /**
27754
+ * Reads a file into a GeoJSON FeatureCollection, on the device (M29/M30).
27755
+ * @param input text, bytes (ArrayBuffer / Uint8Array), or a parsed JSON object (GeoJSON, TopoJSON)
27756
+ * @param o {format, lat, lng, name}: format one of FORMATS (guessed from `name`'s extension or the content when
27757
+ * omitted; polylines are never guessed); lat / lng name the CSV columns (guessed from common headings)
27758
+ */
27759
+ function read(input, o) {
27760
+ o = o || {};
27761
+ var format = o.format ? String(o.format).toLowerCase() : (formatOfName(o.name) || sniff(input));
27762
+ if (!format) { throw new Error('sMap: the file format could not be recognised; pass {format}'); }
27763
+ if (FORMATS.indexOf(format) < 0) { throw new Error('sMap: unknown format "' + format + '"'); }
27764
+ var text = function () { if (typeof input === 'string') { return input; } var b = toBytes(input); if (!b) { throw new Error('sMap: ' + format + ' needs text or bytes'); } return utf8(b); };
27765
+ var json = function () { return input && typeof input === 'object' && !toBytes(input) ? input : JSON.parse(stripBom(text())); };
27766
+ switch (format) {
27767
+ case 'kml': return readKml(text());
27768
+ case 'kmz': { var b = toBytes(input); if (!b) { throw new Error('sMap: kmz needs bytes'); } return readKmz(b); }
27769
+ case 'gpx': return readGpx(text());
27770
+ case 'polyline': return readPolyline(text(), 5);
27771
+ case 'polyline6': return readPolyline(text(), 6);
27772
+ case 'wkt': return readWkt(text());
27773
+ case 'wkb': return readWkb(typeof input === 'string' ? input : (toBytes(input) && /^[\s0-9a-fA-Fx\\]+$/.test(utf8(toBytes(input)).trim()) && toBytes(input)[0] !== 0 && toBytes(input)[0] !== 1 ? utf8(toBytes(input)).trim() : input));
27774
+ case 'csv': return readCsv(text(), o);
27775
+ case 'topojson': return readTopojson(json());
27776
+ case 'geojson': return readGeojson(json());
27777
+ }
27778
+ return fc([]);
27779
+ }
27780
+
27781
+ /** A Blob or File (or a fetch Response) read as `read` would: the format comes from the file name when not given. */
27782
+ function readFile(file, o) {
27783
+ o = Object.assign({}, o || {});
27784
+ if (!o.name && file && file.name) { o.name = file.name; }
27785
+ if (!o.name && file && file.url) { o.name = String(file.url).split('?')[0]; }
27786
+ return Promise.resolve(file.arrayBuffer()).then(function (buf) { return read(new Uint8Array(buf), o); });
27787
+ }
27788
+
27789
+ // ------------------------------------------------------------------ formats: write
27790
+
27791
+ function xmlEscape(v) { return String(v).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;' }[c]; }); }
27792
+
27793
+ // '#rgb' / '#rrggbb' + opacity → KML aabbggrr; anything else null.
27794
+ function toKmlColor(color, opacity) {
27795
+ var m = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(String(color || '').trim());
27796
+ if (!m) { return null; }
27797
+ var h = m[1].length === 3 ? m[1].replace(/./g, '$&$&') : m[1];
27798
+ var a = Math.round(Math.max(0, Math.min(1, opacity === undefined || opacity === null || opacity === '' ? 1 : Number(opacity))) * 255);
27799
+ var aa = (a < 16 ? '0' : '') + a.toString(16);
27800
+ return (aa + h.slice(4, 6) + h.slice(2, 4) + h.slice(0, 2)).toLowerCase();
27801
+ }
27802
+
27803
+ var SIMPLESTYLE = ['marker-color', 'marker-size', 'marker-symbol', 'stroke', 'stroke-width', 'stroke-opacity', 'fill', 'fill-opacity', 'title', 'description'];
27804
+
27805
+ function kmlCoordText(c) { return c.map(function (p) { return p.slice(0, 3).join(','); }).join(' '); }
27806
+ function kmlGeom(g) {
27807
+ if (!g) { return ''; }
27808
+ switch (g.type) {
27809
+ case 'Point': return '<Point><coordinates>' + kmlCoordText([g.coordinates]) + '</coordinates></Point>';
27810
+ case 'LineString': return '<LineString><coordinates>' + kmlCoordText(g.coordinates) + '</coordinates></LineString>';
27811
+ case 'Polygon': return '<Polygon>' + g.coordinates.map(function (r, i) {
27812
+ var tag = i === 0 ? 'outerBoundaryIs' : 'innerBoundaryIs';
27813
+ return '<' + tag + '><LinearRing><coordinates>' + kmlCoordText(r) + '</coordinates></LinearRing></' + tag + '>';
27814
+ }).join('') + '</Polygon>';
27815
+ case 'MultiPoint': return '<MultiGeometry>' + g.coordinates.map(function (c) { return kmlGeom({ type: 'Point', coordinates: c }); }).join('') + '</MultiGeometry>';
27816
+ case 'MultiLineString': return '<MultiGeometry>' + g.coordinates.map(function (c) { return kmlGeom({ type: 'LineString', coordinates: c }); }).join('') + '</MultiGeometry>';
27817
+ case 'MultiPolygon': return '<MultiGeometry>' + g.coordinates.map(function (c) { return kmlGeom({ type: 'Polygon', coordinates: c }); }).join('') + '</MultiGeometry>';
27818
+ case 'GeometryCollection': return '<MultiGeometry>' + g.geometries.map(kmlGeom).join('') + '</MultiGeometry>';
27819
+ }
27820
+ return '';
27821
+ }
27822
+
27823
+ /**
27824
+ * A FeatureCollection as a KML 2.2 document: one Placemark per feature (name from `name` or `title`, `description`,
27825
+ * simplestyle properties as an inline Style, every other property as ExtendedData).
27826
+ */
27827
+ function writeKml(data, o) {
27828
+ o = o || {};
27829
+ var features = readGeojson(data).features;
27830
+ var out = '<?xml version="1.0" encoding="UTF-8"?>\n<kml xmlns="http://www.opengis.net/kml/2.2"><Document>' +
27831
+ (o.name ? '<name>' + xmlEscape(o.name) + '</name>' : '') + '\n';
27832
+ features.forEach(function (f) {
27833
+ var p = f.properties || {};
27834
+ var name = p.name !== undefined && p.name !== null ? p.name : p.title;
27835
+ var s = '<Placemark>';
27836
+ if (name !== undefined && name !== null) { s += '<name>' + xmlEscape(name) + '</name>'; }
27837
+ if (p.description !== undefined && p.description !== null) { s += '<description>' + xmlEscape(p.description) + '</description>'; }
27838
+ var style = '';
27839
+ var icon = toKmlColor(p['marker-color']);
27840
+ if (icon) { style += '<IconStyle><color>' + icon + '</color></IconStyle>'; }
27841
+ var line = toKmlColor(p.stroke || (p['stroke-opacity'] !== undefined || p['stroke-width'] !== undefined ? '#555555' : null), p['stroke-opacity']);
27842
+ if (line || p['stroke-width'] !== undefined) {
27843
+ style += '<LineStyle>' + (line ? '<color>' + line + '</color>' : '') + (p['stroke-width'] !== undefined ? '<width>' + Number(p['stroke-width']) + '</width>' : '') + '</LineStyle>';
27844
+ }
27845
+ var poly = toKmlColor(p.fill || (p['fill-opacity'] !== undefined ? '#555555' : null), p['fill-opacity']);
27846
+ if (poly) { style += '<PolyStyle><color>' + poly + '</color></PolyStyle>'; }
27847
+ if (style) { s += '<Style>' + style + '</Style>'; }
27848
+ var extra = Object.keys(p).filter(function (k) { return k !== 'name' && SIMPLESTYLE.indexOf(k) < 0 && p[k] !== null && p[k] !== undefined; });
27849
+ if (p.name !== undefined && p.name !== null && p.title !== undefined && p.title !== null) { extra.push('title'); }
27850
+ if (extra.length) {
27851
+ s += '<ExtendedData>' + extra.map(function (k) {
27852
+ var v = p[k];
27853
+ return '<Data name="' + xmlEscape(k) + '"><value>' + xmlEscape(typeof v === 'object' ? JSON.stringify(v) : v) + '</value></Data>';
27854
+ }).join('') + '</ExtendedData>';
27855
+ }
27856
+ out += s + kmlGeom(f.geometry) + '</Placemark>\n';
27857
+ });
27858
+ return out + '</Document></kml>\n';
27859
+ }
27860
+
27861
+ /** Writes a FeatureCollection as 'geojson' (text) or 'kml'. */
27862
+ function write(data, format, o) {
27863
+ format = String(format || 'geojson').toLowerCase();
27864
+ if (format === 'geojson') { return JSON.stringify(readGeojson(data)); }
27865
+ if (format === 'kml') { return writeKml(data, o); }
27866
+ throw new Error('sMap: cannot write format "' + format + '" (geojson, kml)');
27867
+ }
27868
+
27869
+ // ------------------------------------------------------------------ the converting endpoint (contract §6)
27870
+
27871
+ var CONVERT_LIMIT = 20 * 1024 * 1024;
27872
+
27873
+ /**
27874
+ * Converts a file on sMap (`POST /v1/convert`, contract §6): for large files or formats a caller cannot parse.
27875
+ * @param input a Blob / File, bytes or text
27876
+ * @param o {format (guessed from the file name or content when omitted), lat, lng, baseUrl, apiKey, token}
27877
+ * @returns Promise of a GeoJSON FeatureCollection
27878
+ */
27879
+ function convert(input, o) {
27880
+ o = o || {};
27881
+ if (typeof fetch === 'undefined') { return Promise.reject(new Error('sMap: convert needs fetch')); }
27882
+ var base = String(o.baseUrl || DEFAULT_BASE).replace(/\/$/, '');
27883
+ var bodyOf = input && typeof input.arrayBuffer === 'function' && !(input instanceof Uint8Array)
27884
+ ? Promise.resolve(input.arrayBuffer()).then(function (b) { return new Uint8Array(b); })
27885
+ : Promise.resolve(typeof input === 'string' ? input : toBytes(input));
27886
+ return bodyOf.then(function (body) {
27887
+ var size = typeof body === 'string' ? body.length : body.length;
27888
+ if (size > CONVERT_LIMIT) { throw new Error('sMap: convert takes files of at most 20 MB'); }
27889
+ var format = o.format ? String(o.format).toLowerCase() : (formatOfName(o.name || (input && input.name)) || sniff(body));
27890
+ if (!format || FORMATS.indexOf(format) < 0) { throw new Error('sMap: the file format could not be recognised; pass {format}'); }
27891
+ var url = base + '/v1/convert?from=' + encodeURIComponent(format);
27892
+ if (o.lat) { url += '&lat=' + encodeURIComponent(o.lat); }
27893
+ if (o.lng) { url += '&lng=' + encodeURIComponent(o.lng); }
27894
+ var headers = { 'Content-Type': 'application/octet-stream' };
27895
+ if (o.token) { headers.Authorization = 'Bearer ' + o.token; }
27896
+ if (o.apiKey) { headers['X-SMap-Key'] = o.apiKey; }
27897
+ return fetch(url, { method: 'POST', headers: headers, body: body }).then(function (res) {
27898
+ if (!res.ok) {
27899
+ return res.text().then(function (t) {
27900
+ var msg = t;
27901
+ try { var j = JSON.parse(t); msg = j.message || j.error || t; } catch (e) { /* plain text */ }
27902
+ throw new Error('sMap: convert failed (HTTP ' + res.status + ')' + (msg ? ': ' + msg : ''));
27903
+ });
27904
+ }
27905
+ return res.json();
27906
+ });
27907
+ });
27908
+ }
27909
+
27910
+ // ------------------------------------------------------------------ URLs
27911
+
27912
+ function styleName(style) {
27913
+ style = String(style || 'light');
27914
+ if (FLAVORS.indexOf(style) >= 0 || /^custom:[A-Za-z0-9_-]+$/.test(style)) { return style; }
27915
+ throw new Error('sMap: unknown style "' + style + '" (a scheme or custom:<id>)');
27916
+ }
27917
+
27918
+ function query(o) {
27919
+ var q = [];
27920
+ if (o.lang) { q.push('lang=' + encodeURIComponent(o.lang)); }
27921
+ if (o.apiKey) { q.push('key=' + encodeURIComponent(o.apiKey)); }
27922
+ return q.length ? '?' + q.join('&') : '';
27923
+ }
27924
+
27925
+ /** The style URL of a scheme or a custom style (`custom:<id>` → /v1/styles/custom/<id>). */
27926
+ function styleUrl(style, o) {
27927
+ o = o || {};
27928
+ var base = String(o.baseUrl || DEFAULT_BASE).replace(/\/$/, '');
27929
+ var s = styleName(style);
27930
+ var path = s.indexOf('custom:') === 0 ? '/v1/styles/custom/' + encodeURIComponent(s.slice(7)) : '/v1/styles/' + s;
27931
+ return base + path + query({ lang: o.lang || o.language || 'en', apiKey: o.apiKey });
27932
+ }
27933
+
27934
+ /**
27935
+ * sMap's raster tiles (contract §5) as an XYZ template, for tools without vector tiles:
27936
+ * `${baseUrl}/v1/raster/<style>/{z}/{x}/{y}[@2x].png?lang=…[&key=…]`.
27937
+ * @param o {lang, retina (512 px tiles), baseUrl, apiKey}
27938
+ */
27939
+ function rasterUrl(style, o) {
27940
+ o = o || {};
27941
+ var base = String(o.baseUrl || DEFAULT_BASE).replace(/\/$/, '');
27942
+ return base + '/v1/raster/' + styleName(style) + '/{z}/{x}/{y}' + (o.retina ? '@2x' : '') + '.png' + query({ lang: o.lang || o.language, apiKey: o.apiKey });
27943
+ }
27944
+
27945
+ /** The TileJSON of sMap's raster tiles of a style. */
27946
+ function rasterTileJsonUrl(style, o) {
27947
+ o = o || {};
27948
+ var base = String(o.baseUrl || DEFAULT_BASE).replace(/\/$/, '');
27949
+ return base + '/v1/raster/' + styleName(style) + '.json' + query({ lang: o.lang || o.language, apiKey: o.apiKey });
27950
+ }
27951
+
27952
+ // Google and Bing tiles are never offered: their licences forbid it (M31).
27953
+ var FORBIDDEN_HOSTS = /(^|\.)(google\.[a-z.]+|googleapis\.com|gstatic\.com|ggpht\.com|virtualearth\.net|bing\.com|bing\.net)$/i;
27954
+ function refuseForbidden(urls) {
27955
+ (urls || []).forEach(function (u) {
27956
+ var m = /^[a-z][a-z0-9+.-]*:\/\/([^/?#:]+)/i.exec(String(u).replace(/^pmtiles:\/\//i, ''));
27957
+ if (m && FORBIDDEN_HOSTS.test(m[1])) { throw new Error('sMap: Google and Bing tiles cannot be used (their licences do not allow it)'); }
27958
+ });
27959
+ }
27960
+
27961
+ /**
27962
+ * A WMS GetMap tile URL template (Web Mercator, the engine fills {bbox-epsg-3857}).
27963
+ * @param o {url, layers, styles, format, transparent, version ('1.3.0' | '1.1.1'), tileSize, params}
27964
+ */
27965
+ function wmsTileUrl(o) {
27966
+ if (!o || !o.url || !o.layers) { throw new Error('sMap: a WMS layer needs url and layers'); }
27967
+ var version = o.version || '1.3.0', size = o.tileSize || 256;
27968
+ var p = {
27969
+ SERVICE: 'WMS', REQUEST: 'GetMap', VERSION: version,
27970
+ LAYERS: Array.isArray(o.layers) ? o.layers.join(',') : o.layers,
27971
+ STYLES: Array.isArray(o.styles) ? o.styles.join(',') : (o.styles || ''),
27972
+ FORMAT: o.format || 'image/png', TRANSPARENT: o.transparent === false ? 'FALSE' : 'TRUE',
27973
+ WIDTH: String(size), HEIGHT: String(size),
27974
+ };
27975
+ p[version === '1.3.0' ? 'CRS' : 'SRS'] = 'EPSG:3857';
27976
+ Object.keys(o.params || {}).forEach(function (k) { p[k] = String(o.params[k]); });
27977
+ var url = String(o.url);
27978
+ var sep = url.indexOf('?') < 0 ? '?' : (/[?&]$/.test(url) ? '' : '&');
27979
+ return url + sep + Object.keys(p).map(function (k) { return k + '=' + encodeURIComponent(p[k]); }).join('&') + '&BBOX={bbox-epsg-3857}';
27980
+ }
27981
+
27982
+ // ------------------------------------------------------------------ WMTS capabilities
27983
+
27984
+ var WEB_MERCATOR_SCALE0 = 559082264.0287178; // scale denominator of zoom 0 (256 px tiles, 0.28 mm pixels)
27985
+ var MERCATOR_CRS = /(3857|900913|3785|102100|102113|GoogleMapsCompatible)/i;
27986
+
27987
+ /** Reads WMTS 1.0.0 capabilities: {layers: [{id, title, styles, formats, tileMatrixSets, resourceUrls, bounds}], tileMatrixSets: {id: {crs, matrices: [{id, zoom}]}}, getTile: kvp href}. */
27988
+ function parseWmtsCapabilities(xml) {
27989
+ var doc = parseXml(stripBom(String(xml)));
27990
+ var caps = descendants(doc, 'Capabilities')[0] || doc;
27991
+ var contents = elem(caps, 'Contents');
27992
+ if (!contents) { throw new Error('sMap: not WMTS capabilities'); }
27993
+ var sets = {};
27994
+ elems(contents, 'TileMatrixSet').forEach(function (s) {
27995
+ var id = trimmed(elem(s, 'Identifier'));
27996
+ var crs = trimmed(elem(s, 'SupportedCRS'));
27997
+ var matrices = elems(s, 'TileMatrix').map(function (m) {
27998
+ var scale = parseFloat(trimmed(elem(m, 'ScaleDenominator')));
27999
+ return { id: trimmed(elem(m, 'Identifier')), zoom: Math.round(Math.log(WEB_MERCATOR_SCALE0 / scale) / Math.LN2), tileWidth: parseInt(trimmed(elem(m, 'TileWidth')), 10) || 256 };
28000
+ });
28001
+ sets[id] = { id: id, crs: crs, wellKnown: trimmed(elem(s, 'WellKnownScaleSet')), matrices: matrices };
28002
+ });
28003
+ var layers = elems(contents, 'Layer').map(function (l) {
28004
+ var bbox = elem(l, 'WGS84BoundingBox');
28005
+ var lower = bbox ? trimmed(elem(bbox, 'LowerCorner')).split(/\s+/).map(Number) : null;
28006
+ var upper = bbox ? trimmed(elem(bbox, 'UpperCorner')).split(/\s+/).map(Number) : null;
28007
+ return {
28008
+ id: trimmed(elem(l, 'Identifier')),
28009
+ title: trimmed(elem(l, 'Title')),
28010
+ styles: elems(l, 'Style').map(function (s) { return { id: trimmed(elem(s, 'Identifier')), isDefault: s.attrs.isDefault === 'true' }; }),
28011
+ formats: elems(l, 'Format').map(trimmed),
28012
+ tileMatrixSets: elems(l, 'TileMatrixSetLink').map(function (k) { return trimmed(elem(k, 'TileMatrixSet')); }),
28013
+ resourceUrls: elems(l, 'ResourceURL').filter(function (r) { return (r.attrs.resourceType || 'tile') === 'tile'; }).map(function (r) { return { format: r.attrs.format, template: r.attrs.template }; }),
28014
+ bounds: lower && upper && lower.length >= 2 && upper.length >= 2 ? [lower[0], lower[1], upper[0], upper[1]] : null,
28015
+ };
28016
+ });
28017
+ var getTile = null;
28018
+ descendants(caps, 'Operation').forEach(function (op) {
28019
+ if (op.attrs.name !== 'GetTile') { return; }
28020
+ descendants(op, 'Get').forEach(function (g) {
28021
+ var enc = descendants(g, 'Value').map(trimmed);
28022
+ if (!getTile && (!enc.length || enc.indexOf('KVP') >= 0)) { getTile = attr(g, 'href'); }
28023
+ });
28024
+ });
28025
+ return { layers: layers, tileMatrixSets: sets, getTile: getTile };
28026
+ }
28027
+
28028
+ // The zoom → matrix identifier rule of a set, as a template piece for {z}: identifiers equal to the zoom, or one
28029
+ // prefix followed by the zoom (EPSG:3857:0, …). Anything else cannot be a {z} template.
28030
+ function matrixTemplate(set) {
28031
+ var ms = set.matrices.slice().sort(function (a, b) { return a.zoom - b.zoom; });
28032
+ if (!ms.length) { throw new Error('sMap: WMTS matrix set ' + set.id + ' has no matrices'); }
28033
+ var prefix = null;
28034
+ for (var i = 0; i < ms.length; i++) {
28035
+ var m = /^(.*?)(\d+)$/.exec(ms[i].id);
28036
+ if (!m || Number(m[2]) !== ms[i].zoom) { throw new Error('sMap: WMTS matrix set ' + set.id + ': matrix ' + ms[i].id + ' is not named by its zoom'); }
28037
+ if (prefix === null) { prefix = m[1]; } else if (prefix !== m[1]) { throw new Error('sMap: WMTS matrix set ' + set.id + ' mixes matrix names'); }
28038
+ }
28039
+ return { prefix: prefix, minzoom: ms[0].zoom, maxzoom: ms[ms.length - 1].zoom, tileSize: ms[0].tileWidth };
28040
+ }
28041
+
28042
+ /**
28043
+ * A raster source for one WMTS layer from its capabilities (KVP or REST; Web Mercator matrix sets only).
28044
+ * @param caps the text of GetCapabilities or parseWmtsCapabilities' answer
28045
+ * @param o {layer, tileMatrixSet, style, format, encoding ('rest' | 'kvp'), url (where the capabilities came
28046
+ * from, for a relative GetTile address)}
28047
+ */
28048
+ function wmtsSource(caps, o) {
28049
+ o = o || {};
28050
+ if (typeof caps === 'string') { caps = parseWmtsCapabilities(caps); }
28051
+ var layer = o.layer ? caps.layers.filter(function (l) { return l.id === o.layer; })[0] : caps.layers[0];
28052
+ if (!layer) { throw new Error('sMap: WMTS has no layer ' + (o.layer || '')); }
28053
+ var setId = o.tileMatrixSet || layer.tileMatrixSets.filter(function (id) {
28054
+ var s = caps.tileMatrixSets[id];
28055
+ return s && (MERCATOR_CRS.test(s.crs) || MERCATOR_CRS.test(s.wellKnown) || MERCATOR_CRS.test(id));
28056
+ })[0];
28057
+ var set = caps.tileMatrixSets[setId];
28058
+ if (!set) { throw new Error('sMap: WMTS layer ' + layer.id + ' has no Web Mercator matrix set'); }
28059
+ var mt = matrixTemplate(set);
28060
+ var style = o.style || (layer.styles.filter(function (s) { return s.isDefault; })[0] || layer.styles[0] || { id: 'default' }).id;
28061
+ var format = o.format || layer.formats.filter(function (f) { return /png/i.test(f); })[0] || layer.formats[0] || 'image/png';
28062
+ var rest = layer.resourceUrls.filter(function (r) { return !o.format || r.format === o.format; })[0] || layer.resourceUrls[0];
28063
+ var tile;
28064
+ if (rest && o.encoding !== 'kvp') {
28065
+ tile = rest.template.replace(/\{TileMatrixSet\}/gi, set.id).replace(/\{TileMatrix\}/gi, mt.prefix + '{z}')
28066
+ .replace(/\{TileRow\}/gi, '{y}').replace(/\{TileCol\}/gi, '{x}').replace(/\{Style\}/gi, style);
28067
+ } else {
28068
+ var href = caps.getTile || o.url;
28069
+ if (!href) { throw new Error('sMap: WMTS capabilities name no GetTile address'); }
28070
+ href = String(href);
28071
+ var sep = href.indexOf('?') < 0 ? '?' : (/[?&]$/.test(href) ? '' : '&');
28072
+ if (sep === '?' && o.url && href === o.url) { href = href.split('?')[0]; }
28073
+ tile = href + sep + 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=' + encodeURIComponent(layer.id) + '&STYLE=' + encodeURIComponent(style) +
28074
+ '&FORMAT=' + encodeURIComponent(format) + '&TILEMATRIXSET=' + encodeURIComponent(set.id) + '&TILEMATRIX=' + encodeURIComponent(mt.prefix) + '{z}&TILEROW={y}&TILECOL={x}';
28075
+ }
28076
+ var src = { type: 'raster', tiles: [tile], tileSize: mt.tileSize, minzoom: mt.minzoom, maxzoom: mt.maxzoom };
28077
+ if (layer.bounds) { src.bounds = layer.bounds; }
28078
+ return src;
28079
+ }
28080
+
28081
+ // ------------------------------------------------------------------ PMTiles v3 (archives read by HTTP range)
28082
+
28083
+ function zxyToTileId(z, x, y) {
28084
+ if (z > 26) { throw new Error('sMap: zoom above 26'); }
28085
+ var acc = (Math.pow(4, z) - 1) / 3;
28086
+ var n = Math.pow(2, z), rx, ry, d = 0, xy = [x, y];
28087
+ for (var s = n / 2; s >= 1; s /= 2) {
28088
+ rx = (xy[0] & s) > 0 ? 1 : 0;
28089
+ ry = (xy[1] & s) > 0 ? 1 : 0;
28090
+ d += s * s * ((3 * rx) ^ ry);
28091
+ if (ry === 0) { if (rx === 1) { xy[0] = s - 1 - xy[0]; xy[1] = s - 1 - xy[1]; } var t = xy[0]; xy[0] = xy[1]; xy[1] = t; }
28092
+ }
28093
+ return acc + d;
28094
+ }
28095
+
28096
+ function readVarint(b, at) {
28097
+ var v = 0, mul = 1, byte;
28098
+ do { byte = b[at.p++]; v += (byte & 0x7f) * mul; mul *= 128; } while (byte & 0x80);
28099
+ return v;
28100
+ }
28101
+
28102
+ function pmDirectory(bytes) {
28103
+ var at = { p: 0 }, n = readVarint(bytes, at), entries = new Array(n), last = 0, i;
28104
+ for (i = 0; i < n; i++) { last += readVarint(bytes, at); entries[i] = { tileId: last, offset: 0, length: 0, runLength: 1 }; }
28105
+ for (i = 0; i < n; i++) { entries[i].runLength = readVarint(bytes, at); }
28106
+ for (i = 0; i < n; i++) { entries[i].length = readVarint(bytes, at); }
28107
+ for (i = 0; i < n; i++) {
28108
+ var v = readVarint(bytes, at);
28109
+ entries[i].offset = v === 0 && i > 0 ? entries[i - 1].offset + entries[i - 1].length : v - 1;
28110
+ }
28111
+ return entries;
28112
+ }
28113
+
28114
+ function pmFind(entries, tileId) {
28115
+ var lo = 0, hi = entries.length - 1;
28116
+ while (lo <= hi) {
28117
+ var mid = (lo + hi) >> 1, c = entries[mid].tileId - tileId;
28118
+ if (c < 0) { lo = mid + 1; } else if (c > 0) { hi = mid - 1; } else { return entries[mid]; }
28119
+ }
28120
+ if (hi >= 0) {
28121
+ var e = entries[hi];
28122
+ if (e.runLength === 0) { return e; }
28123
+ if (tileId - e.tileId < e.runLength) { return e; }
28124
+ }
28125
+ return null;
28126
+ }
28127
+
28128
+ function u64(dv, p) { return dv.getUint32(p, true) + dv.getUint32(p + 4, true) * 4294967296; }
28129
+
28130
+ /** Reads the PMTiles v3 header (the first 127 bytes). */
28131
+ function pmHeader(bytes) {
28132
+ var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
28133
+ if (utf8(bytes.subarray(0, 7)) !== 'PMTiles') { throw new Error('sMap: not a PMTiles archive'); }
28134
+ if (bytes[7] !== 3) { throw new Error('sMap: PMTiles version ' + bytes[7] + ' is not supported (3)'); }
28135
+ return {
28136
+ rootOffset: u64(dv, 8), rootLength: u64(dv, 16), metadataOffset: u64(dv, 24), metadataLength: u64(dv, 32),
28137
+ leafOffset: u64(dv, 40), leafLength: u64(dv, 48), dataOffset: u64(dv, 56), dataLength: u64(dv, 64),
28138
+ internalCompression: bytes[97], tileCompression: bytes[98], tileType: bytes[99], minZoom: bytes[100], maxZoom: bytes[101],
28139
+ bounds: [dv.getInt32(102, true) / 1e7, dv.getInt32(106, true) / 1e7, dv.getInt32(110, true) / 1e7, dv.getInt32(114, true) / 1e7],
28140
+ centerZoom: bytes[118], center: [dv.getInt32(119, true) / 1e7, dv.getInt32(123, true) / 1e7],
28141
+ };
28142
+ }
28143
+
28144
+ function pmDecompress(bytes, compression) {
28145
+ if (compression === 0 || compression === 1) { return bytes; }
28146
+ if (compression === 2) { return gunzip(bytes); }
28147
+ throw new Error('sMap: PMTiles compression ' + compression + ' is not supported');
28148
+ }
28149
+
28150
+ /**
28151
+ * A PMTiles v3 archive read by HTTP range requests. `fetchRange(url, offset, length)` → Promise of bytes (default:
28152
+ * fetch with a Range header).
28153
+ */
28154
+ function PMTilesArchive(url, fetchRange) {
28155
+ this.url = url;
28156
+ this._fetch = fetchRange || function (u, offset, length) {
28157
+ return fetch(u, { headers: { Range: 'bytes=' + offset + '-' + (offset + length - 1) } }).then(function (res) {
28158
+ if (!res.ok) { throw new Error('sMap: PMTiles HTTP ' + res.status); }
28159
+ return res.arrayBuffer();
28160
+ }).then(function (b) { return new Uint8Array(b); });
28161
+ };
28162
+ this._header = null;
28163
+ this._dirs = {};
28164
+ }
28165
+ PMTilesArchive.prototype.header = function () {
28166
+ var self = this;
28167
+ if (!this._header) {
28168
+ this._header = Promise.resolve(this._fetch(this.url, 0, 16384)).then(function (first) {
28169
+ first = toBytes(first);
28170
+ var h = pmHeader(first);
28171
+ if (h.rootOffset + h.rootLength <= first.length) { self._dirs['root'] = pmDirectory(pmDecompress(first.subarray(h.rootOffset, h.rootOffset + h.rootLength), h.internalCompression)); }
28172
+ return h;
28173
+ });
28174
+ }
28175
+ return this._header;
28176
+ };
28177
+ PMTilesArchive.prototype._dir = function (key, offset, length, compression) {
28178
+ var self = this;
28179
+ if (this._dirs[key]) { return Promise.resolve(this._dirs[key]); }
28180
+ return Promise.resolve(this._fetch(this.url, offset, length)).then(function (b) {
28181
+ var d = pmDirectory(pmDecompress(toBytes(b), compression));
28182
+ self._dirs[key] = d;
28183
+ return d;
28184
+ });
28185
+ };
28186
+ PMTilesArchive.prototype.metadata = function () {
28187
+ var self = this;
28188
+ return this.header().then(function (h) {
28189
+ if (!h.metadataLength) { return {}; }
28190
+ return Promise.resolve(self._fetch(self.url, h.metadataOffset, h.metadataLength)).then(function (b) {
28191
+ try { return JSON.parse(utf8(pmDecompress(toBytes(b), h.internalCompression))); } catch (e) { return {}; }
28192
+ });
28193
+ });
28194
+ };
28195
+ /** The tile's bytes (decompressed), or null when the archive has none. */
28196
+ PMTilesArchive.prototype.tile = function (z, x, y) {
28197
+ var self = this, id = zxyToTileId(z, x, y);
28198
+ return this.header().then(function (h) {
28199
+ if (z < h.minZoom || z > h.maxZoom) { return null; }
28200
+ var walk = function (key, offset, length, depth) {
28201
+ return self._dir(key, offset, length, h.internalCompression).then(function (entries) {
28202
+ var e = pmFind(entries, id);
28203
+ if (!e) { return null; }
28204
+ if (e.runLength > 0) {
28205
+ return Promise.resolve(self._fetch(self.url, h.dataOffset + e.offset, e.length)).then(function (b) { return pmDecompress(toBytes(b), h.tileCompression); });
28206
+ }
28207
+ if (depth > 3) { return null; }
28208
+ return walk('leaf:' + e.offset, h.leafOffset + e.offset, e.length, depth + 1);
28209
+ });
28210
+ };
28211
+ return walk('root', h.rootOffset, h.rootLength, 0);
28212
+ });
28213
+ };
28214
+ /** TileJSON for the engine: tiles through the pmtiles:// protocol. */
28215
+ PMTilesArchive.prototype.tileJSON = function () {
28216
+ var self = this;
28217
+ return Promise.all([this.header(), this.metadata()]).then(function (r) {
28218
+ var h = r[0], meta = r[1] || {};
28219
+ var tj = { tilejson: '3.0.0', tiles: ['pmtiles://' + self.url + '/{z}/{x}/{y}'], minzoom: h.minZoom, maxzoom: h.maxZoom, bounds: h.bounds };
28220
+ if (meta.vector_layers) { tj.vector_layers = meta.vector_layers; }
28221
+ if (meta.attribution) { tj.attribution = meta.attribution; }
28222
+ tj.tileType = h.tileType;
28223
+ return tj;
28224
+ });
28225
+ };
28226
+
28227
+ var archives = {};
28228
+ /**
28229
+ * The `pmtiles://` protocol for the engine (engine.addProtocol('pmtiles', pmtilesProtocol)): `pmtiles://<url>`
28230
+ * answers TileJSON, `pmtiles://<url>/{z}/{x}/{y}` the tile.
28231
+ */
28232
+ function pmtilesProtocol(params) {
28233
+ var rest = String(params.url).replace(/^pmtiles:\/\//, '');
28234
+ var m = /^(.*)\/(\d+)\/(\d+)\/(\d+)$/.exec(rest);
28235
+ var url = m ? m[1] : rest;
28236
+ var a = archives[url] || (archives[url] = new PMTilesArchive(url));
28237
+ if (!m) { return a.tileJSON().then(function (tj) { return { data: tj }; }); }
28238
+ return a.tile(Number(m[2]), Number(m[3]), Number(m[4])).then(function (b) {
28239
+ return { data: b ? b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength) : new ArrayBuffer(0) };
28240
+ });
28241
+ }
28242
+
28243
+ // ------------------------------------------------------------------ deck.gl (optional, M36)
28244
+
28245
+ var deckModule = null;
28246
+ /** Registers deck.gl's map overlay: useDeck(await import('@deck.gl/mapbox')) or useDeck({ MapboxOverlay }). */
28247
+ function useDeck(mod) { deckModule = mod && (mod.MapboxOverlay ? mod : (mod['default'] && mod['default'].MapboxOverlay ? mod['default'] : null)); if (!deckModule) { throw new Error('sMap: useDeck needs the @deck.gl/mapbox module (MapboxOverlay)'); } return deckModule; }
28248
+ function deck() {
28249
+ if (deckModule) { return deckModule; }
28250
+ var g = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : {});
28251
+ if (g.deck && g.deck.MapboxOverlay) { return g.deck; }
28252
+ return null;
28253
+ }
28254
+
28255
+ // ------------------------------------------------------------------ layer description (M32, shared/LAYER-JSON.md)
28256
+
28257
+ function jsonSafe(v) {
28258
+ if (v === null || typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean') { return v; }
28259
+ if (typeof v === 'function' || v === undefined) { return undefined; }
28260
+ if (Array.isArray(v)) { return v.map(function (x) { var y = jsonSafe(x); return y === undefined ? null : y; }); }
28261
+ if (typeof v === 'object') {
28262
+ var o = {};
28263
+ Object.keys(v).forEach(function (k) { var y = jsonSafe(v[k]); if (y !== undefined) { o[k] = y; } });
28264
+ return o;
28265
+ }
28266
+ return undefined;
28267
+ }
28268
+
28269
+ /** Checks a layer description's outer shape (LAYER-JSON.md); throws with a message. */
28270
+ function checkLayerJson(json) {
28271
+ if (typeof json === 'string') { json = JSON.parse(json); }
28272
+ if (!json || json.type !== 'smap.layers') { throw new Error('sMap: not an sMap layer description (type smap.layers)'); }
28273
+ if (typeof json.version !== 'number' || Math.floor(json.version) > 1) { throw new Error('sMap: layer description version ' + json.version + ' is newer than this SDK reads (1)'); }
28274
+ if (!Array.isArray(json.layers)) { throw new Error('sMap: a layer description needs layers'); }
28275
+ json.layers.forEach(function (l, i) {
28276
+ if (!l || typeof l.id !== 'string' || !l.id) { throw new Error('sMap: layer ' + i + ' has no id'); }
28277
+ if (['points', 'lines', 'polygons', 'style'].indexOf(l.kind) < 0) { throw new Error('sMap: layer ' + l.id + ' has an unknown kind ' + l.kind); }
28278
+ if (l.slot !== undefined && SLOTS.indexOf(l.slot) < 0) { throw new Error('sMap: layer ' + l.id + ' has an unknown slot ' + l.slot); }
28279
+ });
28280
+ return json;
28281
+ }
28282
+
28283
+ return {
28284
+ DEFAULT_BASE: DEFAULT_BASE,
28285
+ SLOTS: SLOTS,
28286
+ basemapClasses: basemapClasses,
28287
+ basemapLayerIndex: basemapLayerIndex,
28288
+ applyBasemapOverrides: applyBasemapOverrides,
28289
+ overrideOrder: overrideOrder,
28290
+ effectiveOverrides: effectiveOverrides,
28291
+ scaledWidth: scaledWidth,
28292
+ slotAnchors: slotAnchors,
28293
+ formats: {
28294
+ list: FORMATS.slice(),
28295
+ read: read,
28296
+ readFile: readFile,
28297
+ write: write,
28298
+ toKML: writeKml,
28299
+ sniff: sniff,
28300
+ formatOfName: formatOfName,
28301
+ decodePolyline: decodePolyline,
28302
+ encodePolyline: encodePolyline,
28303
+ },
28304
+ convert: convert,
28305
+ styleUrl: styleUrl,
28306
+ rasterUrl: rasterUrl,
28307
+ rasterTileJsonUrl: rasterTileJsonUrl,
28308
+ wmsTileUrl: wmsTileUrl,
28309
+ refuseForbidden: refuseForbidden,
28310
+ parseWmtsCapabilities: parseWmtsCapabilities,
28311
+ wmtsSource: wmtsSource,
28312
+ PMTilesArchive: PMTilesArchive,
28313
+ pmtilesProtocol: pmtilesProtocol,
28314
+ zxyToTileId: zxyToTileId,
28315
+ inflateRaw: inflateRaw,
28316
+ gunzip: gunzip,
28317
+ unzip: unzip,
28318
+ parseXml: parseXml,
28319
+ useDeck: useDeck,
28320
+ deck: deck,
28321
+ jsonSafe: jsonSafe,
28322
+ checkLayerJson: checkLayerJson,
28323
+ };
28324
+ });
28325
+
28326
+ return module.exports;
28327
+ }
28328
+ function smapCreateSdk(engine, interop) {
26850
28329
  var module = { exports: {} };
26851
28330
  var exports = module.exports;
26852
28331
  var define;
26853
28332
  var require = function (name) { throw new Error('sMap: unexpected require of ' + name); };
26854
- var self = { maplibregl: engine };
28333
+ var self = { maplibregl: engine, smapInterop: interop };
26855
28334
  /*!
26856
28335
  * sMap Web SDK — maps from Superroute's sMap service.
26857
28336
  *
@@ -26863,14 +28342,15 @@ function smapCreateSdk(engine) {
26863
28342
  */
26864
28343
  (function (root, factory) {
26865
28344
  if (typeof module === 'object' && module.exports) {
26866
- module.exports = factory(root.maplibregl !== undefined ? root.maplibregl : require('maplibre-gl'));
28345
+ module.exports = factory(root.maplibregl !== undefined ? root.maplibregl : require('maplibre-gl'),
28346
+ root.smapInterop !== undefined ? root.smapInterop : require('./smap-interop.js'));
26867
28347
  } else {
26868
- root.sMap = factory(root.maplibregl);
28348
+ root.sMap = factory(root.maplibregl, root.smapInterop);
26869
28349
  }
26870
- })(typeof self !== 'undefined' ? self : this, function (engine) {
28350
+ })(typeof self !== 'undefined' ? self : this, function (engine, interop) {
26871
28351
  'use strict';
26872
28352
 
26873
- var VERSION = '1.0.5';
28353
+ var VERSION = '1.1.0';
26874
28354
  var FLAVORS = ['light', 'white', 'grayscale', 'dark', 'black'];
26875
28355
  var BASEMAP_SOURCE = 'basemap';
26876
28356
  var BUILDINGS_LAYER = 'smap-buildings-3d';
@@ -26878,6 +28358,8 @@ function smapCreateSdk(engine) {
26878
28358
  // The public sMap address, unless the page names another (`baseUrl`). Never derived from where this file was loaded:
26879
28359
  // the SDK is published apart from the map service.
26880
28360
  var DEFAULT_BASE = 'https://tile.srsr.ca';
28361
+ var CUSTOM_PREFIX = 'custom:';
28362
+ var SLOTS = ['bottom', 'roads', 'labels', 'top'];
26881
28363
 
26882
28364
  function tokenExpiry(token) {
26883
28365
  try {
@@ -27025,7 +28507,7 @@ function smapCreateSdk(engine) {
27025
28507
  // from the position (right of the round part, as the page's badge sits on the turned pin's corner).
27026
28508
  function markerGeometry(shape, size) {
27027
28509
  var k = size / 32;
27028
- if (shape === 'circle') {
28510
+ if (shape === 'circle' || shape === 'vehicle') {
27029
28511
  return { k: k, tipToHead: 0, badge: [size * 0.36, -size * 0.36] };
27030
28512
  }
27031
28513
  var half = size * SQRT1_2; // the centre of the round part, above the tip
@@ -27051,16 +28533,18 @@ function smapCreateSdk(engine) {
27051
28533
 
27052
28534
  // Each draw function gets a 2D context already scaled to the pixel ratio and returns nothing; sizes are in pixels.
27053
28535
  var IMAGE_KINDS = {
27054
- // pin|shape|fill|border|borderWidth|size
28536
+ // pin|shape|fill|border|borderWidth|size (shape pin, circle, or vehicle: the route map's live position, a white
28537
+ // circle with a truck in the border colour)
27055
28538
  pin: function (args) {
27056
28539
  var shape = args[0], fill = args[1], border = args[2], bw = Number(args[3]), size = Number(args[4]);
27057
28540
  var g = markerGeometry(shape, size);
27058
- var reach = shape === 'circle' ? size / 2 : g.tipToHead; // farthest the shape goes from its centre
28541
+ var reach = shape === 'pin' ? g.tipToHead : size / 2; // farthest the shape goes from its centre
27059
28542
  var w = Math.ceil(2 * reach + 2 * PIN_PAD), h = w;
27060
28543
  return {
27061
28544
  width: w, height: h,
27062
28545
  draw: function (ctx, ratio) {
27063
28546
  var c = w / 2;
28547
+ if (shape === 'vehicle') { drawVehicle(ctx, ratio, c, size, border); return; }
27064
28548
  ctx.save();
27065
28549
  ctx.translate(c, c);
27066
28550
  var s = size / 2;
@@ -27091,6 +28575,31 @@ function smapCreateSdk(engine) {
27091
28575
  },
27092
28576
  };
27093
28577
  },
28578
+ // pinnum|shape|fill|border|borderWidth|size|textColor|text: a pin with its number drawn in, so a pin above covers a
28579
+ // pin below completely (used close up, where few pins are in view; further out the number is its own layer).
28580
+ pinnum: function (args, measure) {
28581
+ var pin = IMAGE_KINDS.pin(args.slice(0, 5));
28582
+ var size = Number(args[4]), color = args[5], text = args.slice(6).join('|');
28583
+ var px = 14 * size / 32, font = 'bold ' + px + 'px monospace';
28584
+ return {
28585
+ width: pin.width, height: pin.height,
28586
+ draw: function (ctx, ratio) {
28587
+ pin.draw(ctx, ratio);
28588
+ if (text === '') { return; }
28589
+ ctx.save();
28590
+ ctx.font = font;
28591
+ ctx.textAlign = 'center';
28592
+ ctx.textBaseline = 'middle';
28593
+ ctx.shadowColor = 'rgba(0,0,0,0.5)';
28594
+ ctx.shadowOffsetX = ratio;
28595
+ ctx.shadowOffsetY = ratio;
28596
+ ctx.shadowBlur = 2 * ratio;
28597
+ ctx.fillStyle = color;
28598
+ ctx.fillText(text, pin.width / 2, pin.height / 2 + 0.5);
28599
+ ctx.restore();
28600
+ },
28601
+ };
28602
+ },
27094
28603
  // num|color|size|text (bold, with the route map's text shadow)
27095
28604
  num: function (args, measure) {
27096
28605
  var color = args[0], size = Number(args[1]), text = args.slice(2).join('|');
@@ -27164,6 +28673,9 @@ function smapCreateSdk(engine) {
27164
28673
  },
27165
28674
  };
27166
28675
 
28676
+ // A number from a feature property, `fallback` when it has none.
28677
+ function numberProp(name, fallback) { return ['to-number', ['coalesce', ['get', name], fallback], fallback]; }
28678
+
27167
28679
  function drawnImageId(kind, parts) { return ['concat', IMAGE_PREFIX + kind].concat(parts.reduce(function (a, p) { return a.concat(['|', p]); }, [])); }
27168
28680
 
27169
28681
  var NODE_TTL = 3600 * 1000;
@@ -27259,12 +28771,26 @@ function smapCreateSdk(engine) {
27259
28771
  this._token = options.token || null;
27260
28772
  this._getToken = options.getToken || null;
27261
28773
  this._apiKey = options.apiKey || null;
27262
- this._flavor = typeof options.style === 'string' && FLAVORS.indexOf(options.style) >= 0 ? options.style : 'light';
27263
- this._customStyle = options.style && FLAVORS.indexOf(options.style) < 0 ? options.style : null;
28774
+ this._flavor = 'light';
28775
+ this._customStyle = null;
28776
+ this._serverStyle = null;
28777
+ this._takeStyle(options.style);
27264
28778
  this._language = options.language || (typeof navigator !== 'undefined' && navigator.language) || 'en';
27265
28779
  this._projection = options.projection || 'mercator';
27266
28780
  this._layers = {};
27267
28781
  this._order = [];
28782
+ // Style Spec sources added with addSource (or by the external-layer helpers), re-added after every style change.
28783
+ this._sources = {};
28784
+ this._sourceOrder = [];
28785
+ // Basemap class overrides (setBasemap), the basemap's own layer ids and their values before any override.
28786
+ this._basemapOverrides = {};
28787
+ this._basemapIds = [];
28788
+ this._basemapOriginal = {};
28789
+ // setPaint / setLayout / setFilter on basemap layers, applied again after a style change.
28790
+ this._basemapEdits = {};
28791
+ this._deckLayers = [];
28792
+ this._deckOverlay = null;
28793
+ this._seq = 0;
27268
28794
  this._buildings = false;
27269
28795
  this._handlers = {};
27270
28796
  this._refreshTimer = null;
@@ -27327,8 +28853,16 @@ function smapCreateSdk(engine) {
27327
28853
  if (this._token && this._getToken) { this._scheduleRefresh(); }
27328
28854
  }
27329
28855
 
28856
+ // A scheme, `custom:<id>` (a saved custom style served by sMap), or a style URL / object of your own.
28857
+ SMap.prototype._takeStyle = function (style) {
28858
+ if (typeof style === 'string' && FLAVORS.indexOf(style) >= 0) { this._flavor = style; this._customStyle = null; this._serverStyle = null; }
28859
+ else if (typeof style === 'string' && style.indexOf(CUSTOM_PREFIX) === 0) { this._customStyle = null; this._serverStyle = style; }
28860
+ else if (style) { this._customStyle = style; this._serverStyle = null; }
28861
+ };
28862
+
27330
28863
  SMap.prototype._styleUrl = function () {
27331
28864
  if (this._customStyle) { return this._customStyle; }
28865
+ if (this._serverStyle) { return interop.styleUrl(this._serverStyle, { baseUrl: this._base, lang: this._language, apiKey: this._apiKey }); }
27332
28866
  var url = this._base + '/v1/styles/' + this._flavor + '?lang=' + encodeURIComponent(this._language);
27333
28867
  return this._apiKey ? url + '&key=' + encodeURIComponent(this._apiKey) : url;
27334
28868
  };
@@ -27354,7 +28888,7 @@ function smapCreateSdk(engine) {
27354
28888
 
27355
28889
  /** Switches the colour scheme ('light', 'white', 'grayscale', 'dark', 'black'); your layers are kept. */
27356
28890
  SMap.prototype.setStyle = function (style) {
27357
- if (FLAVORS.indexOf(style) >= 0) { this._flavor = style; this._customStyle = null; } else { this._customStyle = style; }
28891
+ this._takeStyle(style);
27358
28892
  this._styleReady = false;
27359
28893
  this.engine.setStyle(this._styleUrl());
27360
28894
  this._styleClass();
@@ -27366,10 +28900,31 @@ function smapCreateSdk(engine) {
27366
28900
  var c = this.engine && this.engine.getContainer && this.engine.getContainer();
27367
28901
  if (!c || !c.classList) { return; }
27368
28902
  for (var i = 0; i < FLAVORS.length; i++) { c.classList.remove('smap-style-' + FLAVORS[i]); }
27369
- if (!this._customStyle) { c.classList.add('smap-style-' + this._flavor); }
28903
+ if (!this._customStyle && !this._serverStyle) { c.classList.add('smap-style-' + this._flavor); }
27370
28904
  };
27371
28905
 
27372
28906
  // Draws an image the SDK named (IMAGE_PREFIX + kind|args) and adds it to the map; other names are left alone.
28907
+ // The live-position marker of the route map: a white circle (85%) with a truck in the driver colour.
28908
+ function drawVehicle(ctx, ratio, c, size, color) {
28909
+ var r = size / 2, k = size / 24;
28910
+ ctx.save();
28911
+ ctx.translate(c, c);
28912
+ ctx.shadowColor = 'rgba(0,0,0,0.5)';
28913
+ ctx.shadowBlur = 2 * ratio;
28914
+ ctx.beginPath(); ctx.arc(0, 0, r, 0, Math.PI * 2);
28915
+ ctx.fillStyle = 'rgba(255,255,255,0.85)'; ctx.fill();
28916
+ ctx.shadowColor = 'transparent';
28917
+ ctx.scale(k, k);
28918
+ ctx.fillStyle = color;
28919
+ // cargo box, cab and two wheels, about 14 x 10 px on a 24 px marker
28920
+ ctx.fillRect(-7, -5, 8.5, 7);
28921
+ roundedPath(ctx, 2.2, -2.6, 4.8, 4.6, [[1.4, 1.4], [0.6, 0.6], [0, 0], [0, 0]]); ctx.fill();
28922
+ ctx.beginPath(); ctx.arc(-4.2, 3.4, 1.9, 0, Math.PI * 2); ctx.arc(4.1, 3.4, 1.9, 0, Math.PI * 2); ctx.fill();
28923
+ ctx.fillStyle = 'rgba(255,255,255,0.85)';
28924
+ ctx.beginPath(); ctx.arc(-4.2, 3.4, 0.7, 0, Math.PI * 2); ctx.arc(4.1, 3.4, 0.7, 0, Math.PI * 2); ctx.fill();
28925
+ ctx.restore();
28926
+ }
28927
+
27373
28928
  SMap.prototype._drawImage = function (id) {
27374
28929
  if (typeof id !== 'string' || id.indexOf(IMAGE_PREFIX) !== 0) { return; }
27375
28930
  var m = this.engine;
@@ -27545,12 +29100,12 @@ function smapCreateSdk(engine) {
27545
29100
  // Points open at their own position; lines and areas where they were clicked.
27546
29101
  var at = hit.kind === 'points' && f.geometry && f.geometry.type === 'Point' ? f.geometry.coordinates : lngLat;
27547
29102
  var content;
27548
- try { content = hit.o.popup(f.properties || {}, f); } catch (error) { this._reportError(error); return; }
29103
+ try { content = (hit.popupFn || hit.o.popup)(f.properties || {}, f); } catch (error) { this._reportError(error); return; }
27549
29104
  var popupOptions = Object.assign({}, hit.o.popupOptions || {});
27550
29105
  if (hit.o.marker && popupOptions.offset === undefined && this._popupDefaults.offset === undefined) {
27551
29106
  // Just above the pin as it is drawn now (its scale may follow the zoom).
27552
29107
  var mk = hit.o.marker, size = mk.size || 32, k = scaleAt(mk.scale, m.getZoom ? m.getZoom() : 0);
27553
- popupOptions.offset = mk.shape === 'circle' ? Math.round((size / 2) * k + 4) : Math.round((markerGeometry('pin', size).tipToHead + size / 2) * k + 2);
29108
+ popupOptions.offset = mk.shape === 'circle' || mk.shape === 'vehicle' ? Math.round((size / 2) * k + 4) : Math.round((markerGeometry('pin', size).tipToHead + size / 2) * k + 2);
27554
29109
  }
27555
29110
  // Above the pin, always (as on Superroute's route map); the map pans to make room instead of flipping it below.
27556
29111
  if (hit.o.marker && popupOptions.anchor === undefined && this._popupDefaults.anchor === undefined) { popupOptions.anchor = 'bottom'; }
@@ -27595,6 +29150,7 @@ function smapCreateSdk(engine) {
27595
29150
 
27596
29151
  SMap.prototype.setLanguage = function (language) {
27597
29152
  this._language = language;
29153
+ // A scheme or a custom style of sMap is served per language; a style of your own keeps its labels.
27598
29154
  if (!this._customStyle) { this._styleReady = false; this.engine.setStyle(this._styleUrl()); }
27599
29155
  return this;
27600
29156
  };
@@ -27648,7 +29204,15 @@ function smapCreateSdk(engine) {
27648
29204
  if (!this._buildings || !m.getSource(BASEMAP_SOURCE)) { return; }
27649
29205
  var o = this._buildingOptions || {};
27650
29206
  var dark = this._flavor === 'dark' || this._flavor === 'black';
29207
+ // Under the lowest of your layers, wherever its slot put it.
27651
29208
  var firstUserLayer = this._order.length ? this._firstEngineLayer(this._order[0]) : undefined;
29209
+ var st = m.getStyle && m.getStyle();
29210
+ if (st && Array.isArray(st.layers)) {
29211
+ var mine = {}, self = this;
29212
+ this._order.forEach(function (id) { self._layers[id].engineLayers.forEach(function (l) { mine[l] = true; }); });
29213
+ firstUserLayer = undefined;
29214
+ for (var i = 0; i < st.layers.length; i++) { if (mine[st.layers[i].id]) { firstUserLayer = st.layers[i].id; break; } }
29215
+ }
27652
29216
  m.addLayer({
27653
29217
  id: BUILDINGS_LAYER,
27654
29218
  type: 'fill-extrusion',
@@ -27717,12 +29281,24 @@ function smapCreateSdk(engine) {
27717
29281
  return this;
27718
29282
  };
27719
29283
 
29284
+ /**
29285
+ * Removes a layer: one of the SDK's own (addPoints, addLines, addPolygons; addData's whole group), a style layer
29286
+ * (addStyleLayer and the external-layer helpers, with the source a helper made for it), or a source of addSource.
29287
+ */
27720
29288
  SMap.prototype.removeLayer = function (id) {
27721
- var def = this._layers[id];
27722
- if (!def) { return this; }
27723
- this._drop(id, def);
27724
- delete this._layers[id];
27725
- this._order = this._order.filter(function (x) { return x !== id; });
29289
+ var self = this;
29290
+ var ids = this._order.filter(function (x) { return x === id || self._layers[x].group === id; });
29291
+ if (!ids.length) {
29292
+ if (this._sources[id]) { this.removeSource(id); }
29293
+ return this;
29294
+ }
29295
+ ids.forEach(function (x) {
29296
+ var def = self._layers[x];
29297
+ self._drop(x, def);
29298
+ delete self._layers[x];
29299
+ self._order = self._order.filter(function (y) { return y !== x; });
29300
+ if (def.ownsSource && self._sources[def.ownsSource] && !self._sourceInUse(def.ownsSource)) { self.removeSource(def.ownsSource); }
29301
+ });
27726
29302
  return this;
27727
29303
  };
27728
29304
 
@@ -27746,8 +29322,549 @@ function smapCreateSdk(engine) {
27746
29322
  SMap.prototype.flyTo = function (o) { this.engine.flyTo(o); return this; };
27747
29323
  SMap.prototype.remove = function () { clearTimeout(this._refreshTimer); if (this.engine) { this.engine.remove(); } };
27748
29324
 
29325
+ // ------------------------------------------------------------------ Style Specification sources and layers (M24)
29326
+
29327
+ function clone(v) { return v === undefined ? undefined : JSON.parse(JSON.stringify(v)); }
29328
+
29329
+ function sourceUrls(source) {
29330
+ var urls = [];
29331
+ if (source && source.url) { urls.push(source.url); }
29332
+ if (source && Array.isArray(source.tiles)) { urls = urls.concat(source.tiles); }
29333
+ return urls;
29334
+ }
29335
+
29336
+ // The pmtiles:// protocol, registered on the engine the first time a source needs it.
29337
+ var pmtilesRegistered = false;
29338
+ function needsPmtiles(source) { return sourceUrls(source).some(function (u) { return /^pmtiles:\/\//i.test(String(u)); }); }
29339
+ function registerPmtiles() {
29340
+ if (pmtilesRegistered || !engine || !engine.addProtocol) { return; }
29341
+ engine.addProtocol('pmtiles', interop.pmtilesProtocol);
29342
+ pmtilesRegistered = true;
29343
+ }
29344
+
29345
+ SMap.prototype._engineSource = function (id) {
29346
+ var source = this._sources[id];
29347
+ if (needsPmtiles(source)) { registerPmtiles(); }
29348
+ this.engine.addSource(id, source);
29349
+ };
29350
+
29351
+ SMap.prototype._sourceInUse = function (id) {
29352
+ var self = this;
29353
+ return this._order.some(function (x) { var d = self._layers[x]; return d.kind === 'style' && d.layer.source === id; });
29354
+ };
29355
+
29356
+ /**
29357
+ * Adds a source of the Style Specification v8 (geojson, vector, raster, raster-dem, image, video), passed to the
29358
+ * engine as it is and kept across style and language changes. Its `attribution` is shown beside the map's credit.
29359
+ * `pmtiles://<url>` reads a PMTiles archive. Google and Bing tiles are refused.
29360
+ */
29361
+ SMap.prototype.addSource = function (id, source) {
29362
+ if (!id || !source || typeof source !== 'object' || !source.type) { throw new Error('sMap: addSource needs an id and a source with a type'); }
29363
+ if (id === BASEMAP_SOURCE) { throw new Error('sMap: the source id "basemap" belongs to the map'); }
29364
+ interop.refuseForbidden(sourceUrls(source));
29365
+ if (this._sources[id]) { this.removeSource(id); }
29366
+ this._sources[id] = source;
29367
+ this._sourceOrder.push(id);
29368
+ if (this.engine && this._styleReady) { this._engineSource(id); }
29369
+ return this;
29370
+ };
29371
+
29372
+ /** Removes a source of addSource and the style layers that read it. */
29373
+ SMap.prototype.removeSource = function (id) {
29374
+ var self = this;
29375
+ if (!this._sources[id]) { return this; }
29376
+ this._order.filter(function (x) { var d = self._layers[x]; return d.kind === 'style' && d.layer.source === id; })
29377
+ .forEach(function (x) { var d = self._layers[x]; d.ownsSource = null; self.removeLayer(x); });
29378
+ delete this._sources[id];
29379
+ this._sourceOrder = this._sourceOrder.filter(function (x) { return x !== id; });
29380
+ var m = this.engine;
29381
+ if (m && m.getSource && m.getSource(id) && m.removeSource) { m.removeSource(id); }
29382
+ return this;
29383
+ };
29384
+
29385
+ /** The sources added with addSource: {id: source}. */
29386
+ SMap.prototype.sources = function () {
29387
+ var out = {}, self = this;
29388
+ this._sourceOrder.forEach(function (id) { out[id] = clone(self._sources[id]); });
29389
+ return out;
29390
+ };
29391
+
29392
+ /**
29393
+ * Adds a layer of the Style Specification v8, passed to the engine as it is: a layer copied from a style editor or
29394
+ * another map page works unchanged. Its `source` is a source of addSource, an inline source, or `basemap`.
29395
+ * @param {Object} layer
29396
+ * @param {Object} [o] {slot: 'bottom' | 'roads' | 'labels' | 'top', before: layer id, zIndex}; without either the
29397
+ * layer draws on top, in the order added
29398
+ */
29399
+ SMap.prototype.addStyleLayer = function (layer, o) {
29400
+ if (!layer || !layer.id || !layer.type) { throw new Error('sMap: addStyleLayer needs a layer with an id and a type'); }
29401
+ o = o || {};
29402
+ if (typeof layer.source === 'object' && layer.source) { interop.refuseForbidden(sourceUrls(layer.source)); }
29403
+ var def = { kind: 'style', layer: clone(layer), o: { slot: o.slot, before: o.before, zIndex: o.zIndex } };
29404
+ if (layer.layout && layer.layout.visibility === 'none') { def.hidden = true; }
29405
+ this._define(layer.id, def);
29406
+ return this;
29407
+ };
29408
+
29409
+ // The style layer of an id, the basemap's (and any engine layer's) when it is not one of yours.
29410
+ SMap.prototype._edit = function (layerId, apply) {
29411
+ var def = this._layers[layerId];
29412
+ if (def && def.kind !== 'style') { throw new Error('sMap: ' + layerId + ' is a layer of addPoints / addLines / addPolygons; set its look with its options'); }
29413
+ if (def) { apply(def.layer); } else {
29414
+ var e = this._basemapEdits[layerId] = this._basemapEdits[layerId] || { paint: {}, layout: {} };
29415
+ apply(e);
29416
+ }
29417
+ };
29418
+
29419
+ function setKey(obj, group, name, value) {
29420
+ obj[group] = obj[group] || {};
29421
+ if (value === undefined || value === null) { delete obj[group][name]; } else { obj[group][name] = value; }
29422
+ }
29423
+
29424
+ /** Sets a paint property of a style layer (yours or the basemap's); kept across style changes. */
29425
+ SMap.prototype.setPaint = function (layerId, name, value) {
29426
+ this._edit(layerId, function (l) { setKey(l, 'paint', name, value); });
29427
+ var m = this.engine;
29428
+ if (m && this._styleReady && m.getLayer(layerId)) { m.setPaintProperty(layerId, name, value === null ? undefined : value); }
29429
+ return this;
29430
+ };
29431
+
29432
+ /** Sets a layout property of a style layer (yours or the basemap's); kept across style changes. */
29433
+ SMap.prototype.setLayout = function (layerId, name, value) {
29434
+ this._edit(layerId, function (l) { setKey(l, 'layout', name, value); });
29435
+ var def = this._layers[layerId];
29436
+ if (def && name === 'visibility') { def.hidden = value === 'none'; }
29437
+ var m = this.engine;
29438
+ if (m && this._styleReady && m.getLayer(layerId)) { m.setLayoutProperty(layerId, name, value === null ? undefined : value); }
29439
+ return this;
29440
+ };
29441
+
29442
+ /** Sets (or with null clears) the filter of a style layer; kept across style changes. */
29443
+ SMap.prototype.setFilter = function (layerId, filter) {
29444
+ this._edit(layerId, function (l) { if (filter === undefined || filter === null) { delete l.filter; } else { l.filter = filter; } });
29445
+ var m = this.engine;
29446
+ if (m && this._styleReady && m.getLayer(layerId)) { m.setFilter(layerId, filter === undefined ? null : filter); }
29447
+ return this;
29448
+ };
29449
+
29450
+ SMap.prototype._applyEdits = function (id, e) {
29451
+ var m = this.engine;
29452
+ if (!m.getLayer(id)) { return; }
29453
+ Object.keys(e.paint || {}).forEach(function (k) { m.setPaintProperty(id, k, e.paint[k]); });
29454
+ Object.keys(e.layout || {}).forEach(function (k) { m.setLayoutProperty(id, k, e.layout[k]); });
29455
+ if (e.filter !== undefined) { m.setFilter(id, e.filter); }
29456
+ };
29457
+
29458
+ /** A style layer as a Style Specification object (a copy): yours, or the basemap's; null when there is none. */
29459
+ SMap.prototype.getStyleLayer = function (layerId) {
29460
+ var def = this._layers[layerId];
29461
+ if (def && def.kind === 'style') { return clone(def.layer); }
29462
+ if (def) { return null; }
29463
+ var st = this.engine && this.engine.getStyle && this.engine.getStyle();
29464
+ var found = st && st.layers ? st.layers.filter(function (l) { return l.id === layerId; })[0] : null;
29465
+ return found ? clone(found) : null;
29466
+ };
29467
+
29468
+ // ------------------------------------------------------------------ basemap options (M26)
29469
+
29470
+ // The basemap's layers as the style came (before any override): ids in order and, for layers of the class
29471
+ // table, their paint and layout.
29472
+ SMap.prototype._snapshotBasemap = function () {
29473
+ var m = this.engine;
29474
+ var st = m.getStyle && m.getStyle();
29475
+ var layers = st && Array.isArray(st.layers) ? st.layers : [];
29476
+ var index = interop.basemapLayerIndex();
29477
+ var mine = {}, self = this;
29478
+ this._order.forEach(function (id) { (self._layers[id].engineLayers || []).forEach(function (l) { mine[l] = true; }); });
29479
+ this._basemapIds = layers.map(function (l) { return l.id; }).filter(function (id) { return !mine[id] && id !== BUILDINGS_LAYER; });
29480
+ this._basemapOriginal = {};
29481
+ this._basemapApplied = {};
29482
+ layers.forEach(function (l) {
29483
+ if (index[l.id]) { self._basemapOriginal[l.id] = { id: l.id, paint: clone(l.paint) || {}, layout: clone(l.layout) || {} }; }
29484
+ });
29485
+ };
29486
+
29487
+ // Sets on the engine what the overrides change (and back what they no longer change).
29488
+ SMap.prototype._applyBasemap = function () {
29489
+ var m = this.engine, self = this;
29490
+ var ids = Object.keys(this._basemapOriginal);
29491
+ if (!ids.length) { return; }
29492
+ var styled = interop.applyBasemapOverrides({ layers: ids.map(function (id) { return self._basemapOriginal[id]; }) }, this._basemapOverrides);
29493
+ styled.layers.forEach(function (l) {
29494
+ var before = self._basemapApplied[l.id] || self._basemapOriginal[l.id];
29495
+ var same = function (a, b) { return JSON.stringify(a) === JSON.stringify(b); };
29496
+ var keys = function (a, b) { var k = {}; Object.keys(a || {}).concat(Object.keys(b || {})).forEach(function (x) { k[x] = true; }); return Object.keys(k); };
29497
+ if (!m.getLayer(l.id)) { return; }
29498
+ keys(before.paint, l.paint).forEach(function (k) { if (!same(before.paint[k], l.paint[k])) { m.setPaintProperty(l.id, k, l.paint[k]); } });
29499
+ keys(before.layout, l.layout).forEach(function (k) { if (!same(before.layout[k], l.layout[k])) { m.setLayoutProperty(l.id, k, l.layout[k]); } });
29500
+ self._basemapApplied[l.id] = l;
29501
+ });
29502
+ };
29503
+
29504
+ /**
29505
+ * Shows, hides and recolours the basemap by class (`water`, `land`, `parks`, `roads` and `roads.highway` … ,
29506
+ * `labels` and `labels.places` …, `pois`, …): {class: {visible, color, casing, halo, opacity, width}}. Replaces the
29507
+ * overrides set before ({} clears them); kept across style and language changes. An unknown class or field throws.
29508
+ */
29509
+ SMap.prototype.setBasemap = function (overrides) {
29510
+ interop.overrideOrder(overrides || {});
29511
+ this._basemapOverrides = clone(overrides || {});
29512
+ if (this.engine && this._styleReady) { this._applyBasemap(); }
29513
+ return this;
29514
+ };
29515
+
29516
+ /** The basemap overrides in force (a copy). */
29517
+ SMap.prototype.basemap = function () { return clone(this._basemapOverrides); };
29518
+
29519
+ // ------------------------------------------------------------------ files (M28–M30)
29520
+
29521
+ function isFileLike(v) { return !!v && typeof v === 'object' && typeof v.arrayBuffer === 'function' && !(v instanceof Uint8Array); }
29522
+
29523
+ // A popup from simplestyle properties (title or name, description), HTML-escaped; nothing when a feature has neither.
29524
+ function simplestylePopup(p) {
29525
+ var title = p.title !== undefined && p.title !== null && p.title !== '' ? p.title : p.name;
29526
+ var desc = p.description !== undefined && p.description !== null && p.description !== '' ? p.description : p.desc;
29527
+ if ((title === undefined || title === null || title === '') && (desc === undefined || desc === null || desc === '')) { return null; }
29528
+ return '<div class="smap-simplestyle">' + (title !== undefined && title !== null && title !== '' ? '<b>' + escapeHtml(title) + '</b>' : '') +
29529
+ (desc !== undefined && desc !== null && desc !== '' ? (title ? '<br>' : '') + escapeHtml(desc) : '') + '</div>';
29530
+ }
29531
+
29532
+ var FILE_OPTIONS = ['format', 'lat', 'lng', 'name'];
29533
+
29534
+ /**
29535
+ * Reads a file (KML, KMZ, GPX, encoded polyline, WKT, WKB, CSV, TopoJSON, GeoJSON) and draws it: its points as the
29536
+ * layer `<id>`, its lines as `<id>:lines`, its areas as `<id>:areas` (only those it has), with simplestyle
29537
+ * properties drawn when no colour is given, and a popup of each feature's title / name and description unless
29538
+ * `popup` is given. `removeLayer(id)` removes all three, `toGeoJSON(id)` gives them back together.
29539
+ * @param input text, bytes, a parsed object, or a File / Blob (format from its name when not given)
29540
+ * @param o {format, lat, lng, ...the options of addPoints / addLines / addPolygons}
29541
+ * @returns Promise of the map, once drawn (rejected when the file cannot be read)
29542
+ */
29543
+ SMap.prototype.addData = function (id, input, o) {
29544
+ var self = this;
29545
+ o = o || {};
29546
+ var read = isFileLike(input) ? interop.formats.readFile(input, o) : new Promise(function (resolve) { resolve(interop.formats.read(input, o)); });
29547
+ return read.then(function (data) { self._addFeatures(id, data, o); return self; });
29548
+ };
29549
+
29550
+ SMap.prototype._addFeatures = function (id, data, o) {
29551
+ var points = [], lines = [], areas = [];
29552
+ var place = function (f, g) {
29553
+ if (!g) { return; }
29554
+ if (g.type === 'GeometryCollection') { (g.geometries || []).forEach(function (x) { place(f, x); }); return; }
29555
+ var part = { type: 'Feature', properties: f.properties || {}, geometry: g };
29556
+ if (f.id !== undefined) { part.id = f.id; }
29557
+ if (g.type === 'Point' || g.type === 'MultiPoint') { points.push(part); }
29558
+ else if (g.type === 'LineString' || g.type === 'MultiLineString') { lines.push(part); }
29559
+ else if (g.type === 'Polygon' || g.type === 'MultiPolygon') { areas.push(part); }
29560
+ };
29561
+ (data.features || []).forEach(function (f) { place(f, f.geometry); });
29562
+ this.removeLayer(id);
29563
+ var opts = {};
29564
+ Object.keys(o).forEach(function (k) { if (FILE_OPTIONS.indexOf(k) < 0) { opts[k] = o[k]; } });
29565
+ if (opts.popup === undefined) { opts.popup = simplestylePopup; }
29566
+ var fcOf = function (f) { return { type: 'FeatureCollection', features: f }; };
29567
+ if (areas.length) { this.addPolygons(id + ':areas', fcOf(areas), opts); this._layers[id + ':areas'].group = id; }
29568
+ if (lines.length) { this.addLines(id + ':lines', fcOf(lines), opts); this._layers[id + ':lines'].group = id; }
29569
+ if (points.length) { this.addPoints(id, fcOf(points), opts); this._layers[id].group = id; }
29570
+ };
29571
+
29572
+ /** A layer's features as GeoJSON (a copy): an SDK layer, an addData group, or a style layer of a GeoJSON source. */
29573
+ SMap.prototype.toGeoJSON = function (layerId) {
29574
+ var self = this;
29575
+ var members = this._order.filter(function (x) { return self._layers[x].group === layerId; });
29576
+ if (members.length) {
29577
+ var features = [];
29578
+ [layerId + ':areas', layerId + ':lines', layerId].forEach(function (x) { if (members.indexOf(x) >= 0) { features = features.concat(clone(self._layers[x].data.features)); } });
29579
+ return { type: 'FeatureCollection', features: features };
29580
+ }
29581
+ var def = this._layers[layerId];
29582
+ if (def && def.kind !== 'style') { return clone(def.data); }
29583
+ var source = def ? (typeof def.layer.source === 'object' ? def.layer.source : this._sources[def.layer.source]) : this._sources[layerId];
29584
+ if (source && source.type === 'geojson' && source.data && typeof source.data === 'object') { return interop.formats.read(clone(source.data), { format: 'geojson' }); }
29585
+ throw new Error('sMap: ' + layerId + ' has no GeoJSON features to give');
29586
+ };
29587
+
29588
+ /** A layer's features as a KML document (text). */
29589
+ SMap.prototype.toKML = function (layerId) { return interop.formats.toKML(this.toGeoJSON(layerId), { name: layerId }); };
29590
+
29591
+ /** Converts a file on sMap (POST /v1/convert) with this map's address, key and token: Promise of GeoJSON. */
29592
+ SMap.prototype.convert = function (input, o) {
29593
+ return interop.convert(input, Object.assign({ baseUrl: this._base, apiKey: this._apiKey, token: this._token }, o || {}));
29594
+ };
29595
+
29596
+ /**
29597
+ * sMap's raster tiles of a style as a `{z}/{x}/{y}` template on this map's node and key (M35), for tools without
29598
+ * vector tiles. style: a scheme or `custom:<id>` (default: the map's own); o: {lang (default the map's language),
29599
+ * retina}.
29600
+ */
29601
+ SMap.prototype.rasterUrl = function (style, o) {
29602
+ o = o || {};
29603
+ var name = style || this._serverStyle || this._flavor;
29604
+ return interop.rasterUrl(name, { baseUrl: this._base, apiKey: this._apiKey || undefined, lang: o.lang || o.language || this._language, retina: !!o.retina });
29605
+ };
29606
+
29607
+ // ------------------------------------------------------------------ external layers (M31)
29608
+
29609
+ var LAYER_PLACE = ['slot', 'before', 'zIndex'];
29610
+ function placeOf(o) { var p = {}; LAYER_PLACE.forEach(function (k) { if (o[k] !== undefined) { p[k] = o[k]; } }); return p; }
29611
+ function copyKeys(from, to, keys) { keys.forEach(function (k) { if (from[k] !== undefined) { to[k] = from[k]; } }); return to; }
29612
+
29613
+ SMap.prototype._addRaster = function (id, source, o) {
29614
+ this.addSource(id, source);
29615
+ var layer = copyKeys(o, { id: id, type: 'raster', source: id }, ['minzoom', 'maxzoom']);
29616
+ if (o.opacity !== undefined) { layer.paint = { 'raster-opacity': o.opacity }; }
29617
+ this.addStyleLayer(layer, placeOf(o));
29618
+ this._layers[id].ownsSource = id;
29619
+ return this;
29620
+ };
29621
+
29622
+ /**
29623
+ * Raster tiles from any XYZ template (`tiles` or `url` with {z}/{x}/{y}) or a TileJSON address (`url`).
29624
+ * @param o {tiles, url, tileSize (256), attribution, minzoom, maxzoom, bounds, scheme, opacity, slot, before, zIndex}
29625
+ */
29626
+ SMap.prototype.addRasterLayer = function (id, o) {
29627
+ o = o || {};
29628
+ var source = { type: 'raster', tileSize: o.tileSize || 256 };
29629
+ if (o.tiles) { source.tiles = Array.isArray(o.tiles) ? o.tiles.slice() : [o.tiles]; }
29630
+ else if (o.url && /\{z\}/.test(o.url)) { source.tiles = [o.url]; }
29631
+ else if (o.url) { source.url = o.url; }
29632
+ else { throw new Error('sMap: addRasterLayer needs tiles or url'); }
29633
+ copyKeys(o, source, ['attribution', 'minzoom', 'maxzoom', 'bounds', 'scheme']);
29634
+ return this._addRaster(id, source, o);
29635
+ };
29636
+
29637
+ /**
29638
+ * An OGC WMS layer, one GetMap per tile in Web Mercator.
29639
+ * @param o {url, layers, styles, format ('image/png'), transparent (true), version ('1.3.0' | '1.1.1'), params,
29640
+ * tileSize, attribution, opacity, minzoom, maxzoom, slot, before, zIndex}
29641
+ */
29642
+ SMap.prototype.addWmsLayer = function (id, o) {
29643
+ o = o || {};
29644
+ var source = copyKeys(o, { type: 'raster', tiles: [interop.wmsTileUrl(o)], tileSize: o.tileSize || 256 }, ['attribution', 'minzoom', 'maxzoom', 'bounds']);
29645
+ return this._addRaster(id, source, o);
29646
+ };
29647
+
29648
+ /**
29649
+ * An OGC WMTS layer (Web Mercator matrix sets): from the GetCapabilities address (`url`, or its text as
29650
+ * `capabilities`), KVP or REST; or from a REST template with {TileMatrix}, {TileRow}, {TileCol} (`url`).
29651
+ * @param o {url, capabilities, layer, tileMatrixSet, style, format, encoding ('rest' | 'kvp'), attribution, opacity,
29652
+ * minzoom, maxzoom, slot, before, zIndex}
29653
+ * @returns Promise of the map, once added
29654
+ */
29655
+ SMap.prototype.addWmtsLayer = function (id, o) {
29656
+ var self = this;
29657
+ o = o || {};
29658
+ var url = o.url ? String(o.url) : '';
29659
+ var add = function (source) { copyKeys(o, source, ['attribution', 'minzoom', 'maxzoom']); return self._addRaster(id, source, o); };
29660
+ try {
29661
+ if (/\{TileMatrix\}|\{TileRow\}|\{z\}/i.test(url)) {
29662
+ var tile = url.replace(/\{TileMatrixSet\}/gi, o.tileMatrixSet || 'GoogleMapsCompatible').replace(/\{TileMatrix\}/gi, (o.matrixPrefix || '') + '{z}')
29663
+ .replace(/\{TileRow\}/gi, '{y}').replace(/\{TileCol\}/gi, '{x}').replace(/\{Style\}/gi, o.style || 'default');
29664
+ interop.refuseForbidden([tile]);
29665
+ return Promise.resolve(add({ type: 'raster', tiles: [tile], tileSize: o.tileSize || 256 }));
29666
+ }
29667
+ if (o.capabilities) { return Promise.resolve(add(interop.wmtsSource(o.capabilities, o))); }
29668
+ } catch (e) { return Promise.reject(e); }
29669
+ if (!url) { return Promise.reject(new Error('sMap: addWmtsLayer needs url or capabilities')); }
29670
+ var capsUrl = /request=getcapabilities/i.test(url) || /\.xml(\?|$)/i.test(url) ? url : url + (url.indexOf('?') < 0 ? '?' : '&') + 'SERVICE=WMTS&REQUEST=GetCapabilities';
29671
+ try { interop.refuseForbidden([capsUrl]); } catch (e2) { return Promise.reject(e2); }
29672
+ return fetch(capsUrl).then(function (res) {
29673
+ if (!res.ok) { throw new Error('sMap: WMTS capabilities: HTTP ' + res.status); }
29674
+ return res.text();
29675
+ }).then(function (xml) { return add(interop.wmtsSource(xml, Object.assign({}, o, { url: url.split('?')[0] }))); });
29676
+ };
29677
+
29678
+ /**
29679
+ * Vector tiles (Mapbox Vector Tiles) from any address: `tiles` (templates), `url` (TileJSON, or a PMTiles archive
29680
+ * ending in .pmtiles) or `pmtiles` (an archive address); drawn by your style layers (`layers`, their `source` set to
29681
+ * this id when they name none).
29682
+ * @param o {tiles, url, pmtiles, layers, attribution, minzoom, maxzoom, bounds, promoteId, slot, before, zIndex}
29683
+ */
29684
+ SMap.prototype.addVectorTiles = function (id, o) {
29685
+ o = o || {};
29686
+ var source = { type: 'vector' };
29687
+ var archive = o.pmtiles || (o.url && (/^pmtiles:\/\//i.test(o.url) || /\.pmtiles(\?|$)/i.test(o.url)) ? o.url : null);
29688
+ if (archive) { source.url = /^pmtiles:\/\//i.test(archive) ? archive : 'pmtiles://' + archive; }
29689
+ else if (o.tiles) { source.tiles = Array.isArray(o.tiles) ? o.tiles.slice() : [o.tiles]; }
29690
+ else if (o.url) { source.url = o.url; }
29691
+ else { throw new Error('sMap: addVectorTiles needs tiles, url or pmtiles'); }
29692
+ copyKeys(o, source, ['attribution', 'minzoom', 'maxzoom', 'bounds', 'promoteId', 'scheme']);
29693
+ this.addSource(id, source);
29694
+ var self = this, place = placeOf(o);
29695
+ (o.layers || []).forEach(function (l) {
29696
+ var layer = clone(l);
29697
+ if (!layer.source) { layer.source = id; }
29698
+ self.addStyleLayer(layer, place);
29699
+ });
29700
+ return this;
29701
+ };
29702
+
29703
+ /**
29704
+ * An ArcGIS REST service: a MapServer / ImageServer tile service (raster) or a VectorTileServer (vector tiles,
29705
+ * drawn by `layers` or else by the service's own root style). The service's copyright text is its attribution
29706
+ * unless `attribution` is given.
29707
+ * @param o {url, layers, attribution, opacity, minzoom, maxzoom, slot, before, zIndex}
29708
+ * @returns Promise of the map, once added
29709
+ */
29710
+ SMap.prototype.addArcGisLayer = function (id, o) {
29711
+ var self = this;
29712
+ o = o || {};
29713
+ if (!o.url) { return Promise.reject(new Error('sMap: addArcGisLayer needs url')); }
29714
+ var url = String(o.url).split('?')[0].replace(/\/+$/, '');
29715
+ try { interop.refuseForbidden([url]); } catch (e) { return Promise.reject(e); }
29716
+ var vector = /\/VectorTileServer$/i.test(url);
29717
+ if (!vector && !/\/(MapServer|ImageServer)$/i.test(url)) { return Promise.reject(new Error('sMap: an ArcGIS address ends in /MapServer, /ImageServer or /VectorTileServer')); }
29718
+ var json = function (u) { return typeof fetch === 'undefined' ? Promise.resolve({}) : fetch(u).then(function (r) { return r.ok ? r.json() : {}; }).catch(function () { return {}; }); };
29719
+ return json(url + '?f=json').then(function (info) {
29720
+ var attribution = o.attribution || (info && info.copyrightText) || undefined;
29721
+ var opts = Object.assign({}, o, { attribution: attribution });
29722
+ if (!vector) {
29723
+ var tileSize = info && info.tileInfo && info.tileInfo.rows ? info.tileInfo.rows : 256;
29724
+ var source = copyKeys(opts, { type: 'raster', tiles: [url + '/tile/{z}/{y}/{x}'], tileSize: tileSize }, ['attribution', 'minzoom', 'maxzoom']);
29725
+ return self._addRaster(id, source, opts);
29726
+ }
29727
+ var layers = o.layers ? Promise.resolve(o.layers) : json(url + '/' + ((info && info.defaultStyles) || 'resources/styles') + '/root.json').then(function (st) { return (st && st.layers) || []; });
29728
+ return layers.then(function (ls) {
29729
+ var tiles = info && Array.isArray(info.tiles) && info.tiles.length ? info.tiles.map(function (t) { return /^https?:/.test(t) ? t : url + '/' + t; }) : [url + '/tile/{z}/{y}/{x}.pbf'];
29730
+ self.addVectorTiles(id, Object.assign({}, opts, {
29731
+ tiles: tiles,
29732
+ layers: ls.map(function (l) { var c = clone(l); if (c.source) { c.source = id; } return c; }),
29733
+ }));
29734
+ return self;
29735
+ });
29736
+ });
29737
+ };
29738
+
29739
+ // ------------------------------------------------------------------ deck.gl layers (M36, web only)
29740
+
29741
+ /**
29742
+ * Draws a deck.gl layer interleaved with the basemap, through deck.gl's map overlay. deck.gl is optional: load it
29743
+ * once with sMap.useDeck(await import('@deck.gl/mapbox')), or the global `deck` of its script build is used.
29744
+ * A layer with the same id replaces the one before. o: {slot, before} places it (deck.gl's beforeId).
29745
+ */
29746
+ SMap.prototype.addDeckLayer = function (layer, o) {
29747
+ var self = this;
29748
+ if (!this.engine) { this.ready.then(function () { self.addDeckLayer(layer, o); }); return this; }
29749
+ var d = interop.deck();
29750
+ if (!d) { throw new Error('sMap: deck.gl is not loaded; call sMap.useDeck(await import("@deck.gl/mapbox")) first'); }
29751
+ if (!layer || !layer.id) { throw new Error('sMap: addDeckLayer needs a deck.gl layer with an id'); }
29752
+ o = o || {};
29753
+ if ((o.slot || o.before) && typeof layer.clone === 'function') {
29754
+ var anchor = o.before ? (this._layers[o.before] ? this._firstEngineLayer(o.before) : o.before) : this._groupAnchor(o.slot);
29755
+ if (anchor) { layer = layer.clone({ beforeId: anchor }); }
29756
+ }
29757
+ this._deckLayers = this._deckLayers.filter(function (l) { return l.id !== layer.id; }).concat([layer]);
29758
+ if (!this._deckOverlay) {
29759
+ this._deckOverlay = new d.MapboxOverlay({ interleaved: true, layers: this._deckLayers.slice() });
29760
+ this.engine.addControl(this._deckOverlay);
29761
+ } else {
29762
+ this._deckOverlay.setProps({ layers: this._deckLayers.slice() });
29763
+ }
29764
+ return this;
29765
+ };
29766
+
29767
+ /**
29768
+ * The map's deck.gl overlay (deck.gl's MapboxOverlay, interleaved with the basemap; one per map), made on first use.
29769
+ * `props` are other deck.gl props of the overlay (getTooltip, onClick, pickingRadius, …); its layers stay the ones
29770
+ * added with addDeckLayer (a `layers` prop is ignored). Returns null before the map is ready or when deck.gl is not
29771
+ * loaded (sMap.useDeck).
29772
+ */
29773
+ SMap.prototype.deckOverlay = function (props) {
29774
+ if (!this.engine) { return null; }
29775
+ if (!this._deckOverlay) {
29776
+ var d = interop.deck();
29777
+ if (!d) { return null; }
29778
+ this._deckOverlay = new d.MapboxOverlay({ interleaved: true, layers: this._deckLayers.slice() });
29779
+ this.engine.addControl(this._deckOverlay);
29780
+ }
29781
+ if (props && typeof props === 'object') {
29782
+ var own = {};
29783
+ Object.keys(props).forEach(function (k) { if (k !== 'layers' && k !== 'interleaved') { own[k] = props[k]; } });
29784
+ if (Object.keys(own).length) { this._deckOverlay.setProps(own); }
29785
+ }
29786
+ return this._deckOverlay;
29787
+ };
29788
+
29789
+ /** Removes a deck.gl layer by its id. */
29790
+ SMap.prototype.removeDeckLayer = function (id) {
29791
+ this._deckLayers = this._deckLayers.filter(function (l) { return l.id !== id; });
29792
+ if (this._deckOverlay) { this._deckOverlay.setProps({ layers: this._deckLayers.slice() }); }
29793
+ return this;
29794
+ };
29795
+
29796
+ /** The deck.gl layers on the map. */
29797
+ SMap.prototype.deckLayers = function () { return this._deckLayers.slice(); };
29798
+
29799
+ // ------------------------------------------------------------------ layer description (M32, shared/LAYER-JSON.md)
29800
+
29801
+ var NOT_OPTIONS = ['slot', 'before', 'zIndex'];
29802
+
29803
+ /** The map's layers as the shared layer description (LAYER-JSON.md): basemap, sources, layers. */
29804
+ SMap.prototype.toJSON = function () {
29805
+ var self = this;
29806
+ var out = { type: 'smap.layers', version: 1 };
29807
+ if (!this._customStyle) { out.basemap = { style: this._serverStyle || this._flavor, language: this._language, overrides: clone(this._basemapOverrides) }; }
29808
+ if (this._sourceOrder.length) { out.sources = this.sources(); }
29809
+ out.layers = this._order.map(function (id) {
29810
+ var def = self._layers[id];
29811
+ var e = { id: id, kind: def.kind };
29812
+ if (def.slot) { e.slot = def.slot; }
29813
+ if (def.before) { e.before = def.before; }
29814
+ if (def.zIndex) { e.zIndex = def.zIndex; }
29815
+ if (def.hidden) { e.visible = false; }
29816
+ if (def.group) { e.group = def.group; }
29817
+ if (def.kind === 'style') { e.layer = clone(def.layer); return e; }
29818
+ e.data = clone(def.data);
29819
+ var options = {};
29820
+ Object.keys(def.o).forEach(function (k) { if (NOT_OPTIONS.indexOf(k) < 0 && !(k === 'popup' && typeof def.o[k] !== 'string')) { options[k] = def.o[k]; } });
29821
+ e.options = interop.jsonSafe(options);
29822
+ return e;
29823
+ });
29824
+ return out;
29825
+ };
29826
+
29827
+ /**
29828
+ * Loads a layer description (LAYER-JSON.md, an object or its text): its basemap, sources and layers, each through
29829
+ * the call that made it; an id already on the map is replaced, nothing else changes.
29830
+ */
29831
+ SMap.prototype.loadLayers = function (json) {
29832
+ var self = this;
29833
+ json = interop.checkLayerJson(json);
29834
+ var b = json.basemap;
29835
+ if (b) {
29836
+ var style = b.style && b.style !== (this._serverStyle || this._flavor) ? b.style : null;
29837
+ if (b.language) { this._language = b.language; }
29838
+ if (style && this.engine) { this.setStyle(style); } else if (style) { this._takeStyle(style); }
29839
+ else if (b.language && this.engine && !this._customStyle) { this.setLanguage(b.language); }
29840
+ if (b.overrides) { this.setBasemap(b.overrides); }
29841
+ }
29842
+ Object.keys(json.sources || {}).forEach(function (id) { self.addSource(id, clone(json.sources[id])); });
29843
+ json.layers.forEach(function (e) {
29844
+ var place = {};
29845
+ if (e.slot) { place.slot = e.slot; }
29846
+ if (e.before) { place.before = e.before; }
29847
+ if (e.zIndex) { place.zIndex = e.zIndex; }
29848
+ if (e.kind === 'style') { self.addStyleLayer(e.layer, place); } else {
29849
+ var opts = Object.assign({}, clone(e.options) || {}, place);
29850
+ if (e.kind === 'points') { self.addPoints(e.id, e.data, opts); }
29851
+ else if (e.kind === 'lines') { self.addLines(e.id, e.data, opts); }
29852
+ else { self.addPolygons(e.id, e.data, opts); }
29853
+ }
29854
+ if (e.group && self._layers[e.id]) { self._layers[e.id].group = e.group; }
29855
+ if (e.visible === false) { self.setVisible(e.id, false); }
29856
+ });
29857
+ return this;
29858
+ };
29859
+
27749
29860
  SMap.prototype._define = function (id, def) {
27750
29861
  if (this._layers[id]) { this._drop(id, this._layers[id]); }
29862
+ var o = def.o;
29863
+ if (o.slot !== undefined && o.slot !== null && SLOTS.indexOf(o.slot) < 0) { throw new Error('sMap: unknown slot "' + o.slot + '" (bottom, roads, labels, top)'); }
29864
+ if (o.slot && o.before) { throw new Error('sMap: give a layer either slot or before'); }
29865
+ def.slot = o.slot || null;
29866
+ def.before = o.before || null;
29867
+ def.popupFn = typeof o.popup === 'string' ? popupTemplate(o.popup) : null;
27751
29868
  def.zIndex = def.o.zIndex || 0;
27752
29869
  def.engineLayers = [];
27753
29870
  this._layers[id] = def;
@@ -27766,21 +29883,49 @@ function smapCreateSdk(engine) {
27766
29883
  return undefined;
27767
29884
  };
27768
29885
 
27769
- // The layer to insert before, so a higher zIndex always draws on top.
29886
+ // Layers are drawn in groups: a slot, or under one named layer (`before`). A layer without either draws as in 1.0,
29887
+ // on top, in the group of slot `top`.
29888
+ function groupOf(def) { return def.before ? 'before:' + def.before : (def.slot || 'top'); }
29889
+
29890
+ // The layer to insert before: the next layer of the same group (so a higher zIndex always draws on top), else
29891
+ // where the group begins.
27770
29892
  SMap.prototype._beforeId = function (id) {
27771
- var at = this._order.indexOf(id);
29893
+ var at = this._order.indexOf(id), g = groupOf(this._layers[id]);
27772
29894
  for (var i = at + 1; i < this._order.length; i++) {
29895
+ if (groupOf(this._layers[this._order[i]]) !== g) { continue; }
27773
29896
  var first = this._firstEngineLayer(this._order[i]);
27774
29897
  if (first) { return first; }
27775
29898
  }
27776
- return undefined;
29899
+ return this._groupAnchor(g);
29900
+ };
29901
+
29902
+ // Where a group begins: under a named layer (the SDK id of a layer, or any engine layer id), or the slot's first
29903
+ // basemap layer (from the class table, so it stays put when the basemap's layer list changes).
29904
+ SMap.prototype._groupAnchor = function (g) {
29905
+ var m = this.engine;
29906
+ if (g === 'top') { return undefined; }
29907
+ if (g.indexOf('before:') === 0) {
29908
+ var target = g.slice(7);
29909
+ if (this._layers[target]) { return this._firstEngineLayer(target); }
29910
+ return m.getLayer && m.getLayer(target) ? target : undefined;
29911
+ }
29912
+ var present = this._basemapIds.filter(function (x) { return m.getLayer && m.getLayer(x); });
29913
+ var anchor = interop.slotAnchors(present)[g];
29914
+ return anchor && m.getLayer(anchor) ? anchor : undefined;
29915
+ };
29916
+
29917
+ /** Where each slot begins in the current basemap: {bottom, roads, labels, top} as layer ids (top: undefined). */
29918
+ SMap.prototype.slots = function () {
29919
+ var m = this.engine;
29920
+ var present = this._basemapIds.filter(function (x) { return m && m.getLayer && m.getLayer(x); });
29921
+ return interop.slotAnchors(present);
27777
29922
  };
27778
29923
 
27779
29924
  SMap.prototype._drop = function (id, def) {
27780
29925
  var m = this.engine;
27781
29926
  if (!m) { return; }
27782
29927
  def.engineLayers.forEach(function (l) { if (m.getLayer(l)) { m.removeLayer(l); } });
27783
- if (m.getSource('smap-' + id)) { m.removeSource('smap-' + id); }
29928
+ if (def.kind !== 'style' && m.getSource('smap-' + id)) { m.removeSource('smap-' + id); }
27784
29929
  if (m.getSource('smap-' + id + '-hover')) { m.removeSource('smap-' + id + '-hover'); }
27785
29930
  def.hoverLayer = null; def.hovered = null;
27786
29931
  (this._handlers[id] || []).forEach(function (h) { if (h[1]) { m.off(h[0], h[1], h[2]); } else { m.off(h[0], h[2]); } });
@@ -27789,9 +29934,13 @@ function smapCreateSdk(engine) {
27789
29934
  };
27790
29935
 
27791
29936
  SMap.prototype._restore = function () {
27792
- var self = this;
29937
+ var self = this, m = this.engine;
27793
29938
  if (this.engine.setProjection) { this.engine.setProjection({ type: this._projection }); }
27794
29939
  this._order.forEach(function (id) { self._layers[id].engineLayers = []; self._handlers[id] = []; });
29940
+ this._snapshotBasemap();
29941
+ this._applyBasemap();
29942
+ Object.keys(this._basemapEdits).forEach(function (id) { self._applyEdits(id, self._basemapEdits[id]); });
29943
+ this._sourceOrder.forEach(function (id) { if (!m.getSource(id)) { self._engineSource(id); } });
27795
29944
  this._applyBuildings();
27796
29945
  this._order.forEach(function (id) { self._add(id, self._layers[id]); });
27797
29946
  };
@@ -27799,8 +29948,17 @@ function smapCreateSdk(engine) {
27799
29948
  SMap.prototype._add = function (id, def) {
27800
29949
  var m = this.engine;
27801
29950
  var o = def.o;
27802
- var src = 'smap-' + id;
27803
29951
  var before = this._beforeId(id);
29952
+ if (def.kind === 'style') {
29953
+ // Passed to the engine as it is (a copy, so the engine never holds the object you gave).
29954
+ var styleLayer = clone(def.layer);
29955
+ if (def.hidden) { styleLayer.layout = Object.assign({}, styleLayer.layout || {}, { visibility: 'none' }); }
29956
+ if (typeof styleLayer.source === 'string' && this._sources[styleLayer.source] && !m.getSource(styleLayer.source)) { this._engineSource(styleLayer.source); }
29957
+ m.addLayer(styleLayer, before);
29958
+ def.engineLayers.push(styleLayer.id);
29959
+ return;
29960
+ }
29961
+ var src = 'smap-' + id;
27804
29962
  var range = {};
27805
29963
  if (o.minzoom !== undefined) { range.minzoom = o.minzoom; }
27806
29964
  if (o.maxzoom !== undefined) { range.maxzoom = o.maxzoom; }
@@ -27817,6 +29975,7 @@ function smapCreateSdk(engine) {
27817
29975
  // The engine refuses a filter key whose value is undefined (unclustered points have none).
27818
29976
  if (layer.filter === undefined) { delete layer.filter; }
27819
29977
  if (layer.minzoom === undefined) { delete layer.minzoom; }
29978
+ if (layer.maxzoom === undefined) { delete layer.maxzoom; }
27820
29979
  if (layer.paint === undefined) { delete layer.paint; }
27821
29980
  // A layer's own minzoom (a marker's textMinzoom / badgeMinzoom) never shows it before the layer's own range.
27822
29981
  for (var k in range) { layer[k] = k === 'minzoom' && layer.minzoom !== undefined ? Math.max(layer.minzoom, range.minzoom) : range[k]; }
@@ -27854,7 +30013,7 @@ function smapCreateSdk(engine) {
27854
30013
  // Pins like Superroute's route map, drawn on the GPU: one image per colour pair (and per number, per badge),
27855
30014
  // made the first time a feature shows it. Numbers and badges are their own layers above the pins.
27856
30015
  var mk = o.marker;
27857
- var shape = mk.shape === 'circle' ? 'circle' : 'pin';
30016
+ var shape = mk.shape === 'circle' || mk.shape === 'vehicle' ? mk.shape : 'pin';
27858
30017
  var size = mk.size || 32;
27859
30018
  var bw = mk.borderWidth !== undefined ? mk.borderWidth : 3;
27860
30019
  var g = markerGeometry(shape, size);
@@ -27865,22 +30024,29 @@ function smapCreateSdk(engine) {
27865
30024
  // Opacity of the pin, its number and its badge together (the route map draws settled stops at .72).
27866
30025
  var markerPaint = mk.opacity !== undefined ? { 'icon-opacity': valueExpr(mk.opacity, 1) } : undefined;
27867
30026
  main = src + '-points';
30027
+ // From bakeZoom on (default 13) a pin's number is drawn into its own image, so an upper pin covers a lower
30028
+ // one completely. Further out, where thousands of pins can be in view, the numbers stay one layer above the
30029
+ // pins (an image per colour pair instead of per pin and number). bakeZoom: false keeps them separate.
30030
+ var plainPin = drawnImageId('pin', [shape, str(mk.fill, '#ffffff'), str(mk.border, color), String(bw), String(size)]);
30031
+ var bakeZoom = mk.text !== undefined && mk.bakeZoom !== false ? (mk.bakeZoom !== undefined ? mk.bakeZoom : 13) : null;
30032
+ var pinImage = bakeZoom === null ? plainPin : ['step', ['zoom'], plainPin, bakeZoom,
30033
+ drawnImageId('pinnum', [shape, str(mk.fill, '#ffffff'), str(mk.border, color), String(bw), String(size), str(mk.textColor, '#ffffff'), str(mk.text, '')])];
27868
30034
  var markerSpecs = [];
27869
30035
  var addMarker = function (layer) { markerSpecs.push(JSON.parse(JSON.stringify(layer))); add(layer); };
27870
30036
  addMarker({
27871
30037
  id: main, type: 'symbol', filter: only, paint: markerPaint,
27872
30038
  layout: {
27873
- 'icon-image': drawnImageId('pin', [shape, str(mk.fill, '#ffffff'), str(mk.border, color), String(bw), String(size)]),
27874
- 'icon-anchor': shape === 'circle' ? 'center' : 'bottom',
30039
+ 'icon-image': pinImage,
30040
+ 'icon-anchor': shape === 'pin' ? 'bottom' : 'center',
27875
30041
  // The image has room for the shadow below the tip: the tip, not the image's edge, is at the position.
27876
- 'icon-offset': [0, shape === 'circle' ? 0 : PIN_PAD], 'icon-size': scale,
30042
+ 'icon-offset': [0, shape === 'pin' ? PIN_PAD : 0], 'icon-size': scale,
27877
30043
  'icon-allow-overlap': true, 'icon-ignore-placement': true,
27878
30044
  },
27879
30045
  });
27880
30046
  if (mk.text !== undefined) {
27881
30047
  var text = str(mk.text, '');
27882
30048
  addMarker({
27883
- id: src + '-numbers', type: 'symbol', filter: both(['!=', text, '']), minzoom: mk.textMinzoom, paint: markerPaint,
30049
+ id: src + '-numbers', type: 'symbol', filter: both(['!=', text, '']), minzoom: mk.textMinzoom, maxzoom: bakeZoom === null ? undefined : bakeZoom, paint: markerPaint,
27884
30050
  layout: {
27885
30051
  'icon-image': drawnImageId('num', [str(mk.textColor, '#ffffff'), String(size), text]),
27886
30052
  'icon-offset': [0, -g.tipToHead], 'icon-size': scale, 'icon-allow-overlap': true, 'icon-ignore-placement': true,
@@ -27919,7 +30085,9 @@ function smapCreateSdk(engine) {
27919
30085
  add({
27920
30086
  id: main, type: 'circle', filter: only,
27921
30087
  paint: {
27922
- 'circle-color': color, 'circle-radius': o.radius || 6,
30088
+ // simplestyle (marker-color, marker-size) when the layer gives no colour / size of its own
30089
+ 'circle-color': o.color ? color : ['to-color', ['get', 'marker-color'], color],
30090
+ 'circle-radius': o.radius || (o.color ? 6 : ['match', ['get', 'marker-size'], 'small', 4, 'large', 9, 6]),
27923
30091
  'circle-stroke-width': o.strokeWidth !== undefined ? o.strokeWidth : 1.5, 'circle-stroke-color': o.strokeColor || '#ffffff',
27924
30092
  },
27925
30093
  });
@@ -27927,13 +30095,17 @@ function smapCreateSdk(engine) {
27927
30095
  if (o.label) {
27928
30096
  add({
27929
30097
  id: src + '-labels', type: 'symbol', filter: only,
27930
- layout: { 'text-field': ['to-string', ['get', o.label]], 'text-font': ['Noto Sans Regular'], 'text-size': 12, 'text-offset': [0, 1.2], 'text-anchor': 'top', 'text-optional': true },
30098
+ layout: o.labelPosition === 'right'
30099
+ ? { 'text-field': ['to-string', ['get', o.label]], 'text-font': ['Noto Sans Regular'], 'text-size': 11, 'text-offset': [1.3, 0], 'text-anchor': 'left', 'text-optional': true, 'text-max-width': 12 }
30100
+ : { 'text-field': ['to-string', ['get', o.label]], 'text-font': ['Noto Sans Regular'], 'text-size': 12, 'text-offset': [0, 1.2], 'text-anchor': 'top', 'text-optional': true },
27931
30101
  paint: { 'text-color': '#1f2937', 'text-halo-color': '#ffffff', 'text-halo-width': 1.4 },
27932
30102
  });
27933
30103
  }
27934
30104
  } else if (def.kind === 'lines') {
27935
30105
  main = src + '-lines';
27936
- var lineColor = valueExpr(o.color, '#4f46e5');
30106
+ // simplestyle (stroke, stroke-width, stroke-opacity) when the layer gives no colour of its own
30107
+ var simple = o.color === undefined || o.color === null;
30108
+ var lineColor = simple ? ['to-color', ['get', 'stroke'], '#4f46e5'] : valueExpr(o.color, '#4f46e5');
27937
30109
  var round = { 'line-cap': 'round', 'line-join': 'round' };
27938
30110
  // Bottom to top: casing, shadow, the line, its arrows (as on Superroute's route map).
27939
30111
  if (o.casing) {
@@ -27946,7 +30118,11 @@ function smapCreateSdk(engine) {
27946
30118
  add({ id: src + '-shadow', type: 'line', layout: round, paint: {
27947
30119
  'line-color': valueExpr(sh.color, '#000000'), 'line-width': sh.width || 7, 'line-opacity': sh.opacity !== undefined ? sh.opacity : 0.3 } });
27948
30120
  }
27949
- var paint = { 'line-color': lineColor, 'line-width': o.width || 3, 'line-opacity': o.opacity !== undefined ? o.opacity : 1 };
30121
+ var paint = {
30122
+ 'line-color': lineColor,
30123
+ 'line-width': o.width || (simple ? numberProp('stroke-width', 3) : 3),
30124
+ 'line-opacity': o.opacity !== undefined ? o.opacity : (simple ? numberProp('stroke-opacity', 1) : 1),
30125
+ };
27950
30126
  if (o.dash) { paint['line-dasharray'] = o.dash; }
27951
30127
  add({ id: main, type: 'line', layout: round, paint: paint });
27952
30128
  if (o.arrows) {
@@ -27977,8 +30153,16 @@ function smapCreateSdk(engine) {
27977
30153
  });
27978
30154
  } else {
27979
30155
  main = src + '-fill';
27980
- add({ id: main, type: 'fill', paint: { 'fill-color': o.fill || color, 'fill-opacity': o.opacity !== undefined ? o.opacity : 0.25 } });
27981
- add({ id: src + '-outline', type: 'line', paint: { 'line-color': o.outline || o.fill || color, 'line-width': o.outlineWidth || 1.5 } });
30156
+ if (!o.fill && !o.color) {
30157
+ // simplestyle (fill, fill-opacity, stroke, stroke-width, stroke-opacity) when the layer gives no colour
30158
+ add({ id: main, type: 'fill', paint: { 'fill-color': ['to-color', ['get', 'fill'], color], 'fill-opacity': o.opacity !== undefined ? o.opacity : numberProp('fill-opacity', 0.25) } });
30159
+ add({ id: src + '-outline', type: 'line', paint: {
30160
+ 'line-color': o.outline || ['to-color', ['get', 'stroke'], ['get', 'fill'], color],
30161
+ 'line-width': o.outlineWidth || numberProp('stroke-width', 1.5), 'line-opacity': numberProp('stroke-opacity', 1) } });
30162
+ } else {
30163
+ add({ id: main, type: 'fill', paint: { 'fill-color': o.fill || color, 'fill-opacity': o.opacity !== undefined ? o.opacity : 0.25 } });
30164
+ add({ id: src + '-outline', type: 'line', paint: { 'line-color': o.outline || o.fill || color, 'line-width': o.outlineWidth || 1.5 } });
30165
+ }
27982
30166
  }
27983
30167
  }
27984
30168
  // The popup is opened by the map's one click handler (_popupClick), from this layer's own hit test.
@@ -28009,6 +30193,7 @@ function smapCreateSdk(engine) {
28009
30193
  if (layer.filter === undefined) { delete layer.filter; }
28010
30194
  if (layer.paint === undefined) { delete layer.paint; }
28011
30195
  if (layer.minzoom === undefined) { delete layer.minzoom; }
30196
+ if (layer.maxzoom === undefined) { delete layer.maxzoom; }
28012
30197
  m.addLayer(layer);
28013
30198
  def.engineLayers.push(layer.id);
28014
30199
  if (!top) { top = layer.id; }
@@ -28048,10 +30233,24 @@ function smapCreateSdk(engine) {
28048
30233
  (this._handlers[id] = this._handlers[id] || []).push([type, layer, fn]);
28049
30234
  };
28050
30235
 
28051
- return { Map: SMap, version: VERSION, styles: FLAVORS.slice(), engine: engine, popupTemplate: popupTemplate };
30236
+ return {
30237
+ Map: SMap, version: VERSION, styles: FLAVORS.slice(), engine: engine, popupTemplate: popupTemplate,
30238
+ slots: SLOTS.slice(),
30239
+ formats: interop.formats,
30240
+ convert: interop.convert,
30241
+ rasterUrl: interop.rasterUrl,
30242
+ rasterTileJsonUrl: interop.rasterTileJsonUrl,
30243
+ styleUrl: interop.styleUrl,
30244
+ applyBasemapOverrides: interop.applyBasemapOverrides,
30245
+ basemapClasses: interop.basemapClasses,
30246
+ slotAnchors: interop.slotAnchors,
30247
+ parseWmtsCapabilities: interop.parseWmtsCapabilities,
30248
+ PMTilesArchive: interop.PMTilesArchive,
30249
+ useDeck: interop.useDeck,
30250
+ };
28052
30251
  });
28053
30252
 
28054
30253
  return module.exports;
28055
30254
  }
28056
- (typeof globalThis !== 'undefined' ? globalThis : window).sMap = smapCreateSdk(smapCreateEngine());
30255
+ (typeof globalThis !== 'undefined' ? globalThis : window).sMap = smapCreateSdk(smapCreateEngine(), smapCreateInterop());
28057
30256
  })();