mapbox-gl
Version:
A WebGL interactive maps library
1,557 lines (1,539 loc) • 3.48 MB
JavaScript
/* Mapbox GL JS is Copyright © 2020 Mapbox and subject to the Mapbox Terms of Service ((https://www.mapbox.com/legal/tos/). */
(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.mapboxgl = factory());
})(this, (function () { 'use strict';
/* eslint-disable */
var shared, worker, mapboxgl;
// define gets called three times: one for each chunk. we rely on the order
// they're imported to know which is which
function define(_, chunk) {
if (!shared) {
shared = chunk;
} else if (!worker) {
worker = chunk;
} else {
var workerBundleString = "self.onerror = function() { console.error('An error occurred while parsing the WebWorker bundle. This is most likely due to improper transpilation by Babel; please see https://docs.mapbox.com/mapbox-gl-js/guides/install/#transpiling'); }; var sharedChunk = {}; (" + shared + ")(sharedChunk); (" + worker + ")(sharedChunk); self.onerror = null;"
var sharedChunk = {};
shared(sharedChunk);
mapboxgl = chunk(sharedChunk);
if (typeof window !== 'undefined' && window && window.URL && window.URL.createObjectURL) {
mapboxgl.workerUrl = window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' }));
}
}
}
define(['exports'], (function (exports) { 'use strict';
var version = "3.2.0";
// strict
let mapboxHTTPURLRegex;
const config = {
API_URL: 'https://api.mapbox.com',
get API_URL_REGEX() {
if (mapboxHTTPURLRegex == null) {
const prodMapboxHTTPURLRegex = /^((https?:)?\/\/)?([^\/]+\.)?mapbox\.c(n|om)(\/|\?|$)/i;
try {
mapboxHTTPURLRegex = process.env.API_URL_REGEX != null ? new RegExp(process.env.API_URL_REGEX) : prodMapboxHTTPURLRegex;
} catch (e) {
mapboxHTTPURLRegex = prodMapboxHTTPURLRegex;
}
}
return mapboxHTTPURLRegex;
},
get API_TILEJSON_REGEX() {
// https://docs.mapbox.com/api/maps/mapbox-tiling-service/#retrieve-tilejson-metadata
return /^((https?:)?\/\/)?([^\/]+\.)?mapbox\.c(n|om)(\/v[0-9]*\/.*\.json.*$)/i;
},
get API_SPRITE_REGEX() {
// https://docs.mapbox.com/api/maps/styles/#retrieve-a-sprite-image-or-json
return /^((https?:)?\/\/)?([^\/]+\.)?mapbox\.c(n|om)(\/styles\/v[0-9]*\/)(.*\/sprite.*\..*$)/i;
},
get API_FONTS_REGEX() {
// https://docs.mapbox.com/api/maps/fonts/#retrieve-font-glyph-ranges
return /^((https?:)?\/\/)?([^\/]+\.)?mapbox\.c(n|om)(\/fonts\/v[0-9]*\/)(.*\.pbf.*$)/i;
},
get API_STYLE_REGEX() {
// https://docs.mapbox.com/api/maps/styles/#retrieve-a-style
return /^((https?:)?\/\/)?([^\/]+\.)?mapbox\.c(n|om)(\/styles\/v[0-9]*\/)(.*$)/i;
},
get API_CDN_URL_REGEX() {
return /^((https?:)?\/\/)?api\.mapbox\.c(n|om)(\/mapbox-gl-js\/)(.*$)/i;
},
get EVENTS_URL() {
if (!config.API_URL) {
return null;
}
try {
const url = new URL(config.API_URL);
if (url.hostname === 'api.mapbox.cn') {
return 'https://events.mapbox.cn/events/v2';
} else if (url.hostname === 'api.mapbox.com') {
return 'https://events.mapbox.com/events/v2';
} else {
return null;
}
} catch (e) {
return null;
}
},
SESSION_PATH: '/map-sessions/v1',
FEEDBACK_URL: 'https://apps.mapbox.com/feedback',
TILE_URL_VERSION: 'v4',
RASTER_URL_PREFIX: 'raster/v1',
REQUIRE_ACCESS_TOKEN: true,
ACCESS_TOKEN: null,
DEFAULT_STYLE: 'mapbox://styles/mapbox/standard',
MAX_PARALLEL_IMAGE_REQUESTS: 16,
DRACO_URL: 'https://api.mapbox.com/mapbox-gl-js/draco_decoder_gltf_v1.5.6.wasm',
GLYPHS_URL: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf'
};
// strict
const exported$1 = {
supported: false,
testSupport
};
let glForTesting;
let webpCheckComplete = false;
let webpImgTest;
let webpImgTestOnloadComplete = false;
/**
* @note Expressions test suite run in Node.js and use this file
*/
const window$1 = typeof self !== 'undefined' ? self : {};
if (window$1.document) {
webpImgTest = window$1.document.createElement('img');
webpImgTest.onload = function () {
if (glForTesting)
testWebpTextureUpload(glForTesting);
glForTesting = null;
webpImgTestOnloadComplete = true;
};
webpImgTest.onerror = function () {
webpCheckComplete = true;
glForTesting = null;
};
webpImgTest.src = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA=';
}
function testSupport(gl) {
if (webpCheckComplete || !webpImgTest)
return;
// HTMLImageElement.complete is set when an image is done loading it's source
// regardless of whether the load was successful or not.
// It's possible for an error to set HTMLImageElement.complete to true which would trigger
// testWebpTextureUpload and mistakenly set exported.supported to true in browsers which don't support webp
// To avoid this, we set a flag in the image's onload handler and only call testWebpTextureUpload
// after a successful image load event.
if (webpImgTestOnloadComplete) {
testWebpTextureUpload(gl);
} else {
glForTesting = gl;
}
}
function testWebpTextureUpload(gl) {
// Edge 18 supports WebP but not uploading a WebP image to a gl texture
// Test support for this before allowing WebP images.
// https://github.com/mapbox/mapbox-gl-js/issues/7671
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
try {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, webpImgTest);
// The error does not get triggered in Edge if the context is lost
if (gl.isContextLost())
return;
exported$1.supported = true;
} catch (e) {
}
gl.deleteTexture(texture);
webpCheckComplete = true;
}
//
/***** START WARNING REMOVAL OR MODIFICATION OF THE
* FOLLOWING CODE VIOLATES THE MAPBOX TERMS OF SERVICE ******
* The following code is used to access Mapbox's APIs. Removal or modification
* of this code can result in higher fees and/or
* termination of your account with Mapbox.
*
* Under the Mapbox Terms of Service, you may not use this code to access Mapbox
* Mapping APIs other than through Mapbox SDKs.
*
* The Mapping APIs documentation is available at https://docs.mapbox.com/api/maps/#maps
* and the Mapbox Terms of Service are available at https://www.mapbox.com/tos/
******************************************************************************/
const SKU_ID = '01';
function createSkuToken() {
// SKU_ID and TOKEN_VERSION are specified by an internal schema and should not change
const TOKEN_VERSION = '1';
const base62chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
// sessionRandomizer is a randomized 10-digit base-62 number
let sessionRandomizer = '';
for (let i = 0; i < 10; i++) {
sessionRandomizer += base62chars[Math.floor(Math.random() * 62)];
}
const expiration = 12 * 60 * 60 * 1000;
// 12 hours
const token = [
TOKEN_VERSION,
SKU_ID,
sessionRandomizer
].join('');
const tokenExpiresAt = Date.now() + expiration;
return {
token,
tokenExpiresAt
};
}
/***** END WARNING - REMOVAL OR MODIFICATION OF THE
PRECEDING CODE VIOLATES THE MAPBOX TERMS OF SERVICE ******/
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var unitbezier = UnitBezier;
function UnitBezier(p1x, p1y, p2x, p2y) {
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
this.cx = 3 * p1x;
this.bx = 3 * (p2x - p1x) - this.cx;
this.ax = 1 - this.cx - this.bx;
this.cy = 3 * p1y;
this.by = 3 * (p2y - p1y) - this.cy;
this.ay = 1 - this.cy - this.by;
this.p1x = p1x;
this.p1y = p1y;
this.p2x = p2x;
this.p2y = p2y;
}
UnitBezier.prototype = {
sampleCurveX: function (t) {
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((this.ax * t + this.bx) * t + this.cx) * t;
},
sampleCurveY: function (t) {
return ((this.ay * t + this.by) * t + this.cy) * t;
},
sampleCurveDerivativeX: function (t) {
return (3 * this.ax * t + 2 * this.bx) * t + this.cx;
},
solveCurveX: function (x, epsilon) {
if (epsilon === undefined)
epsilon = 0.000001;
if (x < 0)
return 0;
if (x > 1)
return 1;
var t = x;
// First try a few iterations of Newton's method - normally very fast.
for (var i = 0; i < 8; i++) {
var x2 = this.sampleCurveX(t) - x;
if (Math.abs(x2) < epsilon)
return t;
var d2 = this.sampleCurveDerivativeX(t);
if (Math.abs(d2) < 0.000001)
break;
t = t - x2 / d2;
}
// Fall back to the bisection method for reliability.
var t0 = 0;
var t1 = 1;
t = x;
for (i = 0; i < 20; i++) {
x2 = this.sampleCurveX(t);
if (Math.abs(x2 - x) < epsilon)
break;
if (x > x2) {
t0 = t;
} else {
t1 = t;
}
t = (t1 - t0) * 0.5 + t0;
}
return t;
},
solve: function (x, epsilon) {
return this.sampleCurveY(this.solveCurveX(x, epsilon));
}
};
var UnitBezier$1 = /*@__PURE__*/getDefaultExportFromCjs(unitbezier);
var pointGeometry = Point$1;
/**
* A standalone point geometry with useful accessor, comparison, and
* modification methods.
*
* @class Point
* @param {Number} x the x-coordinate. this could be longitude or screen
* pixels, or any other sort of unit.
* @param {Number} y the y-coordinate. this could be latitude or screen
* pixels, or any other sort of unit.
* @example
* var point = new Point(-77, 38);
*/
function Point$1(x, y) {
this.x = x;
this.y = y;
}
Point$1.prototype = {
/**
* Clone this point, returning a new point that can be modified
* without affecting the old one.
* @return {Point} the clone
*/
clone: function () {
return new Point$1(this.x, this.y);
},
/**
* Add this point's x & y coordinates to another point,
* yielding a new point.
* @param {Point} p the other point
* @return {Point} output point
*/
add: function (p) {
return this.clone()._add(p);
},
/**
* Subtract this point's x & y coordinates to from point,
* yielding a new point.
* @param {Point} p the other point
* @return {Point} output point
*/
sub: function (p) {
return this.clone()._sub(p);
},
/**
* Multiply this point's x & y coordinates by point,
* yielding a new point.
* @param {Point} p the other point
* @return {Point} output point
*/
multByPoint: function (p) {
return this.clone()._multByPoint(p);
},
/**
* Divide this point's x & y coordinates by point,
* yielding a new point.
* @param {Point} p the other point
* @return {Point} output point
*/
divByPoint: function (p) {
return this.clone()._divByPoint(p);
},
/**
* Multiply this point's x & y coordinates by a factor,
* yielding a new point.
* @param {Point} k factor
* @return {Point} output point
*/
mult: function (k) {
return this.clone()._mult(k);
},
/**
* Divide this point's x & y coordinates by a factor,
* yielding a new point.
* @param {Point} k factor
* @return {Point} output point
*/
div: function (k) {
return this.clone()._div(k);
},
/**
* Rotate this point around the 0, 0 origin by an angle a,
* given in radians
* @param {Number} a angle to rotate around, in radians
* @return {Point} output point
*/
rotate: function (a) {
return this.clone()._rotate(a);
},
/**
* Rotate this point around p point by an angle a,
* given in radians
* @param {Number} a angle to rotate around, in radians
* @param {Point} p Point to rotate around
* @return {Point} output point
*/
rotateAround: function (a, p) {
return this.clone()._rotateAround(a, p);
},
/**
* Multiply this point by a 4x1 transformation matrix
* @param {Array<Number>} m transformation matrix
* @return {Point} output point
*/
matMult: function (m) {
return this.clone()._matMult(m);
},
/**
* Calculate this point but as a unit vector from 0, 0, meaning
* that the distance from the resulting point to the 0, 0
* coordinate will be equal to 1 and the angle from the resulting
* point to the 0, 0 coordinate will be the same as before.
* @return {Point} unit vector point
*/
unit: function () {
return this.clone()._unit();
},
/**
* Compute a perpendicular point, where the new y coordinate
* is the old x coordinate and the new x coordinate is the old y
* coordinate multiplied by -1
* @return {Point} perpendicular point
*/
perp: function () {
return this.clone()._perp();
},
/**
* Return a version of this point with the x & y coordinates
* rounded to integers.
* @return {Point} rounded point
*/
round: function () {
return this.clone()._round();
},
/**
* Return the magitude of this point: this is the Euclidean
* distance from the 0, 0 coordinate to this point's x and y
* coordinates.
* @return {Number} magnitude
*/
mag: function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
},
/**
* Judge whether this point is equal to another point, returning
* true or false.
* @param {Point} other the other point
* @return {boolean} whether the points are equal
*/
equals: function (other) {
return this.x === other.x && this.y === other.y;
},
/**
* Calculate the distance from this point to another point
* @param {Point} p the other point
* @return {Number} distance
*/
dist: function (p) {
return Math.sqrt(this.distSqr(p));
},
/**
* Calculate the distance from this point to another point,
* without the square root step. Useful if you're comparing
* relative distances.
* @param {Point} p the other point
* @return {Number} distance
*/
distSqr: function (p) {
var dx = p.x - this.x, dy = p.y - this.y;
return dx * dx + dy * dy;
},
/**
* Get the angle from the 0, 0 coordinate to this point, in radians
* coordinates.
* @return {Number} angle
*/
angle: function () {
return Math.atan2(this.y, this.x);
},
/**
* Get the angle from this point to another point, in radians
* @param {Point} b the other point
* @return {Number} angle
*/
angleTo: function (b) {
return Math.atan2(this.y - b.y, this.x - b.x);
},
/**
* Get the angle between this point and another point, in radians
* @param {Point} b the other point
* @return {Number} angle
*/
angleWith: function (b) {
return this.angleWithSep(b.x, b.y);
},
/*
* Find the angle of the two vectors, solving the formula for
* the cross product a x b = |a||b|sin(θ) for θ.
* @param {Number} x the x-coordinate
* @param {Number} y the y-coordinate
* @return {Number} the angle in radians
*/
angleWithSep: function (x, y) {
return Math.atan2(this.x * y - this.y * x, this.x * x + this.y * y);
},
_matMult: function (m) {
var x = m[0] * this.x + m[1] * this.y, y = m[2] * this.x + m[3] * this.y;
this.x = x;
this.y = y;
return this;
},
_add: function (p) {
this.x += p.x;
this.y += p.y;
return this;
},
_sub: function (p) {
this.x -= p.x;
this.y -= p.y;
return this;
},
_mult: function (k) {
this.x *= k;
this.y *= k;
return this;
},
_div: function (k) {
this.x /= k;
this.y /= k;
return this;
},
_multByPoint: function (p) {
this.x *= p.x;
this.y *= p.y;
return this;
},
_divByPoint: function (p) {
this.x /= p.x;
this.y /= p.y;
return this;
},
_unit: function () {
this._div(this.mag());
return this;
},
_perp: function () {
var y = this.y;
this.y = this.x;
this.x = -y;
return this;
},
_rotate: function (angle) {
var cos = Math.cos(angle), sin = Math.sin(angle), x = cos * this.x - sin * this.y, y = sin * this.x + cos * this.y;
this.x = x;
this.y = y;
return this;
},
_rotateAround: function (angle, p) {
var cos = Math.cos(angle), sin = Math.sin(angle), x = p.x + cos * (this.x - p.x) - sin * (this.y - p.y), y = p.y + sin * (this.x - p.x) + cos * (this.y - p.y);
this.x = x;
this.y = y;
return this;
},
_round: function () {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
}
};
/**
* Construct a point from an array if necessary, otherwise if the input
* is already a Point, or an unknown type, return it unchanged
* @param {Array<Number>|Point|*} a any kind of input value
* @return {Point} constructed point, or passed-through value.
* @example
* // this
* var point = Point.convert([0, 1]);
* // is equivalent to
* var point = new Point(0, 1);
*/
Point$1.convert = function (a) {
if (a instanceof Point$1) {
return a;
}
if (Array.isArray(a)) {
return new Point$1(a[0], a[1]);
}
return a;
};
var Point$2 = /*@__PURE__*/getDefaultExportFromCjs(pointGeometry);
//
const DEG_TO_RAD = Math.PI / 180;
const RAD_TO_DEG = 180 / Math.PI;
/**
* Converts an angle in degrees to radians
* copy all properties from the source objects into the destination.
* The last source object given overrides properties from previous
* source objects.
*
* @param a angle to convert
* @returns the angle in radians
* @private
*/
function degToRad(a) {
return a * DEG_TO_RAD;
}
/**
* Converts an angle in radians to degrees
* copy all properties from the source objects into the destination.
* The last source object given overrides properties from previous
* source objects.
*
* @param a angle to convert
* @returns the angle in degrees
* @private
*/
function radToDeg(a) {
return a * RAD_TO_DEG;
}
const TILE_CORNERS = [
[
0,
0
],
[
1,
0
],
[
1,
1
],
[
0,
1
]
];
/**
* Given a particular bearing, returns the corner of the tile thats farthest
* along the bearing.
*
* @param {number} bearing angle in degrees (-180, 180]
* @returns {QuadCorner}
* @private
*/
function furthestTileCorner(bearing) {
const alignedBearing = (bearing + 45 + 360) % 360;
const cornerIdx = Math.round(alignedBearing / 90) % 4;
return TILE_CORNERS[cornerIdx];
}
/**
* @module util
* @private
*/
/**
* Given a value `t` that varies between 0 and 1, return
* an interpolation function that eases between 0 and 1 in a pleasing
* cubic in-out fashion.
*
* @private
*/
function easeCubicInOut(t) {
if (t <= 0)
return 0;
if (t >= 1)
return 1;
const t2 = t * t, t3 = t2 * t;
return 4 * (t < 0.5 ? t3 : 3 * (t - t2) + t3 - 0.75);
}
/**
* Computes an AABB for a set of points.
*
* @param {Point[]} points
* @returns {{ min: Point, max: Point}}
* @private
*/
function getBounds(points) {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (const p of points) {
minX = Math.min(minX, p.x);
minY = Math.min(minY, p.y);
maxX = Math.max(maxX, p.x);
maxY = Math.max(maxY, p.y);
}
return {
min: new Point$2(minX, minY),
max: new Point$2(maxX, maxY)
};
}
/**
* Returns the square of the 2D distance between an AABB defined by min and max and a point.
* If point is null or undefined, the AABB distance from the origin (0,0) is returned.
*
* @param {Array<number>} min The minimum extent of the AABB.
* @param {Array<number>} max The maximum extent of the AABB.
* @param {Array<number>} [point] The point to compute the distance from, may be undefined.
* @returns {number} The square distance from the AABB, 0.0 if the AABB contains the point.
*/
function getAABBPointSquareDist(min, max, point) {
let sqDist = 0;
for (let i = 0; i < 2; ++i) {
const v = point ? point[i] : 0;
if (min[i] > v)
sqDist += (min[i] - v) * (min[i] - v);
if (max[i] < v)
sqDist += (v - max[i]) * (v - max[i]);
}
return sqDist;
}
/**
* Converts a AABB into a polygon with clockwise winding order.
*
* @param {Point} min The top left point.
* @param {Point} max The bottom right point.
* @param {number} [buffer=0] The buffer width.
* @param {boolean} [close=true] Whether to close the polygon or not.
* @returns {Point[]} The polygon.
*/
function polygonizeBounds(min, max, buffer = 0, close = true) {
const offset = new Point$2(buffer, buffer);
const minBuf = min.sub(offset);
const maxBuf = max.add(offset);
const polygon = [
minBuf,
new Point$2(maxBuf.x, minBuf.y),
maxBuf,
new Point$2(minBuf.x, maxBuf.y)
];
if (close) {
polygon.push(minBuf.clone());
}
return polygon;
}
/**
* Takes a convex ring and expands it outward by applying a buffer around it.
* This function assumes that the ring is in clockwise winding order.
*
* @param {Point[]} ring The input ring.
* @param {number} buffer The buffer width.
* @returns {Point[]} The expanded ring.
*/
function bufferConvexPolygon(ring, buffer) {
const output = [];
for (let currIdx = 0; currIdx < ring.length; currIdx++) {
const prevIdx = wrap(currIdx - 1, -1, ring.length - 1);
const nextIdx = wrap(currIdx + 1, -1, ring.length - 1);
const prev = ring[prevIdx];
const curr = ring[currIdx];
const next = ring[nextIdx];
const p1 = prev.sub(curr).unit();
const p2 = next.sub(curr).unit();
const interiorAngle = p2.angleWithSep(p1.x, p1.y);
// Calcuate a vector that points in the direction of the angle bisector between two sides.
// Scale it based on a right angled triangle constructed at that corner.
const offset = p1.add(p2).unit().mult(-1 * buffer / Math.sin(interiorAngle / 2));
output.push(curr.add(offset));
}
return output;
}
/**
* Given given (x, y), (x1, y1) control points for a bezier curve,
* return a function that interpolates along that curve.
*
* @param p1x control point 1 x coordinate
* @param p1y control point 1 y coordinate
* @param p2x control point 2 x coordinate
* @param p2y control point 2 y coordinate
* @private
*/
function bezier$1(p1x, p1y, p2x, p2y) {
const bezier = new UnitBezier$1(p1x, p1y, p2x, p2y);
return function (t) {
return bezier.solve(t);
};
}
/**
* A default bezier-curve powered easing function with
* control points (0.25, 0.1) and (0.25, 1)
*
* @private
*/
const ease = bezier$1(0.25, 0.1, 0.25, 1);
/**
* constrain n to the given range via min + max
*
* @param n value
* @param min the minimum value to be returned
* @param max the maximum value to be returned
* @returns the clamped value
* @private
*/
function clamp(n, min, max) {
return Math.min(max, Math.max(min, n));
}
/**
* Equivalent to GLSL smoothstep.
*
* @param {number} e0 The lower edge of the sigmoid
* @param {number} e1 The upper edge of the sigmoid
* @param {number} x the value to be interpolated
* @returns {number} in the range [0, 1]
* @private
*/
function smoothstep(e0, e1, x) {
x = clamp((x - e0) / (e1 - e0), 0, 1);
return x * x * (3 - 2 * x);
}
/**
* constrain n to the given range, excluding the minimum, via modular arithmetic
*
* @param n value
* @param min the minimum value to be returned, exclusive
* @param max the maximum value to be returned, inclusive
* @returns constrained number
* @private
*/
function wrap(n, min, max) {
const d = max - min;
const w = ((n - min) % d + d) % d + min;
return w === min ? max : w;
}
/**
* Computes shortest angle in range [-180, 180) between two angles.
*
* @param {*} a First angle in degrees
* @param {*} b Second angle in degrees
* @returns Shortest angle
* @private
*/
function shortestAngle(a, b) {
const diff = (b - a + 180) % 360 - 180;
return diff < -180 ? diff + 360 : diff;
}
/*
* Call an asynchronous function on an array of arguments,
* calling `callback` with the completed results of all calls.
*
* @param array input to each call of the async function.
* @param fn an async function with signature (data, callback)
* @param callback a callback run after all async work is done.
* called with an array, containing the results of each async call.
* @private
*/
function asyncAll(array, fn, callback) {
if (!array.length) {
return callback(null, []);
}
let remaining = array.length;
const results = new Array(array.length);
let error = null;
array.forEach((item, i) => {
fn(item, (err, result) => {
if (err)
error = err;
results[i] = result;
// https://github.com/facebook/flow/issues/2123
if (--remaining === 0)
callback(error, results);
});
});
}
/*
* Polyfill for Object.values. Not fully spec compliant, but we don't
* need it to be.
*
* @private
*/
function values(obj) {
const result = [];
for (const k in obj) {
result.push(obj[k]);
}
return result;
}
/*
* Compute the difference between the keys in one object and the keys
* in another object.
*
* @returns keys difference
* @private
*/
function keysDifference(obj, other) {
const difference = [];
for (const i in obj) {
if (!(i in other)) {
difference.push(i);
}
}
return difference;
}
/**
* Given a destination object and optionally many source objects,
* copy all properties from the source objects into the destination.
* The last source object given overrides properties from previous
* source objects.
*
* @param dest destination object
* @param sources sources from which properties are pulled
* @private
*/
function extend$1(dest, ...sources) {
for (const src of sources) {
for (const k in src) {
dest[k] = src[k];
}
}
return dest;
}
/**
* Given an object and a number of properties as strings, return version
* of that object with only those properties.
*
* @param src the object
* @param properties an array of property names chosen
* to appear on the resulting object.
* @returns object with limited properties.
* @example
* var foo = { name: 'Charlie', age: 10 };
* var justName = pick(foo, ['name']);
* // justName = { name: 'Charlie' }
* @private
*/
function pick(src, properties) {
const result = {};
for (let i = 0; i < properties.length; i++) {
const k = properties[i];
if (k in src) {
result[k] = src[k];
}
}
return result;
}
let id = 1;
/**
* Return a unique numeric id, starting at 1 and incrementing with
* each call.
*
* @returns unique numeric id.
* @private
*/
function uniqueId() {
return id++;
}
/**
* Return a random UUID (v4). Taken from: https://gist.github.com/jed/982883
* @private
*/
function uuid() {
function b(a) {
return a ? (a ^ Math.random() * (16 >> a / 4)).toString(16) : //$FlowFixMe: Flow doesn't like the implied array literal conversion here
([10000000] + -[1000] + -4000 + -8000 + -100000000000).replace(/[018]/g, b);
}
return b();
}
/**
* Return the next power of two, or the input value if already a power of two
* @private
*/
function nextPowerOfTwo(value) {
if (value <= 1)
return 1;
return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2));
}
/**
* Return the previous power of two, or the input value if already a power of two
* @private
*/
function prevPowerOfTwo(value) {
if (value <= 1)
return 1;
return Math.pow(2, Math.floor(Math.log(value) / Math.LN2));
}
/**
* Validate a string to match UUID(v4) of the
* form: xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx
* @param str string to validate.
* @private
*/
function validateUuid(str) {
return str ? /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str) : false;
}
/**
* Given an array of member function names as strings, replace all of them
* with bound versions that will always refer to `context` as `this`. This
* is useful for classes where otherwise event bindings would reassign
* `this` to the evented object or some other value: this lets you ensure
* the `this` value always.
*
* @param fns list of member function names
* @param context the context value
* @example
* function MyClass() {
* bindAll(['ontimer'], this);
* this.name = 'Tom';
* }
* MyClass.prototype.ontimer = function() {
* alert(this.name);
* };
* var myClass = new MyClass();
* setTimeout(myClass.ontimer, 100);
* @private
*/
function bindAll(fns, context) {
fns.forEach(fn => {
if (!context[fn]) {
return;
}
context[fn] = context[fn].bind(context);
});
}
/**
* Determine if a string ends with a particular substring
*
* @private
*/
function endsWith(string, suffix) {
return string.indexOf(suffix, string.length - suffix.length) !== -1;
}
/**
* Create an object by mapping all the values of an existing object while
* preserving their keys.
*
* @private
*/
// $FlowFixMe[missing-this-annot]
function mapObject(input, iterator, context) {
const output = {};
for (const key in input) {
output[key] = iterator.call(context || this, input[key], key, input);
}
return output;
}
/**
* Create an object by filtering out values of an existing object.
*
* @private
*/
// $FlowFixMe[missing-this-annot]
function filterObject(input, iterator, context) {
const output = {};
for (const key in input) {
if (iterator.call(context || this, input[key], key, input)) {
output[key] = input[key];
}
}
return output;
}
/**
* Deeply clones two objects.
*
* @private
*/
function clone$9(input) {
if (Array.isArray(input)) {
return input.map(clone$9);
} else if (typeof input === 'object' && input) {
return mapObject(input, clone$9);
} else {
return input;
}
}
/**
* Maps a value from a range between [min, max] to the range [outMin, outMax]
*
* @private
*/
function mapValue(value, min, max, outMin, outMax) {
return clamp((value - min) / (max - min) * (outMax - outMin) + outMin, outMin, outMax);
}
/**
* Check if two arrays have at least one common element.
*
* @private
*/
function arraysIntersect(a, b) {
for (let l = 0; l < a.length; l++) {
if (b.indexOf(a[l]) >= 0)
return true;
}
return false;
}
/**
* Print a warning message to the console and ensure duplicate warning messages
* are not printed.
*
* @private
*/
const warnOnceHistory = {};
function warnOnce(message) {
if (!warnOnceHistory[message]) {
// console isn't defined in some WebWorkers, see #2558
if (typeof console !== 'undefined')
console.warn(message);
warnOnceHistory[message] = true;
}
}
/**
* Indicates if the provided Points are in a counter clockwise (true) or clockwise (false) order
*
* @private
* @returns true for a counter clockwise set of points
*/
// http://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
function isCounterClockwise(a, b, c) {
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
}
/**
* Returns the signed area for the polygon ring. Postive areas are exterior rings and
* have a clockwise winding. Negative areas are interior rings and have a counter clockwise
* ordering.
*
* @private
* @param ring Exterior or interior ring
*/
function calculateSignedArea$1(ring) {
let sum = 0;
for (let i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
p1 = ring[i];
p2 = ring[j];
sum += (p2.x - p1.x) * (p1.y + p2.y);
}
return sum;
}
/**
* Converts spherical coordinates to cartesian position coordinates.
*
* @private
* @param spherical Spherical coordinates, in [radial, azimuthal, polar]
* @return Position cartesian coordinates
*/
function sphericalPositionToCartesian([r, azimuthal, polar]) {
// We abstract "north"/"up" (compass-wise) to be 0° when really this is 90° (π/2):
// correct for that here
const a = degToRad(azimuthal + 90), p = degToRad(polar);
return {
x: r * Math.cos(a) * Math.sin(p),
y: r * Math.sin(a) * Math.sin(p),
z: r * Math.cos(p),
azimuthal,
polar
};
}
/**
* Converts spherical direction to cartesian coordinates.
*
* @private
* @param spherical Spherical direction, in [azimuthal, polar]
* @return Direction cartesian direction
*/
function sphericalDirectionToCartesian([azimuthal, polar]) {
const position = sphericalPositionToCartesian([
1,
azimuthal,
polar
]);
return {
x: position.x,
y: position.y,
z: position.z
};
}
function cartesianPositionToSpherical(x, y, z) {
const radial = Math.sqrt(x * x + y * y + z * z);
const polar = radial > 0 ? Math.acos(z / radial) * RAD_TO_DEG : 0;
// Domain error may occur if x && y are both 0.0
let azimuthal = x !== 0 || y !== 0 ? Math.atan2(-y, -x) * RAD_TO_DEG + 90 : 0;
if (azimuthal < 0) {
azimuthal += 360;
}
return [
radial,
azimuthal,
polar
];
}
/* global WorkerGlobalScope */
/**
* Returns true if run in the web-worker context.
*
* @private
* @returns {boolean}
*/
function isWorker() {
return typeof WorkerGlobalScope !== 'undefined' && typeof self !== 'undefined' && self instanceof WorkerGlobalScope;
}
/**
* Parses data from 'Cache-Control' headers.
*
* @private
* @param cacheControl Value of 'Cache-Control' header
* @return object containing parsed header info.
*/
function parseCacheControl(cacheControl) {
// Taken from [Wreck](https://github.com/hapijs/wreck)
const re = /(?:^|(?:\s*\,\s*))([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)(?:\=(?:([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)|(?:\"((?:[^"\\]|\\.)*)\")))?/g;
const header = {};
cacheControl.replace(re, ($0, $1, $2, $3) => {
const value = $2 || $3;
header[$1] = value ? value.toLowerCase() : true;
return '';
});
if (header['max-age']) {
const maxAge = parseInt(header['max-age'], 10);
if (isNaN(maxAge))
delete header['max-age'];
else
header['max-age'] = maxAge;
}
return header;
}
let _isSafari = null;
/**
* Returns true when run in WebKit derived browsers.
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
* transfer data between WebWorkers and the main thread.
* https://github.com/mapbox/mapbox-gl-js/issues/8771
*
* This should be removed once the underlying Safari issue is fixed.
*
* @private
* @param scope {WindowOrWorkerGlobalScope} Since this function is used both on the main thread and WebWorker context,
* let the calling scope pass in the global scope object.
* @returns {boolean}
*/
function isSafari(scope) {
if (_isSafari == null) {
const userAgent = scope.navigator ? scope.navigator.userAgent : null;
_isSafari = !!scope.safari || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || !!userAgent.match('Safari') && !userAgent.match('Chrome')));
}
return _isSafari;
}
function isSafariWithAntialiasingBug(scope) {
const userAgent = scope.navigator ? scope.navigator.userAgent : null;
if (!isSafari(scope))
return false;
// 15.4 is known to be buggy.
// 15.5 may or may not include the fix. Mark it as buggy to be on the safe side.
return userAgent && (userAgent.match('Version/15.4') || userAgent.match('Version/15.5') || userAgent.match(/CPU (OS|iPhone OS) (15_4|15_5) like Mac OS X/));
}
function isFullscreen() {
return !!document.fullscreenElement || !!document.webkitFullscreenElement;
}
function storageAvailable(type) {
try {
const storage = self[type];
storage.setItem('_mapbox_test_', 1);
storage.removeItem('_mapbox_test_');
return true;
} catch (e) {
return false;
}
}
// The following methods are from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
//Unicode compliant base64 encoder for strings
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => {
return String.fromCharCode(Number('0x' + p1)); //eslint-disable-line
}));
}
// Unicode compliant decoder for base64-encoded strings
function b64DecodeUnicode(str) {
return decodeURIComponent(atob(str).split('').map(c => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); //eslint-disable-line
}).join(''));
}
function base64DecToArr(sBase64) {
const str = atob(sBase64);
const arr = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++)
arr[i] = str.codePointAt(i);
return arr;
}
function getColumn(matrix, col) {
return [
matrix[col * 4],
matrix[col * 4 + 1],
matrix[col * 4 + 2],
matrix[col * 4 + 3]
];
}
function setColumn(matrix, col, values) {
matrix[col * 4 + 0] = values[0];
matrix[col * 4 + 1] = values[1];
matrix[col * 4 + 2] = values[2];
matrix[col * 4 + 3] = values[3];
}
function sRGBToLinearAndScale(v, s) {
return [
Math.pow(v[0], 2.2) * s,
Math.pow(v[1], 2.2) * s,
Math.pow(v[2], 2.2) * s
];
}
function linearVec3TosRGB(v) {
return [
Math.pow(v[0], 1 / 2.2),
Math.pow(v[1], 1 / 2.2),
Math.pow(v[2], 1 / 2.2)
];
}
function lowerBound(array, startIndex, finishIndex, target) {
while (startIndex < finishIndex) {
const middleIndex = startIndex + finishIndex >> 1;
if (array[middleIndex] < target) {
startIndex = middleIndex + 1;
} else {
finishIndex = middleIndex;
}
}
return startIndex;
}
function upperBound(array, startIndex, finishIndex, target) {
while (startIndex < finishIndex) {
const middleIndex = startIndex + finishIndex >> 1;
if (array[middleIndex] <= target) {
startIndex = middleIndex + 1;
} else {
finishIndex = middleIndex;
}
}
return startIndex;
}
//
const CACHE_NAME = 'mapbox-tiles';
let cacheLimit = 500;
// 50MB / (100KB/tile) ~= 500 tiles
let cacheCheckThreshold = 50;
const MIN_TIME_UNTIL_EXPIRY = 1000 * 60 * 7;
// 7 minutes. Skip caching tiles with a short enough max age.
// We're using a global shared cache object. Normally, requesting ad-hoc Cache objects is fine, but
// Safari has a memory leak in which it fails to release memory when requesting keys() from a Cache
// object. See https://bugs.webkit.org/show_bug.cgi?id=203991 for more information.
let sharedCache;
function getCaches() {
try {
return caches;
} catch (e) {
}
}
function cacheOpen() {
const caches = getCaches();
if (caches && !sharedCache) {
sharedCache = caches.open(CACHE_NAME);
}
}
let responseConstructorSupportsReadableStream;
function prepareBody(response, callback) {
if (responseConstructorSupportsReadableStream === undefined) {
try {
new Response(new ReadableStream());
// eslint-disable-line no-undef
responseConstructorSupportsReadableStream = true;
} catch (e) {
// Edge
responseConstructorSupportsReadableStream = false;
}
}
if (responseConstructorSupportsReadableStream) {
callback(response.body);
} else {
response.blob().then(callback);
}
}
function cachePut(request, response, requestTime) {
cacheOpen();
if (!sharedCache)
return;
const options = {
status: response.status,
statusText: response.statusText,
headers: new Headers()
};
response.headers.forEach((v, k) => options.headers.set(k, v));
const cacheControl = parseCacheControl(response.headers.get('Cache-Control') || '');
if (cacheControl['no-store']) {
return;
}
if (cacheControl['max-age']) {
options.headers.set('Expires', new Date(requestTime + cacheControl['max-age'] * 1000).toUTCString());
}
const expires = options.headers.get('Expires');
if (!expires)
return;
const timeUntilExpiry = new Date(expires).getTime() - requestTime;
if (timeUntilExpiry < MIN_TIME_UNTIL_EXPIRY)
return;
prepareBody(response, body => {
// $FlowFixMe[incompatible-call]
const clonedResponse = new Response(body, options);
cacheOpen();
if (!sharedCache)
return;
sharedCache.then(cache => cache.put(stripQueryParameters(request.url), clonedResponse)).catch(e => warnOnce(e.message));
});
}
function getQueryParameters(url) {
const paramStart = url.indexOf('?');
return paramStart > 0 ? url.slice(paramStart + 1).split('&') : [];
}
function stripQueryParameters(url) {
const start = url.indexOf('?');
if (start < 0)
return url;
// preserve `language` and `worldview` params if any
const params = getQueryParameters(url);
const filteredParams = params.filter(param => {
const entry = param.split('=');
return entry[0] === 'language' || entry[0] === 'worldview';
});
if (filteredParams.length) {
return `${ url.slice(0, start) }?${ filteredParams.join('&') }`;
}
return url.slice(0, start);
}
function cacheGet(request, callback) {
cacheOpen();
if (!sharedCache)
return callback(null);
const strippedURL = stripQueryParameters(request.url);
sharedCache.then(cache => {
// manually strip URL instead of `ignoreSearch: true` because of a known
// performance issue in Chrome https://github.com/mapbox/mapbox-gl-js/issues/8431
cache.match(strippedURL).then(response => {
const fresh = isFresh(response);
// Reinsert into cache so that order of keys in the cache is the order of access.
// This line makes the cache a LRU instead of a FIFO cache.
cache.delete(strippedURL);
if (fresh) {
cache.put(strippedURL, response.clone());
}
callback(null, response, fresh);
}).catch(callback);
}).catch(callback);
}
function isFresh(response) {
if (!response)
return false;
const expires = new Date(response.headers.get('Expires') || 0);
const cacheControl = parseCacheControl(response.headers.get('Cache-Control') || '');
return expires > Date.now() && !cacheControl['no-cache'];
}
// `Infinity` triggers a cache check after the first tile is loaded
// so that a check is run at least once on each page load.
let globalEntryCounter = Infinity;
// The cache check gets run on a worker. The reason for this is that
// profiling sometimes shows this as taking up significant time on the
// thread it gets called from. And sometimes it doesn't. It *may* be
// fine to run this on the main thread but out of caution this is being
// dispatched on a worker. This can be investigated further in the future.
function cacheEntryPossiblyAdded(dispatcher) {
globalEntryCounter++;
if (globalEntryCounter > cacheCheckThreshold) {
dispatcher.getActor().send('enforceCacheSizeLimit', cacheLimit);
globalEntryCounter = 0;
}
}
// runs on worker, see above comment
function enforceCacheSizeLimit(limit) {
cacheOpen();
if (!sharedCache)
return;
sharedCache.then(cache => {
cache.keys().then(keys => {
for (let i = 0; i < keys.length - limit; i++) {
cache.delete(keys[i]);
}
});
});
}
function clearTileCache(callback) {
const caches = getCaches();
if (!caches)
return;
const promise = caches.delete(CACHE_NAME);
if (callback) {
promise.catch(callback).then(() => callback());
}
}
function setCacheLimits(limit, checkThreshold) {
cacheLimit = limit;
cacheCheckThreshold = checkThreshold;
}
//
/**
* The type of a resource.
* @private
* @readonly
* @enum {string}
*/
const ResourceType = {
Unknown: 'Unknown',
Style: 'Style',
Source: 'Source',
Tile: 'Tile',
Glyphs: 'Glyphs',
SpriteImage: 'SpriteImage',
SpriteJSON: 'SpriteJSON',
Image: 'Image',
Model: 'Model'
};
if (typeof Object.freeze == 'function') {
Object.freeze(ResourceType);
}
/**
* A `RequestParameters` object to be returned from Map.options.transformRequest callbacks.
* @typedef {Object} RequestParameters
* @property {string} url The URL to be requested.
* @property {Object} headers The headers to be sent with the request.
* @property {string} method Request method `'GET' | 'POST' | 'PUT'`.
* @property {string} body Request body.
* @property {string} type Response body type to be returned `'string' | 'json' | 'arrayBuffer'`.
* @property {string} credentials `'same-origin'|'include'` Use 'include' to send cookies with cross-origin requests.
* @property {boolean} collectResourceTiming If true, Resource Timing API information will be collected for these transformed requests and returned in a resourceTiming property of relevant data events.
* @property {string} referrerPolicy A string representing the request's referrerPolicy. For more information and possible values, see the [Referrer-Policy HTTP header page](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy).
* @example
* // use transformRequest to modify requests that begin with `http://myHost`
* const map = new Map({
* container: 'map',
* style: 'mapbox://styles/mapbox/streets-v11',
* transformRequest: (url, resourceType) => {
* if (resourceType === 'Source' && url.indexOf('http://myHost') > -1) {
* return {
* url: url.replace('http', 'https'),
* headers: {'my-custom-header': true},
* credentials: 'include' // Include cookies for cross-origin requests
* };
* }
* }
* });
*
*/
class AJAXError extends Error {
constructor(message, status, url) {
if (status === 401 && isMapboxHTTPURL(url)) {
message += ': you may have provided an invalid Mapbox access token. See https://docs.mapbox.com/api/overview/#access-tokens-and-token-scopes';
}
super(message);
this.status = status;
this.url = url;
}
toString() {
return `${ this.name }: ${ this.message } (${ this.status }): ${ this.url }`;
}
}
// Ensure that we're sending the correct referrer from blob URL worker bundles.
// For files loaded from the local file system, `location.origin` will be set
// to the string(!) "null" (Firefox), or "file://" (Chrome, Safari, Edge, IE),
// and we will set an empty referrer. Otherwise, we're using the document's URL.
const getReferrer = isWorker() ? () => self.worker && self.worker.referrer : () => (location.protocol === 'blob:' ? parent : self).location.href;
// Determines whether a URL is a file:// URL. This is obviously the case if it begins
// with file://. Relative URLs are also file:// URLs iff the original document was loaded
// via a file:// URL.
const isFileURL = url => /^file:/.test(url) || /^file:/.test(getReferrer()) && !/^\w+:/.test(url);
function makeFetchRequest(requestParameters, callback) {
const controller = new AbortController();
const request = new Request(requestParameters.url, {
method: requestParameters.method || 'GET',
body: requestParameters.body,
credentials: requestParameters.credentials,
headers: requestParameters.headers,
referrer: getReferrer(),
referrerPolicy: requestParameters.referrerPolicy,
signal: controller.signal
});
let complete = false;
let aborted = false;
const cacheIgnoringSearch = hasCacheDefeatingSku(request.url);
if (requestParameters.type === 'json') {
request.headers.set('Accept', 'application/json');
}
const validateOrFetch = (err, cachedResponse, responseIsFresh) => {
if (aborted)
return;
if (err) {
// Do fetch in case of cache error.
// HTTP pages in Edge trigger a security error that can be ignored.
if (err.message !== 'SecurityError') {
warnOnce(err.toString());
}
}
if (cachedResponse && responseIsFresh) {
return finishRequest(cachedResponse);