tangram
Version:
WebGL Maps for Vector Tiles
1,461 lines (1,371 loc) • 1.23 MB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tangram = factory());
})(this, (function () { 'use strict';
// define() gets called for each chunk generated by the first Rollup pass.
// The order the chunks are called in is controlled by the imports in bundle.js:
//
// shared.js: shared dependencies between main and worker threads
// scene_worker.js: worker thread code
// index.js: main thread code
// Once all chunks have been provided, the worker thread code is assembled,
// incorporating the shared chunk code, then turned into a blob URL which
// can be used to instantiate the worker.
var shared, worker, Tangram = {};
function define(_, chunk) {
if (!shared) {
shared = chunk;
} else if (!worker) {
worker = chunk;
} else {
var worker_bundle = 'var shared_chunk = {}; (' + shared + ')(shared_chunk); (' + worker + ')(shared_chunk);'
var shared_chunk = {};
shared(shared_chunk);
Tangram = chunk(shared_chunk);
Tangram.workerURL = window.URL.createObjectURL(new Blob([worker_bundle], { type: 'text/javascript' }));
}
}
define(['exports'], (function (exports) { 'use strict';
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _arrayLikeToArray$1(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _unsupportedIterableToArray$1(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray$1(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$1(r, a) : void 0;
}
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray$1(r, e) || _nonIterableRest();
}
/*jshint worker: true*/
// Mark thread as main or worker
var Thread = {};
try {
if (window instanceof Window && window.document instanceof HTMLDocument) {
// jshint ignore:line
Thread.is_worker = false;
Thread.is_main = true;
}
} catch (e) {
Thread.is_worker = true;
Thread.is_main = false;
// Patch for 3rd party libs that require these globals to be present. Specifically, FontFaceObserver.
// Brittle solution but allows that library to load on worker threads.
self.window = {
document: {}
};
self.document = self.window.document;
}
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function toPropertyKey(t) {
var i = toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _defineProperty(e, r, t) {
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) return _arrayLikeToArray$1(r);
}
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray$1(r) || _nonIterableSpread();
}
function _classCallCheck(a, n) {
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
function _defineProperties(e, r) {
for (var t = 0; t < r.length; t++) {
var o = r[t];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, toPropertyKey(o.key), o);
}
}
function _createClass(e, r, t) {
return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
var version$1 = "0.22.0";
var version = 'v' + version$1;
function _isNativeReflectConstruct$b() {
try {
var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
} catch (t) {}
return (_isNativeReflectConstruct$b = function _isNativeReflectConstruct() {
return !!t;
})();
}
function _setPrototypeOf(t, e) {
return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
return t.__proto__ = e, t;
}, _setPrototypeOf(t, e);
}
function _construct(t, e, r) {
if (_isNativeReflectConstruct$b()) return Reflect.construct.apply(null, arguments);
var o = [null];
o.push.apply(o, e);
var p = new (t.bind.apply(t, o))();
return r && _setPrototypeOf(p, r.prototype), p;
}
var WorkerBroker;
var WorkerBroker$1 = WorkerBroker = {};
// Global list of all worker messages
// Uniquely tracks every call made between main thread and a worker
var message_id = 0;
var messages = {};
// Register an object to receive calls from other thread
WorkerBroker.targets = {};
WorkerBroker.addTarget = function (name, target) {
WorkerBroker.targets[name] = target;
};
WorkerBroker.removeTarget = function (name) {
if (name) {
delete WorkerBroker.targets[name];
}
};
// Given a dot-notation-style method name, e.g. 'Object.object.method',
// find the object to call the method on from the list of registered targets
function findTarget(method) {
var chain = [];
if (typeof method === 'string') {
chain = method.split('.');
method = chain.pop();
}
var target = WorkerBroker.targets;
for (var m = 0; m < chain.length; m++) {
if (target[chain[m]]) {
target = target[chain[m]];
} else {
return [];
}
}
return [method, target];
}
// Main thread:
// - Send messages to workers, and optionally receive an async response as a promise
// - Receive messages from workers, and optionally send an async response back as a promise
function setupMainThread() {
// Send a message to a worker, and optionally get an async response
// Arguments:
// - worker: one or more web worker instances to send the message to (single value or array)
// - method: the method with this name, specified with dot-notation, will be invoked in the worker
// - message: spread of arguments to call the method with
// Returns:
// - a promise that will be fulfilled if the worker method returns a value (could be immediately, or async)
//
WorkerBroker.postMessage = function (worker, method) {
for (var _len = arguments.length, message = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
message[_key - 2] = arguments[_key];
}
// If more than one worker specified, post to multiple
if (Array.isArray(worker)) {
return Promise.all(worker.map(function (w) {
var _WorkerBroker;
return (_WorkerBroker = WorkerBroker).postMessage.apply(_WorkerBroker, [w, method].concat(_toConsumableArray(message)));
}));
}
// Parse options
var options = {};
if (_typeof(method) === 'object') {
options = method;
method = method.method;
}
// Track state of this message
var promise = new Promise(function (resolve, reject) {
messages[message_id] = {
method: method,
message: message,
resolve: resolve,
reject: reject
};
});
var payload,
transferables = [];
if (message && message.length === 1 && message[0] instanceof WorkerBroker.withTransferables) {
transferables = message[0].transferables;
message = message[0].value;
}
payload = {
type: 'main_send',
// mark message as method invocation from main thread
message_id: message_id,
// unique id for this message, for life of program
method: method,
// will dispatch to a function of this name within the worker
message: message // message payload
};
if (options.stringify) {
payload = JSON.stringify(payload);
}
worker.postMessage(payload, transferables.map(function (t) {
return t.object;
}));
freeTransferables(transferables);
if (transferables.length > 0) {
log('trace', "'".concat(method, "' transferred ").concat(transferables.length, " objects to worker thread"));
}
message_id++;
return promise;
};
// Add a worker to communicate with - each worker must be registered from the main thread
WorkerBroker.addWorker = function (worker) {
if (!(worker instanceof Worker)) {
throw Error('Worker broker could not add non-Worker object', worker);
}
worker.addEventListener('message', function WorkerBrokerMainThreadHandler(event) {
var data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
var id = data.message_id;
// Listen for messages coming back from the worker, and fulfill that message's promise
if (data.type === 'worker_reply') {
// Pass the result to the promise
if (messages[id]) {
if (data.error) {
messages[id].reject(data.error);
} else {
messages[id].resolve(data.message);
}
delete messages[id];
}
}
// Listen for messages initiating a call from the worker, dispatch them,
// and send any return value back to the worker
// Unique id for this message & return call to main thread
else if (data.type === 'worker_send' && id != null) {
// Call the requested method and save the return value
var result, error, target, method_name, method;
try {
var _findTarget = findTarget(data.method);
var _findTarget2 = _slicedToArray(_findTarget, 2);
method_name = _findTarget2[0];
target = _findTarget2[1];
if (!target) {
throw Error("Worker broker could not dispatch message type ".concat(data.method, " on target ").concat(data.target, " because no object with that name is registered on main thread"));
}
method = typeof target[method_name] === 'function' && target[method_name];
if (!method) {
throw Error("Worker broker could not dispatch message type ".concat(data.method, " on target ").concat(data.target, " because object has no method with that name"));
}
result = method.apply(target, data.message);
} catch (e) {
// Thrown errors will be passed back (in string form) to worker
error = e;
}
// Send return value to worker
var payload,
transferables = [];
// Async result
if (result instanceof Promise) {
result.then(function (value) {
if (value instanceof WorkerBroker.withTransferables) {
transferables = value.transferables;
value = value.value[0];
}
payload = {
type: 'main_reply',
message_id: id,
message: value
};
worker.postMessage(payload, transferables.map(function (t) {
return t.object;
}));
freeTransferables(transferables);
if (transferables.length > 0) {
log('trace', "'".concat(method_name, "' transferred ").concat(transferables.length, " objects to worker thread"));
}
}, function (error) {
worker.postMessage({
type: 'main_reply',
message_id: id,
error: error instanceof Error ? "".concat(error.message, ": ").concat(error.stack) : error
});
});
}
// Immediate result
else {
if (result instanceof WorkerBroker.withTransferables) {
transferables = result.transferables;
result = result.value[0];
}
payload = {
type: 'main_reply',
message_id: id,
message: result,
error: error instanceof Error ? "".concat(error.message, ": ").concat(error.stack) : error
};
worker.postMessage(payload, transferables.map(function (t) {
return t.object;
}));
freeTransferables(transferables);
if (transferables.length > 0) {
log('trace', "'".concat(method_name, "' transferred ").concat(transferables.length, " objects to worker thread"));
}
}
}
});
};
// Expose for debugging
WorkerBroker.getMessages = function () {
return messages;
};
WorkerBroker.getMessageId = function () {
return message_id;
};
}
// Worker threads:
// - Receive messages from main thread, and optionally send an async response back as a promise
// - Send messages to main thread, and optionally receive an async response as a promise
function setupWorkerThread() {
// Send a message to the main thread, and optionally get an async response as a promise
// Arguments:
// - method: the method with this name, specified with dot-notation, will be invoked on the main thread
// - message: array of arguments to call the method with
// Returns:
// - a promise that will be fulfilled if the main thread method returns a value (could be immediately, or async)
//
WorkerBroker.postMessage = function (method) {
for (var _len2 = arguments.length, message = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
message[_key2 - 1] = arguments[_key2];
}
// Parse options
var options = {};
if (_typeof(method) === 'object') {
options = method;
method = method.method;
}
// Track state of this message
var promise = new Promise(function (resolve, reject) {
messages[message_id] = {
method: method,
message: message,
resolve: resolve,
reject: reject
};
});
var payload,
transferables = [];
if (message && message.length === 1 && message[0] instanceof WorkerBroker.withTransferables) {
transferables = message[0].transferables;
message = message[0].value;
}
payload = {
type: 'worker_send',
// mark message as method invocation from worker
message_id: message_id,
// unique id for this message, for life of program
method: method,
// will dispatch to a method of this name on the main thread
message: message // message payload
};
if (options.stringify) {
payload = JSON.stringify(payload);
}
self.postMessage(payload, transferables.map(function (t) {
return t.object;
}));
freeTransferables(transferables);
if (transferables.length > 0) {
log('trace', "'".concat(method, "' transferred ").concat(transferables.length, " objects to main thread"));
}
message_id++;
return promise;
};
self.addEventListener('message', function WorkerBrokerWorkerThreadHandler(event) {
var data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
var id = data.message_id;
// Listen for messages coming back from the main thread, and fulfill that message's promise
if (data.type === 'main_reply') {
// Pass the result to the promise
if (messages[id]) {
if (data.error) {
messages[id].reject(data.error);
} else {
messages[id].resolve(data.message);
}
delete messages[id];
}
}
// Receive messages from main thread, dispatch them, and send back a reply
// Unique id for this message & return call to main thread
else if (data.type === 'main_send' && id != null) {
// Call the requested worker method and save the return value
var result, error, target, method_name, method;
try {
var _findTarget3 = findTarget(data.method);
var _findTarget4 = _slicedToArray(_findTarget3, 2);
method_name = _findTarget4[0];
target = _findTarget4[1];
if (!target) {
throw Error("Worker broker could not dispatch message type ".concat(data.method, " on target ").concat(data.target, " because no object with that name is registered on main thread"));
}
method = typeof target[method_name] === 'function' && target[method_name];
if (!method) {
throw Error("Worker broker could not dispatch message type ".concat(data.method, " because worker has no method with that name"));
}
result = method.apply(target, data.message);
} catch (e) {
// Thrown errors will be passed back (in string form) to main thread
error = e;
}
// Send return value to main thread
var payload,
transferables = [];
// Async result
if (result instanceof Promise) {
result.then(function (value) {
if (value instanceof WorkerBroker.withTransferables) {
transferables = value.transferables;
value = value.value[0];
}
payload = {
type: 'worker_reply',
message_id: id,
message: value
};
self.postMessage(payload, transferables.map(function (t) {
return t.object;
}));
freeTransferables(transferables);
if (transferables.length > 0) {
log('trace', "'".concat(method_name, "' transferred ").concat(transferables.length, " objects to main thread"));
}
}, function (error) {
self.postMessage({
type: 'worker_reply',
message_id: id,
error: error instanceof Error ? "".concat(error.message, ": ").concat(error.stack) : error
});
});
}
// Immediate result
else {
if (result instanceof WorkerBroker.withTransferables) {
transferables = result.transferables;
result = result.value[0];
}
payload = {
type: 'worker_reply',
message_id: id,
message: result,
error: error instanceof Error ? "".concat(error.message, ": ").concat(error.stack) : error
};
self.postMessage(payload, transferables.map(function (t) {
return t.object;
}));
freeTransferables(transferables);
if (transferables.length > 0) {
log('trace', "'".concat(method_name, "' transferred ").concat(transferables.length, " objects to main thread"));
}
}
}
});
}
// Special value wrapper, to indicate that we want to find and include transferable objects in the message
WorkerBroker.withTransferables = function () {
for (var _len3 = arguments.length, value = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
value[_key3] = arguments[_key3];
}
if (!(this instanceof WorkerBroker.withTransferables)) {
return _construct(WorkerBroker.withTransferables, value);
}
this.value = value;
this.transferables = findTransferables(this.value);
};
// Build a list of transferable objects from a source object
// Returns a list of info about each transferable:
// - object: the actual transferable object
// - parent: the parent object that the transferable is a property of (if any)
// - property: the property name of the transferable on the parent object (if any)
// TODO: add option in case you DON'T want to transfer objects
function findTransferables(source) {
var parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var property = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var list = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
if (!source) {
return list;
}
if (Array.isArray(source)) {
// Check each array element
source.forEach(function (x, i) {
return findTransferables(x, source, i, list);
});
} else if (_typeof(source) === 'object') {
// Is the object a transferable array buffer?
if (source instanceof ArrayBuffer) {
list.push({
object: source,
parent: parent,
property: property
});
}
// Or looks like a typed array (has an array buffer property)?
else if (source.buffer instanceof ArrayBuffer) {
list.push({
object: source.buffer,
parent: parent,
property: property
});
}
// Otherwise check each property
else {
for (var prop in source) {
findTransferables(source[prop], source, prop, list);
}
}
}
return list;
}
// Remove neutered transferables from parent objects, as they should no longer be accessed after transfer
function freeTransferables(transferables) {
if (!Array.isArray(transferables)) {
return;
}
transferables.filter(function (t) {
return t.parent && t.property;
}).forEach(function (t) {
return delete t.parent[t.property];
});
}
// Setup this thread as appropriate
if (Thread.is_main) {
setupMainThread();
}
if (Thread.is_worker) {
setupWorkerThread();
}
var LEVELS = {
silent: -1,
error: 0,
warn: 1,
info: 2,
debug: 3,
trace: 4
};
var methods = {};
var logged_once = {};
function methodForLevel(level) {
if (Thread.is_main) {
methods[level] = methods[level] || (console[level] ? console[level] : console.log).bind(console); // eslint-disable-line no-console
return methods[level];
}
}
// Logs message, proxying any log requests from worker threads back to the main thread.
// Returns (asynchronously, due to proxying) a boolean indicating if the message was logged.
// Option `once: true` can be used to only log each unique log message once (e.g. for warnings
// that would otherwise be repetitive or possibly logged thousands of times, such as per feature).
function log(opts) {
var level = _typeof(opts) === 'object' ? opts.level : opts;
if (LEVELS[level] <= LEVELS[log.level]) {
for (var _len = arguments.length, msg = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
msg[_key - 1] = arguments[_key];
}
if (Thread.is_worker) {
// Proxy to main thread
return WorkerBroker$1.postMessage.apply(WorkerBroker$1, [{
method: '_logProxy',
stringify: true
}, opts].concat(msg));
} else {
// Only log message once?
if (_typeof(opts) === 'object' && opts.once === true) {
if (logged_once[JSON.stringify(msg)]) {
return Promise.resolve(false);
}
logged_once[JSON.stringify(msg)] = true;
}
// Write to console (on main thread)
var logger = methodForLevel(level);
if (msg.length > 1) {
logger.apply(void 0, ["Tangram ".concat(version, " [").concat(level, "]: ").concat(msg[0])].concat(_toConsumableArray(msg.slice(1))));
} else {
logger("Tangram ".concat(version, " [").concat(level, "]: ").concat(msg[0]));
}
}
return Promise.resolve(true);
}
return Promise.resolve(false);
}
log.level = 'info';
log.workers = null;
log.setLevel = function (level) {
log.level = level;
if (Thread.is_main && Array.isArray(log.workers)) {
WorkerBroker$1.postMessage(log.workers, '_logSetLevelProxy', level);
}
};
if (Thread.is_main) {
log.setWorkers = function (workers) {
log.workers = workers;
};
log.reset = function () {
logged_once = {};
};
}
WorkerBroker$1.addTarget('_logProxy', log); // proxy log messages from worker to main thread
WorkerBroker$1.addTarget('_logSetLevelProxy', log.setLevel); // proxy log level setting from main to worker thread
var Utils = {};
WorkerBroker$1.addTarget('Utils', Utils);
// Basic Safari detection
// http://stackoverflow.com/questions/7944460/detect-safari-browser
Utils.isSafari = function () {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
};
// Basic IE11 or Edge detection
Utils.isMicrosoft = function () {
return /(Trident\/7.0|Edge[ /](\d+[.\d]+))/i.test(navigator.userAgent);
};
Utils._requests = {}; // XHR requests on current thread
Utils._proxy_requests = {}; // XHR requests proxied to main thread
// `request_key` is a user-provided key that can be later used to cancel the request
Utils.io = function (url) {
var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60000;
var responseType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'text';
var method = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'GET';
var headers = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
var request_key = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
var proxy = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false;
if (Thread.is_worker && Utils.isMicrosoft()) {
// Some versions of IE11 and Edge will hang web workers when performing XHR requests
// These requests can be proxied through the main thread
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9545866/
log('debug', 'Proxying request for URL to worker', url);
if (request_key) {
Utils._proxy_requests[request_key] = true; // mark as proxied
}
return WorkerBroker$1.postMessage('Utils.io', url, timeout, responseType, method, headers, request_key, true);
} else {
var request = new XMLHttpRequest();
var promise = new Promise(function (resolve, reject) {
request.open(method, url, true);
request.timeout = timeout;
request.responseType = responseType;
// Attach optional request headers
if (headers && _typeof(headers) === 'object') {
for (var key in headers) {
request.setRequestHeader(key, headers[key]);
}
}
request.onload = function () {
if (request.status === 200) {
if (['text', 'json'].indexOf(request.responseType) > -1) {
resolve({
body: request.responseText,
status: request.status
});
} else {
resolve({
body: request.response,
status: request.status
});
}
} else if (request.status === 204) {
// No Content
resolve({
body: null,
status: request.status
});
} else {
reject(Error('Request error with a status of ' + request.statusText));
}
};
request.onerror = function (evt) {
reject(Error('There was a network error' + evt.toString()));
};
request.ontimeout = function (evt) {
reject(Error('timeout ' + evt.toString()));
};
request.send();
});
promise = promise.then(function (response) {
if (request_key) {
delete Utils._requests[request_key];
}
if (proxy) {
return WorkerBroker$1.withTransferables(response);
}
return response;
});
if (request_key) {
Utils._requests[request_key] = request;
}
return promise;
}
};
// Çancel a pending network request by user-provided request key
Utils.cancelRequest = function (key) {
// Check for a request that was proxied to the main thread
if (Thread.is_worker && Utils._proxy_requests[key]) {
return WorkerBroker$1.postMessage('Utils.cancelRequest', key); // forward to main thread
}
var req = Utils._requests[key];
if (req) {
log('trace', "Cancelling network request key '".concat(key, "'"));
Utils._requests[key].abort();
delete Utils._requests[key];
} else {
log('trace', "Could not find network request key '".concat(key, "'"));
}
};
// Stringify an object into JSON, but convert functions to strings
Utils.serializeWithFunctions = function (obj) {
if (typeof obj === 'function') {
return obj.toString();
}
var serialized = JSON.stringify(obj, function (k, v) {
// Convert functions to strings
if (typeof v === 'function') {
return v.toString();
}
return v;
});
return serialized;
};
// Default to allowing high pixel density
// Returns true if display density changed
Utils.use_high_density_display = true;
Utils.updateDevicePixelRatio = function () {
var prev = Utils.device_pixel_ratio;
Utils.device_pixel_ratio = Utils.use_high_density_display && window.devicePixelRatio || 1;
return Utils.device_pixel_ratio !== prev;
};
if (Thread.is_main) {
Utils.updateDevicePixelRatio();
}
// Used for differentiating between power-of-2 and non-power-of-2 textures
// Via: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load
Utils.isPowerOf2 = function (value) {
return (value & value - 1) === 0;
};
// Interpolate 'x' along a series of control points
// 'points' is an array of control points in the form [x, y]
//
// Example:
// Control points:
// [0, 5]: when x=0, y=5
// [4, 10]: when x=4, y=10
//
// Utils.interpolate(2, [[0, 5], [4, 10]]);
// -> computes x=2, halfway between x=0 and x=4: (10 - 5) / 2 +5
// -> returns 7.5
//
// TODO: add other interpolation methods besides linear
//
Utils.interpolate = function (x, points, transform) {
// If this doesn't resemble a list of control points, just return the original value
if (!Array.isArray(points) || !Array.isArray(points[0])) {
return points;
} else if (points.length < 1) {
return points;
}
var x1, x2, d, y, y1, y2;
// Min bounds
if (x <= points[0][0]) {
y = points[0][1];
if (typeof transform === 'function') {
y = transform(y);
}
}
// Max bounds
else if (x >= points[points.length - 1][0]) {
y = points[points.length - 1][1];
if (typeof transform === 'function') {
y = transform(y);
}
}
// Find which control points x is between
else {
for (var i = 0; i < points.length - 1; i++) {
if (x >= points[i][0] && x < points[i + 1][0]) {
// Linear interpolation
x1 = points[i][0];
x2 = points[i + 1][0];
// Multiple values
if (Array.isArray(points[i][1])) {
y = [];
for (var c = 0; c < points[i][1].length; c++) {
if (typeof transform === 'function') {
y1 = transform(points[i][1][c]);
y2 = transform(points[i + 1][1][c]);
d = y2 - y1;
y[c] = d * (x - x1) / (x2 - x1) + y1;
} else {
d = points[i + 1][1][c] - points[i][1][c];
y[c] = d * (x - x1) / (x2 - x1) + points[i][1][c];
}
}
}
// Single value
else {
if (typeof transform === 'function') {
y1 = transform(points[i][1]);
y2 = transform(points[i + 1][1]);
d = y2 - y1;
y = d * (x - x1) / (x2 - x1) + y1;
} else {
d = points[i + 1][1] - points[i][1];
y = d * (x - x1) / (x2 - x1) + points[i][1];
}
}
break;
}
}
}
return y;
};
Utils.toCSSColor = function (color) {
if (color != null) {
if (color[3] === 1) {
// full opacity
return "rgb(".concat(color.slice(0, 3).map(function (c) {
return Math.round(c * 255);
}).join(', '), ")");
}
// RGB is between [0, 255] opacity is between [0, 1]
return "rgba(".concat(color.map(function (c, i) {
return i < 3 && Math.round(c * 255) || c;
}).join(', '), ")");
}
};
var debugSettings;
var debugSettings$1 = debugSettings = {
// draws a blue rectangle border around the collision box of a label
draw_label_collision_boxes: false,
// draws a green rectangle border within the texture box of a label
draw_label_texture_boxes: false,
// suppreses fade-in of labels
suppress_label_fade_in: false,
// suppress animaton of label snap to pixel grid
suppress_label_snap_animation: false,
// show hidden labels for debugging
show_hidden_labels: false,
// collect feature/geometry stats on styling layers
layer_stats: false,
// draw scene in wireframe mode
wireframe: false
};
function mergeDebugSettings(settings) {
Object.assign(debugSettings, settings);
}
// Adds a base origin to relative URLs
function addBaseURL(url, base) {
if (!url || !isRelativeURL(url)) {
return url;
}
var relative_path = url[0] !== '/';
var base_info;
if (base) {
base_info = document.createElement('a'); // use a temporary element to parse URL
base_info.href = base;
} else {
base_info = window.location;
}
if (relative_path) {
var path = pathForURL(base_info.href);
url = path + url;
} else {
var origin = base_info.origin;
if (!origin) {
origin = base_info.protocol + '//' + base_info.host; // IE11 doesn't have origin property
}
url = origin + url;
}
return url;
}
function pathForURL(url) {
if (typeof url === 'string' && url.search(/^(data|blob):/) === -1) {
var qs = url.indexOf('?');
if (qs > -1) {
url = url.substr(0, qs);
}
var hash = url.indexOf('#');
if (hash > -1) {
url = url.substr(0, hash);
}
return url.substr(0, url.lastIndexOf('/') + 1) || '';
}
return '';
}
function extensionForURL(url) {
url = url.split('/').pop();
var last_dot = url.lastIndexOf('.');
if (last_dot > -1) {
return url.substring(last_dot + 1);
}
}
function isLocalURL(url) {
if (typeof url !== 'string') {
return;
}
return url.search(/^(data|blob):/) > -1;
}
function isRelativeURL(url) {
if (typeof url !== 'string') {
return;
}
return !(url.search(/^(http|https|data|blob):/) > -1 || url.substr(0, 2) === '//');
}
// Resolves './' and '../' components from relative path, to get a "flattened" path
function flattenRelativeURL(url) {
var dirs = (url || '').split('/');
for (var d = 1; d < dirs.length; d++) {
if (dirs[d] === '.') {
dirs.splice(d, 1);
d--;
} else if (dirs[d] === '..') {
d = d + 0;
dirs.splice(d - 1, 2);
d--;
}
}
return dirs.join('/');
}
// Add a set of query string params to a URL
// params: hash of key/value pairs of query string parameters
// returns array of: [modified URL, array of duplicate param name and values]
function addParamsToURL(url, params) {
if (!params || Object.keys(params).length === 0) {
return [url, []];
}
var qs_index = url.indexOf('?');
var hash_index = url.indexOf('#');
// Save and trim hash
var hash = '';
if (hash_index > -1) {
hash = url.slice(hash_index);
url = url.slice(0, hash_index);
}
// Start query string
if (qs_index === -1) {
qs_index = url.length;
url += '?';
}
qs_index++; // advanced past '?'
// Build query string params
var url_params = '';
var dupes = [];
for (var p in params) {
if (getURLParameter(p, url) !== '') {
dupes.push([p, params[p]]);
continue;
}
url_params += "".concat(p, "=").concat(params[p], "&");
}
// Insert new query string params and restore hash
url = url.slice(0, qs_index) + url_params + url.slice(qs_index) + hash;
return [url, dupes];
}
// Polyfill (for Safari compatibility)
var _createObjectURL;
function createObjectURL(url) {
if (_createObjectURL === undefined) {
_createObjectURL = window.URL && window.URL.createObjectURL || window.webkitURL && window.webkitURL.createObjectURL;
if (typeof _createObjectURL !== 'function') {
_createObjectURL = null;
log('warn', 'window.URL.createObjectURL (or vendor prefix) not found, unable to create local blob URLs');
}
}
if (_createObjectURL) {
return _createObjectURL(url);
} else {
return url;
}
}
// Via https://davidwalsh.name/query-string-javascript
function getURLParameter(name, url) {
name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(url);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
// import log from './log';
var Task = {
id: 0,
// unique id per task
queue: [],
// current queue of outstanding tasks
max_time: 20,
// default time in which all tasks should complete per frame
start_time: null,
// start time for tasks in current frame
state: {},
// track flags about environment state (ex: whether user is currently moving the view)
add: function add(task) {
task.id = Task.id++;
task.max_time = task.max_time || Task.max_time; // allow task to run for this much time (tasks have a global collective limit per frame, too)
task.pause_factor = task.pause_factor || 1; // pause tasks by this many frames when they run too long
var promise = new Promise(function (resolve, reject) {
task.resolve = resolve;
task.reject = reject;
});
task.promise = promise;
task.elapsed = 0;
task.total_elapsed = 0;
task.stats = {
calls: 0
};
this.queue.push(task);
// Run task immediately if under total frame time
this.start_time = this.start_time || performance.now(); // start frame timer if necessary
this.elapsed = performance.now() - this.start_time;
if (this.elapsed < Task.max_time || task.immediate) {
this.process(task);
}
return task.promise;
},
remove: function remove(task) {
var idx = this.queue.indexOf(task);
if (idx > -1) {
this.queue.splice(idx, 1);
}
},
process: function process(task) {
// Skip task while user is moving the view, if the task requests it
// (for intensive tasks that lock the UI, like canvas rasterization)
if (this.state.user_moving_view && task.user_moving_view === false) {
// log('debug', `*** SKIPPING task id ${task.id}, ${task.type} while user is moving view`);
return;
}
// Skip task if it's currently paused
if (task.pause) {
// log('debug', `*** PAUSING task id ${task.id}, ${task.type} (${task.pause})`);
task.pause--;
return true;
}
task.stats.calls++;
task.start_time = performance.now(); // start task timer
return task.run(task);
},
processAll: function processAll() {
this.start_time = this.start_time || performance.now(); // start frame timer if necessary
for (var i = 0; i < this.queue.length; i++) {
// Exceeded either total task time, or total frame time
var task = this.queue[i];
if (this.process(task) !== true) {
// If the task didn't complete, pause it for a task-specific number of frames
// (can be disabled by setting pause_factor to 0)
if (!task.pause) {
task.pause = task.elapsed > task.max_time ? task.pause_factor : 0;
}
task.total_elapsed += task.elapsed;
}
// Check total frame time
this.elapsed = performance.now() - this.start_time;
if (this.elapsed >= Task.max_time) {
this.start_time = null; // reset frame timer
break;
}
}
},
finish: function finish(task, value) {
task.elapsed = performance.now() - task.start_time;
task.total_elapsed += task.elapsed;
// log('debug', `task type ${task.type}, tile ${task.id}, finish after ${task.stats.calls} calls, ${task.total_elapsed.toFixed(2)} elapsed`);
this.remove(task);
task.resolve(value);
return task.promise;
},
cancel: function cancel(task) {
var val;
if (task.cancel instanceof Function) {
val = task.cancel(task); // optional cancel function
}
task.resolve(val);
},
shouldContinue: function shouldContinue(task) {
// Suspend task if it runs over its specific per-frame limit, or the global limit
task.elapsed = performance.now() - task.start_time;
this.elapsed = performance.now() - this.start_time;
return task.elapsed < task.max_time && this.elapsed < Task.max_time;
},
removeForTile: function removeForTile(tile_id) {
for (var idx = this.queue.length - 1; idx >= 0; idx--) {
if (this.queue[idx].tile_id === tile_id) {
// log('trace', `Task: remove tasks for tile ${tile_id}`);
this.cancel(this.queue[idx]);
this.queue.splice(idx, 1);
}
}
},
setState: function setState(state) {
this.state = state;
}
};
function subscribeMixin(target) {
var listeners = [];
return Object.assign(target, {
subscribe: function subscribe(listener) {
if (listeners.indexOf(listener) === -1) {
listeners.push(listener);
}
},
unsubscribe: function unsubscribe(listener) {
var index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
},
unsubscribeAll: function unsubscribeAll() {
listeners = [];
},
trigger: function trigger(event) {
for (var _len = arguments.length, data = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
data[_key - 1] = arguments[_key];
}
listeners.forEach(function (listener) {
if (typeof listener[event] === 'function') {
try {
listener[event].apply(listener, data);
} catch (e) {
log('warn', "Caught exception in listener for event '".concat(event, "':"), e);
}
}
});
},
hasSubscribersFor: function hasSubscribersFor(event) {
var has = false;
listeners.forEach(function (listener) {
if (typeof listener[event] === 'function') {
has = true;
}
});
return has;
}
});
}
function sliceObject(obj, keys) {
var sliced = {};
keys.forEach(function (k) {
return sliced[k] = obj[k];
});
return sliced;
}
// GL texture wrapper object for keeping track of a global set of textures, keyed by a unique user-defined name
var Texture = /*#__PURE__*/function () {
function Texture(gl, name) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
_classCallCheck(this, Texture);
options = Texture.sliceOptions(options); // exclude any non-texture-specific props
this.gl = gl;
this.texture = gl.createTexture();
if (this.texture) {
this.valid = true;
}
this.bind();
this.name = name;
this.retain_count = 0;
this.config_type = null;
this.loading = null; // a Promise object to track the loading state of this texture
this.loaded = false; // successfully loaded as expected
this.filtering = options.filtering;
this.density = options.density || 1; // native pixel density of texture
this.sprites = options.sprites;
this.texcoords = {}; // sprite UVs ([0, 1] range)
this.sizes = {}; // sprite sizes (pixel size)
this.css_sizes = {}; // sprite sizes, adjusted for native texture pixel density
this.aspects = {}; // sprite aspect ratios
// Default to a 1-pixel transparent black texture so we can safely render while we wait for an image to load
// See: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load
this.setData(1, 1, new Uint8Array([0, 0, 0, 0]), {
filtering: 'nearest'
});
this.loaded = false; // don't consider loaded when only placeholder data is present
// Destroy previous texture if present
if (Texture.textures[this.name]) {
// Preserve previous retain count
this.retain_count = Texture.textures[this.name].retain_count;
Texture.textures[this.name].retain_count = 0; // allow to be freed
Texture.textures[this.name].destroy();
}
// Cache texture instance and definition
Texture.textures[this.name] = this;
Texture.texture_configs[this.name] = JSON.stringify(Object.assign({
name: name
}, options));
this.load(options);
log('trace', "creating Texture ".concat(this.name));
}
// Destroy a single texture instance
return _createClass(Texture, [{
key: "destroy",
value: function destroy() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
force = _ref.force;
if (this.retain_count > 0 && !force) {
log('error', "Texture '".concat(this.name, "': destroying texture with retain count of '").concat(this.retain_count, "'"));
return;
}
if (!this.valid) {
return;
}
this.gl.deleteTexture(this.texture);
this.texture = null;
if (Texture.textures[this.name] === this) {
delete Texture.textures[this.name];
delete Texture.texture_configs[this.name];
}
this.valid = false;
log('trace', "destroying Texture ".concat(this.name));
}
}, {
key: "retain",
value: function retain() {
this.retain_count++;
}
}, {
key: "release",
value: function release() {
if (this.retain_count <= 0) {
log('error', "Texture '".concat(this.name, "': releasing texture with retain count of '").concat(this.retain_count, "'"));
}
this.retain_count--;
if (this.retain_count <= 0) {
this.destroy();
}
}
}, {
key: "bind",
value: function bind() {
var unit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
if (!this.valid) {
return;
}
if (Texture.activeUnit !== unit) {
this.gl.activeTexture(this.gl.TEXTURE0 + unit);
Texture.activeUnit = unit;
Texture.boundTexture = null; // texture must be re-bound when unit changes
}
if (Texture.boundTexture !== this.texture) {
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
Texture.boundTexture = this.texture;
}
}
}, {
key: "load",
value: function load(options) {
var _this = this;
if (!options) {
return this.loading || Promise.resolve(this);
}
this.loading = null;
if (typeof options.url === 'string') {
this.config_type = 'url';
this.setUrl(options.url, options);
} else if (options.element) {
this.config_type = 'element';
this.setElement(options.element, options);
} else if (options.data && options.width && options.height) {
this.config_type = 'data';
this.setData(options.width, options.height, options.data, options);
}
this.loading = this.loading && this.loading.then(function () {
_this.calculateSprites();
return _this;
}) || Promise.resolve(this);
return this.loading;
}
// Sets texture from an url
}, {
key: "setUrl",
value: function setUrl(url) {
var _this2 = this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (!this.valid) {
return;
}
this.url = url; // save URL reference (will be overwritten when element is loaded below)
this.loading = new Promise(function (resolve) {
var image = new Image();
image.onload = function () {
try {
// For data URL images, first draw the image to a separate canvas element. Workaround for
// obscure bug seen with small (<28px) SVG images encoded as data URLs in Chrome and Safari.
if (_this2.url.slice(0, 5) === 'data:') {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
_this2.setElement(canvas, options);
} else {
_this2.setElement(image, options);
}
} catch (e) {
_this2.loaded = false;
log('warn', "Texture '".concat(_this2.name, "': failed to load url: '").concat(_this2.url, "'"), e, options);
Texture.trigger('warning', {
message: "Failed to load texture from ".concat(_this2.url),
error: e,
texture: options
});
}
_this2.loaded = true;
resolve(_this2);
};
image.onerror = function (e) {
// Warn and resolve on error
_this2.loaded = false;
log('warn', "Texture '".concat(_this2.name, "': failed to load url: '"