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