UNPKG

mapbox-gl

Version:
1,557 lines (1,539 loc) 3.48 MB
/* 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);