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