/* 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 = 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 = 'var sharedChunk = {}; (' + shared + ')(sharedChunk); (' + worker + ')(sharedChunk);' var sharedChunk = {}; shared(sharedChunk); mapboxgl = chunk(sharedChunk); if (typeof window !== 'undefined') { mapboxgl.workerUrl = window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })); } } } define(['exports'], function (exports) { 'use strict'; var version = "2.1.0-dev"; var unitbezier = UnitBezier; function UnitBezier(p1x, p1y, p2x, p2y) { 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 = p2y; this.p2x = p2x; this.p2y = p2y; } UnitBezier.prototype.sampleCurveX = function (t) { return ((this.ax * t + this.bx) * t + this.cx) * t; }; UnitBezier.prototype.sampleCurveY = function (t) { return ((this.ay * t + this.by) * t + this.cy) * t; }; UnitBezier.prototype.sampleCurveDerivativeX = function (t) { return (3 * this.ax * t + 2 * this.bx) * t + this.cx; }; UnitBezier.prototype.solveCurveX = function (x, epsilon) { if (typeof epsilon === 'undefined') epsilon = 0.000001; var t0, t1, t2, x2, i; for (t2 = x, i = 0; i < 8; i++) { x2 = this.sampleCurveX(t2) - x; if (Math.abs(x2) < epsilon) return t2; var d2 = this.sampleCurveDerivativeX(t2); if (Math.abs(d2) < 0.000001) break; t2 = t2 - x2 / d2; } t0 = 0; t1 = 1; t2 = x; if (t2 < t0) return t0; if (t2 > t1) return t1; while (t0 < t1) { x2 = this.sampleCurveX(t2); if (Math.abs(x2 - x) < epsilon) return t2; if (x > x2) { t0 = t2; } else { t1 = t2; } t2 = (t1 - t0) * 0.5 + t0; } return t2; }; UnitBezier.prototype.solve = function (x, epsilon) { return this.sampleCurveY(this.solveCurveX(x, epsilon)); }; var pointGeometry = Point; function Point(x, y) { this.x = x; this.y = y; } Point.prototype = { clone: function () { return new Point(this.x, this.y); }, add: function (p) { return this.clone()._add(p); }, sub: function (p) { return this.clone()._sub(p); }, multByPoint: function (p) { return this.clone()._multByPoint(p); }, divByPoint: function (p) { return this.clone()._divByPoint(p); }, mult: function (k) { return this.clone()._mult(k); }, div: function (k) { return this.clone()._div(k); }, rotate: function (a) { return this.clone()._rotate(a); }, rotateAround: function (a, p) { return this.clone()._rotateAround(a, p); }, matMult: function (m) { return this.clone()._matMult(m); }, unit: function () { return this.clone()._unit(); }, perp: function () { return this.clone()._perp(); }, round: function () { return this.clone()._round(); }, mag: function () { return Math.sqrt(this.x * this.x + this.y * this.y); }, equals: function (other) { return this.x === other.x && this.y === other.y; }, dist: function (p) { return Math.sqrt(this.distSqr(p)); }, distSqr: function (p) { var dx = p.x - this.x, dy = p.y - this.y; return dx * dx + dy * dy; }, angle: function () { return Math.atan2(this.y, this.x); }, angleTo: function (b) { return Math.atan2(this.y - b.y, this.x - b.x); }, angleWith: function (b) { return this.angleWithSep(b.x, b.y); }, 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; } }; Point.convert = function (a) { if (a instanceof Point) { return a; } if (Array.isArray(a)) { return new Point(a[0], a[1]); } return a; }; var window$1 = typeof self !== 'undefined' ? self : {}; function deepEqual(a, b) { if (Array.isArray(a)) { if (!Array.isArray(b) || a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (!deepEqual(a[i], b[i])) return false; } return true; } if (typeof a === 'object' && a !== null && b !== null) { if (!(typeof b === 'object')) return false; const keys = Object.keys(a); if (keys.length !== Object.keys(b).length) return false; for (const key in a) { if (!deepEqual(a[key], b[key])) return false; } return true; } return a === b; } const MAX_SAFE_INTEGER = Math.pow(2, 53) - 1; const DEG_TO_RAD = Math.PI / 180; const RAD_TO_DEG = 180 / Math.PI; function degToRad(a) { return a * DEG_TO_RAD; } function radToDeg(a) { return a * RAD_TO_DEG; } 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); } 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 pointGeometry(minX, minY), max: new pointGeometry(maxX, maxY) }; } function polygonizeBounds(min, max, buffer = 0, close = true) { const offset = new pointGeometry(buffer, buffer); const minBuf = min.sub(offset); const maxBuf = max.add(offset); const polygon = [ minBuf, new pointGeometry(maxBuf.x, minBuf.y), maxBuf, new pointGeometry(minBuf.x, maxBuf.y) ]; if (close) { polygon.push(minBuf); } return polygon; } 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); const offset = p1.add(p2).unit().mult(-1 * buffer / Math.sin(interiorAngle / 2)); output.push(curr.add(offset)); } return output; } function bezier(p1x, p1y, p2x, p2y) { const bezier = new unitbezier(p1x, p1y, p2x, p2y); return function (t) { return bezier.solve(t); }; } const ease = bezier(0.25, 0.1, 0.25, 1); function clamp(n, min, max) { return Math.min(max, Math.max(min, n)); } function wrap(n, min, max) { const d = max - min; const w = ((n - min) % d + d) % d + min; return w === min ? max : w; } 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; if (--remaining === 0) callback(error, results); }); }); } function values(obj) { const result = []; for (const k in obj) { result.push(obj[k]); } return result; } function keysDifference(obj, other) { const difference = []; for (const i in obj) { if (!(i in other)) { difference.push(i); } } return difference; } function extend(dest, ...sources) { for (const src of sources) { for (const k in src) { dest[k] = src[k]; } } return dest; } 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; function uniqueId() { return id++; } function uuid() { function b(a) { return a ? (a ^ Math.random() * 16 >> a / 4).toString(16) : ([10000000] + -[1000] + -4000 + -8000 + -100000000000).replace(/[018]/g, b); } return b(); } function nextPowerOfTwo(value) { if (value <= 1) return 1; return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2)); } function prevPowerOfTwo(value) { if (value <= 1) return 1; return Math.pow(2, Math.floor(Math.log(value) / Math.LN2)); } 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; } function bindAll(fns, context) { fns.forEach(fn => { if (!context[fn]) { return; } context[fn] = context[fn].bind(context); }); } function endsWith(string, suffix) { return string.indexOf(suffix, string.length - suffix.length) !== -1; } function mapObject(input, iterator, context) { const output = {}; for (const key in input) { output[key] = iterator.call(context || this, input[key], key, input); } return output; } 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; } function clone(input) { if (Array.isArray(input)) { return input.map(clone); } else if (typeof input === 'object' && input) { return mapObject(input, clone); } else { return input; } } function arraysIntersect(a, b) { for (let l = 0; l < a.length; l++) { if (b.indexOf(a[l]) >= 0) return true; } return false; } const warnOnceHistory = {}; function warnOnce(message) { if (!warnOnceHistory[message]) { if (typeof console !== 'undefined') console.warn(message); warnOnceHistory[message] = true; } } function isCounterClockwise(a, b, c) { return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x); } function calculateSignedArea(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; } function isWorker() { return typeof WorkerGlobalScope !== 'undefined' && typeof self !== 'undefined' && self instanceof WorkerGlobalScope; } function parseCacheControl(cacheControl) { 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; 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 storageAvailable(type) { try { const storage = window$1[type]; storage.setItem('_mapbox_test_', 1); storage.removeItem('_mapbox_test_'); return true; } catch (e) { return false; } } function b64EncodeUnicode(str) { return window$1.btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { return String.fromCharCode(Number('0x' + p1)); })); } function b64DecodeUnicode(str) { return decodeURIComponent(window$1.atob(str).split('').map(c => { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } let linkEl; let reducedMotionQuery; let errorState = false; let stubTime; const exported = { now() { if (stubTime !== undefined) { return stubTime; } return window$1.performance.now(); }, setErrorState() { errorState = true; }, setNow(time) { stubTime = time; }, restoreNow() { stubTime = undefined; }, frame(fn) { if (errorState) return { cancel: () => { } }; const frame = window$1.requestAnimationFrame(fn); return { cancel: () => window$1.cancelAnimationFrame(frame) }; }, getImageData(img, padding = 0) { const canvas = window$1.document.createElement('canvas'); const context = canvas.getContext('2d'); if (!context) { throw new Error('failed to create canvas 2d context'); } canvas.width = img.width; canvas.height = img.height; context.drawImage(img, 0, 0, img.width, img.height); return context.getImageData(-padding, -padding, img.width + 2 * padding, img.height + 2 * padding); }, resolveURL(path) { if (!linkEl) linkEl = window$1.document.createElement('a'); linkEl.href = path; return linkEl.href; }, get devicePixelRatio() { return window$1.devicePixelRatio; }, get prefersReducedMotion() { if (!window$1.matchMedia) return false; if (reducedMotionQuery == null) { reducedMotionQuery = window$1.matchMedia('(prefers-reduced-motion: reduce)'); } return reducedMotionQuery.matches; } }; 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 EVENTS_URL() { if (!this.API_URL) { return null; } if (this.API_URL.indexOf('https://api.mapbox.cn') === 0) { return 'https://events.mapbox.cn/events/v2'; } else if (this.API_URL.indexOf('https://api.mapbox.com') === 0) { return 'https://events.mapbox.com/events/v2'; } else { 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, MAX_PARALLEL_IMAGE_REQUESTS: 16 }; const exported$1 = { supported: false, testSupport }; let glForTesting; let webpCheckComplete = false; let webpImgTest; let webpImgTestOnloadComplete = false; 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; if (webpImgTestOnloadComplete) { testWebpTextureUpload(gl); } else { glForTesting = gl; } } function testWebpTextureUpload(gl) { 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); if (gl.isContextLost()) return; exported$1.supported = true; } catch (e) { } gl.deleteTexture(texture); webpCheckComplete = true; } const SKU_ID = '01'; function createSkuToken() { const TOKEN_VERSION = '1'; const base62chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; let sessionRandomizer = ''; for (let i = 0; i < 10; i++) { sessionRandomizer += base62chars[Math.floor(Math.random() * 62)]; } const expiration = 12 * 60 * 60 * 1000; const token = [ TOKEN_VERSION, SKU_ID, sessionRandomizer ].join(''); const tokenExpiresAt = Date.now() + expiration; return { token, tokenExpiresAt }; } const AUTH_ERR_MSG = 'NO_ACCESS_TOKEN'; class RequestManager { constructor(transformRequestFn, customAccessToken) { this._transformRequestFn = transformRequestFn; this._customAccessToken = customAccessToken; this._createSkuToken(); } _createSkuToken() { const skuToken = createSkuToken(); this._skuToken = skuToken.token; this._skuTokenExpiresAt = skuToken.tokenExpiresAt; } _isSkuTokenExpired() { return Date.now() > this._skuTokenExpiresAt; } transformRequest(url, type) { if (this._transformRequestFn) { return this._transformRequestFn(url, type) || { url }; } return { url }; } normalizeStyleURL(url, accessToken) { if (!isMapboxURL(url)) return url; const urlObject = parseUrl(url); urlObject.path = `/styles/v1${ urlObject.path }`; return this._makeAPIURL(urlObject, this._customAccessToken || accessToken); } normalizeGlyphsURL(url, accessToken) { if (!isMapboxURL(url)) return url; const urlObject = parseUrl(url); urlObject.path = `/fonts/v1${ urlObject.path }`; return this._makeAPIURL(urlObject, this._customAccessToken || accessToken); } normalizeSourceURL(url, accessToken) { if (!isMapboxURL(url)) return url; const urlObject = parseUrl(url); urlObject.path = `/v4/${ urlObject.authority }.json`; urlObject.params.push('secure'); return this._makeAPIURL(urlObject, this._customAccessToken || accessToken); } normalizeSpriteURL(url, format, extension, accessToken) { const urlObject = parseUrl(url); if (!isMapboxURL(url)) { urlObject.path += `${ format }${ extension }`; return formatUrl(urlObject); } urlObject.path = `/styles/v1${ urlObject.path }/sprite${ format }${ extension }`; return this._makeAPIURL(urlObject, this._customAccessToken || accessToken); } normalizeTileURL(tileURL, use2x, rasterTileSize) { if (this._isSkuTokenExpired()) { this._createSkuToken(); } if (tileURL && !isMapboxURL(tileURL)) return tileURL; const urlObject = parseUrl(tileURL); const imageExtensionRe = /(\.(png|jpg)\d*)(?=$)/; const extension = exported$1.supported ? '.webp' : '$1'; const use2xAs512 = rasterTileSize && urlObject.authority !== 'raster' && rasterTileSize === 512; const suffix = use2x || use2xAs512 ? '@2x' : ''; urlObject.path = urlObject.path.replace(imageExtensionRe, `${ suffix }${ extension }`); if (urlObject.authority === 'raster') { urlObject.path = `/${ config.RASTER_URL_PREFIX }${ urlObject.path }`; } else { const tileURLAPIPrefixRe = /^.+\/v4\//; urlObject.path = urlObject.path.replace(tileURLAPIPrefixRe, '/'); urlObject.path = `/${ config.TILE_URL_VERSION }${ urlObject.path }`; } const accessToken = this._customAccessToken || getAccessToken(urlObject.params) || config.ACCESS_TOKEN; if (config.REQUIRE_ACCESS_TOKEN && accessToken && this._skuToken) { urlObject.params.push(`sku=${ this._skuToken }`); } return this._makeAPIURL(urlObject, accessToken); } canonicalizeTileURL(url, removeAccessToken) { const extensionRe = /\.[\w]+$/; const urlObject = parseUrl(url); if (!urlObject.path.match(/^(\/v4\/|\/raster\/v1\/)/) || !urlObject.path.match(extensionRe)) { return url; } let result = 'mapbox://'; if (urlObject.path.match(/^\/raster\/v1\//)) { const rasterPrefix = `/${ config.RASTER_URL_PREFIX }/`; result += `raster/${ urlObject.path.replace(rasterPrefix, '') }`; } else { const tilesPrefix = `/${ config.TILE_URL_VERSION }/`; result += `tiles/${ urlObject.path.replace(tilesPrefix, '') }`; } let params = urlObject.params; if (removeAccessToken) { params = params.filter(p => !p.match(/^access_token=/)); } if (params.length) result += `?${ params.join('&') }`; return result; } canonicalizeTileset(tileJSON, sourceURL) { const removeAccessToken = sourceURL ? isMapboxURL(sourceURL) : false; const canonical = []; for (const url of tileJSON.tiles || []) { if (isMapboxHTTPURL(url)) { canonical.push(this.canonicalizeTileURL(url, removeAccessToken)); } else { canonical.push(url); } } return canonical; } _makeAPIURL(urlObject, accessToken) { const help = 'See https://www.mapbox.com/api-documentation/#access-tokens-and-token-scopes'; const apiUrlObject = parseUrl(config.API_URL); urlObject.protocol = apiUrlObject.protocol; urlObject.authority = apiUrlObject.authority; if (urlObject.protocol === 'http') { const i = urlObject.params.indexOf('secure'); if (i >= 0) urlObject.params.splice(i, 1); } if (apiUrlObject.path !== '/') { urlObject.path = `${ apiUrlObject.path }${ urlObject.path }`; } if (!config.REQUIRE_ACCESS_TOKEN) return formatUrl(urlObject); accessToken = accessToken || config.ACCESS_TOKEN; if (!accessToken) throw new Error(`An API access token is required to use Mapbox GL. ${ help }`); if (accessToken[0] === 's') throw new Error(`Use a public access token (pk.*) with Mapbox GL, not a secret access token (sk.*). ${ help }`); urlObject.params = urlObject.params.filter(d => d.indexOf('access_token') === -1); urlObject.params.push(`access_token=${ accessToken }`); return formatUrl(urlObject); } } function isMapboxURL(url) { return url.indexOf('mapbox:') === 0; } function isMapboxHTTPURL(url) { return config.API_URL_REGEX.test(url); } function hasCacheDefeatingSku(url) { return url.indexOf('sku=') > 0 && isMapboxHTTPURL(url); } function getAccessToken(params) { for (const param of params) { const match = param.match(/^access_token=(.*)$/); if (match) { return match[1]; } } return null; } const urlRe = /^(\w+):\/\/([^/?]*)(\/[^?]+)?\??(.+)?/; function parseUrl(url) { const parts = url.match(urlRe); if (!parts) { throw new Error('Unable to parse URL object'); } return { protocol: parts[1], authority: parts[2], path: parts[3] || '/', params: parts[4] ? parts[4].split('&') : [] }; } function formatUrl(obj) { const params = obj.params.length ? `?${ obj.params.join('&') }` : ''; return `${ obj.protocol }://${ obj.authority }${ obj.path }${ params }`; } const telemEventKey = 'mapbox.eventData'; function parseAccessToken(accessToken) { if (!accessToken) { return null; } const parts = accessToken.split('.'); if (!parts || parts.length !== 3) { return null; } try { const jsonData = JSON.parse(b64DecodeUnicode(parts[1])); return jsonData; } catch (e) { return null; } } class TelemetryEvent { constructor(type) { this.type = type; this.anonId = null; this.eventData = {}; this.queue = []; this.pendingRequest = null; } getStorageKey(domain) { const tokenData = parseAccessToken(config.ACCESS_TOKEN); let u = ''; if (tokenData && tokenData['u']) { u = b64EncodeUnicode(tokenData['u']); } else { u = config.ACCESS_TOKEN || ''; } return domain ? `${ telemEventKey }.${ domain }:${ u }` : `${ telemEventKey }:${ u }`; } fetchEventData() { const isLocalStorageAvailable = storageAvailable('localStorage'); const storageKey = this.getStorageKey(); const uuidKey = this.getStorageKey('uuid'); if (isLocalStorageAvailable) { try { const data = window$1.localStorage.getItem(storageKey); if (data) { this.eventData = JSON.parse(data); } const uuid = window$1.localStorage.getItem(uuidKey); if (uuid) this.anonId = uuid; } catch (e) { warnOnce('Unable to read from LocalStorage'); } } } saveEventData() { const isLocalStorageAvailable = storageAvailable('localStorage'); const storageKey = this.getStorageKey(); const uuidKey = this.getStorageKey('uuid'); if (isLocalStorageAvailable) { try { window$1.localStorage.setItem(uuidKey, this.anonId); if (Object.keys(this.eventData).length >= 1) { window$1.localStorage.setItem(storageKey, JSON.stringify(this.eventData)); } } catch (e) { warnOnce('Unable to write to LocalStorage'); } } } processRequests(_) { } postEvent(timestamp, additionalPayload, callback, customAccessToken) { if (!config.EVENTS_URL) return; const eventsUrlObject = parseUrl(config.EVENTS_URL); eventsUrlObject.params.push(`access_token=${ customAccessToken || config.ACCESS_TOKEN || '' }`); const payload = { event: this.type, created: new Date(timestamp).toISOString(), sdkIdentifier: 'mapbox-gl-js', sdkVersion: version, skuId: SKU_ID, userId: this.anonId }; const finalPayload = additionalPayload ? extend(payload, additionalPayload) : payload; const request = { url: formatUrl(eventsUrlObject), headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify([finalPayload]) }; this.pendingRequest = postData(request, error => { this.pendingRequest = null; callback(error); this.saveEventData(); this.processRequests(customAccessToken); }); } queueRequest(event, customAccessToken) { this.queue.push(event); this.processRequests(customAccessToken); } } class MapLoadEvent extends TelemetryEvent { constructor() { super('map.load'); this.success = {}; this.skuToken = ''; } postMapLoadEvent(mapId, skuToken, customAccessToken, callback) { this.skuToken = skuToken; this.errorCb = callback; if (config.EVENTS_URL) { if (customAccessToken || config.ACCESS_TOKEN) { this.queueRequest({ id: mapId, timestamp: Date.now() }, customAccessToken); } else { this.errorCb(new Error('A valid Mapbox access token is required to use Mapbox GL JS. To create an account or a new access token, visit https://account.mapbox.com/')); } } } processRequests(customAccessToken) { if (this.pendingRequest || this.queue.length === 0) return; const {id, timestamp} = this.queue.shift(); if (id && this.success[id]) return; if (!this.anonId) { this.fetchEventData(); } if (!validateUuid(this.anonId)) { this.anonId = uuid(); } this.postEvent(timestamp, { skuToken: this.skuToken }, err => { if (err) { this.errorCb(err); } else { if (id) this.success[id] = true; } }, customAccessToken); } } class MapSessionAPI extends TelemetryEvent { constructor() { super('map.auth'); this.success = {}; this.skuToken = ''; } getSession(timestamp, token, callback, customAccessToken) { if (!config.API_URL || !config.SESSION_PATH) return; const authUrlObject = parseUrl(config.API_URL + config.SESSION_PATH); authUrlObject.params.push(`sku=${ token || '' }`); authUrlObject.params.push(`access_token=${ customAccessToken || config.ACCESS_TOKEN || '' }`); const request = { url: formatUrl(authUrlObject), headers: { 'Content-Type': 'text/plain' } }; this.pendingRequest = getData(request, error => { this.pendingRequest = null; callback(error); this.saveEventData(); this.processRequests(customAccessToken); }); } getSessionAPI(mapId, skuToken, customAccessToken, callback) { this.skuToken = skuToken; this.errorCb = callback; if (config.SESSION_PATH && config.API_URL) { if (customAccessToken || config.ACCESS_TOKEN) { this.queueRequest({ id: mapId, timestamp: Date.now() }, customAccessToken); } else { this.errorCb(new Error(AUTH_ERR_MSG)); } } } processRequests(customAccessToken) { if (this.pendingRequest || this.queue.length === 0) return; const {id, timestamp} = this.queue.shift(); if (id && this.success[id]) return; this.getSession(timestamp, this.skuToken, err => { if (err) { this.errorCb(err); } else { if (id) this.success[id] = true; } }, customAccessToken); } } class TurnstileEvent extends TelemetryEvent { constructor(customAccessToken) { super('appUserTurnstile'); this._customAccessToken = customAccessToken; } postTurnstileEvent(tileUrls, customAccessToken) { if (config.EVENTS_URL && config.ACCESS_TOKEN && Array.isArray(tileUrls) && tileUrls.some(url => isMapboxURL(url) || isMapboxHTTPURL(url))) { this.queueRequest(Date.now(), customAccessToken); } } processRequests(customAccessToken) { if (this.pendingRequest || this.queue.length === 0) { return; } if (!this.anonId || !this.eventData.lastSuccess || !this.eventData.tokenU) { this.fetchEventData(); } const tokenData = parseAccessToken(config.ACCESS_TOKEN); const tokenU = tokenData ? tokenData['u'] : config.ACCESS_TOKEN; let dueForEvent = tokenU !== this.eventData.tokenU; if (!validateUuid(this.anonId)) { this.anonId = uuid(); dueForEvent = true; } const nextUpdate = this.queue.shift(); if (this.eventData.lastSuccess) { const lastUpdate = new Date(this.eventData.lastSuccess); const nextDate = new Date(nextUpdate); const daysElapsed = (nextUpdate - this.eventData.lastSuccess) / (24 * 60 * 60 * 1000); dueForEvent = dueForEvent || daysElapsed >= 1 || daysElapsed < -1 || lastUpdate.getDate() !== nextDate.getDate(); } else { dueForEvent = true; } if (!dueForEvent) { return this.processRequests(); } this.postEvent(nextUpdate, { 'enabled.telemetry': false }, err => { if (!err) { this.eventData.lastSuccess = nextUpdate; this.eventData.tokenU = tokenU; } }, customAccessToken); } } const turnstileEvent_ = new TurnstileEvent(); const postTurnstileEvent = turnstileEvent_.postTurnstileEvent.bind(turnstileEvent_); const mapLoadEvent_ = new MapLoadEvent(); const postMapLoadEvent = mapLoadEvent_.postMapLoadEvent.bind(mapLoadEvent_); const mapSessionAPI_ = new MapSessionAPI(); const getMapSessionAPI = mapSessionAPI_.getSessionAPI.bind(mapSessionAPI_); const CACHE_NAME = 'mapbox-tiles'; let cacheLimit = 500; let cacheCheckThreshold = 50; const MIN_TIME_UNTIL_EXPIRY = 1000 * 60 * 7; let sharedCache; function cacheOpen() { if (window$1.caches && !sharedCache) { sharedCache = window$1.caches.open(CACHE_NAME); } } let responseConstructorSupportsReadableStream; function prepareBody(response, callback) { if (responseConstructorSupportsReadableStream === undefined) { try { new Response(new ReadableStream()); responseConstructorSupportsReadableStream = true; } catch (e) { 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 window$1.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 timeUntilExpiry = new Date(options.headers.get('Expires')).getTime() - requestTime; if (timeUntilExpiry < MIN_TIME_UNTIL_EXPIRY) return; prepareBody(response, body => { const clonedResponse = new window$1.Response(body, options); cacheOpen(); if (!sharedCache) return; sharedCache.then(cache => cache.put(stripQueryParameters(request.url), clonedResponse)).catch(e => warnOnce(e.message)); }); } function stripQueryParameters(url) { const start = url.indexOf('?'); return start < 0 ? url : url.slice(0, start); } function cacheGet(request, callback) { cacheOpen(); if (!sharedCache) return callback(null); const strippedURL = stripQueryParameters(request.url); sharedCache.then(cache => { cache.match(strippedURL).then(response => { const fresh = isFresh(response); 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']; } let globalEntryCounter = Infinity; function cacheEntryPossiblyAdded(dispatcher) { globalEntryCounter++; if (globalEntryCounter > cacheCheckThreshold) { dispatcher.getActor().send('enforceCacheSizeLimit', cacheLimit); globalEntryCounter = 0; } } 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 promise = window$1.caches.delete(CACHE_NAME); if (callback) { promise.catch(callback).then(() => callback()); } } function setCacheLimits(limit, checkThreshold) { cacheLimit = limit; cacheCheckThreshold = checkThreshold; } let supportsOffscreenCanvas; function offscreenCanvasSupported() { if (supportsOffscreenCanvas == null) { supportsOffscreenCanvas = window$1.OffscreenCanvas && new window$1.OffscreenCanvas(1, 1).getContext('2d') && typeof window$1.createImageBitmap === 'function'; } return supportsOffscreenCanvas; } const ResourceType = { Unknown: 'Unknown', Style: 'Style', Source: 'Source', Tile: 'Tile', Glyphs: 'Glyphs', SpriteImage: 'SpriteImage', SpriteJSON: 'SpriteJSON', Image: 'Image' }; if (typeof Object.freeze == 'function') { Object.freeze(ResourceType); } 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://www.mapbox.com/api-documentation/#access-tokens-and-token-scopes'; } super(message); this.status = status; this.url = url; } toString() { return `${ this.name }: ${ this.message } (${ this.status }): ${ this.url }`; } } const getReferrer = isWorker() ? () => self.worker && self.worker.referrer : () => (window$1.location.protocol === 'blob:' ? window$1.parent : window$1).location.href; const isFileURL = url => /^file:/.test(url) || /^file:/.test(getReferrer()) && !/^\w+:/.test(url); function makeFetchRequest(requestParameters, callback) { const controller = new window$1.AbortController(); const request = new window$1.Request(requestParameters.url, { method: requestParameters.method || 'GET', body: requestParameters.body, credentials: requestParameters.credentials, headers: requestParameters.headers, referrer: getReferrer(), 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) { if (err.message !== 'SecurityError') { warnOnce(err); } } if (cachedResponse && responseIsFresh) { return finishRequest(cachedResponse); } const requestTime = Date.now(); window$1.fetch(request).then(response => { if (response.ok) { const cacheableResponse = cacheIgnoringSearch ? response.clone() : null; return finishRequest(response, cacheableResponse, requestTime); } else { return callback(new AJAXError(response.statusText, response.status, requestParameters.url)); } }).catch(error => { if (error.code === 20) { return; } callback(new Error(error.message)); }); }; const finishRequest = (response, cacheableResponse, requestTime) => { (requestParameters.type === 'arrayBuffer' ? response.arrayBuffer() : requestParameters.type === 'json' ? response.json() : response.text()).then(result => { if (aborted) return; if (cacheableResponse && requestTime) { cachePut(request, cacheableResponse, requestTime); } complete = true; callback(null, result, response.headers.get('Cache-Control'), response.headers.get('Expires')); }).catch(err => { if (!aborted) callback(new Error(err.message)); }); }; if (cacheIgnoringSearch) { cacheGet(request, validateOrFetch); } else { validateOrFetch(null, null); } return { cancel: () => { aborted = true; if (!complete) controller.abort(); } }; } function makeXMLHttpRequest(requestParameters, callback) { const xhr = new window$1.XMLHttpRequest(); xhr.open(requestParameters.method || 'GET', requestParameters.url, true); if (requestParameters.type === 'arrayBuffer') { xhr.responseType = 'arraybuffer'; } for (const k in requestParameters.headers) { xhr.setRequestHeader(k, requestParameters.headers[k]); } if (requestParameters.type === 'json') { xhr.responseType = 'text'; xhr.setRequestHeader('Accept', 'application/json'); } xhr.withCredentials = requestParameters.credentials === 'include'; xhr.onerror = () => { callback(new Error(xhr.statusText)); }; xhr.onload = () => { if ((xhr.status >= 200 && xhr.status < 300 || xhr.status === 0) && xhr.response !== null) { let data = xhr.response; if (requestParameters.type === 'json') { try { data = JSON.parse(xhr.response); } catch (err) { return callback(err); } } callback(null, data, xhr.getResponseHeader('Cache-Control'), xhr.getResponseHeader('Expires')); } else { callback(new AJAXError(xhr.statusText, xhr.status, requestParameters.url)); } }; xhr.send(requestParameters.body); return { cancel: () => xhr.abort() }; } const makeRequest = function (requestParameters, callback) { if (!isFileURL(requestParameters.url)) { if (window$1.fetch && window$1.Request && window$1.AbortController && window$1.Request.prototype.hasOwnProperty('signal')) { return makeFetchRequest(requestParameters, callback); } if (isWorker() && self.worker && self.worker.actor) { const queueOnMainThread = true; return self.worker.actor.send('getResource', requestParameters, callback, undefined, queueOnMainThread); } } return makeXMLHttpRequest(requestParameters, callback); }; const getJSON = function (requestParameters, callback) { return makeRequest(extend(requestParameters, { type: 'json' }), callback); }; const getArrayBuffer = function (requestParameters, callback) { return makeRequest(extend(requestParameters, { type: 'arrayBuffer' }), callback); }; const postData = function (requestParameters, callback) { return makeRequest(extend(requestParameters, { method: 'POST' }), callback); }; const getData = function (requestParameters, callback) { return makeRequest(extend(requestParameters, { method: 'GET' }), callback); }; function sameOrigin(url) { const a = window$1.document.createElement('a'); a.href = url; return a.protocol === window$1.document.location.protocol && a.host === window$1.document.location.host; } const transparentPngUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII='; function arrayBufferToImage(data, callback, cacheControl, expires) { const img = new window$1.Image(); const URL = window$1.URL; img.onload = () => { callback(null, img); URL.revokeObjectURL(img.src); img.onload = null; window$1.requestAnimationFrame(() => { img.src = transparentPngUrl; }); }; img.onerror = () => callback(new Error('Could not load image. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported.')); const blob = new window$1.Blob([new Uint8Array(data)], { type: 'image/png' }); img.cacheControl = cacheControl; img.expires = expires; img.src = data.byteLength ? URL.createObjectURL(blob) : transparentPngUrl; } function arrayBufferToImageBitmap(data, callback) { const blob = new window$1.Blob([new Uint8Array(data)], { type: 'image/png' }); window$1.createImageBitmap(blob).then(imgBitmap => { callback(null, imgBitmap); }).catch(e => { callback(new Error(`Could not load image because of ${ e.message }. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported.`)); }); } let imageQueue, numImageRequests; const resetImageRequestQueue = () => { imageQueue = []; numImageRequests = 0; }; resetImageRequestQueue(); const getImage = function (requestParameters, callback) { if (exported$1.supported) { if (!requestParameters.headers) { requestParameters.headers = {}; } requestParameters.headers.accept = 'image/webp,*/*'; } if (numImageRequests >= config.MAX_PARALLEL_IMAGE_REQUESTS) { const queued = { requestParameters, callback, cancelled: false, cancel() { this.cancelled = true; } }; imageQueue.push(queued); return queued; } numImageRequests++; let advanced = false; const advanceImageRequestQueue = () => { if (advanced) return; advanced = true; numImageRequests--; while (imageQueue.length && numImageRequests < config.MAX_PARALLEL_IMAGE_REQUESTS) { const request = imageQueue.shift(); const {requestParameters, callback, cancelled} = request; if (!cancelled) { request.cancel = getImage(requestParameters, callback).cancel; } } }; const request = getArrayBuffer(requestParameters, (err, data, cacheControl, expires) => { advanceImageRequestQueue(); if (err) { callback(err); } else if (data) { if (offscreenCanvasSupported()) { arrayBufferToImageBitmap(data, callback); } else { arrayBufferToImage(data, callback, cacheControl, expires); } } }); return { cancel: () => { request.cancel(); advanceImageRequestQueue(); } }; }; const getVideo = function (urls, callback) { const video = window$1.document.createElement('video'); video.muted = true; video.onloadstart = function () { callback(null, video); }; for (let i = 0; i < urls.length; i++) { const s = window$1.document.createElement('source'); if (!sameOrigin(urls[i])) { video.crossOrigin = 'Anonymous'; } s.src = urls[i]; video.appendChild(s); } return { cancel: () => { } }; }; function _addEventListener(type, listener, listenerList) { const listenerExists = listenerList[type] && listenerList[type].indexOf(listener) !== -1; if (!listenerExists) { listenerList[type] = listenerList[type] || []; listenerList[type].push(listener); } } function _removeEventListener(type, listener, listenerList) { if (listenerList && listenerList[type]) { const index = listenerList[type].indexOf(listener); if (index !== -1) { listenerList[type].splice(index, 1); } } } class Event { constructor(type, data = {}) { extend(this, data); this.type = type; } } class ErrorEvent extends Event { constructor(error, data = {}) { super('error', extend({ error }, data)); } } class Evented { on(type, listener) { this._listeners = this._listeners || {}; _addEventListener(type, listener, this._listeners); return this; } off(type, listener) { _removeEventListener(type, listener, this._listeners); _removeEventListener(type, listener, this._oneTimeListeners); return this; } once(type, listener) { if (!listener) { return new Promise(resolve => this.once(type, resolve)); } this._oneTimeListeners = this._oneTimeListeners || {}; _addEventListener(type, listener, this._oneTimeListeners); return this; } fire(event, properties) { if (typeof event === 'string') { event = new Event(event, properties || {}); } const type = event.type; if (this.listens(type)) { event.target = this; const listeners = this._listeners && this._listeners[type] ? this._listeners[type].slice() : []; for (const listener of listeners) { listener.call(this, event); } const oneTimeListeners = this._oneTimeListeners && this._oneTimeListeners[type] ? this._oneTimeListeners[type].slice() : []; for (const listener of oneTimeListeners) { _removeEventListener(type, listener, this._oneTimeListeners); listener.call(this, event); } const parent = this._eventedParent; if (parent) { extend(event, typeof this._eventedParentData === 'function' ? this._eventedParentData() : this._eventedParentData); parent.fire(event); } } else if (event instanceof ErrorEvent) { console.error(event.error); } return this; } listens(type) { return !!(this._listeners && this._listeners[type] && this._listeners[type].length > 0 || this._oneTimeListeners && this._oneTimeListeners[type] && this._oneTimeListeners[type].length > 0 || this._eventedParent && this._eventedParent.listens(type)); } setEventedParent(parent, data) { this._eventedParent = parent; this._eventedParentData = data; return this; } } var $version = 8; var $root = { version: { required: true, type: "enum", values: [ 8 ] }, name: { type: "string" }, metadata: { type: "*" }, center: { type: "array", value: "number" }, zoom: { type: "number" }, bearing: { type: "number", "default": 0, period: 360, units: "degrees" }, pitch: { type: "number", "default": 0, units: "degrees" }, light: { type: "light" }, terrain: { type: "terrain" }, sources: { required: true, type: "sources" }, sprite: { type: "string" }, glyphs: { type: "string" }, transition: { type: "transition" }, layers: { required: true, type: "array", value: "layer" } }; var sources = { "*": { type: "source" } }; var source = [ "source_vector", "source_raster", "source_raster_dem", "source_geojson", "source_video", "source_image" ]; var source_vector = { type: { required: true, type: "enum", values: { vector: { } } }, url: { type: "string" }, tiles: { type: "array", value: "string" }, bounds: { type: "array", value: "number", length: 4, "default": [ -180, -85.051129, 180, 85.051129 ] }, scheme: { type: "enum", values: { xyz: { }, tms: { } }, "default": "xyz" }, minzoom: { type: "number", "default": 0 }, maxzoom: { type: "number", "default": 22 }, attribution: { type: "string" }, promoteId: { type: "promoteId" }, volatile: { type: "boolean", "default": false }, "*": { type: "*" } }; var source_raster = { type: { required: true, type: "enum", values: { raster: { } } }, url: { type: "string" }, tiles: { type: "array", value: "string" }, bounds: { type: "array", value: "number", length: 4, "default": [ -180, -85.051129, 180, 85.051129 ] }, minzoom: { type: "number", "default": 0 }, maxzoom: { type: "number", "default": 22 }, tileSize: { type: "number", "default": 512, units: "pixels" }, scheme: { type: "enum", values: { xyz: { }, tms: { } }, "default": "xyz" }, attribution: { type: "string" }, volatile: { type: "boolean", "default": false }, "*": { type: "*" } }; var source_raster_dem = { type: { required: true, type: "enum", values: { "raster-dem": { } } }, url: { type: "string" }, tiles: { type: "array", value: "string" }, bounds: { type: "array", value: "number", length: 4, "default": [ -180, -85.051129, 180, 85.051129 ] }, minzoom: { type: "number", "default": 0 }, maxzoom: { type: "number", "default": 22 }, tileSize: { type: "number", "default": 512, units: "pixels" }, attribution: { type: "string" }, encoding: { type: "enum", values: { terrarium: { }, mapbox: { } }, "default": "mapbox" }, volatile: { type: "boolean", "default": false }, "*": { type: "*" } }; var source_geojson = { type: { required: true, type: "enum", values: { geojson: { } } }, data: { type: "*" }, maxzoom: { type: "number", "default": 18 }, attribution: { type: "string" }, buffer: { type: "number", "default": 128, maximum: 512, minimum: 0 }, filter: { type: "*" }, tolerance: { type: "number", "default": 0.375 }, cluster: { type: "boolean", "default": false }, clusterRadius: { type: "number", "default": 50, minimum: 0 }, clusterMaxZoom: { type: "number" }, clusterMinPoints: { type: "number" }, clusterProperties: { type: "*" }, lineMetrics: { type: "boolean", "default": false }, generateId: { type: "boolean", "default": false }, promoteId: { type: "promoteId" } }; var source_video = { type: { required: true, type: "enum", values: { video: { } } }, urls: { required: true, type: "array", value: "string" }, coordinates: { required: true, type: "array", length: 4, value: { type: "array", length: 2, value: "number" } } }; var source_image = { type: { required: true, type: "enum", values: { image: { } } }, url: { required: true, type: "string" }, coordinates: { required: true, type: "array", length: 4, value: { type: "array", length: 2, value: "number" } } }; var layer = { id: { type: "string", required: true }, type: { type: "enum", values: { fill: { }, line: { }, symbol: { }, circle: { }, heatmap: { }, "fill-extrusion": { }, raster: { }, hillshade: { }, background: { }, sky: { } }, required: true }, metadata: { type: "*" }, source: { type: "string" }, "source-layer": { type: "string" }, minzoom: { type: "number", minimum: 0, maximum: 24 }, maxzoom: { type: "number", minimum: 0, maximum: 24 }, filter: { type: "filter" }, layout: { type: "layout" }, paint: { type: "paint" } }; var layout = [ "layout_fill", "layout_line", "layout_circle", "layout_heatmap", "layout_fill-extrusion", "layout_symbol", "layout_raster", "layout_hillshade", "layout_background", "layout_sky" ]; var layout_background = { visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var layout_sky = { visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var layout_fill = { "fill-sort-key": { type: "number", expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var layout_circle = { "circle-sort-key": { type: "number", expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var layout_heatmap = { visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var layout_line = { "line-cap": { type: "enum", values: { butt: { }, round: { }, square: { } }, "default": "butt", expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "line-join": { type: "enum", values: { bevel: { }, round: { }, miter: { } }, "default": "miter", expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "line-miter-limit": { type: "number", "default": 2, requires: [ { "line-join": "miter" } ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "line-round-limit": { type: "number", "default": 1.05, requires: [ { "line-join": "round" } ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "line-sort-key": { type: "number", expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var layout_symbol = { "symbol-placement": { type: "enum", values: { point: { }, line: { }, "line-center": { } }, "default": "point", expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "symbol-spacing": { type: "number", "default": 250, minimum: 1, units: "pixels", requires: [ { "symbol-placement": "line" } ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "symbol-avoid-edges": { type: "boolean", "default": false, expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "symbol-sort-key": { type: "number", expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "symbol-z-order": { type: "enum", values: { auto: { }, "viewport-y": { }, source: { } }, "default": "auto", expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-allow-overlap": { type: "boolean", "default": false, requires: [ "icon-image" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-ignore-placement": { type: "boolean", "default": false, requires: [ "icon-image" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-optional": { type: "boolean", "default": false, requires: [ "icon-image", "text-field" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-rotation-alignment": { type: "enum", values: { map: { }, viewport: { }, auto: { } }, "default": "auto", requires: [ "icon-image" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-size": { type: "number", "default": 1, minimum: 0, units: "factor of the original icon size", requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "icon-text-fit": { type: "enum", values: { none: { }, width: { }, height: { }, both: { } }, "default": "none", requires: [ "icon-image", "text-field" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-text-fit-padding": { type: "array", value: "number", length: 4, "default": [ 0, 0, 0, 0 ], units: "pixels", requires: [ "icon-image", "text-field", { "icon-text-fit": [ "both", "width", "height" ] } ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-image": { type: "resolvedImage", tokens: true, expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "icon-rotate": { type: "number", "default": 0, period: 360, units: "degrees", requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "icon-padding": { type: "number", "default": 2, minimum: 0, units: "pixels", requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-keep-upright": { type: "boolean", "default": false, requires: [ "icon-image", { "icon-rotation-alignment": "map" }, { "symbol-placement": [ "line", "line-center" ] } ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-offset": { type: "array", value: "number", length: 2, "default": [ 0, 0 ], requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "icon-anchor": { type: "enum", values: { center: { }, left: { }, right: { }, top: { }, bottom: { }, "top-left": { }, "top-right": { }, "bottom-left": { }, "bottom-right": { } }, "default": "center", requires: [ "icon-image" ], expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "icon-pitch-alignment": { type: "enum", values: { map: { }, viewport: { }, auto: { } }, "default": "auto", requires: [ "icon-image" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-pitch-alignment": { type: "enum", values: { map: { }, viewport: { }, auto: { } }, "default": "auto", requires: [ "text-field" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-rotation-alignment": { type: "enum", values: { map: { }, viewport: { }, auto: { } }, "default": "auto", requires: [ "text-field" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-field": { type: "formatted", "default": "", tokens: true, expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-font": { type: "array", value: "string", "default": [ "Open Sans Regular", "Arial Unicode MS Regular" ], requires: [ "text-field" ], expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-size": { type: "number", "default": 16, minimum: 0, units: "pixels", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-max-width": { type: "number", "default": 10, minimum: 0, units: "ems", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-line-height": { type: "number", "default": 1.2, units: "ems", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-letter-spacing": { type: "number", "default": 0, units: "ems", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-justify": { type: "enum", values: { auto: { }, left: { }, center: { }, right: { } }, "default": "center", requires: [ "text-field" ], expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-radial-offset": { type: "number", units: "ems", "default": 0, requires: [ "text-field" ], "property-type": "data-driven", expression: { interpolated: true, parameters: [ "zoom", "feature" ] } }, "text-variable-anchor": { type: "array", value: "enum", values: { center: { }, left: { }, right: { }, top: { }, bottom: { }, "top-left": { }, "top-right": { }, "bottom-left": { }, "bottom-right": { } }, requires: [ "text-field", { "symbol-placement": [ "point" ] } ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-anchor": { type: "enum", values: { center: { }, left: { }, right: { }, top: { }, bottom: { }, "top-left": { }, "top-right": { }, "bottom-left": { }, "bottom-right": { } }, "default": "center", requires: [ "text-field", { "!": "text-variable-anchor" } ], expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-max-angle": { type: "number", "default": 45, units: "degrees", requires: [ "text-field", { "symbol-placement": [ "line", "line-center" ] } ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-writing-mode": { type: "array", value: "enum", values: { horizontal: { }, vertical: { } }, requires: [ "text-field", { "symbol-placement": [ "point" ] } ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-rotate": { type: "number", "default": 0, period: 360, units: "degrees", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-padding": { type: "number", "default": 2, minimum: 0, units: "pixels", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-keep-upright": { type: "boolean", "default": true, requires: [ "text-field", { "text-rotation-alignment": "map" }, { "symbol-placement": [ "line", "line-center" ] } ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-transform": { type: "enum", values: { none: { }, uppercase: { }, lowercase: { } }, "default": "none", requires: [ "text-field" ], expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-offset": { type: "array", value: "number", units: "ems", length: 2, "default": [ 0, 0 ], requires: [ "text-field", { "!": "text-radial-offset" } ], expression: { interpolated: true, parameters: [ "zoom", "feature" ] }, "property-type": "data-driven" }, "text-allow-overlap": { type: "boolean", "default": false, requires: [ "text-field" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-ignore-placement": { type: "boolean", "default": false, requires: [ "text-field" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-optional": { type: "boolean", "default": false, requires: [ "text-field", "icon-image" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var layout_raster = { visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var layout_hillshade = { visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }; var filter = { type: "array", value: "*" }; var filter_operator = { type: "enum", values: { "==": { }, "!=": { }, ">": { }, ">=": { }, "<": { }, "<=": { }, "in": { }, "!in": { }, all: { }, any: { }, none: { }, has: { }, "!has": { }, within: { } } }; var geometry_type = { type: "enum", values: { Point: { }, LineString: { }, Polygon: { } } }; var function_stop = { type: "array", minimum: 0, maximum: 24, value: [ "number", "color" ], length: 2 }; var expression = { type: "array", value: "*", minimum: 1 }; var light = { anchor: { type: "enum", "default": "viewport", values: { map: { }, viewport: { } }, "property-type": "data-constant", transition: false, expression: { interpolated: false, parameters: [ "zoom" ] } }, position: { type: "array", "default": [ 1.15, 210, 30 ], length: 3, value: "number", "property-type": "data-constant", transition: true, expression: { interpolated: true, parameters: [ "zoom" ] } }, color: { type: "color", "property-type": "data-constant", "default": "#ffffff", expression: { interpolated: true, parameters: [ "zoom" ] }, transition: true }, intensity: { type: "number", "property-type": "data-constant", "default": 0.5, minimum: 0, maximum: 1, expression: { interpolated: true, parameters: [ "zoom" ] }, transition: true } }; var terrain = { source: { type: "string", required: true }, exaggeration: { type: "number", "property-type": "data-constant", "default": 1, minimum: 0, maximum: 1000, expression: { interpolated: true, parameters: [ "zoom" ] }, transition: true } }; var paint = [ "paint_fill", "paint_line", "paint_circle", "paint_heatmap", "paint_fill-extrusion", "paint_symbol", "paint_raster", "paint_hillshade", "paint_background", "paint_sky" ]; var paint_fill = { "fill-antialias": { type: "boolean", "default": true, expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "fill-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "fill-color": { type: "color", "default": "#000000", transition: true, requires: [ { "!": "fill-pattern" } ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "fill-outline-color": { type: "color", transition: true, requires: [ { "!": "fill-pattern" }, { "fill-antialias": true } ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "fill-translate": { type: "array", value: "number", length: 2, "default": [ 0, 0 ], transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "fill-translate-anchor": { type: "enum", values: { map: { }, viewport: { } }, "default": "map", requires: [ "fill-translate" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "fill-pattern": { type: "resolvedImage", transition: true, expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "cross-faded-data-driven" } }; var paint_line = { "line-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "line-color": { type: "color", "default": "#000000", transition: true, requires: [ { "!": "line-pattern" } ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "line-translate": { type: "array", value: "number", length: 2, "default": [ 0, 0 ], transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "line-translate-anchor": { type: "enum", values: { map: { }, viewport: { } }, "default": "map", requires: [ "line-translate" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "line-width": { type: "number", "default": 1, minimum: 0, transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "line-gap-width": { type: "number", "default": 0, minimum: 0, transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "line-offset": { type: "number", "default": 0, transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "line-blur": { type: "number", "default": 0, minimum: 0, transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "line-dasharray": { type: "array", value: "number", minimum: 0, transition: true, units: "line widths", requires: [ { "!": "line-pattern" } ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "cross-faded" }, "line-pattern": { type: "resolvedImage", transition: true, expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "cross-faded-data-driven" }, "line-gradient": { type: "color", transition: false, requires: [ { "!": "line-dasharray" }, { "!": "line-pattern" }, { source: "geojson", has: { lineMetrics: true } } ], expression: { interpolated: true, parameters: [ "line-progress" ] }, "property-type": "color-ramp" } }; var paint_circle = { "circle-radius": { type: "number", "default": 5, minimum: 0, transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "circle-color": { type: "color", "default": "#000000", transition: true, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "circle-blur": { type: "number", "default": 0, transition: true, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "circle-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "circle-translate": { type: "array", value: "number", length: 2, "default": [ 0, 0 ], transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "circle-translate-anchor": { type: "enum", values: { map: { }, viewport: { } }, "default": "map", requires: [ "circle-translate" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "circle-pitch-scale": { type: "enum", values: { map: { }, viewport: { } }, "default": "map", expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "circle-pitch-alignment": { type: "enum", values: { map: { }, viewport: { } }, "default": "viewport", expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "circle-stroke-width": { type: "number", "default": 0, minimum: 0, transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "circle-stroke-color": { type: "color", "default": "#000000", transition: true, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "circle-stroke-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" } }; var paint_heatmap = { "heatmap-radius": { type: "number", "default": 30, minimum: 1, transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "heatmap-weight": { type: "number", "default": 1, minimum: 0, transition: false, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "heatmap-intensity": { type: "number", "default": 1, minimum: 0, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "heatmap-color": { type: "color", "default": [ "interpolate", [ "linear" ], [ "heatmap-density" ], 0, "rgba(0, 0, 255, 0)", 0.1, "royalblue", 0.3, "cyan", 0.5, "lime", 0.7, "yellow", 1, "red" ], transition: false, expression: { interpolated: true, parameters: [ "heatmap-density" ] }, "property-type": "color-ramp" }, "heatmap-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" } }; var paint_symbol = { "icon-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "icon-color": { type: "color", "default": "#000000", transition: true, requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "icon-halo-color": { type: "color", "default": "rgba(0, 0, 0, 0)", transition: true, requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "icon-halo-width": { type: "number", "default": 0, minimum: 0, transition: true, units: "pixels", requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "icon-halo-blur": { type: "number", "default": 0, minimum: 0, transition: true, units: "pixels", requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "icon-translate": { type: "array", value: "number", length: 2, "default": [ 0, 0 ], transition: true, units: "pixels", requires: [ "icon-image" ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "icon-translate-anchor": { type: "enum", values: { map: { }, viewport: { } }, "default": "map", requires: [ "icon-image", "icon-translate" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "text-color": { type: "color", "default": "#000000", transition: true, overridable: true, requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "text-halo-color": { type: "color", "default": "rgba(0, 0, 0, 0)", transition: true, requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "text-halo-width": { type: "number", "default": 0, minimum: 0, transition: true, units: "pixels", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "text-halo-blur": { type: "number", "default": 0, minimum: 0, transition: true, units: "pixels", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "text-translate": { type: "array", value: "number", length: 2, "default": [ 0, 0 ], transition: true, units: "pixels", requires: [ "text-field" ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "text-translate-anchor": { type: "enum", values: { map: { }, viewport: { } }, "default": "map", requires: [ "text-field", "text-translate" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" } }; var paint_raster = { "raster-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "raster-hue-rotate": { type: "number", "default": 0, period: 360, transition: true, units: "degrees", expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "raster-brightness-min": { type: "number", "default": 0, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "raster-brightness-max": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "raster-saturation": { type: "number", "default": 0, minimum: -1, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "raster-contrast": { type: "number", "default": 0, minimum: -1, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "raster-resampling": { type: "enum", values: { linear: { }, nearest: { } }, "default": "linear", expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "raster-fade-duration": { type: "number", "default": 300, minimum: 0, transition: false, units: "milliseconds", expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" } }; var paint_hillshade = { "hillshade-illumination-direction": { type: "number", "default": 335, minimum: 0, maximum: 359, transition: false, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "hillshade-illumination-anchor": { type: "enum", values: { map: { }, viewport: { } }, "default": "viewport", expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "hillshade-exaggeration": { type: "number", "default": 0.5, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "hillshade-shadow-color": { type: "color", "default": "#000000", transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "hillshade-highlight-color": { type: "color", "default": "#FFFFFF", transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "hillshade-accent-color": { type: "color", "default": "#000000", transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" } }; var paint_background = { "background-color": { type: "color", "default": "#000000", transition: true, requires: [ { "!": "background-pattern" } ], expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "background-pattern": { type: "resolvedImage", transition: true, expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "cross-faded" }, "background-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" } }; var paint_sky = { "sky-type": { type: "enum", values: { gradient: { }, atmosphere: { } }, "default": "atmosphere", expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "sky-atmosphere-sun": { type: "array", value: "number", length: 2, units: "degrees", minimum: [ 0, 0 ], maximum: [ 360, 180 ], transition: false, requires: [ { "sky-type": "atmosphere" } ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "sky-atmosphere-sun-intensity": { type: "number", requires: [ { "sky-type": "atmosphere" } ], "default": 10, minimum: 0, maximum: 100, transition: false, "property-type": "data-constant" }, "sky-gradient-center": { type: "array", requires: [ { "sky-type": "gradient" } ], value: "number", "default": [ 0, 0 ], length: 2, units: "degrees", minimum: [ 0, 0 ], maximum: [ 360, 180 ], transition: false, expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "sky-gradient-radius": { type: "number", requires: [ { "sky-type": "gradient" } ], "default": 90, minimum: 0, maximum: 180, transition: false, expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "sky-gradient": { type: "color", "default": [ "interpolate", [ "linear" ], [ "sky-radial-progress" ], 0.8, "#87ceeb", 1, "white" ], transition: false, requires: [ { "sky-type": "gradient" } ], expression: { interpolated: true, parameters: [ "sky-radial-progress" ] }, "property-type": "color-ramp" }, "sky-atmosphere-halo-color": { type: "color", "default": "white", transition: false, requires: [ { "sky-type": "atmosphere" } ], "property-type": "data-constant" }, "sky-atmosphere-color": { type: "color", "default": "white", transition: false, requires: [ { "sky-type": "atmosphere" } ], "property-type": "data-constant" }, "sky-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" } }; var transition = { duration: { type: "number", "default": 300, minimum: 0, units: "milliseconds" }, delay: { type: "number", "default": 0, minimum: 0, units: "milliseconds" } }; var promoteId = { "*": { type: "string" } }; var spec = { $version: $version, $root: $root, sources: sources, source: source, source_vector: source_vector, source_raster: source_raster, source_raster_dem: source_raster_dem, source_geojson: source_geojson, source_video: source_video, source_image: source_image, layer: layer, layout: layout, layout_background: layout_background, layout_sky: layout_sky, layout_fill: layout_fill, layout_circle: layout_circle, layout_heatmap: layout_heatmap, "layout_fill-extrusion": { visibility: { type: "enum", values: { visible: { }, none: { } }, "default": "visible", "property-type": "constant" } }, layout_line: layout_line, layout_symbol: layout_symbol, layout_raster: layout_raster, layout_hillshade: layout_hillshade, filter: filter, filter_operator: filter_operator, geometry_type: geometry_type, "function": { expression: { type: "expression" }, stops: { type: "array", value: "function_stop" }, base: { type: "number", "default": 1, minimum: 0 }, property: { type: "string", "default": "$zoom" }, type: { type: "enum", values: { identity: { }, exponential: { }, interval: { }, categorical: { } }, "default": "exponential" }, colorSpace: { type: "enum", values: { rgb: { }, lab: { }, hcl: { } }, "default": "rgb" }, "default": { type: "*", required: false } }, function_stop: function_stop, expression: expression, light: light, terrain: terrain, paint: paint, paint_fill: paint_fill, "paint_fill-extrusion": { "fill-extrusion-opacity": { type: "number", "default": 1, minimum: 0, maximum: 1, transition: true, expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "fill-extrusion-color": { type: "color", "default": "#000000", transition: true, requires: [ { "!": "fill-extrusion-pattern" } ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "fill-extrusion-translate": { type: "array", value: "number", length: 2, "default": [ 0, 0 ], transition: true, units: "pixels", expression: { interpolated: true, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "fill-extrusion-translate-anchor": { type: "enum", values: { map: { }, viewport: { } }, "default": "map", requires: [ "fill-extrusion-translate" ], expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" }, "fill-extrusion-pattern": { type: "resolvedImage", transition: true, expression: { interpolated: false, parameters: [ "zoom", "feature" ] }, "property-type": "cross-faded-data-driven" }, "fill-extrusion-height": { type: "number", "default": 0, minimum: 0, units: "meters", transition: true, expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "fill-extrusion-base": { type: "number", "default": 0, minimum: 0, units: "meters", transition: true, requires: [ "fill-extrusion-height" ], expression: { interpolated: true, parameters: [ "zoom", "feature", "feature-state" ] }, "property-type": "data-driven" }, "fill-extrusion-vertical-gradient": { type: "boolean", "default": true, transition: false, expression: { interpolated: false, parameters: [ "zoom" ] }, "property-type": "data-constant" } }, paint_line: paint_line, paint_circle: paint_circle, paint_heatmap: paint_heatmap, paint_symbol: paint_symbol, paint_raster: paint_raster, paint_hillshade: paint_hillshade, paint_background: paint_background, paint_sky: paint_sky, transition: transition, "property-type": { "data-driven": { type: "property-type" }, "cross-faded": { type: "property-type" }, "cross-faded-data-driven": { type: "property-type" }, "color-ramp": { type: "property-type" }, "data-constant": { type: "property-type" }, constant: { type: "property-type" } }, promoteId: promoteId }; class ValidationError { constructor(key, value, message, identifier) { this.message = (key ? `${ key }: ` : '') + message; if (identifier) this.identifier = identifier; if (value !== null && value !== undefined && value.__line__) { this.line = value.__line__; } } } function validateConstants(options) { const key = options.key; const constants = options.value; if (constants) { return [new ValidationError(key, constants, 'constants have been deprecated as of v8')]; } else { return []; } } function extend$1 (output, ...inputs) { for (const input of inputs) { for (const k in input) { output[k] = input[k]; } } return output; } function unbundle(value) { if (value instanceof Number || value instanceof String || value instanceof Boolean) { return value.valueOf(); } else { return value; } } function deepUnbundle(value) { if (Array.isArray(value)) { return value.map(deepUnbundle); } else if (value instanceof Object && !(value instanceof Number || value instanceof String || value instanceof Boolean)) { const unbundledValue = {}; for (const key in value) { unbundledValue[key] = deepUnbundle(value[key]); } return unbundledValue; } return unbundle(value); } class ParsingError extends Error { constructor(key, message) { super(message); this.message = message; this.key = key; } } class Scope { constructor(parent, bindings = []) { this.parent = parent; this.bindings = {}; for (const [name, expression] of bindings) { this.bindings[name] = expression; } } concat(bindings) { return new Scope(this, bindings); } get(name) { if (this.bindings[name]) { return this.bindings[name]; } if (this.parent) { return this.parent.get(name); } throw new Error(`${ name } not found in scope.`); } has(name) { if (this.bindings[name]) return true; return this.parent ? this.parent.has(name) : false; } } const NullType = { kind: 'null' }; const NumberType = { kind: 'number' }; const StringType = { kind: 'string' }; const BooleanType = { kind: 'boolean' }; const ColorType = { kind: 'color' }; const ObjectType = { kind: 'object' }; const ValueType = { kind: 'value' }; const ErrorType = { kind: 'error' }; const CollatorType = { kind: 'collator' }; const FormattedType = { kind: 'formatted' }; const ResolvedImageType = { kind: 'resolvedImage' }; function array(itemType, N) { return { kind: 'array', itemType, N }; } function toString(type) { if (type.kind === 'array') { const itemType = toString(type.itemType); return typeof type.N === 'number' ? `array<${ itemType }, ${ type.N }>` : type.itemType.kind === 'value' ? 'array' : `array<${ itemType }>`; } else { return type.kind; } } const valueMemberTypes = [ NullType, NumberType, StringType, BooleanType, ColorType, FormattedType, ObjectType, array(ValueType), ResolvedImageType ]; function checkSubtype(expected, t) { if (t.kind === 'error') { return null; } else if (expected.kind === 'array') { if (t.kind === 'array' && (t.N === 0 && t.itemType.kind === 'value' || !checkSubtype(expected.itemType, t.itemType)) && (typeof expected.N !== 'number' || expected.N === t.N)) { return null; } } else if (expected.kind === t.kind) { return null; } else if (expected.kind === 'value') { for (const memberType of valueMemberTypes) { if (!checkSubtype(memberType, t)) { return null; } } } return `Expected ${ toString(expected) } but found ${ toString(t) } instead.`; } function isValidType(provided, allowedTypes) { return allowedTypes.some(t => t.kind === provided.kind); } function isValidNativeType(provided, allowedTypes) { return allowedTypes.some(t => { if (t === 'null') { return provided === null; } else if (t === 'array') { return Array.isArray(provided); } else if (t === 'object') { return provided && !Array.isArray(provided) && typeof provided === 'object'; } else { return t === typeof provided; } }); } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var csscolorparser = createCommonjsModule(function (module, exports) { var kCSSColorTable = { 'transparent': [ 0, 0, 0, 0 ], 'aliceblue': [ 240, 248, 255, 1 ], 'antiquewhite': [ 250, 235, 215, 1 ], 'aqua': [ 0, 255, 255, 1 ], 'aquamarine': [ 127, 255, 212, 1 ], 'azure': [ 240, 255, 255, 1 ], 'beige': [ 245, 245, 220, 1 ], 'bisque': [ 255, 228, 196, 1 ], 'black': [ 0, 0, 0, 1 ], 'blanchedalmond': [ 255, 235, 205, 1 ], 'blue': [ 0, 0, 255, 1 ], 'blueviolet': [ 138, 43, 226, 1 ], 'brown': [ 165, 42, 42, 1 ], 'burlywood': [ 222, 184, 135, 1 ], 'cadetblue': [ 95, 158, 160, 1 ], 'chartreuse': [ 127, 255, 0, 1 ], 'chocolate': [ 210, 105, 30, 1 ], 'coral': [ 255, 127, 80, 1 ], 'cornflowerblue': [ 100, 149, 237, 1 ], 'cornsilk': [ 255, 248, 220, 1 ], 'crimson': [ 220, 20, 60, 1 ], 'cyan': [ 0, 255, 255, 1 ], 'darkblue': [ 0, 0, 139, 1 ], 'darkcyan': [ 0, 139, 139, 1 ], 'darkgoldenrod': [ 184, 134, 11, 1 ], 'darkgray': [ 169, 169, 169, 1 ], 'darkgreen': [ 0, 100, 0, 1 ], 'darkgrey': [ 169, 169, 169, 1 ], 'darkkhaki': [ 189, 183, 107, 1 ], 'darkmagenta': [ 139, 0, 139, 1 ], 'darkolivegreen': [ 85, 107, 47, 1 ], 'darkorange': [ 255, 140, 0, 1 ], 'darkorchid': [ 153, 50, 204, 1 ], 'darkred': [ 139, 0, 0, 1 ], 'darksalmon': [ 233, 150, 122, 1 ], 'darkseagreen': [ 143, 188, 143, 1 ], 'darkslateblue': [ 72, 61, 139, 1 ], 'darkslategray': [ 47, 79, 79, 1 ], 'darkslategrey': [ 47, 79, 79, 1 ], 'darkturquoise': [ 0, 206, 209, 1 ], 'darkviolet': [ 148, 0, 211, 1 ], 'deeppink': [ 255, 20, 147, 1 ], 'deepskyblue': [ 0, 191, 255, 1 ], 'dimgray': [ 105, 105, 105, 1 ], 'dimgrey': [ 105, 105, 105, 1 ], 'dodgerblue': [ 30, 144, 255, 1 ], 'firebrick': [ 178, 34, 34, 1 ], 'floralwhite': [ 255, 250, 240, 1 ], 'forestgreen': [ 34, 139, 34, 1 ], 'fuchsia': [ 255, 0, 255, 1 ], 'gainsboro': [ 220, 220, 220, 1 ], 'ghostwhite': [ 248, 248, 255, 1 ], 'gold': [ 255, 215, 0, 1 ], 'goldenrod': [ 218, 165, 32, 1 ], 'gray': [ 128, 128, 128, 1 ], 'green': [ 0, 128, 0, 1 ], 'greenyellow': [ 173, 255, 47, 1 ], 'grey': [ 128, 128, 128, 1 ], 'honeydew': [ 240, 255, 240, 1 ], 'hotpink': [ 255, 105, 180, 1 ], 'indianred': [ 205, 92, 92, 1 ], 'indigo': [ 75, 0, 130, 1 ], 'ivory': [ 255, 255, 240, 1 ], 'khaki': [ 240, 230, 140, 1 ], 'lavender': [ 230, 230, 250, 1 ], 'lavenderblush': [ 255, 240, 245, 1 ], 'lawngreen': [ 124, 252, 0, 1 ], 'lemonchiffon': [ 255, 250, 205, 1 ], 'lightblue': [ 173, 216, 230, 1 ], 'lightcoral': [ 240, 128, 128, 1 ], 'lightcyan': [ 224, 255, 255, 1 ], 'lightgoldenrodyellow': [ 250, 250, 210, 1 ], 'lightgray': [ 211, 211, 211, 1 ], 'lightgreen': [ 144, 238, 144, 1 ], 'lightgrey': [ 211, 211, 211, 1 ], 'lightpink': [ 255, 182, 193, 1 ], 'lightsalmon': [ 255, 160, 122, 1 ], 'lightseagreen': [ 32, 178, 170, 1 ], 'lightskyblue': [ 135, 206, 250, 1 ], 'lightslategray': [ 119, 136, 153, 1 ], 'lightslategrey': [ 119, 136, 153, 1 ], 'lightsteelblue': [ 176, 196, 222, 1 ], 'lightyellow': [ 255, 255, 224, 1 ], 'lime': [ 0, 255, 0, 1 ], 'limegreen': [ 50, 205, 50, 1 ], 'linen': [ 250, 240, 230, 1 ], 'magenta': [ 255, 0, 255, 1 ], 'maroon': [ 128, 0, 0, 1 ], 'mediumaquamarine': [ 102, 205, 170, 1 ], 'mediumblue': [ 0, 0, 205, 1 ], 'mediumorchid': [ 186, 85, 211, 1 ], 'mediumpurple': [ 147, 112, 219, 1 ], 'mediumseagreen': [ 60, 179, 113, 1 ], 'mediumslateblue': [ 123, 104, 238, 1 ], 'mediumspringgreen': [ 0, 250, 154, 1 ], 'mediumturquoise': [ 72, 209, 204, 1 ], 'mediumvioletred': [ 199, 21, 133, 1 ], 'midnightblue': [ 25, 25, 112, 1 ], 'mintcream': [ 245, 255, 250, 1 ], 'mistyrose': [ 255, 228, 225, 1 ], 'moccasin': [ 255, 228, 181, 1 ], 'navajowhite': [ 255, 222, 173, 1 ], 'navy': [ 0, 0, 128, 1 ], 'oldlace': [ 253, 245, 230, 1 ], 'olive': [ 128, 128, 0, 1 ], 'olivedrab': [ 107, 142, 35, 1 ], 'orange': [ 255, 165, 0, 1 ], 'orangered': [ 255, 69, 0, 1 ], 'orchid': [ 218, 112, 214, 1 ], 'palegoldenrod': [ 238, 232, 170, 1 ], 'palegreen': [ 152, 251, 152, 1 ], 'paleturquoise': [ 175, 238, 238, 1 ], 'palevioletred': [ 219, 112, 147, 1 ], 'papayawhip': [ 255, 239, 213, 1 ], 'peachpuff': [ 255, 218, 185, 1 ], 'peru': [ 205, 133, 63, 1 ], 'pink': [ 255, 192, 203, 1 ], 'plum': [ 221, 160, 221, 1 ], 'powderblue': [ 176, 224, 230, 1 ], 'purple': [ 128, 0, 128, 1 ], 'rebeccapurple': [ 102, 51, 153, 1 ], 'red': [ 255, 0, 0, 1 ], 'rosybrown': [ 188, 143, 143, 1 ], 'royalblue': [ 65, 105, 225, 1 ], 'saddlebrown': [ 139, 69, 19, 1 ], 'salmon': [ 250, 128, 114, 1 ], 'sandybrown': [ 244, 164, 96, 1 ], 'seagreen': [ 46, 139, 87, 1 ], 'seashell': [ 255, 245, 238, 1 ], 'sienna': [ 160, 82, 45, 1 ], 'silver': [ 192, 192, 192, 1 ], 'skyblue': [ 135, 206, 235, 1 ], 'slateblue': [ 106, 90, 205, 1 ], 'slategray': [ 112, 128, 144, 1 ], 'slategrey': [ 112, 128, 144, 1 ], 'snow': [ 255, 250, 250, 1 ], 'springgreen': [ 0, 255, 127, 1 ], 'steelblue': [ 70, 130, 180, 1 ], 'tan': [ 210, 180, 140, 1 ], 'teal': [ 0, 128, 128, 1 ], 'thistle': [ 216, 191, 216, 1 ], 'tomato': [ 255, 99, 71, 1 ], 'turquoise': [ 64, 224, 208, 1 ], 'violet': [ 238, 130, 238, 1 ], 'wheat': [ 245, 222, 179, 1 ], 'white': [ 255, 255, 255, 1 ], 'whitesmoke': [ 245, 245, 245, 1 ], 'yellow': [ 255, 255, 0, 1 ], 'yellowgreen': [ 154, 205, 50, 1 ] }; function clamp_css_byte(i) { i = Math.round(i); return i < 0 ? 0 : i > 255 ? 255 : i; } function clamp_css_float(f) { return f < 0 ? 0 : f > 1 ? 1 : f; } function parse_css_int(str) { if (str[str.length - 1] === '%') return clamp_css_byte(parseFloat(str) / 100 * 255); return clamp_css_byte(parseInt(str)); } function parse_css_float(str) { if (str[str.length - 1] === '%') return clamp_css_float(parseFloat(str) / 100); return clamp_css_float(parseFloat(str)); } function css_hue_to_rgb(m1, m2, h) { if (h < 0) h += 1; else if (h > 1) h -= 1; if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; if (h * 2 < 1) return m2; if (h * 3 < 2) return m1 + (m2 - m1) * (2 / 3 - h) * 6; return m1; } function parseCSSColor(css_str) { var str = css_str.replace(/ /g, '').toLowerCase(); if (str in kCSSColorTable) return kCSSColorTable[str].slice(); if (str[0] === '#') { if (str.length === 4) { var iv = parseInt(str.substr(1), 16); if (!(iv >= 0 && iv <= 4095)) return null; return [ (iv & 3840) >> 4 | (iv & 3840) >> 8, iv & 240 | (iv & 240) >> 4, iv & 15 | (iv & 15) << 4, 1 ]; } else if (str.length === 7) { var iv = parseInt(str.substr(1), 16); if (!(iv >= 0 && iv <= 16777215)) return null; return [ (iv & 16711680) >> 16, (iv & 65280) >> 8, iv & 255, 1 ]; } return null; } var op = str.indexOf('('), ep = str.indexOf(')'); if (op !== -1 && ep + 1 === str.length) { var fname = str.substr(0, op); var params = str.substr(op + 1, ep - (op + 1)).split(','); var alpha = 1; switch (fname) { case 'rgba': if (params.length !== 4) return null; alpha = parse_css_float(params.pop()); case 'rgb': if (params.length !== 3) return null; return [ parse_css_int(params[0]), parse_css_int(params[1]), parse_css_int(params[2]), alpha ]; case 'hsla': if (params.length !== 4) return null; alpha = parse_css_float(params.pop()); case 'hsl': if (params.length !== 3) return null; var h = (parseFloat(params[0]) % 360 + 360) % 360 / 360; var s = parse_css_float(params[1]); var l = parse_css_float(params[2]); var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; var m1 = l * 2 - m2; return [ clamp_css_byte(css_hue_to_rgb(m1, m2, h + 1 / 3) * 255), clamp_css_byte(css_hue_to_rgb(m1, m2, h) * 255), clamp_css_byte(css_hue_to_rgb(m1, m2, h - 1 / 3) * 255), alpha ]; default: return null; } } return null; } try { exports.parseCSSColor = parseCSSColor; } catch (e) { } }); var csscolorparser_1 = csscolorparser.parseCSSColor; class Color { constructor(r, g, b, a = 1) { this.r = r; this.g = g; this.b = b; this.a = a; } static parse(input) { if (!input) { return undefined; } if (input instanceof Color) { return input; } if (typeof input !== 'string') { return undefined; } const rgba = csscolorparser_1(input); if (!rgba) { return undefined; } return new Color(rgba[0] / 255 * rgba[3], rgba[1] / 255 * rgba[3], rgba[2] / 255 * rgba[3], rgba[3]); } toString() { const [r, g, b, a] = this.toArray(); return `rgba(${ Math.round(r) },${ Math.round(g) },${ Math.round(b) },${ a })`; } toArray() { const {r, g, b, a} = this; return a === 0 ? [ 0, 0, 0, 0 ] : [ r * 255 / a, g * 255 / a, b * 255 / a, a ]; } } Color.black = new Color(0, 0, 0, 1); Color.white = new Color(1, 1, 1, 1); Color.transparent = new Color(0, 0, 0, 0); Color.red = new Color(1, 0, 0, 1); Color.blue = new Color(0, 0, 1, 1); class Collator { constructor(caseSensitive, diacriticSensitive, locale) { if (caseSensitive) this.sensitivity = diacriticSensitive ? 'variant' : 'case'; else this.sensitivity = diacriticSensitive ? 'accent' : 'base'; this.locale = locale; this.collator = new Intl.Collator(this.locale ? this.locale : [], { sensitivity: this.sensitivity, usage: 'search' }); } compare(lhs, rhs) { return this.collator.compare(lhs, rhs); } resolvedLocale() { return new Intl.Collator(this.locale ? this.locale : []).resolvedOptions().locale; } } class FormattedSection { constructor(text, image, scale, fontStack, textColor) { this.text = text; this.image = image; this.scale = scale; this.fontStack = fontStack; this.textColor = textColor; } } class Formatted { constructor(sections) { this.sections = sections; } static fromString(unformatted) { return new Formatted([new FormattedSection(unformatted, null, null, null, null)]); } isEmpty() { if (this.sections.length === 0) return true; return !this.sections.some(section => section.text.length !== 0 || section.image && section.image.name.length !== 0); } static factory(text) { if (text instanceof Formatted) { return text; } else { return Formatted.fromString(text); } } toString() { if (this.sections.length === 0) return ''; return this.sections.map(section => section.text).join(''); } serialize() { const serialized = ['format']; for (const section of this.sections) { if (section.image) { serialized.push([ 'image', section.image.name ]); continue; } serialized.push(section.text); const options = {}; if (section.fontStack) { options['text-font'] = [ 'literal', section.fontStack.split(',') ]; } if (section.scale) { options['font-scale'] = section.scale; } if (section.textColor) { options['text-color'] = ['rgba'].concat(section.textColor.toArray()); } serialized.push(options); } return serialized; } } class ResolvedImage { constructor(options) { this.name = options.name; this.available = options.available; } toString() { return this.name; } static fromString(name) { if (!name) return null; return new ResolvedImage({ name, available: false }); } serialize() { return [ 'image', this.name ]; } } function validateRGBA(r, g, b, a) { if (!(typeof r === 'number' && r >= 0 && r <= 255 && typeof g === 'number' && g >= 0 && g <= 255 && typeof b === 'number' && b >= 0 && b <= 255)) { const value = typeof a === 'number' ? [ r, g, b, a ] : [ r, g, b ]; return `Invalid rgba value [${ value.join(', ') }]: 'r', 'g', and 'b' must be between 0 and 255.`; } if (!(typeof a === 'undefined' || typeof a === 'number' && a >= 0 && a <= 1)) { return `Invalid rgba value [${ [ r, g, b, a ].join(', ') }]: 'a' must be between 0 and 1.`; } return null; } function isValue(mixed) { if (mixed === null) { return true; } else if (typeof mixed === 'string') { return true; } else if (typeof mixed === 'boolean') { return true; } else if (typeof mixed === 'number') { return true; } else if (mixed instanceof Color) { return true; } else if (mixed instanceof Collator) { return true; } else if (mixed instanceof Formatted) { return true; } else if (mixed instanceof ResolvedImage) { return true; } else if (Array.isArray(mixed)) { for (const item of mixed) { if (!isValue(item)) { return false; } } return true; } else if (typeof mixed === 'object') { for (const key in mixed) { if (!isValue(mixed[key])) { return false; } } return true; } else { return false; } } function typeOf(value) { if (value === null) { return NullType; } else if (typeof value === 'string') { return StringType; } else if (typeof value === 'boolean') { return BooleanType; } else if (typeof value === 'number') { return NumberType; } else if (value instanceof Color) { return ColorType; } else if (value instanceof Collator) { return CollatorType; } else if (value instanceof Formatted) { return FormattedType; } else if (value instanceof ResolvedImage) { return ResolvedImageType; } else if (Array.isArray(value)) { const length = value.length; let itemType; for (const item of value) { const t = typeOf(item); if (!itemType) { itemType = t; } else if (itemType === t) { continue; } else { itemType = ValueType; break; } } return array(itemType || ValueType, length); } else { return ObjectType; } } function toString$1(value) { const type = typeof value; if (value === null) { return ''; } else if (type === 'string' || type === 'number' || type === 'boolean') { return String(value); } else if (value instanceof Color || value instanceof Formatted || value instanceof ResolvedImage) { return value.toString(); } else { return JSON.stringify(value); } } class Literal { constructor(type, value) { this.type = type; this.value = value; } static parse(args, context) { if (args.length !== 2) return context.error(`'literal' expression requires exactly one argument, but found ${ args.length - 1 } instead.`); if (!isValue(args[1])) return context.error(`invalid value`); const value = args[1]; let type = typeOf(value); const expected = context.expectedType; if (type.kind === 'array' && type.N === 0 && expected && expected.kind === 'array' && (typeof expected.N !== 'number' || expected.N === 0)) { type = expected; } return new Literal(type, value); } evaluate() { return this.value; } eachChild() { } outputDefined() { return true; } serialize() { if (this.type.kind === 'array' || this.type.kind === 'object') { return [ 'literal', this.value ]; } else if (this.value instanceof Color) { return ['rgba'].concat(this.value.toArray()); } else if (this.value instanceof Formatted) { return this.value.serialize(); } else { return this.value; } } } class RuntimeError { constructor(message) { this.name = 'ExpressionEvaluationError'; this.message = message; } toJSON() { return this.message; } } const types = { string: StringType, number: NumberType, boolean: BooleanType, object: ObjectType }; class Assertion { constructor(type, args) { this.type = type; this.args = args; } static parse(args, context) { if (args.length < 2) return context.error(`Expected at least one argument.`); let i = 1; let type; const name = args[0]; if (name === 'array') { let itemType; if (args.length > 2) { const type = args[1]; if (typeof type !== 'string' || !(type in types) || type === 'object') return context.error('The item type argument of "array" must be one of string, number, boolean', 1); itemType = types[type]; i++; } else { itemType = ValueType; } let N; if (args.length > 3) { if (args[2] !== null && (typeof args[2] !== 'number' || args[2] < 0 || args[2] !== Math.floor(args[2]))) { return context.error('The length argument to "array" must be a positive integer literal', 2); } N = args[2]; i++; } type = array(itemType, N); } else { type = types[name]; } const parsed = []; for (; i < args.length; i++) { const input = context.parse(args[i], i, ValueType); if (!input) return null; parsed.push(input); } return new Assertion(type, parsed); } evaluate(ctx) { for (let i = 0; i < this.args.length; i++) { const value = this.args[i].evaluate(ctx); const error = checkSubtype(this.type, typeOf(value)); if (!error) { return value; } else if (i === this.args.length - 1) { throw new RuntimeError(`Expected value to be of type ${ toString(this.type) }, but found ${ toString(typeOf(value)) } instead.`); } } return null; } eachChild(fn) { this.args.forEach(fn); } outputDefined() { return this.args.every(arg => arg.outputDefined()); } serialize() { const type = this.type; const serialized = [type.kind]; if (type.kind === 'array') { const itemType = type.itemType; if (itemType.kind === 'string' || itemType.kind === 'number' || itemType.kind === 'boolean') { serialized.push(itemType.kind); const N = type.N; if (typeof N === 'number' || this.args.length > 1) { serialized.push(N); } } } return serialized.concat(this.args.map(arg => arg.serialize())); } } class FormatExpression { constructor(sections) { this.type = FormattedType; this.sections = sections; } static parse(args, context) { if (args.length < 2) { return context.error(`Expected at least one argument.`); } const firstArg = args[1]; if (!Array.isArray(firstArg) && typeof firstArg === 'object') { return context.error(`First argument must be an image or text section.`); } const sections = []; let nextTokenMayBeObject = false; for (let i = 1; i <= args.length - 1; ++i) { const arg = args[i]; if (nextTokenMayBeObject && typeof arg === 'object' && !Array.isArray(arg)) { nextTokenMayBeObject = false; let scale = null; if (arg['font-scale']) { scale = context.parse(arg['font-scale'], 1, NumberType); if (!scale) return null; } let font = null; if (arg['text-font']) { font = context.parse(arg['text-font'], 1, array(StringType)); if (!font) return null; } let textColor = null; if (arg['text-color']) { textColor = context.parse(arg['text-color'], 1, ColorType); if (!textColor) return null; } const lastExpression = sections[sections.length - 1]; lastExpression.scale = scale; lastExpression.font = font; lastExpression.textColor = textColor; } else { const content = context.parse(args[i], 1, ValueType); if (!content) return null; const kind = content.type.kind; if (kind !== 'string' && kind !== 'value' && kind !== 'null' && kind !== 'resolvedImage') return context.error(`Formatted text type must be 'string', 'value', 'image' or 'null'.`); nextTokenMayBeObject = true; sections.push({ content, scale: null, font: null, textColor: null }); } } return new FormatExpression(sections); } evaluate(ctx) { const evaluateSection = section => { const evaluatedContent = section.content.evaluate(ctx); if (typeOf(evaluatedContent) === ResolvedImageType) { return new FormattedSection('', evaluatedContent, null, null, null); } return new FormattedSection(toString$1(evaluatedContent), null, section.scale ? section.scale.evaluate(ctx) : null, section.font ? section.font.evaluate(ctx).join(',') : null, section.textColor ? section.textColor.evaluate(ctx) : null); }; return new Formatted(this.sections.map(evaluateSection)); } eachChild(fn) { for (const section of this.sections) { fn(section.content); if (section.scale) { fn(section.scale); } if (section.font) { fn(section.font); } if (section.textColor) { fn(section.textColor); } } } outputDefined() { return false; } serialize() { const serialized = ['format']; for (const section of this.sections) { serialized.push(section.content.serialize()); const options = {}; if (section.scale) { options['font-scale'] = section.scale.serialize(); } if (section.font) { options['text-font'] = section.font.serialize(); } if (section.textColor) { options['text-color'] = section.textColor.serialize(); } serialized.push(options); } return serialized; } } class ImageExpression { constructor(input) { this.type = ResolvedImageType; this.input = input; } static parse(args, context) { if (args.length !== 2) { return context.error(`Expected two arguments.`); } const name = context.parse(args[1], 1, StringType); if (!name) return context.error(`No image name provided.`); return new ImageExpression(name); } evaluate(ctx) { const evaluatedImageName = this.input.evaluate(ctx); const value = ResolvedImage.fromString(evaluatedImageName); if (value && ctx.availableImages) value.available = ctx.availableImages.indexOf(evaluatedImageName) > -1; return value; } eachChild(fn) { fn(this.input); } outputDefined() { return false; } serialize() { return [ 'image', this.input.serialize() ]; } } const types$1 = { 'to-boolean': BooleanType, 'to-color': ColorType, 'to-number': NumberType, 'to-string': StringType }; class Coercion { constructor(type, args) { this.type = type; this.args = args; } static parse(args, context) { if (args.length < 2) return context.error(`Expected at least one argument.`); const name = args[0]; if ((name === 'to-boolean' || name === 'to-string') && args.length !== 2) return context.error(`Expected one argument.`); const type = types$1[name]; const parsed = []; for (let i = 1; i < args.length; i++) { const input = context.parse(args[i], i, ValueType); if (!input) return null; parsed.push(input); } return new Coercion(type, parsed); } evaluate(ctx) { if (this.type.kind === 'boolean') { return Boolean(this.args[0].evaluate(ctx)); } else if (this.type.kind === 'color') { let input; let error; for (const arg of this.args) { input = arg.evaluate(ctx); error = null; if (input instanceof Color) { return input; } else if (typeof input === 'string') { const c = ctx.parseColor(input); if (c) return c; } else if (Array.isArray(input)) { if (input.length < 3 || input.length > 4) { error = `Invalid rbga value ${ JSON.stringify(input) }: expected an array containing either three or four numeric values.`; } else { error = validateRGBA(input[0], input[1], input[2], input[3]); } if (!error) { return new Color(input[0] / 255, input[1] / 255, input[2] / 255, input[3]); } } } throw new RuntimeError(error || `Could not parse color from value '${ typeof input === 'string' ? input : String(JSON.stringify(input)) }'`); } else if (this.type.kind === 'number') { let value = null; for (const arg of this.args) { value = arg.evaluate(ctx); if (value === null) return 0; const num = Number(value); if (isNaN(num)) continue; return num; } throw new RuntimeError(`Could not convert ${ JSON.stringify(value) } to number.`); } else if (this.type.kind === 'formatted') { return Formatted.fromString(toString$1(this.args[0].evaluate(ctx))); } else if (this.type.kind === 'resolvedImage') { return ResolvedImage.fromString(toString$1(this.args[0].evaluate(ctx))); } else { return toString$1(this.args[0].evaluate(ctx)); } } eachChild(fn) { this.args.forEach(fn); } outputDefined() { return this.args.every(arg => arg.outputDefined()); } serialize() { if (this.type.kind === 'formatted') { return new FormatExpression([{ content: this.args[0], scale: null, font: null, textColor: null }]).serialize(); } if (this.type.kind === 'resolvedImage') { return new ImageExpression(this.args[0]).serialize(); } const serialized = [`to-${ this.type.kind }`]; this.eachChild(child => { serialized.push(child.serialize()); }); return serialized; } } const geometryTypes = [ 'Unknown', 'Point', 'LineString', 'Polygon' ]; class EvaluationContext { constructor() { this.globals = null; this.feature = null; this.featureState = null; this.formattedSection = null; this._parseColorCache = {}; this.availableImages = null; this.canonical = null; } id() { return this.feature && 'id' in this.feature ? this.feature.id : null; } geometryType() { return this.feature ? typeof this.feature.type === 'number' ? geometryTypes[this.feature.type] : this.feature.type : null; } geometry() { return this.feature && 'geometry' in this.feature ? this.feature.geometry : null; } canonicalID() { return this.canonical; } properties() { return this.feature && this.feature.properties || {}; } parseColor(input) { let cached = this._parseColorCache[input]; if (!cached) { cached = this._parseColorCache[input] = Color.parse(input); } return cached; } } class CompoundExpression { constructor(name, type, evaluate, args) { this.name = name; this.type = type; this._evaluate = evaluate; this.args = args; } evaluate(ctx) { return this._evaluate(ctx, this.args); } eachChild(fn) { this.args.forEach(fn); } outputDefined() { return false; } serialize() { return [this.name].concat(this.args.map(arg => arg.serialize())); } static parse(args, context) { const op = args[0]; const definition = CompoundExpression.definitions[op]; if (!definition) { return context.error(`Unknown expression "${ op }". If you wanted a literal array, use ["literal", [...]].`, 0); } const type = Array.isArray(definition) ? definition[0] : definition.type; const availableOverloads = Array.isArray(definition) ? [[ definition[1], definition[2] ]] : definition.overloads; const overloads = availableOverloads.filter(([signature]) => !Array.isArray(signature) || signature.length === args.length - 1); let signatureContext = null; for (const [params, evaluate] of overloads) { signatureContext = new ParsingContext(context.registry, context.path, null, context.scope); const parsedArgs = []; let argParseFailed = false; for (let i = 1; i < args.length; i++) { const arg = args[i]; const expectedType = Array.isArray(params) ? params[i - 1] : params.type; const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType); if (!parsed) { argParseFailed = true; break; } parsedArgs.push(parsed); } if (argParseFailed) { continue; } if (Array.isArray(params)) { if (params.length !== parsedArgs.length) { signatureContext.error(`Expected ${ params.length } arguments, but found ${ parsedArgs.length } instead.`); continue; } } for (let i = 0; i < parsedArgs.length; i++) { const expected = Array.isArray(params) ? params[i] : params.type; const arg = parsedArgs[i]; signatureContext.concat(i + 1).checkSubtype(expected, arg.type); } if (signatureContext.errors.length === 0) { return new CompoundExpression(op, type, evaluate, parsedArgs); } } if (overloads.length === 1) { context.errors.push(...signatureContext.errors); } else { const expected = overloads.length ? overloads : availableOverloads; const signatures = expected.map(([params]) => stringifySignature(params)).join(' | '); const actualTypes = []; for (let i = 1; i < args.length; i++) { const parsed = context.parse(args[i], 1 + actualTypes.length); if (!parsed) return null; actualTypes.push(toString(parsed.type)); } context.error(`Expected arguments of type ${ signatures }, but found (${ actualTypes.join(', ') }) instead.`); } return null; } static register(registry, definitions) { CompoundExpression.definitions = definitions; for (const name in definitions) { registry[name] = CompoundExpression; } } } function stringifySignature(signature) { if (Array.isArray(signature)) { return `(${ signature.map(toString).join(', ') })`; } else { return `(${ toString(signature.type) }...)`; } } class CollatorExpression { constructor(caseSensitive, diacriticSensitive, locale) { this.type = CollatorType; this.locale = locale; this.caseSensitive = caseSensitive; this.diacriticSensitive = diacriticSensitive; } static parse(args, context) { if (args.length !== 2) return context.error(`Expected one argument.`); const options = args[1]; if (typeof options !== 'object' || Array.isArray(options)) return context.error(`Collator options argument must be an object.`); const caseSensitive = context.parse(options['case-sensitive'] === undefined ? false : options['case-sensitive'], 1, BooleanType); if (!caseSensitive) return null; const diacriticSensitive = context.parse(options['diacritic-sensitive'] === undefined ? false : options['diacritic-sensitive'], 1, BooleanType); if (!diacriticSensitive) return null; let locale = null; if (options['locale']) { locale = context.parse(options['locale'], 1, StringType); if (!locale) return null; } return new CollatorExpression(caseSensitive, diacriticSensitive, locale); } evaluate(ctx) { return new Collator(this.caseSensitive.evaluate(ctx), this.diacriticSensitive.evaluate(ctx), this.locale ? this.locale.evaluate(ctx) : null); } eachChild(fn) { fn(this.caseSensitive); fn(this.diacriticSensitive); if (this.locale) { fn(this.locale); } } outputDefined() { return false; } serialize() { const options = {}; options['case-sensitive'] = this.caseSensitive.serialize(); options['diacritic-sensitive'] = this.diacriticSensitive.serialize(); if (this.locale) { options['locale'] = this.locale.serialize(); } return [ 'collator', options ]; } } const EXTENT = 8192; function updateBBox(bbox, coord) { bbox[0] = Math.min(bbox[0], coord[0]); bbox[1] = Math.min(bbox[1], coord[1]); bbox[2] = Math.max(bbox[2], coord[0]); bbox[3] = Math.max(bbox[3], coord[1]); } function mercatorXfromLng(lng) { return (180 + lng) / 360; } function mercatorYfromLat(lat) { return (180 - 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360))) / 360; } function boxWithinBox(bbox1, bbox2) { if (bbox1[0] <= bbox2[0]) return false; if (bbox1[2] >= bbox2[2]) return false; if (bbox1[1] <= bbox2[1]) return false; if (bbox1[3] >= bbox2[3]) return false; return true; } function getTileCoordinates(p, canonical) { const x = mercatorXfromLng(p[0]); const y = mercatorYfromLat(p[1]); const tilesAtZoom = Math.pow(2, canonical.z); return [ Math.round(x * tilesAtZoom * EXTENT), Math.round(y * tilesAtZoom * EXTENT) ]; } function onBoundary(p, p1, p2) { const x1 = p[0] - p1[0]; const y1 = p[1] - p1[1]; const x2 = p[0] - p2[0]; const y2 = p[1] - p2[1]; return x1 * y2 - x2 * y1 === 0 && x1 * x2 <= 0 && y1 * y2 <= 0; } function rayIntersect(p, p1, p2) { return p1[1] > p[1] !== p2[1] > p[1] && p[0] < (p2[0] - p1[0]) * (p[1] - p1[1]) / (p2[1] - p1[1]) + p1[0]; } function pointWithinPolygon(point, rings) { let inside = false; for (let i = 0, len = rings.length; i < len; i++) { const ring = rings[i]; for (let j = 0, len2 = ring.length; j < len2 - 1; j++) { if (onBoundary(point, ring[j], ring[j + 1])) return false; if (rayIntersect(point, ring[j], ring[j + 1])) inside = !inside; } } return inside; } function pointWithinPolygons(point, polygons) { for (let i = 0; i < polygons.length; i++) { if (pointWithinPolygon(point, polygons[i])) return true; } return false; } function perp(v1, v2) { return v1[0] * v2[1] - v1[1] * v2[0]; } function twoSided(p1, p2, q1, q2) { const x1 = p1[0] - q1[0]; const y1 = p1[1] - q1[1]; const x2 = p2[0] - q1[0]; const y2 = p2[1] - q1[1]; const x3 = q2[0] - q1[0]; const y3 = q2[1] - q1[1]; const det1 = x1 * y3 - x3 * y1; const det2 = x2 * y3 - x3 * y2; if (det1 > 0 && det2 < 0 || det1 < 0 && det2 > 0) return true; return false; } function lineIntersectLine(a, b, c, d) { const vectorP = [ b[0] - a[0], b[1] - a[1] ]; const vectorQ = [ d[0] - c[0], d[1] - c[1] ]; if (perp(vectorQ, vectorP) === 0) return false; if (twoSided(a, b, c, d) && twoSided(c, d, a, b)) return true; return false; } function lineIntersectPolygon(p1, p2, polygon) { for (const ring of polygon) { for (let j = 0; j < ring.length - 1; ++j) { if (lineIntersectLine(p1, p2, ring[j], ring[j + 1])) { return true; } } } return false; } function lineStringWithinPolygon(line, polygon) { for (let i = 0; i < line.length; ++i) { if (!pointWithinPolygon(line[i], polygon)) { return false; } } for (let i = 0; i < line.length - 1; ++i) { if (lineIntersectPolygon(line[i], line[i + 1], polygon)) { return false; } } return true; } function lineStringWithinPolygons(line, polygons) { for (let i = 0; i < polygons.length; i++) { if (lineStringWithinPolygon(line, polygons[i])) return true; } return false; } function getTilePolygon(coordinates, bbox, canonical) { const polygon = []; for (let i = 0; i < coordinates.length; i++) { const ring = []; for (let j = 0; j < coordinates[i].length; j++) { const coord = getTileCoordinates(coordinates[i][j], canonical); updateBBox(bbox, coord); ring.push(coord); } polygon.push(ring); } return polygon; } function getTilePolygons(coordinates, bbox, canonical) { const polygons = []; for (let i = 0; i < coordinates.length; i++) { const polygon = getTilePolygon(coordinates[i], bbox, canonical); polygons.push(polygon); } return polygons; } function updatePoint(p, bbox, polyBBox, worldSize) { if (p[0] < polyBBox[0] || p[0] > polyBBox[2]) { const halfWorldSize = worldSize * 0.5; let shift = p[0] - polyBBox[0] > halfWorldSize ? -worldSize : polyBBox[0] - p[0] > halfWorldSize ? worldSize : 0; if (shift === 0) { shift = p[0] - polyBBox[2] > halfWorldSize ? -worldSize : polyBBox[2] - p[0] > halfWorldSize ? worldSize : 0; } p[0] += shift; } updateBBox(bbox, p); } function resetBBox(bbox) { bbox[0] = bbox[1] = Infinity; bbox[2] = bbox[3] = -Infinity; } function getTilePoints(geometry, pointBBox, polyBBox, canonical) { const worldSize = Math.pow(2, canonical.z) * EXTENT; const shifts = [ canonical.x * EXTENT, canonical.y * EXTENT ]; const tilePoints = []; for (const points of geometry) { for (const point of points) { const p = [ point.x + shifts[0], point.y + shifts[1] ]; updatePoint(p, pointBBox, polyBBox, worldSize); tilePoints.push(p); } } return tilePoints; } function getTileLines(geometry, lineBBox, polyBBox, canonical) { const worldSize = Math.pow(2, canonical.z) * EXTENT; const shifts = [ canonical.x * EXTENT, canonical.y * EXTENT ]; const tileLines = []; for (const line of geometry) { const tileLine = []; for (const point of line) { const p = [ point.x + shifts[0], point.y + shifts[1] ]; updateBBox(lineBBox, p); tileLine.push(p); } tileLines.push(tileLine); } if (lineBBox[2] - lineBBox[0] <= worldSize / 2) { resetBBox(lineBBox); for (const line of tileLines) { for (const p of line) { updatePoint(p, lineBBox, polyBBox, worldSize); } } } return tileLines; } function pointsWithinPolygons(ctx, polygonGeometry) { const pointBBox = [ Infinity, Infinity, -Infinity, -Infinity ]; const polyBBox = [ Infinity, Infinity, -Infinity, -Infinity ]; const canonical = ctx.canonicalID(); if (polygonGeometry.type === 'Polygon') { const tilePolygon = getTilePolygon(polygonGeometry.coordinates, polyBBox, canonical); const tilePoints = getTilePoints(ctx.geometry(), pointBBox, polyBBox, canonical); if (!boxWithinBox(pointBBox, polyBBox)) return false; for (const point of tilePoints) { if (!pointWithinPolygon(point, tilePolygon)) return false; } } if (polygonGeometry.type === 'MultiPolygon') { const tilePolygons = getTilePolygons(polygonGeometry.coordinates, polyBBox, canonical); const tilePoints = getTilePoints(ctx.geometry(), pointBBox, polyBBox, canonical); if (!boxWithinBox(pointBBox, polyBBox)) return false; for (const point of tilePoints) { if (!pointWithinPolygons(point, tilePolygons)) return false; } } return true; } function linesWithinPolygons(ctx, polygonGeometry) { const lineBBox = [ Infinity, Infinity, -Infinity, -Infinity ]; const polyBBox = [ Infinity, Infinity, -Infinity, -Infinity ]; const canonical = ctx.canonicalID(); if (polygonGeometry.type === 'Polygon') { const tilePolygon = getTilePolygon(polygonGeometry.coordinates, polyBBox, canonical); const tileLines = getTileLines(ctx.geometry(), lineBBox, polyBBox, canonical); if (!boxWithinBox(lineBBox, polyBBox)) return false; for (const line of tileLines) { if (!lineStringWithinPolygon(line, tilePolygon)) return false; } } if (polygonGeometry.type === 'MultiPolygon') { const tilePolygons = getTilePolygons(polygonGeometry.coordinates, polyBBox, canonical); const tileLines = getTileLines(ctx.geometry(), lineBBox, polyBBox, canonical); if (!boxWithinBox(lineBBox, polyBBox)) return false; for (const line of tileLines) { if (!lineStringWithinPolygons(line, tilePolygons)) return false; } } return true; } class Within { constructor(geojson, geometries) { this.type = BooleanType; this.geojson = geojson; this.geometries = geometries; } static parse(args, context) { if (args.length !== 2) return context.error(`'within' expression requires exactly one argument, but found ${ args.length - 1 } instead.`); if (isValue(args[1])) { const geojson = args[1]; if (geojson.type === 'FeatureCollection') { for (let i = 0; i < geojson.features.length; ++i) { const type = geojson.features[i].geometry.type; if (type === 'Polygon' || type === 'MultiPolygon') { return new Within(geojson, geojson.features[i].geometry); } } } else if (geojson.type === 'Feature') { const type = geojson.geometry.type; if (type === 'Polygon' || type === 'MultiPolygon') { return new Within(geojson, geojson.geometry); } } else if (geojson.type === 'Polygon' || geojson.type === 'MultiPolygon') { return new Within(geojson, geojson); } } return context.error(`'within' expression requires valid geojson object that contains polygon geometry type.`); } evaluate(ctx) { if (ctx.geometry() != null && ctx.canonicalID() != null) { if (ctx.geometryType() === 'Point') { return pointsWithinPolygons(ctx, this.geometries); } else if (ctx.geometryType() === 'LineString') { return linesWithinPolygons(ctx, this.geometries); } } return false; } eachChild() { } outputDefined() { return true; } serialize() { return [ 'within', this.geojson ]; } } function isFeatureConstant(e) { if (e instanceof CompoundExpression) { if (e.name === 'get' && e.args.length === 1) { return false; } else if (e.name === 'feature-state') { return false; } else if (e.name === 'has' && e.args.length === 1) { return false; } else if (e.name === 'properties' || e.name === 'geometry-type' || e.name === 'id') { return false; } else if (/^filter-/.test(e.name)) { return false; } } if (e instanceof Within) { return false; } let result = true; e.eachChild(arg => { if (result && !isFeatureConstant(arg)) { result = false; } }); return result; } function isStateConstant(e) { if (e instanceof CompoundExpression) { if (e.name === 'feature-state') { return false; } } let result = true; e.eachChild(arg => { if (result && !isStateConstant(arg)) { result = false; } }); return result; } function isGlobalPropertyConstant(e, properties) { if (e instanceof CompoundExpression && properties.indexOf(e.name) >= 0) { return false; } let result = true; e.eachChild(arg => { if (result && !isGlobalPropertyConstant(arg, properties)) { result = false; } }); return result; } class Var { constructor(name, boundExpression) { this.type = boundExpression.type; this.name = name; this.boundExpression = boundExpression; } static parse(args, context) { if (args.length !== 2 || typeof args[1] !== 'string') return context.error(`'var' expression requires exactly one string literal argument.`); const name = args[1]; if (!context.scope.has(name)) { return context.error(`Unknown variable "${ name }". Make sure "${ name }" has been bound in an enclosing "let" expression before using it.`, 1); } return new Var(name, context.scope.get(name)); } evaluate(ctx) { return this.boundExpression.evaluate(ctx); } eachChild() { } outputDefined() { return false; } serialize() { return [ 'var', this.name ]; } } class ParsingContext { constructor(registry, path = [], expectedType, scope = new Scope(), errors = []) { this.registry = registry; this.path = path; this.key = path.map(part => `[${ part }]`).join(''); this.scope = scope; this.errors = errors; this.expectedType = expectedType; } parse(expr, index, expectedType, bindings, options = {}) { if (index) { return this.concat(index, expectedType, bindings)._parse(expr, options); } return this._parse(expr, options); } _parse(expr, options) { if (expr === null || typeof expr === 'string' || typeof expr === 'boolean' || typeof expr === 'number') { expr = [ 'literal', expr ]; } function annotate(parsed, type, typeAnnotation) { if (typeAnnotation === 'assert') { return new Assertion(type, [parsed]); } else if (typeAnnotation === 'coerce') { return new Coercion(type, [parsed]); } else { return parsed; } } if (Array.isArray(expr)) { if (expr.length === 0) { return this.error(`Expected an array with at least one element. If you wanted a literal array, use ["literal", []].`); } const op = expr[0]; if (typeof op !== 'string') { this.error(`Expression name must be a string, but found ${ typeof op } instead. If you wanted a literal array, use ["literal", [...]].`, 0); return null; } const Expr = this.registry[op]; if (Expr) { let parsed = Expr.parse(expr, this); if (!parsed) return null; if (this.expectedType) { const expected = this.expectedType; const actual = parsed.type; if ((expected.kind === 'string' || expected.kind === 'number' || expected.kind === 'boolean' || expected.kind === 'object' || expected.kind === 'array') && actual.kind === 'value') { parsed = annotate(parsed, expected, options.typeAnnotation || 'assert'); } else if ((expected.kind === 'color' || expected.kind === 'formatted' || expected.kind === 'resolvedImage') && (actual.kind === 'value' || actual.kind === 'string')) { parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce'); } else if (this.checkSubtype(expected, actual)) { return null; } } if (!(parsed instanceof Literal) && parsed.type.kind !== 'resolvedImage' && isConstant(parsed)) { const ec = new EvaluationContext(); try { parsed = new Literal(parsed.type, parsed.evaluate(ec)); } catch (e) { this.error(e.message); return null; } } return parsed; } return this.error(`Unknown expression "${ op }". If you wanted a literal array, use ["literal", [...]].`, 0); } else if (typeof expr === 'undefined') { return this.error(`'undefined' value invalid. Use null instead.`); } else if (typeof expr === 'object') { return this.error(`Bare objects invalid. Use ["literal", {...}] instead.`); } else { return this.error(`Expected an array, but found ${ typeof expr } instead.`); } } concat(index, expectedType, bindings) { const path = typeof index === 'number' ? this.path.concat(index) : this.path; const scope = bindings ? this.scope.concat(bindings) : this.scope; return new ParsingContext(this.registry, path, expectedType || null, scope, this.errors); } error(error, ...keys) { const key = `${ this.key }${ keys.map(k => `[${ k }]`).join('') }`; this.errors.push(new ParsingError(key, error)); } checkSubtype(expected, t) { const error = checkSubtype(expected, t); if (error) this.error(error); return error; } } function isConstant(expression) { if (expression instanceof Var) { return isConstant(expression.boundExpression); } else if (expression instanceof CompoundExpression && expression.name === 'error') { return false; } else if (expression instanceof CollatorExpression) { return false; } else if (expression instanceof Within) { return false; } const isTypeAnnotation = expression instanceof Coercion || expression instanceof Assertion; let childrenConstant = true; expression.eachChild(child => { if (isTypeAnnotation) { childrenConstant = childrenConstant && isConstant(child); } else { childrenConstant = childrenConstant && child instanceof Literal; } }); if (!childrenConstant) { return false; } return isFeatureConstant(expression) && isGlobalPropertyConstant(expression, [ 'zoom', 'heatmap-density', 'line-progress', 'sky-radial-progress', 'accumulated', 'is-supported-script' ]); } function findStopLessThanOrEqualTo(stops, input) { const lastIndex = stops.length - 1; let lowerIndex = 0; let upperIndex = lastIndex; let currentIndex = 0; let currentValue, nextValue; while (lowerIndex <= upperIndex) { currentIndex = Math.floor((lowerIndex + upperIndex) / 2); currentValue = stops[currentIndex]; nextValue = stops[currentIndex + 1]; if (currentValue <= input) { if (currentIndex === lastIndex || input < nextValue) { return currentIndex; } lowerIndex = currentIndex + 1; } else if (currentValue > input) { upperIndex = currentIndex - 1; } else { throw new RuntimeError('Input is not a number.'); } } return 0; } class Step { constructor(type, input, stops) { this.type = type; this.input = input; this.labels = []; this.outputs = []; for (const [label, expression] of stops) { this.labels.push(label); this.outputs.push(expression); } } static parse(args, context) { if (args.length - 1 < 4) { return context.error(`Expected at least 4 arguments, but found only ${ args.length - 1 }.`); } if ((args.length - 1) % 2 !== 0) { return context.error(`Expected an even number of arguments.`); } const input = context.parse(args[1], 1, NumberType); if (!input) return null; const stops = []; let outputType = null; if (context.expectedType && context.expectedType.kind !== 'value') { outputType = context.expectedType; } for (let i = 1; i < args.length; i += 2) { const label = i === 1 ? -Infinity : args[i]; const value = args[i + 1]; const labelKey = i; const valueKey = i + 1; if (typeof label !== 'number') { return context.error('Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey); } if (stops.length && stops[stops.length - 1][0] >= label) { return context.error('Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.', labelKey); } const parsed = context.parse(value, valueKey, outputType); if (!parsed) return null; outputType = outputType || parsed.type; stops.push([ label, parsed ]); } return new Step(outputType, input, stops); } evaluate(ctx) { const labels = this.labels; const outputs = this.outputs; if (labels.length === 1) { return outputs[0].evaluate(ctx); } const value = this.input.evaluate(ctx); if (value <= labels[0]) { return outputs[0].evaluate(ctx); } const stopCount = labels.length; if (value >= labels[stopCount - 1]) { return outputs[stopCount - 1].evaluate(ctx); } const index = findStopLessThanOrEqualTo(labels, value); return outputs[index].evaluate(ctx); } eachChild(fn) { fn(this.input); for (const expression of this.outputs) { fn(expression); } } outputDefined() { return this.outputs.every(out => out.outputDefined()); } serialize() { const serialized = [ 'step', this.input.serialize() ]; for (let i = 0; i < this.labels.length; i++) { if (i > 0) { serialized.push(this.labels[i]); } serialized.push(this.outputs[i].serialize()); } return serialized; } } function number(a, b, t) { return a * (1 - t) + b * t; } function color(from, to, t) { return new Color(number(from.r, to.r, t), number(from.g, to.g, t), number(from.b, to.b, t), number(from.a, to.a, t)); } function array$1(from, to, t) { return from.map((d, i) => { return number(d, to[i], t); }); } var interpolate = /*#__PURE__*/Object.freeze({ __proto__: null, number: number, color: color, array: array$1 }); const Xn = 0.95047, Yn = 1, Zn = 1.08883, t0 = 4 / 29, t1 = 6 / 29, t2 = 3 * t1 * t1, t3 = t1 * t1 * t1, deg2rad = Math.PI / 180, rad2deg = 180 / Math.PI; function xyz2lab(t) { return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0; } function lab2xyz(t) { return t > t1 ? t * t * t : t2 * (t - t0); } function xyz2rgb(x) { return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055); } function rgb2xyz(x) { x /= 255; return x <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); } function rgbToLab(rgbColor) { const b = rgb2xyz(rgbColor.r), a = rgb2xyz(rgbColor.g), l = rgb2xyz(rgbColor.b), x = xyz2lab((0.4124564 * b + 0.3575761 * a + 0.1804375 * l) / Xn), y = xyz2lab((0.2126729 * b + 0.7151522 * a + 0.072175 * l) / Yn), z = xyz2lab((0.0193339 * b + 0.119192 * a + 0.9503041 * l) / Zn); return { l: 116 * y - 16, a: 500 * (x - y), b: 200 * (y - z), alpha: rgbColor.a }; } function labToRgb(labColor) { let y = (labColor.l + 16) / 116, x = isNaN(labColor.a) ? y : y + labColor.a / 500, z = isNaN(labColor.b) ? y : y - labColor.b / 200; y = Yn * lab2xyz(y); x = Xn * lab2xyz(x); z = Zn * lab2xyz(z); return new Color(xyz2rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z), xyz2rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z), xyz2rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z), labColor.alpha); } function interpolateLab(from, to, t) { return { l: number(from.l, to.l, t), a: number(from.a, to.a, t), b: number(from.b, to.b, t), alpha: number(from.alpha, to.alpha, t) }; } function rgbToHcl(rgbColor) { const {l, a, b} = rgbToLab(rgbColor); const h = Math.atan2(b, a) * rad2deg; return { h: h < 0 ? h + 360 : h, c: Math.sqrt(a * a + b * b), l, alpha: rgbColor.a }; } function hclToRgb(hclColor) { const h = hclColor.h * deg2rad, c = hclColor.c, l = hclColor.l; return labToRgb({ l, a: Math.cos(h) * c, b: Math.sin(h) * c, alpha: hclColor.alpha }); } function interpolateHue(a, b, t) { const d = b - a; return a + t * (d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d); } function interpolateHcl(from, to, t) { return { h: interpolateHue(from.h, to.h, t), c: number(from.c, to.c, t), l: number(from.l, to.l, t), alpha: number(from.alpha, to.alpha, t) }; } const lab = { forward: rgbToLab, reverse: labToRgb, interpolate: interpolateLab }; const hcl = { forward: rgbToHcl, reverse: hclToRgb, interpolate: interpolateHcl }; var colorSpaces = /*#__PURE__*/Object.freeze({ __proto__: null, lab: lab, hcl: hcl }); class Interpolate { constructor(type, operator, interpolation, input, stops) { this.type = type; this.operator = operator; this.interpolation = interpolation; this.input = input; this.labels = []; this.outputs = []; for (const [label, expression] of stops) { this.labels.push(label); this.outputs.push(expression); } } static interpolationFactor(interpolation, input, lower, upper) { let t = 0; if (interpolation.name === 'exponential') { t = exponentialInterpolation(input, interpolation.base, lower, upper); } else if (interpolation.name === 'linear') { t = exponentialInterpolation(input, 1, lower, upper); } else if (interpolation.name === 'cubic-bezier') { const c = interpolation.controlPoints; const ub = new unitbezier(c[0], c[1], c[2], c[3]); t = ub.solve(exponentialInterpolation(input, 1, lower, upper)); } return t; } static parse(args, context) { let [operator, interpolation, input, ...rest] = args; if (!Array.isArray(interpolation) || interpolation.length === 0) { return context.error(`Expected an interpolation type expression.`, 1); } if (interpolation[0] === 'linear') { interpolation = { name: 'linear' }; } else if (interpolation[0] === 'exponential') { const base = interpolation[1]; if (typeof base !== 'number') return context.error(`Exponential interpolation requires a numeric base.`, 1, 1); interpolation = { name: 'exponential', base }; } else if (interpolation[0] === 'cubic-bezier') { const controlPoints = interpolation.slice(1); if (controlPoints.length !== 4 || controlPoints.some(t => typeof t !== 'number' || t < 0 || t > 1)) { return context.error('Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.', 1); } interpolation = { name: 'cubic-bezier', controlPoints: controlPoints }; } else { return context.error(`Unknown interpolation type ${ String(interpolation[0]) }`, 1, 0); } if (args.length - 1 < 4) { return context.error(`Expected at least 4 arguments, but found only ${ args.length - 1 }.`); } if ((args.length - 1) % 2 !== 0) { return context.error(`Expected an even number of arguments.`); } input = context.parse(input, 2, NumberType); if (!input) return null; const stops = []; let outputType = null; if (operator === 'interpolate-hcl' || operator === 'interpolate-lab') { outputType = ColorType; } else if (context.expectedType && context.expectedType.kind !== 'value') { outputType = context.expectedType; } for (let i = 0; i < rest.length; i += 2) { const label = rest[i]; const value = rest[i + 1]; const labelKey = i + 3; const valueKey = i + 4; if (typeof label !== 'number') { return context.error('Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey); } if (stops.length && stops[stops.length - 1][0] >= label) { return context.error('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.', labelKey); } const parsed = context.parse(value, valueKey, outputType); if (!parsed) return null; outputType = outputType || parsed.type; stops.push([ label, parsed ]); } if (outputType.kind !== 'number' && outputType.kind !== 'color' && !(outputType.kind === 'array' && outputType.itemType.kind === 'number' && typeof outputType.N === 'number')) { return context.error(`Type ${ toString(outputType) } is not interpolatable.`); } return new Interpolate(outputType, operator, interpolation, input, stops); } evaluate(ctx) { const labels = this.labels; const outputs = this.outputs; if (labels.length === 1) { return outputs[0].evaluate(ctx); } const value = this.input.evaluate(ctx); if (value <= labels[0]) { return outputs[0].evaluate(ctx); } const stopCount = labels.length; if (value >= labels[stopCount - 1]) { return outputs[stopCount - 1].evaluate(ctx); } const index = findStopLessThanOrEqualTo(labels, value); const lower = labels[index]; const upper = labels[index + 1]; const t = Interpolate.interpolationFactor(this.interpolation, value, lower, upper); const outputLower = outputs[index].evaluate(ctx); const outputUpper = outputs[index + 1].evaluate(ctx); if (this.operator === 'interpolate') { return interpolate[this.type.kind.toLowerCase()](outputLower, outputUpper, t); } else if (this.operator === 'interpolate-hcl') { return hcl.reverse(hcl.interpolate(hcl.forward(outputLower), hcl.forward(outputUpper), t)); } else { return lab.reverse(lab.interpolate(lab.forward(outputLower), lab.forward(outputUpper), t)); } } eachChild(fn) { fn(this.input); for (const expression of this.outputs) { fn(expression); } } outputDefined() { return this.outputs.every(out => out.outputDefined()); } serialize() { let interpolation; if (this.interpolation.name === 'linear') { interpolation = ['linear']; } else if (this.interpolation.name === 'exponential') { if (this.interpolation.base === 1) { interpolation = ['linear']; } else { interpolation = [ 'exponential', this.interpolation.base ]; } } else { interpolation = ['cubic-bezier'].concat(this.interpolation.controlPoints); } const serialized = [ this.operator, interpolation, this.input.serialize() ]; for (let i = 0; i < this.labels.length; i++) { serialized.push(this.labels[i], this.outputs[i].serialize()); } return serialized; } } function exponentialInterpolation(input, base, lowerValue, upperValue) { const difference = upperValue - lowerValue; const progress = input - lowerValue; if (difference === 0) { return 0; } else if (base === 1) { return progress / difference; } else { return (Math.pow(base, progress) - 1) / (Math.pow(base, difference) - 1); } } class Coalesce { constructor(type, args) { this.type = type; this.args = args; } static parse(args, context) { if (args.length < 2) { return context.error('Expectected at least one argument.'); } let outputType = null; const expectedType = context.expectedType; if (expectedType && expectedType.kind !== 'value') { outputType = expectedType; } const parsedArgs = []; for (const arg of args.slice(1)) { const parsed = context.parse(arg, 1 + parsedArgs.length, outputType, undefined, { typeAnnotation: 'omit' }); if (!parsed) return null; outputType = outputType || parsed.type; parsedArgs.push(parsed); } const needsAnnotation = expectedType && parsedArgs.some(arg => checkSubtype(expectedType, arg.type)); return needsAnnotation ? new Coalesce(ValueType, parsedArgs) : new Coalesce(outputType, parsedArgs); } evaluate(ctx) { let result = null; let argCount = 0; let requestedImageName; for (const arg of this.args) { argCount++; result = arg.evaluate(ctx); if (result && result instanceof ResolvedImage && !result.available) { if (!requestedImageName) { requestedImageName = result.name; } result = null; if (argCount === this.args.length) { result = requestedImageName; } } if (result !== null) break; } return result; } eachChild(fn) { this.args.forEach(fn); } outputDefined() { return this.args.every(arg => arg.outputDefined()); } serialize() { const serialized = ['coalesce']; this.eachChild(child => { serialized.push(child.serialize()); }); return serialized; } } class Let { constructor(bindings, result) { this.type = result.type; this.bindings = [].concat(bindings); this.result = result; } evaluate(ctx) { return this.result.evaluate(ctx); } eachChild(fn) { for (const binding of this.bindings) { fn(binding[1]); } fn(this.result); } static parse(args, context) { if (args.length < 4) return context.error(`Expected at least 3 arguments, but found ${ args.length - 1 } instead.`); const bindings = []; for (let i = 1; i < args.length - 1; i += 2) { const name = args[i]; if (typeof name !== 'string') { return context.error(`Expected string, but found ${ typeof name } instead.`, i); } if (/[^a-zA-Z0-9_]/.test(name)) { return context.error(`Variable names must contain only alphanumeric characters or '_'.`, i); } const value = context.parse(args[i + 1], i + 1); if (!value) return null; bindings.push([ name, value ]); } const result = context.parse(args[args.length - 1], args.length - 1, context.expectedType, bindings); if (!result) return null; return new Let(bindings, result); } outputDefined() { return this.result.outputDefined(); } serialize() { const serialized = ['let']; for (const [name, expr] of this.bindings) { serialized.push(name, expr.serialize()); } serialized.push(this.result.serialize()); return serialized; } } class At { constructor(type, index, input) { this.type = type; this.index = index; this.input = input; } static parse(args, context) { if (args.length !== 3) return context.error(`Expected 2 arguments, but found ${ args.length - 1 } instead.`); const index = context.parse(args[1], 1, NumberType); const input = context.parse(args[2], 2, array(context.expectedType || ValueType)); if (!index || !input) return null; const t = input.type; return new At(t.itemType, index, input); } evaluate(ctx) { const index = this.index.evaluate(ctx); const array = this.input.evaluate(ctx); if (index < 0) { throw new RuntimeError(`Array index out of bounds: ${ index } < 0.`); } if (index >= array.length) { throw new RuntimeError(`Array index out of bounds: ${ index } > ${ array.length - 1 }.`); } if (index !== Math.floor(index)) { throw new RuntimeError(`Array index must be an integer, but found ${ index } instead.`); } return array[index]; } eachChild(fn) { fn(this.index); fn(this.input); } outputDefined() { return false; } serialize() { return [ 'at', this.index.serialize(), this.input.serialize() ]; } } class In { constructor(needle, haystack) { this.type = BooleanType; this.needle = needle; this.haystack = haystack; } static parse(args, context) { if (args.length !== 3) { return context.error(`Expected 2 arguments, but found ${ args.length - 1 } instead.`); } const needle = context.parse(args[1], 1, ValueType); const haystack = context.parse(args[2], 2, ValueType); if (!needle || !haystack) return null; if (!isValidType(needle.type, [ BooleanType, StringType, NumberType, NullType, ValueType ])) { return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${ toString(needle.type) } instead`); } return new In(needle, haystack); } evaluate(ctx) { const needle = this.needle.evaluate(ctx); const haystack = this.haystack.evaluate(ctx); if (!haystack) return false; if (!isValidNativeType(needle, [ 'boolean', 'string', 'number', 'null' ])) { throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${ toString(typeOf(needle)) } instead.`); } if (!isValidNativeType(haystack, [ 'string', 'array' ])) { throw new RuntimeError(`Expected second argument to be of type array or string, but found ${ toString(typeOf(haystack)) } instead.`); } return haystack.indexOf(needle) >= 0; } eachChild(fn) { fn(this.needle); fn(this.haystack); } outputDefined() { return true; } serialize() { return [ 'in', this.needle.serialize(), this.haystack.serialize() ]; } } class IndexOf { constructor(needle, haystack, fromIndex) { this.type = NumberType; this.needle = needle; this.haystack = haystack; this.fromIndex = fromIndex; } static parse(args, context) { if (args.length <= 2 || args.length >= 5) { return context.error(`Expected 3 or 4 arguments, but found ${ args.length - 1 } instead.`); } const needle = context.parse(args[1], 1, ValueType); const haystack = context.parse(args[2], 2, ValueType); if (!needle || !haystack) return null; if (!isValidType(needle.type, [ BooleanType, StringType, NumberType, NullType, ValueType ])) { return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${ toString(needle.type) } instead`); } if (args.length === 4) { const fromIndex = context.parse(args[3], 3, NumberType); if (!fromIndex) return null; return new IndexOf(needle, haystack, fromIndex); } else { return new IndexOf(needle, haystack); } } evaluate(ctx) { const needle = this.needle.evaluate(ctx); const haystack = this.haystack.evaluate(ctx); if (!isValidNativeType(needle, [ 'boolean', 'string', 'number', 'null' ])) { throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${ toString(typeOf(needle)) } instead.`); } if (!isValidNativeType(haystack, [ 'string', 'array' ])) { throw new RuntimeError(`Expected second argument to be of type array or string, but found ${ toString(typeOf(haystack)) } instead.`); } if (this.fromIndex) { const fromIndex = this.fromIndex.evaluate(ctx); return haystack.indexOf(needle, fromIndex); } return haystack.indexOf(needle); } eachChild(fn) { fn(this.needle); fn(this.haystack); if (this.fromIndex) { fn(this.fromIndex); } } outputDefined() { return false; } serialize() { if (this.fromIndex != null && this.fromIndex !== undefined) { const fromIndex = this.fromIndex.serialize(); return [ 'index-of', this.needle.serialize(), this.haystack.serialize(), fromIndex ]; } return [ 'index-of', this.needle.serialize(), this.haystack.serialize() ]; } } class Match { constructor(inputType, outputType, input, cases, outputs, otherwise) { this.inputType = inputType; this.type = outputType; this.input = input; this.cases = cases; this.outputs = outputs; this.otherwise = otherwise; } static parse(args, context) { if (args.length < 5) return context.error(`Expected at least 4 arguments, but found only ${ args.length - 1 }.`); if (args.length % 2 !== 1) return context.error(`Expected an even number of arguments.`); let inputType; let outputType; if (context.expectedType && context.expectedType.kind !== 'value') { outputType = context.expectedType; } const cases = {}; const outputs = []; for (let i = 2; i < args.length - 1; i += 2) { let labels = args[i]; const value = args[i + 1]; if (!Array.isArray(labels)) { labels = [labels]; } const labelContext = context.concat(i); if (labels.length === 0) { return labelContext.error('Expected at least one branch label.'); } for (const label of labels) { if (typeof label !== 'number' && typeof label !== 'string') { return labelContext.error(`Branch labels must be numbers or strings.`); } else if (typeof label === 'number' && Math.abs(label) > Number.MAX_SAFE_INTEGER) { return labelContext.error(`Branch labels must be integers no larger than ${ Number.MAX_SAFE_INTEGER }.`); } else if (typeof label === 'number' && Math.floor(label) !== label) { return labelContext.error(`Numeric branch labels must be integer values.`); } else if (!inputType) { inputType = typeOf(label); } else if (labelContext.checkSubtype(inputType, typeOf(label))) { return null; } if (typeof cases[String(label)] !== 'undefined') { return labelContext.error('Branch labels must be unique.'); } cases[String(label)] = outputs.length; } const result = context.parse(value, i, outputType); if (!result) return null; outputType = outputType || result.type; outputs.push(result); } const input = context.parse(args[1], 1, ValueType); if (!input) return null; const otherwise = context.parse(args[args.length - 1], args.length - 1, outputType); if (!otherwise) return null; if (input.type.kind !== 'value' && context.concat(1).checkSubtype(inputType, input.type)) { return null; } return new Match(inputType, outputType, input, cases, outputs, otherwise); } evaluate(ctx) { const input = this.input.evaluate(ctx); const output = typeOf(input) === this.inputType && this.outputs[this.cases[input]] || this.otherwise; return output.evaluate(ctx); } eachChild(fn) { fn(this.input); this.outputs.forEach(fn); fn(this.otherwise); } outputDefined() { return this.outputs.every(out => out.outputDefined()) && this.otherwise.outputDefined(); } serialize() { const serialized = [ 'match', this.input.serialize() ]; const sortedLabels = Object.keys(this.cases).sort(); const groupedByOutput = []; const outputLookup = {}; for (const label of sortedLabels) { const outputIndex = outputLookup[this.cases[label]]; if (outputIndex === undefined) { outputLookup[this.cases[label]] = groupedByOutput.length; groupedByOutput.push([ this.cases[label], [label] ]); } else { groupedByOutput[outputIndex][1].push(label); } } const coerceLabel = label => this.inputType.kind === 'number' ? Number(label) : label; for (const [outputIndex, labels] of groupedByOutput) { if (labels.length === 1) { serialized.push(coerceLabel(labels[0])); } else { serialized.push(labels.map(coerceLabel)); } serialized.push(this.outputs[outputIndex].serialize()); } serialized.push(this.otherwise.serialize()); return serialized; } } class Case { constructor(type, branches, otherwise) { this.type = type; this.branches = branches; this.otherwise = otherwise; } static parse(args, context) { if (args.length < 4) return context.error(`Expected at least 3 arguments, but found only ${ args.length - 1 }.`); if (args.length % 2 !== 0) return context.error(`Expected an odd number of arguments.`); let outputType; if (context.expectedType && context.expectedType.kind !== 'value') { outputType = context.expectedType; } const branches = []; for (let i = 1; i < args.length - 1; i += 2) { const test = context.parse(args[i], i, BooleanType); if (!test) return null; const result = context.parse(args[i + 1], i + 1, outputType); if (!result) return null; branches.push([ test, result ]); outputType = outputType || result.type; } const otherwise = context.parse(args[args.length - 1], args.length - 1, outputType); if (!otherwise) return null; return new Case(outputType, branches, otherwise); } evaluate(ctx) { for (const [test, expression] of this.branches) { if (test.evaluate(ctx)) { return expression.evaluate(ctx); } } return this.otherwise.evaluate(ctx); } eachChild(fn) { for (const [test, expression] of this.branches) { fn(test); fn(expression); } fn(this.otherwise); } outputDefined() { return this.branches.every(([_, out]) => out.outputDefined()) && this.otherwise.outputDefined(); } serialize() { const serialized = ['case']; this.eachChild(child => { serialized.push(child.serialize()); }); return serialized; } } class Slice { constructor(type, input, beginIndex, endIndex) { this.type = type; this.input = input; this.beginIndex = beginIndex; this.endIndex = endIndex; } static parse(args, context) { if (args.length <= 2 || args.length >= 5) { return context.error(`Expected 3 or 4 arguments, but found ${ args.length - 1 } instead.`); } const input = context.parse(args[1], 1, ValueType); const beginIndex = context.parse(args[2], 2, NumberType); if (!input || !beginIndex) return null; if (!isValidType(input.type, [ array(ValueType), StringType, ValueType ])) { return context.error(`Expected first argument to be of type array or string, but found ${ toString(input.type) } instead`); } if (args.length === 4) { const endIndex = context.parse(args[3], 3, NumberType); if (!endIndex) return null; return new Slice(input.type, input, beginIndex, endIndex); } else { return new Slice(input.type, input, beginIndex); } } evaluate(ctx) { const input = this.input.evaluate(ctx); const beginIndex = this.beginIndex.evaluate(ctx); if (!isValidNativeType(input, [ 'string', 'array' ])) { throw new RuntimeError(`Expected first argument to be of type array or string, but found ${ toString(typeOf(input)) } instead.`); } if (this.endIndex) { const endIndex = this.endIndex.evaluate(ctx); return input.slice(beginIndex, endIndex); } return input.slice(beginIndex); } eachChild(fn) { fn(this.input); fn(this.beginIndex); if (this.endIndex) { fn(this.endIndex); } } outputDefined() { return false; } serialize() { if (this.endIndex != null && this.endIndex !== undefined) { const endIndex = this.endIndex.serialize(); return [ 'slice', this.input.serialize(), this.beginIndex.serialize(), endIndex ]; } return [ 'slice', this.input.serialize(), this.beginIndex.serialize() ]; } } function isComparableType(op, type) { if (op === '==' || op === '!=') { return type.kind === 'boolean' || type.kind === 'string' || type.kind === 'number' || type.kind === 'null' || type.kind === 'value'; } else { return type.kind === 'string' || type.kind === 'number' || type.kind === 'value'; } } function eq(ctx, a, b) { return a === b; } function neq(ctx, a, b) { return a !== b; } function lt(ctx, a, b) { return a < b; } function gt(ctx, a, b) { return a > b; } function lteq(ctx, a, b) { return a <= b; } function gteq(ctx, a, b) { return a >= b; } function eqCollate(ctx, a, b, c) { return c.compare(a, b) === 0; } function neqCollate(ctx, a, b, c) { return !eqCollate(ctx, a, b, c); } function ltCollate(ctx, a, b, c) { return c.compare(a, b) < 0; } function gtCollate(ctx, a, b, c) { return c.compare(a, b) > 0; } function lteqCollate(ctx, a, b, c) { return c.compare(a, b) <= 0; } function gteqCollate(ctx, a, b, c) { return c.compare(a, b) >= 0; } function makeComparison(op, compareBasic, compareWithCollator) { const isOrderComparison = op !== '==' && op !== '!='; return class Comparison { constructor(lhs, rhs, collator) { this.type = BooleanType; this.lhs = lhs; this.rhs = rhs; this.collator = collator; this.hasUntypedArgument = lhs.type.kind === 'value' || rhs.type.kind === 'value'; } static parse(args, context) { if (args.length !== 3 && args.length !== 4) return context.error(`Expected two or three arguments.`); const op = args[0]; let lhs = context.parse(args[1], 1, ValueType); if (!lhs) return null; if (!isComparableType(op, lhs.type)) { return context.concat(1).error(`"${ op }" comparisons are not supported for type '${ toString(lhs.type) }'.`); } let rhs = context.parse(args[2], 2, ValueType); if (!rhs) return null; if (!isComparableType(op, rhs.type)) { return context.concat(2).error(`"${ op }" comparisons are not supported for type '${ toString(rhs.type) }'.`); } if (lhs.type.kind !== rhs.type.kind && lhs.type.kind !== 'value' && rhs.type.kind !== 'value') { return context.error(`Cannot compare types '${ toString(lhs.type) }' and '${ toString(rhs.type) }'.`); } if (isOrderComparison) { if (lhs.type.kind === 'value' && rhs.type.kind !== 'value') { lhs = new Assertion(rhs.type, [lhs]); } else if (lhs.type.kind !== 'value' && rhs.type.kind === 'value') { rhs = new Assertion(lhs.type, [rhs]); } } let collator = null; if (args.length === 4) { if (lhs.type.kind !== 'string' && rhs.type.kind !== 'string' && lhs.type.kind !== 'value' && rhs.type.kind !== 'value') { return context.error(`Cannot use collator to compare non-string types.`); } collator = context.parse(args[3], 3, CollatorType); if (!collator) return null; } return new Comparison(lhs, rhs, collator); } evaluate(ctx) { const lhs = this.lhs.evaluate(ctx); const rhs = this.rhs.evaluate(ctx); if (isOrderComparison && this.hasUntypedArgument) { const lt = typeOf(lhs); const rt = typeOf(rhs); if (lt.kind !== rt.kind || !(lt.kind === 'string' || lt.kind === 'number')) { throw new RuntimeError(`Expected arguments for "${ op }" to be (string, string) or (number, number), but found (${ lt.kind }, ${ rt.kind }) instead.`); } } if (this.collator && !isOrderComparison && this.hasUntypedArgument) { const lt = typeOf(lhs); const rt = typeOf(rhs); if (lt.kind !== 'string' || rt.kind !== 'string') { return compareBasic(ctx, lhs, rhs); } } return this.collator ? compareWithCollator(ctx, lhs, rhs, this.collator.evaluate(ctx)) : compareBasic(ctx, lhs, rhs); } eachChild(fn) { fn(this.lhs); fn(this.rhs); if (this.collator) { fn(this.collator); } } outputDefined() { return true; } serialize() { const serialized = [op]; this.eachChild(child => { serialized.push(child.serialize()); }); return serialized; } }; } const Equals = makeComparison('==', eq, eqCollate); const NotEquals = makeComparison('!=', neq, neqCollate); const LessThan = makeComparison('<', lt, ltCollate); const GreaterThan = makeComparison('>', gt, gtCollate); const LessThanOrEqual = makeComparison('<=', lteq, lteqCollate); const GreaterThanOrEqual = makeComparison('>=', gteq, gteqCollate); class NumberFormat { constructor(number, locale, currency, minFractionDigits, maxFractionDigits) { this.type = StringType; this.number = number; this.locale = locale; this.currency = currency; this.minFractionDigits = minFractionDigits; this.maxFractionDigits = maxFractionDigits; } static parse(args, context) { if (args.length !== 3) return context.error(`Expected two arguments.`); const number = context.parse(args[1], 1, NumberType); if (!number) return null; const options = args[2]; if (typeof options !== 'object' || Array.isArray(options)) return context.error(`NumberFormat options argument must be an object.`); let locale = null; if (options['locale']) { locale = context.parse(options['locale'], 1, StringType); if (!locale) return null; } let currency = null; if (options['currency']) { currency = context.parse(options['currency'], 1, StringType); if (!currency) return null; } let minFractionDigits = null; if (options['min-fraction-digits']) { minFractionDigits = context.parse(options['min-fraction-digits'], 1, NumberType); if (!minFractionDigits) return null; } let maxFractionDigits = null; if (options['max-fraction-digits']) { maxFractionDigits = context.parse(options['max-fraction-digits'], 1, NumberType); if (!maxFractionDigits) return null; } return new NumberFormat(number, locale, currency, minFractionDigits, maxFractionDigits); } evaluate(ctx) { return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [], { style: this.currency ? 'currency' : 'decimal', currency: this.currency ? this.currency.evaluate(ctx) : undefined, minimumFractionDigits: this.minFractionDigits ? this.minFractionDigits.evaluate(ctx) : undefined, maximumFractionDigits: this.maxFractionDigits ? this.maxFractionDigits.evaluate(ctx) : undefined }).format(this.number.evaluate(ctx)); } eachChild(fn) { fn(this.number); if (this.locale) { fn(this.locale); } if (this.currency) { fn(this.currency); } if (this.minFractionDigits) { fn(this.minFractionDigits); } if (this.maxFractionDigits) { fn(this.maxFractionDigits); } } outputDefined() { return false; } serialize() { const options = {}; if (this.locale) { options['locale'] = this.locale.serialize(); } if (this.currency) { options['currency'] = this.currency.serialize(); } if (this.minFractionDigits) { options['min-fraction-digits'] = this.minFractionDigits.serialize(); } if (this.maxFractionDigits) { options['max-fraction-digits'] = this.maxFractionDigits.serialize(); } return [ 'number-format', this.number.serialize(), options ]; } } class Length { constructor(input) { this.type = NumberType; this.input = input; } static parse(args, context) { if (args.length !== 2) return context.error(`Expected 1 argument, but found ${ args.length - 1 } instead.`); const input = context.parse(args[1], 1); if (!input) return null; if (input.type.kind !== 'array' && input.type.kind !== 'string' && input.type.kind !== 'value') return context.error(`Expected argument of type string or array, but found ${ toString(input.type) } instead.`); return new Length(input); } evaluate(ctx) { const input = this.input.evaluate(ctx); if (typeof input === 'string') { return input.length; } else if (Array.isArray(input)) { return input.length; } else { throw new RuntimeError(`Expected value to be of type string or array, but found ${ toString(typeOf(input)) } instead.`); } } eachChild(fn) { fn(this.input); } outputDefined() { return false; } serialize() { const serialized = ['length']; this.eachChild(child => { serialized.push(child.serialize()); }); return serialized; } } const expressions = { '==': Equals, '!=': NotEquals, '>': GreaterThan, '<': LessThan, '>=': GreaterThanOrEqual, '<=': LessThanOrEqual, 'array': Assertion, 'at': At, 'boolean': Assertion, 'case': Case, 'coalesce': Coalesce, 'collator': CollatorExpression, 'format': FormatExpression, 'image': ImageExpression, 'in': In, 'index-of': IndexOf, 'interpolate': Interpolate, 'interpolate-hcl': Interpolate, 'interpolate-lab': Interpolate, 'length': Length, 'let': Let, 'literal': Literal, 'match': Match, 'number': Assertion, 'number-format': NumberFormat, 'object': Assertion, 'slice': Slice, 'step': Step, 'string': Assertion, 'to-boolean': Coercion, 'to-color': Coercion, 'to-number': Coercion, 'to-string': Coercion, 'var': Var, 'within': Within }; function rgba(ctx, [r, g, b, a]) { r = r.evaluate(ctx); g = g.evaluate(ctx); b = b.evaluate(ctx); const alpha = a ? a.evaluate(ctx) : 1; const error = validateRGBA(r, g, b, alpha); if (error) throw new RuntimeError(error); return new Color(r / 255 * alpha, g / 255 * alpha, b / 255 * alpha, alpha); } function has(key, obj) { return key in obj; } function get(key, obj) { const v = obj[key]; return typeof v === 'undefined' ? null : v; } function binarySearch(v, a, i, j) { while (i <= j) { const m = i + j >> 1; if (a[m] === v) return true; if (a[m] > v) j = m - 1; else i = m + 1; } return false; } function varargs(type) { return { type }; } CompoundExpression.register(expressions, { 'error': [ ErrorType, [StringType], (ctx, [v]) => { throw new RuntimeError(v.evaluate(ctx)); } ], 'typeof': [ StringType, [ValueType], (ctx, [v]) => toString(typeOf(v.evaluate(ctx))) ], 'to-rgba': [ array(NumberType, 4), [ColorType], (ctx, [v]) => { return v.evaluate(ctx).toArray(); } ], 'rgb': [ ColorType, [ NumberType, NumberType, NumberType ], rgba ], 'rgba': [ ColorType, [ NumberType, NumberType, NumberType, NumberType ], rgba ], 'has': { type: BooleanType, overloads: [ [ [StringType], (ctx, [key]) => has(key.evaluate(ctx), ctx.properties()) ], [ [ StringType, ObjectType ], (ctx, [key, obj]) => has(key.evaluate(ctx), obj.evaluate(ctx)) ] ] }, 'get': { type: ValueType, overloads: [ [ [StringType], (ctx, [key]) => get(key.evaluate(ctx), ctx.properties()) ], [ [ StringType, ObjectType ], (ctx, [key, obj]) => get(key.evaluate(ctx), obj.evaluate(ctx)) ] ] }, 'feature-state': [ ValueType, [StringType], (ctx, [key]) => get(key.evaluate(ctx), ctx.featureState || {}) ], 'properties': [ ObjectType, [], ctx => ctx.properties() ], 'geometry-type': [ StringType, [], ctx => ctx.geometryType() ], 'id': [ ValueType, [], ctx => ctx.id() ], 'zoom': [ NumberType, [], ctx => ctx.globals.zoom ], 'heatmap-density': [ NumberType, [], ctx => ctx.globals.heatmapDensity || 0 ], 'line-progress': [ NumberType, [], ctx => ctx.globals.lineProgress || 0 ], 'sky-radial-progress': [ NumberType, [], ctx => ctx.globals.skyRadialProgress || 0 ], 'accumulated': [ ValueType, [], ctx => ctx.globals.accumulated === undefined ? null : ctx.globals.accumulated ], '+': [ NumberType, varargs(NumberType), (ctx, args) => { let result = 0; for (const arg of args) { result += arg.evaluate(ctx); } return result; } ], '*': [ NumberType, varargs(NumberType), (ctx, args) => { let result = 1; for (const arg of args) { result *= arg.evaluate(ctx); } return result; } ], '-': { type: NumberType, overloads: [ [ [ NumberType, NumberType ], (ctx, [a, b]) => a.evaluate(ctx) - b.evaluate(ctx) ], [ [NumberType], (ctx, [a]) => -a.evaluate(ctx) ] ] }, '/': [ NumberType, [ NumberType, NumberType ], (ctx, [a, b]) => a.evaluate(ctx) / b.evaluate(ctx) ], '%': [ NumberType, [ NumberType, NumberType ], (ctx, [a, b]) => a.evaluate(ctx) % b.evaluate(ctx) ], 'ln2': [ NumberType, [], () => Math.LN2 ], 'pi': [ NumberType, [], () => Math.PI ], 'e': [ NumberType, [], () => Math.E ], '^': [ NumberType, [ NumberType, NumberType ], (ctx, [b, e]) => Math.pow(b.evaluate(ctx), e.evaluate(ctx)) ], 'sqrt': [ NumberType, [NumberType], (ctx, [x]) => Math.sqrt(x.evaluate(ctx)) ], 'log10': [ NumberType, [NumberType], (ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN10 ], 'ln': [ NumberType, [NumberType], (ctx, [n]) => Math.log(n.evaluate(ctx)) ], 'log2': [ NumberType, [NumberType], (ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN2 ], 'sin': [ NumberType, [NumberType], (ctx, [n]) => Math.sin(n.evaluate(ctx)) ], 'cos': [ NumberType, [NumberType], (ctx, [n]) => Math.cos(n.evaluate(ctx)) ], 'tan': [ NumberType, [NumberType], (ctx, [n]) => Math.tan(n.evaluate(ctx)) ], 'asin': [ NumberType, [NumberType], (ctx, [n]) => Math.asin(n.evaluate(ctx)) ], 'acos': [ NumberType, [NumberType], (ctx, [n]) => Math.acos(n.evaluate(ctx)) ], 'atan': [ NumberType, [NumberType], (ctx, [n]) => Math.atan(n.evaluate(ctx)) ], 'min': [ NumberType, varargs(NumberType), (ctx, args) => Math.min(...args.map(arg => arg.evaluate(ctx))) ], 'max': [ NumberType, varargs(NumberType), (ctx, args) => Math.max(...args.map(arg => arg.evaluate(ctx))) ], 'abs': [ NumberType, [NumberType], (ctx, [n]) => Math.abs(n.evaluate(ctx)) ], 'round': [ NumberType, [NumberType], (ctx, [n]) => { const v = n.evaluate(ctx); return v < 0 ? -Math.round(-v) : Math.round(v); } ], 'floor': [ NumberType, [NumberType], (ctx, [n]) => Math.floor(n.evaluate(ctx)) ], 'ceil': [ NumberType, [NumberType], (ctx, [n]) => Math.ceil(n.evaluate(ctx)) ], 'filter-==': [ BooleanType, [ StringType, ValueType ], (ctx, [k, v]) => ctx.properties()[k.value] === v.value ], 'filter-id-==': [ BooleanType, [ValueType], (ctx, [v]) => ctx.id() === v.value ], 'filter-type-==': [ BooleanType, [StringType], (ctx, [v]) => ctx.geometryType() === v.value ], 'filter-<': [ BooleanType, [ StringType, ValueType ], (ctx, [k, v]) => { const a = ctx.properties()[k.value]; const b = v.value; return typeof a === typeof b && a < b; } ], 'filter-id-<': [ BooleanType, [ValueType], (ctx, [v]) => { const a = ctx.id(); const b = v.value; return typeof a === typeof b && a < b; } ], 'filter->': [ BooleanType, [ StringType, ValueType ], (ctx, [k, v]) => { const a = ctx.properties()[k.value]; const b = v.value; return typeof a === typeof b && a > b; } ], 'filter-id->': [ BooleanType, [ValueType], (ctx, [v]) => { const a = ctx.id(); const b = v.value; return typeof a === typeof b && a > b; } ], 'filter-<=': [ BooleanType, [ StringType, ValueType ], (ctx, [k, v]) => { const a = ctx.properties()[k.value]; const b = v.value; return typeof a === typeof b && a <= b; } ], 'filter-id-<=': [ BooleanType, [ValueType], (ctx, [v]) => { const a = ctx.id(); const b = v.value; return typeof a === typeof b && a <= b; } ], 'filter->=': [ BooleanType, [ StringType, ValueType ], (ctx, [k, v]) => { const a = ctx.properties()[k.value]; const b = v.value; return typeof a === typeof b && a >= b; } ], 'filter-id->=': [ BooleanType, [ValueType], (ctx, [v]) => { const a = ctx.id(); const b = v.value; return typeof a === typeof b && a >= b; } ], 'filter-has': [ BooleanType, [ValueType], (ctx, [k]) => k.value in ctx.properties() ], 'filter-has-id': [ BooleanType, [], ctx => ctx.id() !== null && ctx.id() !== undefined ], 'filter-type-in': [ BooleanType, [array(StringType)], (ctx, [v]) => v.value.indexOf(ctx.geometryType()) >= 0 ], 'filter-id-in': [ BooleanType, [array(ValueType)], (ctx, [v]) => v.value.indexOf(ctx.id()) >= 0 ], 'filter-in-small': [ BooleanType, [ StringType, array(ValueType) ], (ctx, [k, v]) => v.value.indexOf(ctx.properties()[k.value]) >= 0 ], 'filter-in-large': [ BooleanType, [ StringType, array(ValueType) ], (ctx, [k, v]) => binarySearch(ctx.properties()[k.value], v.value, 0, v.value.length - 1) ], 'all': { type: BooleanType, overloads: [ [ [ BooleanType, BooleanType ], (ctx, [a, b]) => a.evaluate(ctx) && b.evaluate(ctx) ], [ varargs(BooleanType), (ctx, args) => { for (const arg of args) { if (!arg.evaluate(ctx)) return false; } return true; } ] ] }, 'any': { type: BooleanType, overloads: [ [ [ BooleanType, BooleanType ], (ctx, [a, b]) => a.evaluate(ctx) || b.evaluate(ctx) ], [ varargs(BooleanType), (ctx, args) => { for (const arg of args) { if (arg.evaluate(ctx)) return true; } return false; } ] ] }, '!': [ BooleanType, [BooleanType], (ctx, [b]) => !b.evaluate(ctx) ], 'is-supported-script': [ BooleanType, [StringType], (ctx, [s]) => { const isSupportedScript = ctx.globals && ctx.globals.isSupportedScript; if (isSupportedScript) { return isSupportedScript(s.evaluate(ctx)); } return true; } ], 'upcase': [ StringType, [StringType], (ctx, [s]) => s.evaluate(ctx).toUpperCase() ], 'downcase': [ StringType, [StringType], (ctx, [s]) => s.evaluate(ctx).toLowerCase() ], 'concat': [ StringType, varargs(ValueType), (ctx, args) => args.map(arg => toString$1(arg.evaluate(ctx))).join('') ], 'resolved-locale': [ StringType, [CollatorType], (ctx, [collator]) => collator.evaluate(ctx).resolvedLocale() ] }); function success(value) { return { result: 'success', value }; } function error(value) { return { result: 'error', value }; } function supportsPropertyExpression(spec) { return spec['property-type'] === 'data-driven' || spec['property-type'] === 'cross-faded-data-driven'; } function supportsZoomExpression(spec) { return !!spec.expression && spec.expression.parameters.indexOf('zoom') > -1; } function supportsInterpolation(spec) { return !!spec.expression && spec.expression.interpolated; } function getType(val) { if (val instanceof Number) { return 'number'; } else if (val instanceof String) { return 'string'; } else if (val instanceof Boolean) { return 'boolean'; } else if (Array.isArray(val)) { return 'array'; } else if (val === null) { return 'null'; } else { return typeof val; } } function isFunction(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); } function identityFunction(x) { return x; } function createFunction(parameters, propertySpec) { const isColor = propertySpec.type === 'color'; const zoomAndFeatureDependent = parameters.stops && typeof parameters.stops[0][0] === 'object'; const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined; const zoomDependent = zoomAndFeatureDependent || !featureDependent; const type = parameters.type || (supportsInterpolation(propertySpec) ? 'exponential' : 'interval'); if (isColor) { parameters = extend$1({}, parameters); if (parameters.stops) { parameters.stops = parameters.stops.map(stop => { return [ stop[0], Color.parse(stop[1]) ]; }); } if (parameters.default) { parameters.default = Color.parse(parameters.default); } else { parameters.default = Color.parse(propertySpec.default); } } if (parameters.colorSpace && parameters.colorSpace !== 'rgb' && !colorSpaces[parameters.colorSpace]) { throw new Error(`Unknown color space: ${ parameters.colorSpace }`); } let innerFun; let hashedStops; let categoricalKeyType; if (type === 'exponential') { innerFun = evaluateExponentialFunction; } else if (type === 'interval') { innerFun = evaluateIntervalFunction; } else if (type === 'categorical') { innerFun = evaluateCategoricalFunction; hashedStops = Object.create(null); for (const stop of parameters.stops) { hashedStops[stop[0]] = stop[1]; } categoricalKeyType = typeof parameters.stops[0][0]; } else if (type === 'identity') { innerFun = evaluateIdentityFunction; } else { throw new Error(`Unknown function type "${ type }"`); } if (zoomAndFeatureDependent) { const featureFunctions = {}; const zoomStops = []; for (let s = 0; s < parameters.stops.length; s++) { const stop = parameters.stops[s]; const zoom = stop[0].zoom; if (featureFunctions[zoom] === undefined) { featureFunctions[zoom] = { zoom, type: parameters.type, property: parameters.property, default: parameters.default, stops: [] }; zoomStops.push(zoom); } featureFunctions[zoom].stops.push([ stop[0].value, stop[1] ]); } const featureFunctionStops = []; for (const z of zoomStops) { featureFunctionStops.push([ featureFunctions[z].zoom, createFunction(featureFunctions[z], propertySpec) ]); } const interpolationType = { name: 'linear' }; return { kind: 'composite', interpolationType, interpolationFactor: Interpolate.interpolationFactor.bind(undefined, interpolationType), zoomStops: featureFunctionStops.map(s => s[0]), evaluate({zoom}, properties) { return evaluateExponentialFunction({ stops: featureFunctionStops, base: parameters.base }, propertySpec, zoom).evaluate(zoom, properties); } }; } else if (zoomDependent) { const interpolationType = type === 'exponential' ? { name: 'exponential', base: parameters.base !== undefined ? parameters.base : 1 } : null; return { kind: 'camera', interpolationType, interpolationFactor: Interpolate.interpolationFactor.bind(undefined, interpolationType), zoomStops: parameters.stops.map(s => s[0]), evaluate: ({zoom}) => innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType) }; } else { return { kind: 'source', evaluate(_, feature) { const value = feature && feature.properties ? feature.properties[parameters.property] : undefined; if (value === undefined) { return coalesce(parameters.default, propertySpec.default); } return innerFun(parameters, propertySpec, value, hashedStops, categoricalKeyType); } }; } } function coalesce(a, b, c) { if (a !== undefined) return a; if (b !== undefined) return b; if (c !== undefined) return c; } function evaluateCategoricalFunction(parameters, propertySpec, input, hashedStops, keyType) { const evaluated = typeof input === keyType ? hashedStops[input] : undefined; return coalesce(evaluated, parameters.default, propertySpec.default); } function evaluateIntervalFunction(parameters, propertySpec, input) { if (getType(input) !== 'number') return coalesce(parameters.default, propertySpec.default); const n = parameters.stops.length; if (n === 1) return parameters.stops[0][1]; if (input <= parameters.stops[0][0]) return parameters.stops[0][1]; if (input >= parameters.stops[n - 1][0]) return parameters.stops[n - 1][1]; const index = findStopLessThanOrEqualTo(parameters.stops.map(stop => stop[0]), input); return parameters.stops[index][1]; } function evaluateExponentialFunction(parameters, propertySpec, input) { const base = parameters.base !== undefined ? parameters.base : 1; if (getType(input) !== 'number') return coalesce(parameters.default, propertySpec.default); const n = parameters.stops.length; if (n === 1) return parameters.stops[0][1]; if (input <= parameters.stops[0][0]) return parameters.stops[0][1]; if (input >= parameters.stops[n - 1][0]) return parameters.stops[n - 1][1]; const index = findStopLessThanOrEqualTo(parameters.stops.map(stop => stop[0]), input); const t = interpolationFactor(input, base, parameters.stops[index][0], parameters.stops[index + 1][0]); const outputLower = parameters.stops[index][1]; const outputUpper = parameters.stops[index + 1][1]; let interp = interpolate[propertySpec.type] || identityFunction; if (parameters.colorSpace && parameters.colorSpace !== 'rgb') { const colorspace = colorSpaces[parameters.colorSpace]; interp = (a, b) => colorspace.reverse(colorspace.interpolate(colorspace.forward(a), colorspace.forward(b), t)); } if (typeof outputLower.evaluate === 'function') { return { evaluate(...args) { const evaluatedLower = outputLower.evaluate.apply(undefined, args); const evaluatedUpper = outputUpper.evaluate.apply(undefined, args); if (evaluatedLower === undefined || evaluatedUpper === undefined) { return undefined; } return interp(evaluatedLower, evaluatedUpper, t); } }; } return interp(outputLower, outputUpper, t); } function evaluateIdentityFunction(parameters, propertySpec, input) { if (propertySpec.type === 'color') { input = Color.parse(input); } else if (propertySpec.type === 'formatted') { input = Formatted.fromString(input.toString()); } else if (propertySpec.type === 'resolvedImage') { input = ResolvedImage.fromString(input.toString()); } else if (getType(input) !== propertySpec.type && (propertySpec.type !== 'enum' || !propertySpec.values[input])) { input = undefined; } return coalesce(input, parameters.default, propertySpec.default); } function interpolationFactor(input, base, lowerValue, upperValue) { const difference = upperValue - lowerValue; const progress = input - lowerValue; if (difference === 0) { return 0; } else if (base === 1) { return progress / difference; } else { return (Math.pow(base, progress) - 1) / (Math.pow(base, difference) - 1); } } class StyleExpression { constructor(expression, propertySpec) { this.expression = expression; this._warningHistory = {}; this._evaluator = new EvaluationContext(); this._defaultValue = propertySpec ? getDefaultValue(propertySpec) : null; this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null; } evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) { this._evaluator.globals = globals; this._evaluator.feature = feature; this._evaluator.featureState = featureState; this._evaluator.canonical = canonical; this._evaluator.availableImages = availableImages || null; this._evaluator.formattedSection = formattedSection; return this.expression.evaluate(this._evaluator); } evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) { this._evaluator.globals = globals; this._evaluator.feature = feature || null; this._evaluator.featureState = featureState || null; this._evaluator.canonical = canonical; this._evaluator.availableImages = availableImages || null; this._evaluator.formattedSection = formattedSection || null; try { const val = this.expression.evaluate(this._evaluator); if (val === null || val === undefined || typeof val === 'number' && val !== val) { return this._defaultValue; } if (this._enumValues && !(val in this._enumValues)) { throw new RuntimeError(`Expected value to be one of ${ Object.keys(this._enumValues).map(v => JSON.stringify(v)).join(', ') }, but found ${ JSON.stringify(val) } instead.`); } return val; } catch (e) { if (!this._warningHistory[e.message]) { this._warningHistory[e.message] = true; if (typeof console !== 'undefined') { console.warn(e.message); } } return this._defaultValue; } } } function isExpression(expression) { return Array.isArray(expression) && expression.length > 0 && typeof expression[0] === 'string' && expression[0] in expressions; } function createExpression(expression, propertySpec) { const parser = new ParsingContext(expressions, [], propertySpec ? getExpectedType(propertySpec) : undefined); const parsed = parser.parse(expression, undefined, undefined, undefined, propertySpec && propertySpec.type === 'string' ? { typeAnnotation: 'coerce' } : undefined); if (!parsed) { return error(parser.errors); } return success(new StyleExpression(parsed, propertySpec)); } class ZoomConstantExpression { constructor(kind, expression) { this.kind = kind; this._styleExpression = expression; this.isStateDependent = kind !== 'constant' && !isStateConstant(expression.expression); } evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) { return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection); } evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) { return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection); } } class ZoomDependentExpression { constructor(kind, expression, zoomStops, interpolationType) { this.kind = kind; this.zoomStops = zoomStops; this._styleExpression = expression; this.isStateDependent = kind !== 'camera' && !isStateConstant(expression.expression); this.interpolationType = interpolationType; } evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) { return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection); } evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) { return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection); } interpolationFactor(input, lower, upper) { if (this.interpolationType) { return Interpolate.interpolationFactor(this.interpolationType, input, lower, upper); } else { return 0; } } } function createPropertyExpression(expression, propertySpec) { expression = createExpression(expression, propertySpec); if (expression.result === 'error') { return expression; } const parsed = expression.value.expression; const isFeatureConstant$1 = isFeatureConstant(parsed); if (!isFeatureConstant$1 && !supportsPropertyExpression(propertySpec)) { return error([new ParsingError('', 'data expressions not supported')]); } const isZoomConstant = isGlobalPropertyConstant(parsed, ['zoom']); if (!isZoomConstant && !supportsZoomExpression(propertySpec)) { return error([new ParsingError('', 'zoom expressions not supported')]); } const zoomCurve = findZoomCurve(parsed); if (!zoomCurve && !isZoomConstant) { return error([new ParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.')]); } else if (zoomCurve instanceof ParsingError) { return error([zoomCurve]); } else if (zoomCurve instanceof Interpolate && !supportsInterpolation(propertySpec)) { return error([new ParsingError('', '"interpolate" expressions cannot be used with this property')]); } if (!zoomCurve) { return success(isFeatureConstant$1 ? new ZoomConstantExpression('constant', expression.value) : new ZoomConstantExpression('source', expression.value)); } const interpolationType = zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined; return success(isFeatureConstant$1 ? new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType) : new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType)); } class StylePropertyFunction { constructor(parameters, specification) { this._parameters = parameters; this._specification = specification; extend$1(this, createFunction(this._parameters, this._specification)); } static deserialize(serialized) { return new StylePropertyFunction(serialized._parameters, serialized._specification); } static serialize(input) { return { _parameters: input._parameters, _specification: input._specification }; } } function normalizePropertyExpression(value, specification) { if (isFunction(value)) { return new StylePropertyFunction(value, specification); } else if (isExpression(value)) { const expression = createPropertyExpression(value, specification); if (expression.result === 'error') { throw new Error(expression.value.map(err => `${ err.key }: ${ err.message }`).join(', ')); } return expression.value; } else { let constant = value; if (typeof value === 'string' && specification.type === 'color') { constant = Color.parse(value); } return { kind: 'constant', evaluate: () => constant }; } } function findZoomCurve(expression) { let result = null; if (expression instanceof Let) { result = findZoomCurve(expression.result); } else if (expression instanceof Coalesce) { for (const arg of expression.args) { result = findZoomCurve(arg); if (result) { break; } } } else if ((expression instanceof Step || expression instanceof Interpolate) && expression.input instanceof CompoundExpression && expression.input.name === 'zoom') { result = expression; } if (result instanceof ParsingError) { return result; } expression.eachChild(child => { const childResult = findZoomCurve(child); if (childResult instanceof ParsingError) { result = childResult; } else if (!result && childResult) { result = new ParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.'); } else if (result && childResult && result !== childResult) { result = new ParsingError('', 'Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.'); } }); return result; } function getExpectedType(spec) { const types = { color: ColorType, string: StringType, number: NumberType, enum: StringType, boolean: BooleanType, formatted: FormattedType, resolvedImage: ResolvedImageType }; if (spec.type === 'array') { return array(types[spec.value] || ValueType, spec.length); } return types[spec.type]; } function getDefaultValue(spec) { if (spec.type === 'color' && isFunction(spec.default)) { return new Color(0, 0, 0, 0); } else if (spec.type === 'color') { return Color.parse(spec.default) || null; } else if (spec.default === undefined) { return null; } else { return spec.default; } } function validateObject(options) { const key = options.key; const object = options.value; const elementSpecs = options.valueSpec || {}; const elementValidators = options.objectElementValidators || {}; const style = options.style; const styleSpec = options.styleSpec; let errors = []; const type = getType(object); if (type !== 'object') { return [new ValidationError(key, object, `object expected, ${ type } found`)]; } for (const objectKey in object) { const elementSpecKey = objectKey.split('.')[0]; const elementSpec = elementSpecs[elementSpecKey] || elementSpecs['*']; let validateElement; if (elementValidators[elementSpecKey]) { validateElement = elementValidators[elementSpecKey]; } else if (elementSpecs[elementSpecKey]) { validateElement = validate; } else if (elementValidators['*']) { validateElement = elementValidators['*']; } else if (elementSpecs['*']) { validateElement = validate; } else { errors.push(new ValidationError(key, object[objectKey], `unknown property "${ objectKey }"`)); continue; } errors = errors.concat(validateElement({ key: (key ? `${ key }.` : key) + objectKey, value: object[objectKey], valueSpec: elementSpec, style, styleSpec, object, objectKey }, object)); } for (const elementSpecKey in elementSpecs) { if (elementValidators[elementSpecKey]) { continue; } if (elementSpecs[elementSpecKey].required && elementSpecs[elementSpecKey]['default'] === undefined && object[elementSpecKey] === undefined) { errors.push(new ValidationError(key, object, `missing required property "${ elementSpecKey }"`)); } } return errors; } function validateArray(options) { const array = options.value; const arraySpec = options.valueSpec; const style = options.style; const styleSpec = options.styleSpec; const key = options.key; const validateArrayElement = options.arrayElementValidator || validate; if (getType(array) !== 'array') { return [new ValidationError(key, array, `array expected, ${ getType(array) } found`)]; } if (arraySpec.length && array.length !== arraySpec.length) { return [new ValidationError(key, array, `array length ${ arraySpec.length } expected, length ${ array.length } found`)]; } if (arraySpec['min-length'] && array.length < arraySpec['min-length']) { return [new ValidationError(key, array, `array length at least ${ arraySpec['min-length'] } expected, length ${ array.length } found`)]; } let arrayElementSpec = { 'type': arraySpec.value, 'values': arraySpec.values, 'minimum': arraySpec.minimum, 'maximum': arraySpec.maximum }; if (styleSpec.$version < 7) { arrayElementSpec.function = arraySpec.function; } if (getType(arraySpec.value) === 'object') { arrayElementSpec = arraySpec.value; } let errors = []; for (let i = 0; i < array.length; i++) { errors = errors.concat(validateArrayElement({ array, arrayIndex: i, value: array[i], valueSpec: arrayElementSpec, style, styleSpec, key: `${ key }[${ i }]` })); } return errors; } function validateNumber(options) { const key = options.key; const value = options.value; const valueSpec = options.valueSpec; let type = getType(value); if (type === 'number' && value !== value) { type = 'NaN'; } if (type !== 'number') { return [new ValidationError(key, value, `number expected, ${ type } found`)]; } if ('minimum' in valueSpec) { let specMin = valueSpec.minimum; if (getType(valueSpec.minimum) === 'array') { const i = options.arrayIndex; specMin = valueSpec.minimum[i]; } if (value < specMin) { return [new ValidationError(key, value, `${ value } is less than the minimum value ${ specMin }`)]; } } if ('maximum' in valueSpec) { let specMax = valueSpec.maximum; if (getType(valueSpec.maximum) === 'array') { const i = options.arrayIndex; specMax = valueSpec.maximum[i]; } if (value > specMax) { return [new ValidationError(key, value, `${ value } is greater than the maximum value ${ specMax }`)]; } } return []; } function validateFunction(options) { const functionValueSpec = options.valueSpec; const functionType = unbundle(options.value.type); let stopKeyType; let stopDomainValues = {}; let previousStopDomainValue; let previousStopDomainZoom; const isZoomFunction = functionType !== 'categorical' && options.value.property === undefined; const isPropertyFunction = !isZoomFunction; const isZoomAndPropertyFunction = getType(options.value.stops) === 'array' && getType(options.value.stops[0]) === 'array' && getType(options.value.stops[0][0]) === 'object'; const errors = validateObject({ key: options.key, value: options.value, valueSpec: options.styleSpec.function, style: options.style, styleSpec: options.styleSpec, objectElementValidators: { stops: validateFunctionStops, default: validateFunctionDefault } }); if (functionType === 'identity' && isZoomFunction) { errors.push(new ValidationError(options.key, options.value, 'missing required property "property"')); } if (functionType !== 'identity' && !options.value.stops) { errors.push(new ValidationError(options.key, options.value, 'missing required property "stops"')); } if (functionType === 'exponential' && options.valueSpec.expression && !supportsInterpolation(options.valueSpec)) { errors.push(new ValidationError(options.key, options.value, 'exponential functions not supported')); } if (options.styleSpec.$version >= 8) { if (isPropertyFunction && !supportsPropertyExpression(options.valueSpec)) { errors.push(new ValidationError(options.key, options.value, 'property functions not supported')); } else if (isZoomFunction && !supportsZoomExpression(options.valueSpec)) { errors.push(new ValidationError(options.key, options.value, 'zoom functions not supported')); } } if ((functionType === 'categorical' || isZoomAndPropertyFunction) && options.value.property === undefined) { errors.push(new ValidationError(options.key, options.value, '"property" property is required')); } return errors; function validateFunctionStops(options) { if (functionType === 'identity') { return [new ValidationError(options.key, options.value, 'identity function may not have a "stops" property')]; } let errors = []; const value = options.value; errors = errors.concat(validateArray({ key: options.key, value, valueSpec: options.valueSpec, style: options.style, styleSpec: options.styleSpec, arrayElementValidator: validateFunctionStop })); if (getType(value) === 'array' && value.length === 0) { errors.push(new ValidationError(options.key, value, 'array must have at least one stop')); } return errors; } function validateFunctionStop(options) { let errors = []; const value = options.value; const key = options.key; if (getType(value) !== 'array') { return [new ValidationError(key, value, `array expected, ${ getType(value) } found`)]; } if (value.length !== 2) { return [new ValidationError(key, value, `array length 2 expected, length ${ value.length } found`)]; } if (isZoomAndPropertyFunction) { if (getType(value[0]) !== 'object') { return [new ValidationError(key, value, `object expected, ${ getType(value[0]) } found`)]; } if (value[0].zoom === undefined) { return [new ValidationError(key, value, 'object stop key must have zoom')]; } if (value[0].value === undefined) { return [new ValidationError(key, value, 'object stop key must have value')]; } if (previousStopDomainZoom && previousStopDomainZoom > unbundle(value[0].zoom)) { return [new ValidationError(key, value[0].zoom, 'stop zoom values must appear in ascending order')]; } if (unbundle(value[0].zoom) !== previousStopDomainZoom) { previousStopDomainZoom = unbundle(value[0].zoom); previousStopDomainValue = undefined; stopDomainValues = {}; } errors = errors.concat(validateObject({ key: `${ key }[0]`, value: value[0], valueSpec: { zoom: {} }, style: options.style, styleSpec: options.styleSpec, objectElementValidators: { zoom: validateNumber, value: validateStopDomainValue } })); } else { errors = errors.concat(validateStopDomainValue({ key: `${ key }[0]`, value: value[0], valueSpec: {}, style: options.style, styleSpec: options.styleSpec }, value)); } if (isExpression(deepUnbundle(value[1]))) { return errors.concat([new ValidationError(`${ key }[1]`, value[1], 'expressions are not allowed in function stops.')]); } return errors.concat(validate({ key: `${ key }[1]`, value: value[1], valueSpec: functionValueSpec, style: options.style, styleSpec: options.styleSpec })); } function validateStopDomainValue(options, stop) { const type = getType(options.value); const value = unbundle(options.value); const reportValue = options.value !== null ? options.value : stop; if (!stopKeyType) { stopKeyType = type; } else if (type !== stopKeyType) { return [new ValidationError(options.key, reportValue, `${ type } stop domain type must match previous stop domain type ${ stopKeyType }`)]; } if (type !== 'number' && type !== 'string' && type !== 'boolean') { return [new ValidationError(options.key, reportValue, 'stop domain value must be a number, string, or boolean')]; } if (type !== 'number' && functionType !== 'categorical') { let message = `number expected, ${ type } found`; if (supportsPropertyExpression(functionValueSpec) && functionType === undefined) { message += '\nIf you intended to use a categorical function, specify `"type": "categorical"`.'; } return [new ValidationError(options.key, reportValue, message)]; } if (functionType === 'categorical' && type === 'number' && (!isFinite(value) || Math.floor(value) !== value)) { return [new ValidationError(options.key, reportValue, `integer expected, found ${ value }`)]; } if (functionType !== 'categorical' && type === 'number' && previousStopDomainValue !== undefined && value < previousStopDomainValue) { return [new ValidationError(options.key, reportValue, 'stop domain values must appear in ascending order')]; } else { previousStopDomainValue = value; } if (functionType === 'categorical' && value in stopDomainValues) { return [new ValidationError(options.key, reportValue, 'stop domain values must be unique')]; } else { stopDomainValues[value] = true; } return []; } function validateFunctionDefault(options) { return validate({ key: options.key, value: options.value, valueSpec: functionValueSpec, style: options.style, styleSpec: options.styleSpec }); } } function validateExpression(options) { const expression = (options.expressionContext === 'property' ? createPropertyExpression : createExpression)(deepUnbundle(options.value), options.valueSpec); if (expression.result === 'error') { return expression.value.map(error => { return new ValidationError(`${ options.key }${ error.key }`, options.value, error.message); }); } const expressionObj = expression.value.expression || expression.value._styleExpression.expression; if (options.expressionContext === 'property' && options.propertyKey === 'text-font' && !expressionObj.outputDefined()) { return [new ValidationError(options.key, options.value, `Invalid data expression for "${ options.propertyKey }". Output values must be contained as literals within the expression.`)]; } if (options.expressionContext === 'property' && options.propertyType === 'layout' && !isStateConstant(expressionObj)) { return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with layout properties.')]; } if (options.expressionContext === 'filter' && !isStateConstant(expressionObj)) { return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with filters.')]; } if (options.expressionContext && options.expressionContext.indexOf('cluster') === 0) { if (!isGlobalPropertyConstant(expressionObj, [ 'zoom', 'feature-state' ])) { return [new ValidationError(options.key, options.value, '"zoom" and "feature-state" expressions are not supported with cluster properties.')]; } if (options.expressionContext === 'cluster-initial' && !isFeatureConstant(expressionObj)) { return [new ValidationError(options.key, options.value, 'Feature data expressions are not supported with initial expression part of cluster properties.')]; } } return []; } function validateBoolean(options) { const value = options.value; const key = options.key; const type = getType(value); if (type !== 'boolean') { return [new ValidationError(key, value, `boolean expected, ${ type } found`)]; } return []; } function validateColor(options) { const key = options.key; const value = options.value; const type = getType(value); if (type !== 'string') { return [new ValidationError(key, value, `color expected, ${ type } found`)]; } if (csscolorparser_1(value) === null) { return [new ValidationError(key, value, `color expected, "${ value }" found`)]; } return []; } function validateEnum(options) { const key = options.key; const value = options.value; const valueSpec = options.valueSpec; const errors = []; if (Array.isArray(valueSpec.values)) { if (valueSpec.values.indexOf(unbundle(value)) === -1) { errors.push(new ValidationError(key, value, `expected one of [${ valueSpec.values.join(', ') }], ${ JSON.stringify(value) } found`)); } } else { if (Object.keys(valueSpec.values).indexOf(unbundle(value)) === -1) { errors.push(new ValidationError(key, value, `expected one of [${ Object.keys(valueSpec.values).join(', ') }], ${ JSON.stringify(value) } found`)); } } return errors; } function isExpressionFilter(filter) { if (filter === true || filter === false) { return true; } if (!Array.isArray(filter) || filter.length === 0) { return false; } switch (filter[0]) { case 'has': return filter.length >= 2 && filter[1] !== '$id' && filter[1] !== '$type'; case 'in': return filter.length >= 3 && (typeof filter[1] !== 'string' || Array.isArray(filter[2])); case '!in': case '!has': case 'none': return false; case '==': case '!=': case '>': case '>=': case '<': case '<=': return filter.length !== 3 || (Array.isArray(filter[1]) || Array.isArray(filter[2])); case 'any': case 'all': for (const f of filter.slice(1)) { if (!isExpressionFilter(f) && typeof f !== 'boolean') { return false; } } return true; default: return true; } } const filterSpec = { 'type': 'boolean', 'default': false, 'transition': false, 'property-type': 'data-driven', 'expression': { 'interpolated': false, 'parameters': [ 'zoom', 'feature' ] } }; function createFilter(filter) { if (filter === null || filter === undefined) { return { filter: () => true, needGeometry: false }; } if (!isExpressionFilter(filter)) { filter = convertFilter(filter); } const compiled = createExpression(filter, filterSpec); if (compiled.result === 'error') { throw new Error(compiled.value.map(err => `${ err.key }: ${ err.message }`).join(', ')); } else { const needGeometry = geometryNeeded(filter); return { filter: (globalProperties, feature, canonical) => compiled.value.evaluate(globalProperties, feature, {}, canonical), needGeometry }; } } function compare(a, b) { return a < b ? -1 : a > b ? 1 : 0; } function geometryNeeded(filter) { if (!Array.isArray(filter)) return false; if (filter[0] === 'within') return true; for (let index = 1; index < filter.length; index++) { if (geometryNeeded(filter[index])) return true; } return false; } function convertFilter(filter) { if (!filter) return true; const op = filter[0]; if (filter.length <= 1) return op !== 'any'; const converted = op === '==' ? convertComparisonOp(filter[1], filter[2], '==') : op === '!=' ? convertNegation(convertComparisonOp(filter[1], filter[2], '==')) : op === '<' || op === '>' || op === '<=' || op === '>=' ? convertComparisonOp(filter[1], filter[2], op) : op === 'any' ? convertDisjunctionOp(filter.slice(1)) : op === 'all' ? ['all'].concat(filter.slice(1).map(convertFilter)) : op === 'none' ? ['all'].concat(filter.slice(1).map(convertFilter).map(convertNegation)) : op === 'in' ? convertInOp(filter[1], filter.slice(2)) : op === '!in' ? convertNegation(convertInOp(filter[1], filter.slice(2))) : op === 'has' ? convertHasOp(filter[1]) : op === '!has' ? convertNegation(convertHasOp(filter[1])) : op === 'within' ? filter : true; return converted; } function convertComparisonOp(property, value, op) { switch (property) { case '$type': return [ `filter-type-${ op }`, value ]; case '$id': return [ `filter-id-${ op }`, value ]; default: return [ `filter-${ op }`, property, value ]; } } function convertDisjunctionOp(filters) { return ['any'].concat(filters.map(convertFilter)); } function convertInOp(property, values) { if (values.length === 0) { return false; } switch (property) { case '$type': return [ `filter-type-in`, [ 'literal', values ] ]; case '$id': return [ `filter-id-in`, [ 'literal', values ] ]; default: if (values.length > 200 && !values.some(v => typeof v !== typeof values[0])) { return [ 'filter-in-large', property, [ 'literal', values.sort(compare) ] ]; } else { return [ 'filter-in-small', property, [ 'literal', values ] ]; } } } function convertHasOp(property) { switch (property) { case '$type': return true; case '$id': return [`filter-has-id`]; default: return [ `filter-has`, property ]; } } function convertNegation(filter) { return [ '!', filter ]; } function validateFilter(options) { if (isExpressionFilter(deepUnbundle(options.value))) { return validateExpression(extend$1({}, options, { expressionContext: 'filter', valueSpec: { value: 'boolean' } })); } else { return validateNonExpressionFilter(options); } } function validateNonExpressionFilter(options) { const value = options.value; const key = options.key; if (getType(value) !== 'array') { return [new ValidationError(key, value, `array expected, ${ getType(value) } found`)]; } const styleSpec = options.styleSpec; let type; let errors = []; if (value.length < 1) { return [new ValidationError(key, value, 'filter array must have at least 1 element')]; } errors = errors.concat(validateEnum({ key: `${ key }[0]`, value: value[0], valueSpec: styleSpec.filter_operator, style: options.style, styleSpec: options.styleSpec })); switch (unbundle(value[0])) { case '<': case '<=': case '>': case '>=': if (value.length >= 2 && unbundle(value[1]) === '$type') { errors.push(new ValidationError(key, value, `"$type" cannot be use with operator "${ value[0] }"`)); } case '==': case '!=': if (value.length !== 3) { errors.push(new ValidationError(key, value, `filter array for operator "${ value[0] }" must have 3 elements`)); } case 'in': case '!in': if (value.length >= 2) { type = getType(value[1]); if (type !== 'string') { errors.push(new ValidationError(`${ key }[1]`, value[1], `string expected, ${ type } found`)); } } for (let i = 2; i < value.length; i++) { type = getType(value[i]); if (unbundle(value[1]) === '$type') { errors = errors.concat(validateEnum({ key: `${ key }[${ i }]`, value: value[i], valueSpec: styleSpec.geometry_type, style: options.style, styleSpec: options.styleSpec })); } else if (type !== 'string' && type !== 'number' && type !== 'boolean') { errors.push(new ValidationError(`${ key }[${ i }]`, value[i], `string, number, or boolean expected, ${ type } found`)); } } break; case 'any': case 'all': case 'none': for (let i = 1; i < value.length; i++) { errors = errors.concat(validateNonExpressionFilter({ key: `${ key }[${ i }]`, value: value[i], style: options.style, styleSpec: options.styleSpec })); } break; case 'has': case '!has': type = getType(value[1]); if (value.length !== 2) { errors.push(new ValidationError(key, value, `filter array for "${ value[0] }" operator must have 2 elements`)); } else if (type !== 'string') { errors.push(new ValidationError(`${ key }[1]`, value[1], `string expected, ${ type } found`)); } break; case 'within': type = getType(value[1]); if (value.length !== 2) { errors.push(new ValidationError(key, value, `filter array for "${ value[0] }" operator must have 2 elements`)); } else if (type !== 'object') { errors.push(new ValidationError(`${ key }[1]`, value[1], `object expected, ${ type } found`)); } break; } return errors; } function validateProperty(options, propertyType) { const key = options.key; const style = options.style; const styleSpec = options.styleSpec; const value = options.value; const propertyKey = options.objectKey; const layerSpec = styleSpec[`${ propertyType }_${ options.layerType }`]; if (!layerSpec) return []; const transitionMatch = propertyKey.match(/^(.*)-transition$/); if (propertyType === 'paint' && transitionMatch && layerSpec[transitionMatch[1]] && layerSpec[transitionMatch[1]].transition) { return validate({ key, value, valueSpec: styleSpec.transition, style, styleSpec }); } const valueSpec = options.valueSpec || layerSpec[propertyKey]; if (!valueSpec) { return [new ValidationError(key, value, `unknown property "${ propertyKey }"`)]; } let tokenMatch; if (getType(value) === 'string' && supportsPropertyExpression(valueSpec) && !valueSpec.tokens && (tokenMatch = /^{([^}]+)}$/.exec(value))) { return [new ValidationError(key, value, `"${ propertyKey }" does not support interpolation syntax\n` + `Use an identity property function instead: \`{ "type": "identity", "property": ${ JSON.stringify(tokenMatch[1]) } }\`.`)]; } const errors = []; if (options.layerType === 'symbol') { if (propertyKey === 'text-field' && style && !style.glyphs) { errors.push(new ValidationError(key, value, 'use of "text-field" requires a style "glyphs" property')); } if (propertyKey === 'text-font' && isFunction(deepUnbundle(value)) && unbundle(value.type) === 'identity') { errors.push(new ValidationError(key, value, '"text-font" does not support identity functions')); } } return errors.concat(validate({ key: options.key, value, valueSpec, style, styleSpec, expressionContext: 'property', propertyType, propertyKey })); } function validatePaintProperty(options) { return validateProperty(options, 'paint'); } function validateLayoutProperty(options) { return validateProperty(options, 'layout'); } function validateLayer(options) { let errors = []; const layer = options.value; const key = options.key; const style = options.style; const styleSpec = options.styleSpec; if (!layer.type && !layer.ref) { errors.push(new ValidationError(key, layer, 'either "type" or "ref" is required')); } let type = unbundle(layer.type); const ref = unbundle(layer.ref); if (layer.id) { const layerId = unbundle(layer.id); for (let i = 0; i < options.arrayIndex; i++) { const otherLayer = style.layers[i]; if (unbundle(otherLayer.id) === layerId) { errors.push(new ValidationError(key, layer.id, `duplicate layer id "${ layer.id }", previously used at line ${ otherLayer.id.__line__ }`)); } } } if ('ref' in layer) { [ 'type', 'source', 'source-layer', 'filter', 'layout' ].forEach(p => { if (p in layer) { errors.push(new ValidationError(key, layer[p], `"${ p }" is prohibited for ref layers`)); } }); let parent; style.layers.forEach(layer => { if (unbundle(layer.id) === ref) parent = layer; }); if (!parent) { errors.push(new ValidationError(key, layer.ref, `ref layer "${ ref }" not found`)); } else if (parent.ref) { errors.push(new ValidationError(key, layer.ref, 'ref cannot reference another ref layer')); } else { type = unbundle(parent.type); } } else if (!(type === 'background' || type === 'sky')) { if (!layer.source) { errors.push(new ValidationError(key, layer, 'missing required property "source"')); } else { const source = style.sources && style.sources[layer.source]; const sourceType = source && unbundle(source.type); if (!source) { errors.push(new ValidationError(key, layer.source, `source "${ layer.source }" not found`)); } else if (sourceType === 'vector' && type === 'raster') { errors.push(new ValidationError(key, layer.source, `layer "${ layer.id }" requires a raster source`)); } else if (sourceType === 'raster' && type !== 'raster') { errors.push(new ValidationError(key, layer.source, `layer "${ layer.id }" requires a vector source`)); } else if (sourceType === 'vector' && !layer['source-layer']) { errors.push(new ValidationError(key, layer, `layer "${ layer.id }" must specify a "source-layer"`)); } else if (sourceType === 'raster-dem' && type !== 'hillshade') { errors.push(new ValidationError(key, layer.source, 'raster-dem source can only be used with layer type \'hillshade\'.')); } else if (type === 'line' && layer.paint && layer.paint['line-gradient'] && (sourceType !== 'geojson' || !source.lineMetrics)) { errors.push(new ValidationError(key, layer, `layer "${ layer.id }" specifies a line-gradient, which requires a GeoJSON source with \`lineMetrics\` enabled.`)); } } } errors = errors.concat(validateObject({ key, value: layer, valueSpec: styleSpec.layer, style: options.style, styleSpec: options.styleSpec, objectElementValidators: { '*'() { return []; }, type() { return validate({ key: `${ key }.type`, value: layer.type, valueSpec: styleSpec.layer.type, style: options.style, styleSpec: options.styleSpec, object: layer, objectKey: 'type' }); }, filter: validateFilter, layout(options) { return validateObject({ layer, key: options.key, value: options.value, style: options.style, styleSpec: options.styleSpec, objectElementValidators: { '*'(options) { return validateLayoutProperty(extend$1({ layerType: type }, options)); } } }); }, paint(options) { return validateObject({ layer, key: options.key, value: options.value, style: options.style, styleSpec: options.styleSpec, objectElementValidators: { '*'(options) { return validatePaintProperty(extend$1({ layerType: type }, options)); } } }); } } })); return errors; } function validateString(options) { const value = options.value; const key = options.key; const type = getType(value); if (type !== 'string') { return [new ValidationError(key, value, `string expected, ${ type } found`)]; } return []; } const objectElementValidators = { promoteId: validatePromoteId }; function validateSource(options) { const value = options.value; const key = options.key; const styleSpec = options.styleSpec; const style = options.style; if (!value.type) { return [new ValidationError(key, value, '"type" is required')]; } const type = unbundle(value.type); let errors; switch (type) { case 'vector': case 'raster': case 'raster-dem': errors = validateObject({ key, value, valueSpec: styleSpec[`source_${ type.replace('-', '_') }`], style: options.style, styleSpec, objectElementValidators }); return errors; case 'geojson': errors = validateObject({ key, value, valueSpec: styleSpec.source_geojson, style, styleSpec, objectElementValidators }); if (value.cluster) { for (const prop in value.clusterProperties) { const [operator, mapExpr] = value.clusterProperties[prop]; const reduceExpr = typeof operator === 'string' ? [ operator, ['accumulated'], [ 'get', prop ] ] : operator; errors.push(...validateExpression({ key: `${ key }.${ prop }.map`, value: mapExpr, expressionContext: 'cluster-map' })); errors.push(...validateExpression({ key: `${ key }.${ prop }.reduce`, value: reduceExpr, expressionContext: 'cluster-reduce' })); } } return errors; case 'video': return validateObject({ key, value, valueSpec: styleSpec.source_video, style, styleSpec }); case 'image': return validateObject({ key, value, valueSpec: styleSpec.source_image, style, styleSpec }); case 'canvas': return [new ValidationError(key, null, `Please use runtime APIs to add canvas sources, rather than including them in stylesheets.`, 'source.canvas')]; default: return validateEnum({ key: `${ key }.type`, value: value.type, valueSpec: { values: [ 'vector', 'raster', 'raster-dem', 'geojson', 'video', 'image' ] }, style, styleSpec }); } } function validatePromoteId({key, value}) { if (getType(value) === 'string') { return validateString({ key, value }); } else { const errors = []; for (const prop in value) { errors.push(...validateString({ key: `${ key }.${ prop }`, value: value[prop] })); } return errors; } } function validateLight(options) { const light = options.value; const styleSpec = options.styleSpec; const lightSpec = styleSpec.light; const style = options.style; let errors = []; const rootType = getType(light); if (light === undefined) { return errors; } else if (rootType !== 'object') { errors = errors.concat([new ValidationError('light', light, `object expected, ${ rootType } found`)]); return errors; } for (const key in light) { const transitionMatch = key.match(/^(.*)-transition$/); if (transitionMatch && lightSpec[transitionMatch[1]] && lightSpec[transitionMatch[1]].transition) { errors = errors.concat(validate({ key, value: light[key], valueSpec: styleSpec.transition, style, styleSpec })); } else if (lightSpec[key]) { errors = errors.concat(validate({ key, value: light[key], valueSpec: lightSpec[key], style, styleSpec })); } else { errors = errors.concat([new ValidationError(key, light[key], `unknown property "${ key }"`)]); } } return errors; } function validateTerrain(options) { const terrain = options.value; const key = options.key; const style = options.style; const styleSpec = options.styleSpec; const terrainSpec = styleSpec.terrain; let errors = []; const rootType = getType(terrain); if (terrain === undefined) { return errors; } else if (rootType !== 'object') { errors = errors.concat([new ValidationError('terrain', terrain, `object expected, ${ rootType } found`)]); return errors; } for (const key in terrain) { const transitionMatch = key.match(/^(.*)-transition$/); if (transitionMatch && terrainSpec[transitionMatch[1]] && terrainSpec[transitionMatch[1]].transition) { errors = errors.concat(validate({ key, value: terrain[key], valueSpec: styleSpec.transition, style, styleSpec })); } else if (terrainSpec[key]) { errors = errors.concat(validate({ key, value: terrain[key], valueSpec: terrainSpec[key], style, styleSpec })); } else { errors = errors.concat([new ValidationError(key, terrain[key], `unknown property "${ key }"`)]); } } if (!terrain.source) { errors.push(new ValidationError(key, terrain, `terrain is missing required property "source"`)); } else { const source = style.sources && style.sources[terrain.source]; const sourceType = source && unbundle(source.type); if (!source) { errors.push(new ValidationError(key, terrain.source, `source "${ terrain.source }" not found`)); } else if (sourceType !== 'raster-dem') { errors.push(new ValidationError(key, terrain.source, `terrain cannot be used with a source of type ${ sourceType }, it only be used with a "raster-dem" source type`)); } } return errors; } function validateFormatted(options) { if (validateString(options).length === 0) { return []; } return validateExpression(options); } function validateImage(options) { if (validateString(options).length === 0) { return []; } return validateExpression(options); } const VALIDATORS = { '*'() { return []; }, 'array': validateArray, 'boolean': validateBoolean, 'number': validateNumber, 'color': validateColor, 'constants': validateConstants, 'enum': validateEnum, 'filter': validateFilter, 'function': validateFunction, 'layer': validateLayer, 'object': validateObject, 'source': validateSource, 'light': validateLight, 'terrain': validateTerrain, 'string': validateString, 'formatted': validateFormatted, 'resolvedImage': validateImage }; function validate(options) { const value = options.value; const valueSpec = options.valueSpec; const styleSpec = options.styleSpec; if (valueSpec.expression && isFunction(unbundle(value))) { return validateFunction(options); } else if (valueSpec.expression && isExpression(deepUnbundle(value))) { return validateExpression(options); } else if (valueSpec.type && VALIDATORS[valueSpec.type]) { return VALIDATORS[valueSpec.type](options); } else { const valid = validateObject(extend$1({}, options, { valueSpec: valueSpec.type ? styleSpec[valueSpec.type] : valueSpec })); return valid; } } function validateGlyphsURL (options) { const value = options.value; const key = options.key; const errors = validateString(options); if (errors.length) return errors; if (value.indexOf('{fontstack}') === -1) { errors.push(new ValidationError(key, value, '"glyphs" url must include a "{fontstack}" token')); } if (value.indexOf('{range}') === -1) { errors.push(new ValidationError(key, value, '"glyphs" url must include a "{range}" token')); } return errors; } function validateStyleMin(style, styleSpec = spec) { let errors = []; errors = errors.concat(validate({ key: '', value: style, valueSpec: styleSpec.$root, styleSpec, style, objectElementValidators: { glyphs: validateGlyphsURL, '*'() { return []; } } })); if (style.constants) { errors = errors.concat(validateConstants({ key: 'constants', value: style.constants, style, styleSpec })); } return sortErrors(errors); } validateStyleMin.source = wrapCleanErrors(validateSource); validateStyleMin.light = wrapCleanErrors(validateLight); validateStyleMin.terrain = wrapCleanErrors(validateTerrain); validateStyleMin.layer = wrapCleanErrors(validateLayer); validateStyleMin.filter = wrapCleanErrors(validateFilter); validateStyleMin.paintProperty = wrapCleanErrors(validatePaintProperty); validateStyleMin.layoutProperty = wrapCleanErrors(validateLayoutProperty); function sortErrors(errors) { return [].concat(errors).sort((a, b) => { return a.line - b.line; }); } function wrapCleanErrors(inner) { return function (...args) { return sortErrors(inner.apply(this, args)); }; } const validateStyle = validateStyleMin; const validateLight$1 = validateStyle.light; const validatePaintProperty$1 = validateStyle.paintProperty; const validateLayoutProperty$1 = validateStyle.layoutProperty; function emitValidationErrors(emitter, errors) { let hasErrors = false; if (errors && errors.length) { for (const error of errors) { emitter.fire(new ErrorEvent(new Error(error.message))); hasErrors = true; } } return hasErrors; } var gridIndex = GridIndex; var NUM_PARAMS = 3; function GridIndex(extent, n, padding) { var cells = this.cells = []; if (extent instanceof ArrayBuffer) { this.arrayBuffer = extent; var array = new Int32Array(this.arrayBuffer); extent = array[0]; n = array[1]; padding = array[2]; this.d = n + 2 * padding; for (var k = 0; k < this.d * this.d; k++) { var start = array[NUM_PARAMS + k]; var end = array[NUM_PARAMS + k + 1]; cells.push(start === end ? null : array.subarray(start, end)); } var keysOffset = array[NUM_PARAMS + cells.length]; var bboxesOffset = array[NUM_PARAMS + cells.length + 1]; this.keys = array.subarray(keysOffset, bboxesOffset); this.bboxes = array.subarray(bboxesOffset); this.insert = this._insertReadonly; } else { this.d = n + 2 * padding; for (var i = 0; i < this.d * this.d; i++) { cells.push([]); } this.keys = []; this.bboxes = []; } this.n = n; this.extent = extent; this.padding = padding; this.scale = n / extent; this.uid = 0; var p = padding / n * extent; this.min = -p; this.max = extent + p; } GridIndex.prototype.insert = function (key, x1, y1, x2, y2) { this._forEachCell(x1, y1, x2, y2, this._insertCell, this.uid++); this.keys.push(key); this.bboxes.push(x1); this.bboxes.push(y1); this.bboxes.push(x2); this.bboxes.push(y2); }; GridIndex.prototype._insertReadonly = function () { throw 'Cannot insert into a GridIndex created from an ArrayBuffer.'; }; GridIndex.prototype._insertCell = function (x1, y1, x2, y2, cellIndex, uid) { this.cells[cellIndex].push(uid); }; GridIndex.prototype.query = function (x1, y1, x2, y2, intersectionTest) { var min = this.min; var max = this.max; if (x1 <= min && y1 <= min && max <= x2 && max <= y2 && !intersectionTest) { return Array.prototype.slice.call(this.keys); } else { var result = []; var seenUids = {}; this._forEachCell(x1, y1, x2, y2, this._queryCell, result, seenUids, intersectionTest); return result; } }; GridIndex.prototype._queryCell = function (x1, y1, x2, y2, cellIndex, result, seenUids, intersectionTest) { var cell = this.cells[cellIndex]; if (cell !== null) { var keys = this.keys; var bboxes = this.bboxes; for (var u = 0; u < cell.length; u++) { var uid = cell[u]; if (seenUids[uid] === undefined) { var offset = uid * 4; if (intersectionTest ? intersectionTest(bboxes[offset + 0], bboxes[offset + 1], bboxes[offset + 2], bboxes[offset + 3]) : x1 <= bboxes[offset + 2] && y1 <= bboxes[offset + 3] && x2 >= bboxes[offset + 0] && y2 >= bboxes[offset + 1]) { seenUids[uid] = true; result.push(keys[uid]); } else { seenUids[uid] = false; } } } } }; GridIndex.prototype._forEachCell = function (x1, y1, x2, y2, fn, arg1, arg2, intersectionTest) { var cx1 = this._convertToCellCoord(x1); var cy1 = this._convertToCellCoord(y1); var cx2 = this._convertToCellCoord(x2); var cy2 = this._convertToCellCoord(y2); for (var x = cx1; x <= cx2; x++) { for (var y = cy1; y <= cy2; y++) { var cellIndex = this.d * y + x; if (intersectionTest && !intersectionTest(this._convertFromCellCoord(x), this._convertFromCellCoord(y), this._convertFromCellCoord(x + 1), this._convertFromCellCoord(y + 1))) continue; if (fn.call(this, x1, y1, x2, y2, cellIndex, arg1, arg2, intersectionTest)) return; } } }; GridIndex.prototype._convertFromCellCoord = function (x) { return (x - this.padding) / this.scale; }; GridIndex.prototype._convertToCellCoord = function (x) { return Math.max(0, Math.min(this.d - 1, Math.floor(x * this.scale) + this.padding)); }; GridIndex.prototype.toArrayBuffer = function () { if (this.arrayBuffer) return this.arrayBuffer; var cells = this.cells; var metadataLength = NUM_PARAMS + this.cells.length + 1 + 1; var totalCellLength = 0; for (var i = 0; i < this.cells.length; i++) { totalCellLength += this.cells[i].length; } var array = new Int32Array(metadataLength + totalCellLength + this.keys.length + this.bboxes.length); array[0] = this.extent; array[1] = this.n; array[2] = this.padding; var offset = metadataLength; for (var k = 0; k < cells.length; k++) { var cell = cells[k]; array[NUM_PARAMS + k] = offset; array.set(cell, offset); offset += cell.length; } array[NUM_PARAMS + cells.length] = offset; array.set(this.keys, offset); offset += this.keys.length; array[NUM_PARAMS + cells.length + 1] = offset; array.set(this.bboxes, offset); offset += this.bboxes.length; return array.buffer; }; const {ImageData, ImageBitmap} = window$1; const registry = {}; function register(name, klass, options = {}) { Object.defineProperty(klass, '_classRegistryKey', { value: name, writeable: false }); registry[name] = { klass, omit: options.omit || [], shallow: options.shallow || [] }; } register('Object', Object); gridIndex.serialize = function serialize(grid, transferables) { const buffer = grid.toArrayBuffer(); if (transferables) { transferables.push(buffer); } return { buffer }; }; gridIndex.deserialize = function deserialize(serialized) { return new gridIndex(serialized.buffer); }; register('Grid', gridIndex); register('Color', Color); register('Error', Error); register('ResolvedImage', ResolvedImage); register('StylePropertyFunction', StylePropertyFunction); register('StyleExpression', StyleExpression, { omit: ['_evaluator'] }); register('ZoomDependentExpression', ZoomDependentExpression); register('ZoomConstantExpression', ZoomConstantExpression); register('CompoundExpression', CompoundExpression, { omit: ['_evaluate'] }); for (const name in expressions) { if (expressions[name]._classRegistryKey) continue; register(`Expression_${ name }`, expressions[name]); } function isArrayBuffer(val) { return val && typeof ArrayBuffer !== 'undefined' && (val instanceof ArrayBuffer || val.constructor && val.constructor.name === 'ArrayBuffer'); } function isImageBitmap(val) { return ImageBitmap && val instanceof ImageBitmap; } function serialize(input, transferables) { if (input === null || input === undefined || typeof input === 'boolean' || typeof input === 'number' || typeof input === 'string' || input instanceof Boolean || input instanceof Number || input instanceof String || input instanceof Date || input instanceof RegExp) { return input; } if (isArrayBuffer(input) || isImageBitmap(input)) { if (transferables) { transferables.push(input); } return input; } if (ArrayBuffer.isView(input)) { const view = input; if (transferables) { transferables.push(view.buffer); } return view; } if (input instanceof ImageData) { if (transferables) { transferables.push(input.data.buffer); } return input; } if (Array.isArray(input)) { const serialized = []; for (const item of input) { serialized.push(serialize(item, transferables)); } return serialized; } if (typeof input === 'object') { const klass = input.constructor; const name = klass._classRegistryKey; if (!name) { throw new Error(`can't serialize object of unregistered class`); } const properties = klass.serialize ? klass.serialize(input, transferables) : {}; if (!klass.serialize) { for (const key in input) { if (!input.hasOwnProperty(key)) continue; if (registry[name].omit.indexOf(key) >= 0) continue; const property = input[key]; properties[key] = registry[name].shallow.indexOf(key) >= 0 ? property : serialize(property, transferables); } if (input instanceof Error) { properties.message = input.message; } } if (properties.$name) { throw new Error('$name property is reserved for worker serialization logic.'); } if (name !== 'Object') { properties.$name = name; } return properties; } throw new Error(`can't serialize object of type ${ typeof input }`); } function deserialize(input) { if (input === null || input === undefined || typeof input === 'boolean' || typeof input === 'number' || typeof input === 'string' || input instanceof Boolean || input instanceof Number || input instanceof String || input instanceof Date || input instanceof RegExp || isArrayBuffer(input) || isImageBitmap(input) || ArrayBuffer.isView(input) || input instanceof ImageData) { return input; } if (Array.isArray(input)) { return input.map(deserialize); } if (typeof input === 'object') { const name = input.$name || 'Object'; const {klass} = registry[name]; if (!klass) { throw new Error(`can't deserialize unregistered class ${ name }`); } if (klass.deserialize) { return klass.deserialize(input); } const result = Object.create(klass.prototype); for (const key of Object.keys(input)) { if (key === '$name') continue; const value = input[key]; result[key] = registry[name].shallow.indexOf(key) >= 0 ? value : deserialize(value); } return result; } throw new Error(`can't deserialize object of type ${ typeof input }`); } class ZoomHistory { constructor() { this.first = true; } update(z, now) { const floorZ = Math.floor(z); if (this.first) { this.first = false; this.lastIntegerZoom = floorZ; this.lastIntegerZoomTime = 0; this.lastZoom = z; this.lastFloorZoom = floorZ; return true; } if (this.lastFloorZoom > floorZ) { this.lastIntegerZoom = floorZ + 1; this.lastIntegerZoomTime = now; } else if (this.lastFloorZoom < floorZ) { this.lastIntegerZoom = floorZ; this.lastIntegerZoomTime = now; } if (z !== this.lastZoom) { this.lastZoom = z; this.lastFloorZoom = floorZ; return true; } return false; } } const unicodeBlockLookup = { 'Latin-1 Supplement': char => char >= 128 && char <= 255, 'Arabic': char => char >= 1536 && char <= 1791, 'Arabic Supplement': char => char >= 1872 && char <= 1919, 'Arabic Extended-A': char => char >= 2208 && char <= 2303, 'Hangul Jamo': char => char >= 4352 && char <= 4607, 'Unified Canadian Aboriginal Syllabics': char => char >= 5120 && char <= 5759, 'Khmer': char => char >= 6016 && char <= 6143, 'Unified Canadian Aboriginal Syllabics Extended': char => char >= 6320 && char <= 6399, 'General Punctuation': char => char >= 8192 && char <= 8303, 'Letterlike Symbols': char => char >= 8448 && char <= 8527, 'Number Forms': char => char >= 8528 && char <= 8591, 'Miscellaneous Technical': char => char >= 8960 && char <= 9215, 'Control Pictures': char => char >= 9216 && char <= 9279, 'Optical Character Recognition': char => char >= 9280 && char <= 9311, 'Enclosed Alphanumerics': char => char >= 9312 && char <= 9471, 'Geometric Shapes': char => char >= 9632 && char <= 9727, 'Miscellaneous Symbols': char => char >= 9728 && char <= 9983, 'Miscellaneous Symbols and Arrows': char => char >= 11008 && char <= 11263, 'CJK Radicals Supplement': char => char >= 11904 && char <= 12031, 'Kangxi Radicals': char => char >= 12032 && char <= 12255, 'Ideographic Description Characters': char => char >= 12272 && char <= 12287, 'CJK Symbols and Punctuation': char => char >= 12288 && char <= 12351, 'Hiragana': char => char >= 12352 && char <= 12447, 'Katakana': char => char >= 12448 && char <= 12543, 'Bopomofo': char => char >= 12544 && char <= 12591, 'Hangul Compatibility Jamo': char => char >= 12592 && char <= 12687, 'Kanbun': char => char >= 12688 && char <= 12703, 'Bopomofo Extended': char => char >= 12704 && char <= 12735, 'CJK Strokes': char => char >= 12736 && char <= 12783, 'Katakana Phonetic Extensions': char => char >= 12784 && char <= 12799, 'Enclosed CJK Letters and Months': char => char >= 12800 && char <= 13055, 'CJK Compatibility': char => char >= 13056 && char <= 13311, 'CJK Unified Ideographs Extension A': char => char >= 13312 && char <= 19903, 'Yijing Hexagram Symbols': char => char >= 19904 && char <= 19967, 'CJK Unified Ideographs': char => char >= 19968 && char <= 40959, 'Yi Syllables': char => char >= 40960 && char <= 42127, 'Yi Radicals': char => char >= 42128 && char <= 42191, 'Hangul Jamo Extended-A': char => char >= 43360 && char <= 43391, 'Hangul Syllables': char => char >= 44032 && char <= 55215, 'Hangul Jamo Extended-B': char => char >= 55216 && char <= 55295, 'Private Use Area': char => char >= 57344 && char <= 63743, 'CJK Compatibility Ideographs': char => char >= 63744 && char <= 64255, 'Arabic Presentation Forms-A': char => char >= 64336 && char <= 65023, 'Vertical Forms': char => char >= 65040 && char <= 65055, 'CJK Compatibility Forms': char => char >= 65072 && char <= 65103, 'Small Form Variants': char => char >= 65104 && char <= 65135, 'Arabic Presentation Forms-B': char => char >= 65136 && char <= 65279, 'Halfwidth and Fullwidth Forms': char => char >= 65280 && char <= 65519 }; function allowsVerticalWritingMode(chars) { for (const char of chars) { if (charHasUprightVerticalOrientation(char.charCodeAt(0))) return true; } return false; } function allowsLetterSpacing(chars) { for (const char of chars) { if (!charAllowsLetterSpacing(char.charCodeAt(0))) return false; } return true; } function charAllowsLetterSpacing(char) { if (unicodeBlockLookup['Arabic'](char)) return false; if (unicodeBlockLookup['Arabic Supplement'](char)) return false; if (unicodeBlockLookup['Arabic Extended-A'](char)) return false; if (unicodeBlockLookup['Arabic Presentation Forms-A'](char)) return false; if (unicodeBlockLookup['Arabic Presentation Forms-B'](char)) return false; return true; } function charAllowsIdeographicBreaking(char) { if (char < 11904) return false; if (unicodeBlockLookup['Bopomofo Extended'](char)) return true; if (unicodeBlockLookup['Bopomofo'](char)) return true; if (unicodeBlockLookup['CJK Compatibility Forms'](char)) return true; if (unicodeBlockLookup['CJK Compatibility Ideographs'](char)) return true; if (unicodeBlockLookup['CJK Compatibility'](char)) return true; if (unicodeBlockLookup['CJK Radicals Supplement'](char)) return true; if (unicodeBlockLookup['CJK Strokes'](char)) return true; if (unicodeBlockLookup['CJK Symbols and Punctuation'](char)) return true; if (unicodeBlockLookup['CJK Unified Ideographs Extension A'](char)) return true; if (unicodeBlockLookup['CJK Unified Ideographs'](char)) return true; if (unicodeBlockLookup['Enclosed CJK Letters and Months'](char)) return true; if (unicodeBlockLookup['Halfwidth and Fullwidth Forms'](char)) return true; if (unicodeBlockLookup['Hiragana'](char)) return true; if (unicodeBlockLookup['Ideographic Description Characters'](char)) return true; if (unicodeBlockLookup['Kangxi Radicals'](char)) return true; if (unicodeBlockLookup['Katakana Phonetic Extensions'](char)) return true; if (unicodeBlockLookup['Katakana'](char)) return true; if (unicodeBlockLookup['Vertical Forms'](char)) return true; if (unicodeBlockLookup['Yi Radicals'](char)) return true; if (unicodeBlockLookup['Yi Syllables'](char)) return true; return false; } function charHasUprightVerticalOrientation(char) { if (char === 746 || char === 747) { return true; } if (char < 4352) return false; if (unicodeBlockLookup['Bopomofo Extended'](char)) return true; if (unicodeBlockLookup['Bopomofo'](char)) return true; if (unicodeBlockLookup['CJK Compatibility Forms'](char)) { if (!(char >= 65097 && char <= 65103)) { return true; } } if (unicodeBlockLookup['CJK Compatibility Ideographs'](char)) return true; if (unicodeBlockLookup['CJK Compatibility'](char)) return true; if (unicodeBlockLookup['CJK Radicals Supplement'](char)) return true; if (unicodeBlockLookup['CJK Strokes'](char)) return true; if (unicodeBlockLookup['CJK Symbols and Punctuation'](char)) { if (!(char >= 12296 && char <= 12305) && !(char >= 12308 && char <= 12319) && char !== 12336) { return true; } } if (unicodeBlockLookup['CJK Unified Ideographs Extension A'](char)) return true; if (unicodeBlockLookup['CJK Unified Ideographs'](char)) return true; if (unicodeBlockLookup['Enclosed CJK Letters and Months'](char)) return true; if (unicodeBlockLookup['Hangul Compatibility Jamo'](char)) return true; if (unicodeBlockLookup['Hangul Jamo Extended-A'](char)) return true; if (unicodeBlockLookup['Hangul Jamo Extended-B'](char)) return true; if (unicodeBlockLookup['Hangul Jamo'](char)) return true; if (unicodeBlockLookup['Hangul Syllables'](char)) return true; if (unicodeBlockLookup['Hiragana'](char)) return true; if (unicodeBlockLookup['Ideographic Description Characters'](char)) return true; if (unicodeBlockLookup['Kanbun'](char)) return true; if (unicodeBlockLookup['Kangxi Radicals'](char)) return true; if (unicodeBlockLookup['Katakana Phonetic Extensions'](char)) return true; if (unicodeBlockLookup['Katakana'](char)) { if (char !== 12540) { return true; } } if (unicodeBlockLookup['Halfwidth and Fullwidth Forms'](char)) { if (char !== 65288 && char !== 65289 && char !== 65293 && !(char >= 65306 && char <= 65310) && char !== 65339 && char !== 65341 && char !== 65343 && !(char >= 65371 && char <= 65503) && char !== 65507 && !(char >= 65512 && char <= 65519)) { return true; } } if (unicodeBlockLookup['Small Form Variants'](char)) { if (!(char >= 65112 && char <= 65118) && !(char >= 65123 && char <= 65126)) { return true; } } if (unicodeBlockLookup['Unified Canadian Aboriginal Syllabics'](char)) return true; if (unicodeBlockLookup['Unified Canadian Aboriginal Syllabics Extended'](char)) return true; if (unicodeBlockLookup['Vertical Forms'](char)) return true; if (unicodeBlockLookup['Yijing Hexagram Symbols'](char)) return true; if (unicodeBlockLookup['Yi Syllables'](char)) return true; if (unicodeBlockLookup['Yi Radicals'](char)) return true; return false; } function charHasNeutralVerticalOrientation(char) { if (unicodeBlockLookup['Latin-1 Supplement'](char)) { if (char === 167 || char === 169 || char === 174 || char === 177 || char === 188 || char === 189 || char === 190 || char === 215 || char === 247) { return true; } } if (unicodeBlockLookup['General Punctuation'](char)) { if (char === 8214 || char === 8224 || char === 8225 || char === 8240 || char === 8241 || char === 8251 || char === 8252 || char === 8258 || char === 8263 || char === 8264 || char === 8265 || char === 8273) { return true; } } if (unicodeBlockLookup['Letterlike Symbols'](char)) return true; if (unicodeBlockLookup['Number Forms'](char)) return true; if (unicodeBlockLookup['Miscellaneous Technical'](char)) { if (char >= 8960 && char <= 8967 || char >= 8972 && char <= 8991 || char >= 8996 && char <= 9000 || char === 9003 || char >= 9085 && char <= 9114 || char >= 9150 && char <= 9165 || char === 9167 || char >= 9169 && char <= 9179 || char >= 9186 && char <= 9215) { return true; } } if (unicodeBlockLookup['Control Pictures'](char) && char !== 9251) return true; if (unicodeBlockLookup['Optical Character Recognition'](char)) return true; if (unicodeBlockLookup['Enclosed Alphanumerics'](char)) return true; if (unicodeBlockLookup['Geometric Shapes'](char)) return true; if (unicodeBlockLookup['Miscellaneous Symbols'](char)) { if (!(char >= 9754 && char <= 9759)) { return true; } } if (unicodeBlockLookup['Miscellaneous Symbols and Arrows'](char)) { if (char >= 11026 && char <= 11055 || char >= 11088 && char <= 11097 || char >= 11192 && char <= 11243) { return true; } } if (unicodeBlockLookup['CJK Symbols and Punctuation'](char)) return true; if (unicodeBlockLookup['Katakana'](char)) return true; if (unicodeBlockLookup['Private Use Area'](char)) return true; if (unicodeBlockLookup['CJK Compatibility Forms'](char)) return true; if (unicodeBlockLookup['Small Form Variants'](char)) return true; if (unicodeBlockLookup['Halfwidth and Fullwidth Forms'](char)) return true; if (char === 8734 || char === 8756 || char === 8757 || char >= 9984 && char <= 10087 || char >= 10102 && char <= 10131 || char === 65532 || char === 65533) { return true; } return false; } function charHasRotatedVerticalOrientation(char) { return !(charHasUprightVerticalOrientation(char) || charHasNeutralVerticalOrientation(char)); } function charInComplexShapingScript(char) { return unicodeBlockLookup['Arabic'](char) || unicodeBlockLookup['Arabic Supplement'](char) || unicodeBlockLookup['Arabic Extended-A'](char) || unicodeBlockLookup['Arabic Presentation Forms-A'](char) || unicodeBlockLookup['Arabic Presentation Forms-B'](char); } function charInRTLScript(char) { return char >= 1424 && char <= 2303 || unicodeBlockLookup['Arabic Presentation Forms-A'](char) || unicodeBlockLookup['Arabic Presentation Forms-B'](char); } function charInSupportedScript(char, canRenderRTL) { if (!canRenderRTL && charInRTLScript(char)) { return false; } if (char >= 2304 && char <= 3583 || char >= 3840 && char <= 4255 || unicodeBlockLookup['Khmer'](char)) { return false; } return true; } function stringContainsRTLText(chars) { for (const char of chars) { if (charInRTLScript(char.charCodeAt(0))) { return true; } } return false; } function isStringInSupportedScript(chars, canRenderRTL) { for (const char of chars) { if (!charInSupportedScript(char.charCodeAt(0), canRenderRTL)) { return false; } } return true; } const status = { unavailable: 'unavailable', deferred: 'deferred', loading: 'loading', loaded: 'loaded', error: 'error' }; let _completionCallback = null; let pluginStatus = status.unavailable; let pluginURL = null; const triggerPluginCompletionEvent = function (error) { if (error && typeof error === 'string' && error.indexOf('NetworkError') > -1) { pluginStatus = status.error; } if (_completionCallback) { _completionCallback(error); } }; function sendPluginStateToWorker() { evented.fire(new Event('pluginStateChange', { pluginStatus, pluginURL })); } const evented = new Evented(); const getRTLTextPluginStatus = function () { return pluginStatus; }; const registerForPluginStateChange = function (callback) { callback({ pluginStatus, pluginURL }); evented.on('pluginStateChange', callback); return callback; }; const setRTLTextPlugin = function (url, callback, deferred = false) { if (pluginStatus === status.deferred || pluginStatus === status.loading || pluginStatus === status.loaded) { throw new Error('setRTLTextPlugin cannot be called multiple times.'); } pluginURL = exported.resolveURL(url); pluginStatus = status.deferred; _completionCallback = callback; sendPluginStateToWorker(); if (!deferred) { downloadRTLTextPlugin(); } }; const downloadRTLTextPlugin = function () { if (pluginStatus !== status.deferred || !pluginURL) { throw new Error('rtl-text-plugin cannot be downloaded unless a pluginURL is specified'); } pluginStatus = status.loading; sendPluginStateToWorker(); if (pluginURL) { getArrayBuffer({ url: pluginURL }, error => { if (error) { triggerPluginCompletionEvent(error); } else { pluginStatus = status.loaded; sendPluginStateToWorker(); } }); } }; const plugin = { applyArabicShaping: null, processBidirectionalText: null, processStyledBidirectionalText: null, isLoaded() { return pluginStatus === status.loaded || plugin.applyArabicShaping != null; }, isLoading() { return pluginStatus === status.loading; }, setState(state) { pluginStatus = state.pluginStatus; pluginURL = state.pluginURL; }, isParsed() { return plugin.applyArabicShaping != null && plugin.processBidirectionalText != null && plugin.processStyledBidirectionalText != null; }, getPluginURL() { return pluginURL; } }; const lazyLoadRTLTextPlugin = function () { if (!plugin.isLoading() && !plugin.isLoaded() && getRTLTextPluginStatus() === 'deferred') { downloadRTLTextPlugin(); } }; class EvaluationParameters { constructor(zoom, options) { this.zoom = zoom; if (options) { this.now = options.now; this.fadeDuration = options.fadeDuration; this.zoomHistory = options.zoomHistory; this.transition = options.transition; } else { this.now = 0; this.fadeDuration = 0; this.zoomHistory = new ZoomHistory(); this.transition = {}; } } isSupportedScript(str) { return isStringInSupportedScript(str, plugin.isLoaded()); } crossFadingFactor() { if (this.fadeDuration === 0) { return 1; } else { return Math.min((this.now - this.zoomHistory.lastIntegerZoomTime) / this.fadeDuration, 1); } } getCrossfadeParameters() { const z = this.zoom; const fraction = z - Math.floor(z); const t = this.crossFadingFactor(); return z > this.zoomHistory.lastIntegerZoom ? { fromScale: 2, toScale: 1, t: fraction + (1 - fraction) * t } : { fromScale: 0.5, toScale: 1, t: 1 - (1 - t) * fraction }; } } class PropertyValue { constructor(property, value) { this.property = property; this.value = value; this.expression = normalizePropertyExpression(value === undefined ? property.specification.default : value, property.specification); } isDataDriven() { return this.expression.kind === 'source' || this.expression.kind === 'composite'; } possiblyEvaluate(parameters, canonical, availableImages) { return this.property.possiblyEvaluate(this, parameters, canonical, availableImages); } } class TransitionablePropertyValue { constructor(property) { this.property = property; this.value = new PropertyValue(property, undefined); } transitioned(parameters, prior) { return new TransitioningPropertyValue(this.property, this.value, prior, extend({}, parameters.transition, this.transition), parameters.now); } untransitioned() { return new TransitioningPropertyValue(this.property, this.value, null, {}, 0); } } class Transitionable { constructor(properties) { this._properties = properties; this._values = Object.create(properties.defaultTransitionablePropertyValues); } getValue(name) { return clone(this._values[name].value.value); } setValue(name, value) { if (!this._values.hasOwnProperty(name)) { this._values[name] = new TransitionablePropertyValue(this._values[name].property); } this._values[name].value = new PropertyValue(this._values[name].property, value === null ? undefined : clone(value)); } getTransition(name) { return clone(this._values[name].transition); } setTransition(name, value) { if (!this._values.hasOwnProperty(name)) { this._values[name] = new TransitionablePropertyValue(this._values[name].property); } this._values[name].transition = clone(value) || undefined; } serialize() { const result = {}; for (const property of Object.keys(this._values)) { const value = this.getValue(property); if (value !== undefined) { result[property] = value; } const transition = this.getTransition(property); if (transition !== undefined) { result[`${ property }-transition`] = transition; } } return result; } transitioned(parameters, prior) { const result = new Transitioning(this._properties); for (const property of Object.keys(this._values)) { result._values[property] = this._values[property].transitioned(parameters, prior._values[property]); } return result; } untransitioned() { const result = new Transitioning(this._properties); for (const property of Object.keys(this._values)) { result._values[property] = this._values[property].untransitioned(); } return result; } } class TransitioningPropertyValue { constructor(property, value, prior, transition, now) { this.property = property; this.value = value; this.begin = now + transition.delay || 0; this.end = this.begin + transition.duration || 0; if (property.specification.transition && (transition.delay || transition.duration)) { this.prior = prior; } } possiblyEvaluate(parameters, canonical, availableImages) { const now = parameters.now || 0; const finalValue = this.value.possiblyEvaluate(parameters, canonical, availableImages); const prior = this.prior; if (!prior) { return finalValue; } else if (now > this.end) { this.prior = null; return finalValue; } else if (this.value.isDataDriven()) { this.prior = null; return finalValue; } else if (now < this.begin) { return prior.possiblyEvaluate(parameters, canonical, availableImages); } else { const t = (now - this.begin) / (this.end - this.begin); return this.property.interpolate(prior.possiblyEvaluate(parameters, canonical, availableImages), finalValue, easeCubicInOut(t)); } } } class Transitioning { constructor(properties) { this._properties = properties; this._values = Object.create(properties.defaultTransitioningPropertyValues); } possiblyEvaluate(parameters, canonical, availableImages) { const result = new PossiblyEvaluated(this._properties); for (const property of Object.keys(this._values)) { result._values[property] = this._values[property].possiblyEvaluate(parameters, canonical, availableImages); } return result; } hasTransition() { for (const property of Object.keys(this._values)) { if (this._values[property].prior) { return true; } } return false; } } class Layout { constructor(properties) { this._properties = properties; this._values = Object.create(properties.defaultPropertyValues); } getValue(name) { return clone(this._values[name].value); } setValue(name, value) { this._values[name] = new PropertyValue(this._values[name].property, value === null ? undefined : clone(value)); } serialize() { const result = {}; for (const property of Object.keys(this._values)) { const value = this.getValue(property); if (value !== undefined) { result[property] = value; } } return result; } possiblyEvaluate(parameters, canonical, availableImages) { const result = new PossiblyEvaluated(this._properties); for (const property of Object.keys(this._values)) { result._values[property] = this._values[property].possiblyEvaluate(parameters, canonical, availableImages); } return result; } } class PossiblyEvaluatedPropertyValue { constructor(property, value, parameters) { this.property = property; this.value = value; this.parameters = parameters; } isConstant() { return this.value.kind === 'constant'; } constantOr(value) { if (this.value.kind === 'constant') { return this.value.value; } else { return value; } } evaluate(feature, featureState, canonical, availableImages) { return this.property.evaluate(this.value, this.parameters, feature, featureState, canonical, availableImages); } } class PossiblyEvaluated { constructor(properties) { this._properties = properties; this._values = Object.create(properties.defaultPossiblyEvaluatedValues); } get(name) { return this._values[name]; } } class DataConstantProperty { constructor(specification) { this.specification = specification; } possiblyEvaluate(value, parameters) { return value.expression.evaluate(parameters); } interpolate(a, b, t) { const interp = interpolate[this.specification.type]; if (interp) { return interp(a, b, t); } else { return a; } } } class DataDrivenProperty { constructor(specification, overrides) { this.specification = specification; this.overrides = overrides; } possiblyEvaluate(value, parameters, canonical, availableImages) { if (value.expression.kind === 'constant' || value.expression.kind === 'camera') { return new PossiblyEvaluatedPropertyValue(this, { kind: 'constant', value: value.expression.evaluate(parameters, null, {}, canonical, availableImages) }, parameters); } else { return new PossiblyEvaluatedPropertyValue(this, value.expression, parameters); } } interpolate(a, b, t) { if (a.value.kind !== 'constant' || b.value.kind !== 'constant') { return a; } if (a.value.value === undefined || b.value.value === undefined) { return new PossiblyEvaluatedPropertyValue(this, { kind: 'constant', value: undefined }, a.parameters); } const interp = interpolate[this.specification.type]; if (interp) { return new PossiblyEvaluatedPropertyValue(this, { kind: 'constant', value: interp(a.value.value, b.value.value, t) }, a.parameters); } else { return a; } } evaluate(value, parameters, feature, featureState, canonical, availableImages) { if (value.kind === 'constant') { return value.value; } else { return value.evaluate(parameters, feature, featureState, canonical, availableImages); } } } class CrossFadedDataDrivenProperty extends DataDrivenProperty { possiblyEvaluate(value, parameters, canonical, availableImages) { if (value.value === undefined) { return new PossiblyEvaluatedPropertyValue(this, { kind: 'constant', value: undefined }, parameters); } else if (value.expression.kind === 'constant') { const evaluatedValue = value.expression.evaluate(parameters, null, {}, canonical, availableImages); const isImageExpression = value.property.specification.type === 'resolvedImage'; const constantValue = isImageExpression && typeof evaluatedValue !== 'string' ? evaluatedValue.name : evaluatedValue; const constant = this._calculate(constantValue, constantValue, constantValue, parameters); return new PossiblyEvaluatedPropertyValue(this, { kind: 'constant', value: constant }, parameters); } else if (value.expression.kind === 'camera') { const cameraVal = this._calculate(value.expression.evaluate({ zoom: parameters.zoom - 1 }), value.expression.evaluate({ zoom: parameters.zoom }), value.expression.evaluate({ zoom: parameters.zoom + 1 }), parameters); return new PossiblyEvaluatedPropertyValue(this, { kind: 'constant', value: cameraVal }, parameters); } else { return new PossiblyEvaluatedPropertyValue(this, value.expression, parameters); } } evaluate(value, globals, feature, featureState, canonical, availableImages) { if (value.kind === 'source') { const constant = value.evaluate(globals, feature, featureState, canonical, availableImages); return this._calculate(constant, constant, constant, globals); } else if (value.kind === 'composite') { return this._calculate(value.evaluate({ zoom: Math.floor(globals.zoom) - 1 }, feature, featureState), value.evaluate({ zoom: Math.floor(globals.zoom) }, feature, featureState), value.evaluate({ zoom: Math.floor(globals.zoom) + 1 }, feature, featureState), globals); } else { return value.value; } } _calculate(min, mid, max, parameters) { const z = parameters.zoom; return z > parameters.zoomHistory.lastIntegerZoom ? { from: min, to: mid } : { from: max, to: mid }; } interpolate(a) { return a; } } class CrossFadedProperty { constructor(specification) { this.specification = specification; } possiblyEvaluate(value, parameters, canonical, availableImages) { if (value.value === undefined) { return undefined; } else if (value.expression.kind === 'constant') { const constant = value.expression.evaluate(parameters, null, {}, canonical, availableImages); return this._calculate(constant, constant, constant, parameters); } else { return this._calculate(value.expression.evaluate(new EvaluationParameters(Math.floor(parameters.zoom - 1), parameters)), value.expression.evaluate(new EvaluationParameters(Math.floor(parameters.zoom), parameters)), value.expression.evaluate(new EvaluationParameters(Math.floor(parameters.zoom + 1), parameters)), parameters); } } _calculate(min, mid, max, parameters) { const z = parameters.zoom; return z > parameters.zoomHistory.lastIntegerZoom ? { from: min, to: mid } : { from: max, to: mid }; } interpolate(a) { return a; } } class ColorRampProperty { constructor(specification) { this.specification = specification; } possiblyEvaluate(value, parameters, canonical, availableImages) { return !!value.expression.evaluate(parameters, null, {}, canonical, availableImages); } interpolate() { return false; } } class Properties { constructor(properties) { this.properties = properties; this.defaultPropertyValues = {}; this.defaultTransitionablePropertyValues = {}; this.defaultTransitioningPropertyValues = {}; this.defaultPossiblyEvaluatedValues = {}; this.overridableProperties = []; for (const property in properties) { const prop = properties[property]; if (prop.specification.overridable) { this.overridableProperties.push(property); } const defaultPropertyValue = this.defaultPropertyValues[property] = new PropertyValue(prop, undefined); const defaultTransitionablePropertyValue = this.defaultTransitionablePropertyValues[property] = new TransitionablePropertyValue(prop); this.defaultTransitioningPropertyValues[property] = defaultTransitionablePropertyValue.untransitioned(); this.defaultPossiblyEvaluatedValues[property] = defaultPropertyValue.possiblyEvaluate({}); } } } register('DataDrivenProperty', DataDrivenProperty); register('DataConstantProperty', DataConstantProperty); register('CrossFadedDataDrivenProperty', CrossFadedDataDrivenProperty); register('CrossFadedProperty', CrossFadedProperty); register('ColorRampProperty', ColorRampProperty); function packUint8ToFloat(a, b) { a = clamp(Math.floor(a), 0, 255); b = clamp(Math.floor(b), 0, 255); return 256 * a + b; } const viewTypes = { 'Int8': Int8Array, 'Uint8': Uint8Array, 'Int16': Int16Array, 'Uint16': Uint16Array, 'Int32': Int32Array, 'Uint32': Uint32Array, 'Float32': Float32Array }; class Struct { constructor(structArray, index) { this._structArray = structArray; this._pos1 = index * this.size; this._pos2 = this._pos1 / 2; this._pos4 = this._pos1 / 4; this._pos8 = this._pos1 / 8; } } const DEFAULT_CAPACITY = 128; const RESIZE_MULTIPLIER = 5; class StructArray { constructor() { this.isTransferred = false; this.capacity = -1; this.resize(0); } static serialize(array, transferables) { array._trim(); if (transferables) { array.isTransferred = true; transferables.push(array.arrayBuffer); } return { length: array.length, arrayBuffer: array.arrayBuffer }; } static deserialize(input) { const structArray = Object.create(this.prototype); structArray.arrayBuffer = input.arrayBuffer; structArray.length = input.length; structArray.capacity = input.arrayBuffer.byteLength / structArray.bytesPerElement; structArray._refreshViews(); return structArray; } _trim() { if (this.length !== this.capacity) { this.capacity = this.length; this.arrayBuffer = this.arrayBuffer.slice(0, this.length * this.bytesPerElement); this._refreshViews(); } } clear() { this.length = 0; } resize(n) { this.reserve(n); this.length = n; } reserve(n) { if (n > this.capacity) { this.capacity = Math.max(n, Math.floor(this.capacity * RESIZE_MULTIPLIER), DEFAULT_CAPACITY); this.arrayBuffer = new ArrayBuffer(this.capacity * this.bytesPerElement); const oldUint8Array = this.uint8; this._refreshViews(); if (oldUint8Array) this.uint8.set(oldUint8Array); } } _refreshViews() { throw new Error('_refreshViews() must be implemented by each concrete StructArray layout'); } } function createLayout(members, alignment = 1) { let offset = 0; let maxSize = 0; const layoutMembers = members.map(member => { const typeSize = sizeOf(member.type); const memberOffset = offset = align(offset, Math.max(alignment, typeSize)); const components = member.components || 1; maxSize = Math.max(maxSize, typeSize); offset += typeSize * components; return { name: member.name, type: member.type, components, offset: memberOffset }; }); const size = align(offset, Math.max(maxSize, alignment)); return { members: layoutMembers, size, alignment }; } function sizeOf(type) { return viewTypes[type].BYTES_PER_ELEMENT; } function align(offset, size) { return Math.ceil(offset / size) * size; } class StructArrayLayout2i4 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); } emplaceBack(v0, v1) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1); } emplace(i, v0, v1) { const o2 = i * 2; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; return i; } } StructArrayLayout2i4.prototype.bytesPerElement = 4; register('StructArrayLayout2i4', StructArrayLayout2i4); class StructArrayLayout4i8 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3); } emplace(i, v0, v1, v2, v3) { const o2 = i * 4; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.int16[o2 + 2] = v2; this.int16[o2 + 3] = v3; return i; } } StructArrayLayout4i8.prototype.bytesPerElement = 8; register('StructArrayLayout4i8', StructArrayLayout4i8); class StructArrayLayout2i4ub1f12 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3, v4, v5, v6) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3, v4, v5, v6); } emplace(i, v0, v1, v2, v3, v4, v5, v6) { const o2 = i * 6; const o1 = i * 12; const o4 = i * 3; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.uint8[o1 + 4] = v2; this.uint8[o1 + 5] = v3; this.uint8[o1 + 6] = v4; this.uint8[o1 + 7] = v5; this.float32[o4 + 2] = v6; return i; } } StructArrayLayout2i4ub1f12.prototype.bytesPerElement = 12; register('StructArrayLayout2i4ub1f12', StructArrayLayout2i4ub1f12); class StructArrayLayout2f8 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); } emplaceBack(v0, v1) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1); } emplace(i, v0, v1) { const o4 = i * 2; this.float32[o4 + 0] = v0; this.float32[o4 + 1] = v1; return i; } } StructArrayLayout2f8.prototype.bytesPerElement = 8; register('StructArrayLayout2f8', StructArrayLayout2f8); class StructArrayLayout10ui20 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); } emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9) { const o2 = i * 10; this.uint16[o2 + 0] = v0; this.uint16[o2 + 1] = v1; this.uint16[o2 + 2] = v2; this.uint16[o2 + 3] = v3; this.uint16[o2 + 4] = v4; this.uint16[o2 + 5] = v5; this.uint16[o2 + 6] = v6; this.uint16[o2 + 7] = v7; this.uint16[o2 + 8] = v8; this.uint16[o2 + 9] = v9; return i; } } StructArrayLayout10ui20.prototype.bytesPerElement = 20; register('StructArrayLayout10ui20', StructArrayLayout10ui20); class StructArrayLayout4i4ui4i24 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); } emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) { const o2 = i * 12; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.int16[o2 + 2] = v2; this.int16[o2 + 3] = v3; this.uint16[o2 + 4] = v4; this.uint16[o2 + 5] = v5; this.uint16[o2 + 6] = v6; this.uint16[o2 + 7] = v7; this.int16[o2 + 8] = v8; this.int16[o2 + 9] = v9; this.int16[o2 + 10] = v10; this.int16[o2 + 11] = v11; return i; } } StructArrayLayout4i4ui4i24.prototype.bytesPerElement = 24; register('StructArrayLayout4i4ui4i24', StructArrayLayout4i4ui4i24); class StructArrayLayout3f12 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); } emplaceBack(v0, v1, v2) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2); } emplace(i, v0, v1, v2) { const o4 = i * 3; this.float32[o4 + 0] = v0; this.float32[o4 + 1] = v1; this.float32[o4 + 2] = v2; return i; } } StructArrayLayout3f12.prototype.bytesPerElement = 12; register('StructArrayLayout3f12', StructArrayLayout3f12); class StructArrayLayout1ul4 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.uint32 = new Uint32Array(this.arrayBuffer); } emplaceBack(v0) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0); } emplace(i, v0) { const o4 = i * 1; this.uint32[o4 + 0] = v0; return i; } } StructArrayLayout1ul4.prototype.bytesPerElement = 4; register('StructArrayLayout1ul4', StructArrayLayout1ul4); class StructArrayLayout2i4f1i1ul2ui32 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); this.uint32 = new Uint32Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); } emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9) { const o2 = i * 16; const o4 = i * 8; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.float32[o4 + 1] = v2; this.float32[o4 + 2] = v3; this.float32[o4 + 3] = v4; this.float32[o4 + 4] = v5; this.int16[o2 + 10] = v6; this.uint32[o4 + 6] = v7; this.uint16[o2 + 14] = v8; this.uint16[o2 + 15] = v9; return i; } } StructArrayLayout2i4f1i1ul2ui32.prototype.bytesPerElement = 32; register('StructArrayLayout2i4f1i1ul2ui32', StructArrayLayout2i4f1i1ul2ui32); class StructArrayLayout2i2i2i12 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3, v4, v5) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3, v4, v5); } emplace(i, v0, v1, v2, v3, v4, v5) { const o2 = i * 6; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.int16[o2 + 2] = v2; this.int16[o2 + 3] = v3; this.int16[o2 + 4] = v4; this.int16[o2 + 5] = v5; return i; } } StructArrayLayout2i2i2i12.prototype.bytesPerElement = 12; register('StructArrayLayout2i2i2i12', StructArrayLayout2i2i2i12); class StructArrayLayout2f1f2i16 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3, v4) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3, v4); } emplace(i, v0, v1, v2, v3, v4) { const o4 = i * 4; const o2 = i * 8; this.float32[o4 + 0] = v0; this.float32[o4 + 1] = v1; this.float32[o4 + 2] = v2; this.int16[o2 + 6] = v3; this.int16[o2 + 7] = v4; return i; } } StructArrayLayout2f1f2i16.prototype.bytesPerElement = 16; register('StructArrayLayout2f1f2i16', StructArrayLayout2f1f2i16); class StructArrayLayout2ub2f12 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3); } emplace(i, v0, v1, v2, v3) { const o1 = i * 12; const o4 = i * 3; this.uint8[o1 + 0] = v0; this.uint8[o1 + 1] = v1; this.float32[o4 + 1] = v2; this.float32[o4 + 2] = v3; return i; } } StructArrayLayout2ub2f12.prototype.bytesPerElement = 12; register('StructArrayLayout2ub2f12', StructArrayLayout2ub2f12); class StructArrayLayout3ui6 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2); } emplace(i, v0, v1, v2) { const o2 = i * 3; this.uint16[o2 + 0] = v0; this.uint16[o2 + 1] = v1; this.uint16[o2 + 2] = v2; return i; } } StructArrayLayout3ui6.prototype.bytesPerElement = 6; register('StructArrayLayout3ui6', StructArrayLayout3ui6); class StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); this.uint32 = new Uint32Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16); } emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { const o2 = i * 24; const o4 = i * 12; const o1 = i * 48; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.uint16[o2 + 2] = v2; this.uint16[o2 + 3] = v3; this.uint32[o4 + 2] = v4; this.uint32[o4 + 3] = v5; this.uint32[o4 + 4] = v6; this.uint16[o2 + 10] = v7; this.uint16[o2 + 11] = v8; this.uint16[o2 + 12] = v9; this.float32[o4 + 7] = v10; this.float32[o4 + 8] = v11; this.uint8[o1 + 36] = v12; this.uint8[o1 + 37] = v13; this.uint8[o1 + 38] = v14; this.uint32[o4 + 10] = v15; this.int16[o2 + 22] = v16; return i; } } StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48.prototype.bytesPerElement = 48; register('StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48', StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48); class StructArrayLayout8i15ui1ul4f68 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); this.uint32 = new Uint32Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27); } emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) { const o2 = i * 34; const o4 = i * 17; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.int16[o2 + 2] = v2; this.int16[o2 + 3] = v3; this.int16[o2 + 4] = v4; this.int16[o2 + 5] = v5; this.int16[o2 + 6] = v6; this.int16[o2 + 7] = v7; this.uint16[o2 + 8] = v8; this.uint16[o2 + 9] = v9; this.uint16[o2 + 10] = v10; this.uint16[o2 + 11] = v11; this.uint16[o2 + 12] = v12; this.uint16[o2 + 13] = v13; this.uint16[o2 + 14] = v14; this.uint16[o2 + 15] = v15; this.uint16[o2 + 16] = v16; this.uint16[o2 + 17] = v17; this.uint16[o2 + 18] = v18; this.uint16[o2 + 19] = v19; this.uint16[o2 + 20] = v20; this.uint16[o2 + 21] = v21; this.uint16[o2 + 22] = v22; this.uint32[o4 + 12] = v23; this.float32[o4 + 13] = v24; this.float32[o4 + 14] = v25; this.float32[o4 + 15] = v26; this.float32[o4 + 16] = v27; return i; } } StructArrayLayout8i15ui1ul4f68.prototype.bytesPerElement = 68; register('StructArrayLayout8i15ui1ul4f68', StructArrayLayout8i15ui1ul4f68); class StructArrayLayout1f4 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); } emplaceBack(v0) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0); } emplace(i, v0) { const o4 = i * 1; this.float32[o4 + 0] = v0; return i; } } StructArrayLayout1f4.prototype.bytesPerElement = 4; register('StructArrayLayout1f4', StructArrayLayout1f4); class StructArrayLayout3i6 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.int16 = new Int16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2); } emplace(i, v0, v1, v2) { const o2 = i * 3; this.int16[o2 + 0] = v0; this.int16[o2 + 1] = v1; this.int16[o2 + 2] = v2; return i; } } StructArrayLayout3i6.prototype.bytesPerElement = 6; register('StructArrayLayout3i6', StructArrayLayout3i6); class StructArrayLayout1ul3ui12 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.uint32 = new Uint32Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3); } emplace(i, v0, v1, v2, v3) { const o4 = i * 3; const o2 = i * 6; this.uint32[o4 + 0] = v0; this.uint16[o2 + 2] = v1; this.uint16[o2 + 3] = v2; this.uint16[o2 + 4] = v3; return i; } } StructArrayLayout1ul3ui12.prototype.bytesPerElement = 12; register('StructArrayLayout1ul3ui12', StructArrayLayout1ul3ui12); class StructArrayLayout2ui4 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); } emplaceBack(v0, v1) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1); } emplace(i, v0, v1) { const o2 = i * 2; this.uint16[o2 + 0] = v0; this.uint16[o2 + 1] = v1; return i; } } StructArrayLayout2ui4.prototype.bytesPerElement = 4; register('StructArrayLayout2ui4', StructArrayLayout2ui4); class StructArrayLayout1ui2 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.uint16 = new Uint16Array(this.arrayBuffer); } emplaceBack(v0) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0); } emplace(i, v0) { const o2 = i * 1; this.uint16[o2 + 0] = v0; return i; } } StructArrayLayout1ui2.prototype.bytesPerElement = 2; register('StructArrayLayout1ui2', StructArrayLayout1ui2); class StructArrayLayout4f16 extends StructArray { _refreshViews() { this.uint8 = new Uint8Array(this.arrayBuffer); this.float32 = new Float32Array(this.arrayBuffer); } emplaceBack(v0, v1, v2, v3) { const i = this.length; this.resize(i + 1); return this.emplace(i, v0, v1, v2, v3); } emplace(i, v0, v1, v2, v3) { const o4 = i * 4; this.float32[o4 + 0] = v0; this.float32[o4 + 1] = v1; this.float32[o4 + 2] = v2; this.float32[o4 + 3] = v3; return i; } } StructArrayLayout4f16.prototype.bytesPerElement = 16; register('StructArrayLayout4f16', StructArrayLayout4f16); class CollisionBoxStruct extends Struct { get anchorPointX() { return this._structArray.int16[this._pos2 + 0]; } get anchorPointY() { return this._structArray.int16[this._pos2 + 1]; } get x1() { return this._structArray.float32[this._pos4 + 1]; } get y1() { return this._structArray.float32[this._pos4 + 2]; } get x2() { return this._structArray.float32[this._pos4 + 3]; } get y2() { return this._structArray.float32[this._pos4 + 4]; } get padding() { return this._structArray.int16[this._pos2 + 10]; } get featureIndex() { return this._structArray.uint32[this._pos4 + 6]; } get sourceLayerIndex() { return this._structArray.uint16[this._pos2 + 14]; } get bucketIndex() { return this._structArray.uint16[this._pos2 + 15]; } get anchorPoint() { return new pointGeometry(this.anchorPointX, this.anchorPointY); } } CollisionBoxStruct.prototype.size = 32; class CollisionBoxArray extends StructArrayLayout2i4f1i1ul2ui32 { get(index) { return new CollisionBoxStruct(this, index); } } register('CollisionBoxArray', CollisionBoxArray); class PlacedSymbolStruct extends Struct { get anchorX() { return this._structArray.int16[this._pos2 + 0]; } get anchorY() { return this._structArray.int16[this._pos2 + 1]; } get glyphStartIndex() { return this._structArray.uint16[this._pos2 + 2]; } get numGlyphs() { return this._structArray.uint16[this._pos2 + 3]; } get vertexStartIndex() { return this._structArray.uint32[this._pos4 + 2]; } get lineStartIndex() { return this._structArray.uint32[this._pos4 + 3]; } get lineLength() { return this._structArray.uint32[this._pos4 + 4]; } get segment() { return this._structArray.uint16[this._pos2 + 10]; } get lowerSize() { return this._structArray.uint16[this._pos2 + 11]; } get upperSize() { return this._structArray.uint16[this._pos2 + 12]; } get lineOffsetX() { return this._structArray.float32[this._pos4 + 7]; } get lineOffsetY() { return this._structArray.float32[this._pos4 + 8]; } get writingMode() { return this._structArray.uint8[this._pos1 + 36]; } get placedOrientation() { return this._structArray.uint8[this._pos1 + 37]; } set placedOrientation(x) { this._structArray.uint8[this._pos1 + 37] = x; } get hidden() { return this._structArray.uint8[this._pos1 + 38]; } set hidden(x) { this._structArray.uint8[this._pos1 + 38] = x; } get crossTileID() { return this._structArray.uint32[this._pos4 + 10]; } set crossTileID(x) { this._structArray.uint32[this._pos4 + 10] = x; } get associatedIconIndex() { return this._structArray.int16[this._pos2 + 22]; } } PlacedSymbolStruct.prototype.size = 48; class PlacedSymbolArray extends StructArrayLayout2i2ui3ul3ui2f3ub1ul1i48 { get(index) { return new PlacedSymbolStruct(this, index); } } register('PlacedSymbolArray', PlacedSymbolArray); class SymbolInstanceStruct extends Struct { get anchorX() { return this._structArray.int16[this._pos2 + 0]; } get anchorY() { return this._structArray.int16[this._pos2 + 1]; } get rightJustifiedTextSymbolIndex() { return this._structArray.int16[this._pos2 + 2]; } get centerJustifiedTextSymbolIndex() { return this._structArray.int16[this._pos2 + 3]; } get leftJustifiedTextSymbolIndex() { return this._structArray.int16[this._pos2 + 4]; } get verticalPlacedTextSymbolIndex() { return this._structArray.int16[this._pos2 + 5]; } get placedIconSymbolIndex() { return this._structArray.int16[this._pos2 + 6]; } get verticalPlacedIconSymbolIndex() { return this._structArray.int16[this._pos2 + 7]; } get key() { return this._structArray.uint16[this._pos2 + 8]; } get textBoxStartIndex() { return this._structArray.uint16[this._pos2 + 9]; } get textBoxEndIndex() { return this._structArray.uint16[this._pos2 + 10]; } get verticalTextBoxStartIndex() { return this._structArray.uint16[this._pos2 + 11]; } get verticalTextBoxEndIndex() { return this._structArray.uint16[this._pos2 + 12]; } get iconBoxStartIndex() { return this._structArray.uint16[this._pos2 + 13]; } get iconBoxEndIndex() { return this._structArray.uint16[this._pos2 + 14]; } get verticalIconBoxStartIndex() { return this._structArray.uint16[this._pos2 + 15]; } get verticalIconBoxEndIndex() { return this._structArray.uint16[this._pos2 + 16]; } get featureIndex() { return this._structArray.uint16[this._pos2 + 17]; } get numHorizontalGlyphVertices() { return this._structArray.uint16[this._pos2 + 18]; } get numVerticalGlyphVertices() { return this._structArray.uint16[this._pos2 + 19]; } get numIconVertices() { return this._structArray.uint16[this._pos2 + 20]; } get numVerticalIconVertices() { return this._structArray.uint16[this._pos2 + 21]; } get useRuntimeCollisionCircles() { return this._structArray.uint16[this._pos2 + 22]; } get crossTileID() { return this._structArray.uint32[this._pos4 + 12]; } set crossTileID(x) { this._structArray.uint32[this._pos4 + 12] = x; } get textBoxScale() { return this._structArray.float32[this._pos4 + 13]; } get textOffset0() { return this._structArray.float32[this._pos4 + 14]; } get textOffset1() { return this._structArray.float32[this._pos4 + 15]; } get collisionCircleDiameter() { return this._structArray.float32[this._pos4 + 16]; } } SymbolInstanceStruct.prototype.size = 68; class SymbolInstanceArray extends StructArrayLayout8i15ui1ul4f68 { get(index) { return new SymbolInstanceStruct(this, index); } } register('SymbolInstanceArray', SymbolInstanceArray); class GlyphOffsetArray extends StructArrayLayout1f4 { getoffsetX(index) { return this.float32[index * 1 + 0]; } } register('GlyphOffsetArray', GlyphOffsetArray); class SymbolLineVertexArray extends StructArrayLayout3i6 { getx(index) { return this.int16[index * 3 + 0]; } gety(index) { return this.int16[index * 3 + 1]; } gettileUnitDistanceFromAnchor(index) { return this.int16[index * 3 + 2]; } } register('SymbolLineVertexArray', SymbolLineVertexArray); class FeatureIndexStruct extends Struct { get featureIndex() { return this._structArray.uint32[this._pos4 + 0]; } get sourceLayerIndex() { return this._structArray.uint16[this._pos2 + 2]; } get bucketIndex() { return this._structArray.uint16[this._pos2 + 3]; } get layoutVertexArrayOffset() { return this._structArray.uint16[this._pos2 + 4]; } } FeatureIndexStruct.prototype.size = 12; class FeatureIndexArray extends StructArrayLayout1ul3ui12 { get(index) { return new FeatureIndexStruct(this, index); } } register('FeatureIndexArray', FeatureIndexArray); class FillExtrusionCentroidStruct extends Struct { get a_centroid_pos0() { return this._structArray.uint16[this._pos2 + 0]; } get a_centroid_pos1() { return this._structArray.uint16[this._pos2 + 1]; } } FillExtrusionCentroidStruct.prototype.size = 4; class FillExtrusionCentroidArray extends StructArrayLayout2ui4 { get(index) { return new FillExtrusionCentroidStruct(this, index); } } register('FillExtrusionCentroidArray', FillExtrusionCentroidArray); const patternAttributes = createLayout([ { name: 'a_pattern_to', components: 4, type: 'Uint16' }, { name: 'a_pattern_from', components: 4, type: 'Uint16' }, { name: 'a_pixel_ratio_to', components: 1, type: 'Uint16' }, { name: 'a_pixel_ratio_from', components: 1, type: 'Uint16' } ]); var murmurhash3_gc = createCommonjsModule(function (module) { function murmurhash3_32_gc(key, seed) { var remainder, bytes, h1, h1b, c1, c2, k1, i; remainder = key.length & 3; bytes = key.length - remainder; h1 = seed; c1 = 3432918353; c2 = 461845907; i = 0; while (i < bytes) { k1 = key.charCodeAt(i) & 255 | (key.charCodeAt(++i) & 255) << 8 | (key.charCodeAt(++i) & 255) << 16 | (key.charCodeAt(++i) & 255) << 24; ++i; k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295; k1 = k1 << 15 | k1 >>> 17; k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295; h1 ^= k1; h1 = h1 << 13 | h1 >>> 19; h1b = (h1 & 65535) * 5 + (((h1 >>> 16) * 5 & 65535) << 16) & 4294967295; h1 = (h1b & 65535) + 27492 + (((h1b >>> 16) + 58964 & 65535) << 16); } k1 = 0; switch (remainder) { case 3: k1 ^= (key.charCodeAt(i + 2) & 255) << 16; case 2: k1 ^= (key.charCodeAt(i + 1) & 255) << 8; case 1: k1 ^= key.charCodeAt(i) & 255; k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295; k1 = k1 << 15 | k1 >>> 17; k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295; h1 ^= k1; } h1 ^= key.length; h1 ^= h1 >>> 16; h1 = (h1 & 65535) * 2246822507 + (((h1 >>> 16) * 2246822507 & 65535) << 16) & 4294967295; h1 ^= h1 >>> 13; h1 = (h1 & 65535) * 3266489909 + (((h1 >>> 16) * 3266489909 & 65535) << 16) & 4294967295; h1 ^= h1 >>> 16; return h1 >>> 0; } { module.exports = murmurhash3_32_gc; } }); var murmurhash2_gc = createCommonjsModule(function (module) { function murmurhash2_32_gc(str, seed) { var l = str.length, h = seed ^ l, i = 0, k; while (l >= 4) { k = str.charCodeAt(i) & 255 | (str.charCodeAt(++i) & 255) << 8 | (str.charCodeAt(++i) & 255) << 16 | (str.charCodeAt(++i) & 255) << 24; k = (k & 65535) * 1540483477 + (((k >>> 16) * 1540483477 & 65535) << 16); k ^= k >>> 24; k = (k & 65535) * 1540483477 + (((k >>> 16) * 1540483477 & 65535) << 16); h = (h & 65535) * 1540483477 + (((h >>> 16) * 1540483477 & 65535) << 16) ^ k; l -= 4; ++i; } switch (l) { case 3: h ^= (str.charCodeAt(i + 2) & 255) << 16; case 2: h ^= (str.charCodeAt(i + 1) & 255) << 8; case 1: h ^= str.charCodeAt(i) & 255; h = (h & 65535) * 1540483477 + (((h >>> 16) * 1540483477 & 65535) << 16); } h ^= h >>> 13; h = (h & 65535) * 1540483477 + (((h >>> 16) * 1540483477 & 65535) << 16); h ^= h >>> 15; return h >>> 0; } { module.exports = murmurhash2_32_gc; } }); var murmurhashJs = murmurhash3_gc; var murmur3_1 = murmurhash3_gc; var murmur2_1 = murmurhash2_gc; murmurhashJs.murmur3 = murmur3_1; murmurhashJs.murmur2 = murmur2_1; class FeaturePositionMap { constructor() { this.ids = []; this.positions = []; this.indexed = false; } add(id, index, start, end) { this.ids.push(getNumericId(id)); this.positions.push(index, start, end); } getPositions(id) { const intId = getNumericId(id); let i = 0; let j = this.ids.length - 1; while (i < j) { const m = i + j >> 1; if (this.ids[m] >= intId) { j = m; } else { i = m + 1; } } const positions = []; while (this.ids[i] === intId) { const index = this.positions[3 * i]; const start = this.positions[3 * i + 1]; const end = this.positions[3 * i + 2]; positions.push({ index, start, end }); i++; } return positions; } static serialize(map, transferables) { const ids = new Float64Array(map.ids); const positions = new Uint32Array(map.positions); sort(ids, positions, 0, ids.length - 1); if (transferables) { transferables.push(ids.buffer, positions.buffer); } return { ids, positions }; } static deserialize(obj) { const map = new FeaturePositionMap(); map.ids = obj.ids; map.positions = obj.positions; map.indexed = true; return map; } } function getNumericId(value) { const numValue = +value; if (!isNaN(numValue) && numValue <= MAX_SAFE_INTEGER) { return numValue; } return murmurhashJs(String(value)); } function sort(ids, positions, left, right) { while (left < right) { const pivot = ids[left + right >> 1]; let i = left - 1; let j = right + 1; while (true) { do i++; while (ids[i] < pivot); do j--; while (ids[j] > pivot); if (i >= j) break; swap(ids, i, j); swap(positions, 3 * i, 3 * j); swap(positions, 3 * i + 1, 3 * j + 1); swap(positions, 3 * i + 2, 3 * j + 2); } if (j - left < right - j) { sort(ids, positions, left, j); left = j + 1; } else { sort(ids, positions, j + 1, right); right = j; } } } function swap(arr, i, j) { const tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } register('FeaturePositionMap', FeaturePositionMap); class Uniform { constructor(context, location) { this.gl = context.gl; this.location = location; } } class Uniform1i extends Uniform { constructor(context, location) { super(context, location); this.current = 0; } set(v) { if (this.current !== v) { this.current = v; this.gl.uniform1i(this.location, v); } } } class Uniform1f extends Uniform { constructor(context, location) { super(context, location); this.current = 0; } set(v) { if (this.current !== v) { this.current = v; this.gl.uniform1f(this.location, v); } } } class Uniform2f extends Uniform { constructor(context, location) { super(context, location); this.current = [ 0, 0 ]; } set(v) { if (v[0] !== this.current[0] || v[1] !== this.current[1]) { this.current = v; this.gl.uniform2f(this.location, v[0], v[1]); } } } class Uniform3f extends Uniform { constructor(context, location) { super(context, location); this.current = [ 0, 0, 0 ]; } set(v) { if (v[0] !== this.current[0] || v[1] !== this.current[1] || v[2] !== this.current[2]) { this.current = v; this.gl.uniform3f(this.location, v[0], v[1], v[2]); } } } class Uniform4f extends Uniform { constructor(context, location) { super(context, location); this.current = [ 0, 0, 0, 0 ]; } set(v) { if (v[0] !== this.current[0] || v[1] !== this.current[1] || v[2] !== this.current[2] || v[3] !== this.current[3]) { this.current = v; this.gl.uniform4f(this.location, v[0], v[1], v[2], v[3]); } } } class UniformColor extends Uniform { constructor(context, location) { super(context, location); this.current = Color.transparent; } set(v) { if (v.r !== this.current.r || v.g !== this.current.g || v.b !== this.current.b || v.a !== this.current.a) { this.current = v; this.gl.uniform4f(this.location, v.r, v.g, v.b, v.a); } } } const emptyMat4 = new Float32Array(16); class UniformMatrix4f extends Uniform { constructor(context, location) { super(context, location); this.current = emptyMat4; } set(v) { if (v[12] !== this.current[12] || v[0] !== this.current[0]) { this.current = v; this.gl.uniformMatrix4fv(this.location, false, v); return; } for (let i = 1; i < 16; i++) { if (v[i] !== this.current[i]) { this.current = v; this.gl.uniformMatrix4fv(this.location, false, v); break; } } } } const emptyMat3 = new Float32Array(9); class UniformMatrix3f extends Uniform { constructor(context, location) { super(context, location); this.current = emptyMat3; } set(v) { for (let i = 0; i < 9; i++) { if (v[i] !== this.current[i]) { this.current = v; this.gl.uniformMatrix3fv(this.location, false, v); break; } } } } function packColor(color) { return [ packUint8ToFloat(255 * color.r, 255 * color.g), packUint8ToFloat(255 * color.b, 255 * color.a) ]; } class ConstantBinder { constructor(value, names, type) { this.value = value; this.uniformNames = names.map(name => `u_${ name }`); this.type = type; } setUniform(uniform, globals, currentValue) { uniform.set(currentValue.constantOr(this.value)); } getBinding(context, location, _) { return this.type === 'color' ? new UniformColor(context, location) : new Uniform1f(context, location); } } class CrossFadedConstantBinder { constructor(value, names) { this.uniformNames = names.map(name => `u_${ name }`); this.patternFrom = null; this.patternTo = null; this.pixelRatioFrom = 1; this.pixelRatioTo = 1; } setConstantPatternPositions(posTo, posFrom) { this.pixelRatioFrom = posFrom.pixelRatio; this.pixelRatioTo = posTo.pixelRatio; this.patternFrom = posFrom.tlbr; this.patternTo = posTo.tlbr; } setUniform(uniform, globals, currentValue, uniformName) { const pos = uniformName === 'u_pattern_to' ? this.patternTo : uniformName === 'u_pattern_from' ? this.patternFrom : uniformName === 'u_pixel_ratio_to' ? this.pixelRatioTo : uniformName === 'u_pixel_ratio_from' ? this.pixelRatioFrom : null; if (pos) uniform.set(pos); } getBinding(context, location, name) { return name.substr(0, 9) === 'u_pattern' ? new Uniform4f(context, location) : new Uniform1f(context, location); } } class SourceExpressionBinder { constructor(expression, names, type, PaintVertexArray) { this.expression = expression; this.type = type; this.maxValue = 0; this.paintVertexAttributes = names.map(name => ({ name: `a_${ name }`, type: 'Float32', components: type === 'color' ? 2 : 1, offset: 0 })); this.paintVertexArray = new PaintVertexArray(); } populatePaintArray(newLength, feature, imagePositions, canonical, formattedSection) { const start = this.paintVertexArray.length; const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, canonical, [], formattedSection); this.paintVertexArray.resize(newLength); this._setPaintValue(start, newLength, value); } updatePaintArray(start, end, feature, featureState) { const value = this.expression.evaluate({ zoom: 0 }, feature, featureState); this._setPaintValue(start, end, value); } _setPaintValue(start, end, value) { if (this.type === 'color') { const color = packColor(value); for (let i = start; i < end; i++) { this.paintVertexArray.emplace(i, color[0], color[1]); } } else { for (let i = start; i < end; i++) { this.paintVertexArray.emplace(i, value); } this.maxValue = Math.max(this.maxValue, Math.abs(value)); } } upload(context) { if (this.paintVertexArray && this.paintVertexArray.arrayBuffer) { if (this.paintVertexBuffer && this.paintVertexBuffer.buffer) { this.paintVertexBuffer.updateData(this.paintVertexArray); } else { this.paintVertexBuffer = context.createVertexBuffer(this.paintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent); } } } destroy() { if (this.paintVertexBuffer) { this.paintVertexBuffer.destroy(); } } } class CompositeExpressionBinder { constructor(expression, names, type, useIntegerZoom, zoom, PaintVertexArray) { this.expression = expression; this.uniformNames = names.map(name => `u_${ name }_t`); this.type = type; this.useIntegerZoom = useIntegerZoom; this.zoom = zoom; this.maxValue = 0; this.paintVertexAttributes = names.map(name => ({ name: `a_${ name }`, type: 'Float32', components: type === 'color' ? 4 : 2, offset: 0 })); this.paintVertexArray = new PaintVertexArray(); } populatePaintArray(newLength, feature, imagePositions, canonical, formattedSection) { const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, canonical, [], formattedSection); const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, canonical, [], formattedSection); const start = this.paintVertexArray.length; this.paintVertexArray.resize(newLength); this._setPaintValue(start, newLength, min, max); } updatePaintArray(start, end, feature, featureState) { const min = this.expression.evaluate({ zoom: this.zoom }, feature, featureState); const max = this.expression.evaluate({ zoom: this.zoom + 1 }, feature, featureState); this._setPaintValue(start, end, min, max); } _setPaintValue(start, end, min, max) { if (this.type === 'color') { const minColor = packColor(min); const maxColor = packColor(max); for (let i = start; i < end; i++) { this.paintVertexArray.emplace(i, minColor[0], minColor[1], maxColor[0], maxColor[1]); } } else { for (let i = start; i < end; i++) { this.paintVertexArray.emplace(i, min, max); } this.maxValue = Math.max(this.maxValue, Math.abs(min), Math.abs(max)); } } upload(context) { if (this.paintVertexArray && this.paintVertexArray.arrayBuffer) { if (this.paintVertexBuffer && this.paintVertexBuffer.buffer) { this.paintVertexBuffer.updateData(this.paintVertexArray); } else { this.paintVertexBuffer = context.createVertexBuffer(this.paintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent); } } } destroy() { if (this.paintVertexBuffer) { this.paintVertexBuffer.destroy(); } } setUniform(uniform, globals) { const currentZoom = this.useIntegerZoom ? Math.floor(globals.zoom) : globals.zoom; const factor = clamp(this.expression.interpolationFactor(currentZoom, this.zoom, this.zoom + 1), 0, 1); uniform.set(factor); } getBinding(context, location, _) { return new Uniform1f(context, location); } } class CrossFadedCompositeBinder { constructor(expression, names, type, useIntegerZoom, zoom, PaintVertexArray, layerId) { this.expression = expression; this.type = type; this.useIntegerZoom = useIntegerZoom; this.zoom = zoom; this.layerId = layerId; for (let i = 0; i < names.length; ++i) { } this.zoomInPaintVertexArray = new PaintVertexArray(); this.zoomOutPaintVertexArray = new PaintVertexArray(); } populatePaintArray(length, feature, imagePositions) { const start = this.zoomInPaintVertexArray.length; this.zoomInPaintVertexArray.resize(length); this.zoomOutPaintVertexArray.resize(length); this._setPaintValues(start, length, feature.patterns && feature.patterns[this.layerId], imagePositions); } updatePaintArray(start, end, feature, featureState, imagePositions) { this._setPaintValues(start, end, feature.patterns && feature.patterns[this.layerId], imagePositions); } _setPaintValues(start, end, patterns, positions) { if (!positions || !patterns) return; const {min, mid, max} = patterns; const imageMin = positions[min]; const imageMid = positions[mid]; const imageMax = positions[max]; if (!imageMin || !imageMid || !imageMax) return; for (let i = start; i < end; i++) { this.zoomInPaintVertexArray.emplace(i, imageMid.tl[0], imageMid.tl[1], imageMid.br[0], imageMid.br[1], imageMin.tl[0], imageMin.tl[1], imageMin.br[0], imageMin.br[1], imageMid.pixelRatio, imageMin.pixelRatio); this.zoomOutPaintVertexArray.emplace(i, imageMid.tl[0], imageMid.tl[1], imageMid.br[0], imageMid.br[1], imageMax.tl[0], imageMax.tl[1], imageMax.br[0], imageMax.br[1], imageMid.pixelRatio, imageMax.pixelRatio); } } upload(context) { if (this.zoomInPaintVertexArray && this.zoomInPaintVertexArray.arrayBuffer && this.zoomOutPaintVertexArray && this.zoomOutPaintVertexArray.arrayBuffer) { this.zoomInPaintVertexBuffer = context.createVertexBuffer(this.zoomInPaintVertexArray, patternAttributes.members, this.expression.isStateDependent); this.zoomOutPaintVertexBuffer = context.createVertexBuffer(this.zoomOutPaintVertexArray, patternAttributes.members, this.expression.isStateDependent); } } destroy() { if (this.zoomOutPaintVertexBuffer) this.zoomOutPaintVertexBuffer.destroy(); if (this.zoomInPaintVertexBuffer) this.zoomInPaintVertexBuffer.destroy(); } } class ProgramConfiguration { constructor(layer, zoom, filterProperties = () => true) { this.binders = {}; this._buffers = []; const keys = []; for (const property in layer.paint._values) { if (!filterProperties(property)) continue; const value = layer.paint.get(property); if (!(value instanceof PossiblyEvaluatedPropertyValue) || !supportsPropertyExpression(value.property.specification)) { continue; } const names = paintAttributeNames(property, layer.type); const expression = value.value; const type = value.property.specification.type; const useIntegerZoom = value.property.useIntegerZoom; const propType = value.property.specification['property-type']; const isCrossFaded = propType === 'cross-faded' || propType === 'cross-faded-data-driven'; if (expression.kind === 'constant') { this.binders[property] = isCrossFaded ? new CrossFadedConstantBinder(expression.value, names) : new ConstantBinder(expression.value, names, type); keys.push(`/u_${ property }`); } else if (expression.kind === 'source' || isCrossFaded) { const StructArrayLayout = layoutType(property, type, 'source'); this.binders[property] = isCrossFaded ? new CrossFadedCompositeBinder(expression, names, type, useIntegerZoom, zoom, StructArrayLayout, layer.id) : new SourceExpressionBinder(expression, names, type, StructArrayLayout); keys.push(`/a_${ property }`); } else { const StructArrayLayout = layoutType(property, type, 'composite'); this.binders[property] = new CompositeExpressionBinder(expression, names, type, useIntegerZoom, zoom, StructArrayLayout); keys.push(`/z_${ property }`); } } this.cacheKey = keys.sort().join(''); } getMaxValue(property) { const binder = this.binders[property]; return binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder ? binder.maxValue : 0; } populatePaintArrays(newLength, feature, imagePositions, canonical, formattedSection) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) binder.populatePaintArray(newLength, feature, imagePositions, canonical, formattedSection); } } setConstantPatternPositions(posTo, posFrom) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof CrossFadedConstantBinder) binder.setConstantPatternPositions(posTo, posFrom); } } updatePaintArrays(featureStates, featureMap, vtLayer, layer, imagePositions) { let dirty = false; for (const id in featureStates) { const positions = featureMap.getPositions(id); for (const pos of positions) { const feature = vtLayer.feature(pos.index); for (const property in this.binders) { const binder = this.binders[property]; if ((binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) && binder.expression.isStateDependent === true) { const value = layer.paint.get(property); binder.expression = value.value; binder.updatePaintArray(pos.start, pos.end, feature, featureStates[id], imagePositions); dirty = true; } } } } return dirty; } defines() { const result = []; for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder) { result.push(...binder.uniformNames.map(name => `#define HAS_UNIFORM_${ name }`)); } } return result; } getBinderAttributes() { const result = []; for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder) { for (let i = 0; i < binder.paintVertexAttributes.length; i++) { result.push(binder.paintVertexAttributes[i].name); } } else if (binder instanceof CrossFadedCompositeBinder) { for (let i = 0; i < patternAttributes.members.length; i++) { result.push(patternAttributes.members[i].name); } } } return result; } getBinderUniforms() { const uniforms = []; for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder || binder instanceof CompositeExpressionBinder) { for (const uniformName of binder.uniformNames) { uniforms.push(uniformName); } } } return uniforms; } getPaintVertexBuffers() { return this._buffers; } getUniforms(context, locations) { const uniforms = []; for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder || binder instanceof CompositeExpressionBinder) { for (const name of binder.uniformNames) { if (locations[name]) { const binding = binder.getBinding(context, locations[name], name); uniforms.push({ name, property, binding }); } } } } return uniforms; } setUniforms(context, binderUniforms, properties, globals) { for (const {name, property, binding} of binderUniforms) { this.binders[property].setUniform(binding, globals, properties.get(property), name); } } updatePaintBuffers(crossfade) { this._buffers = []; for (const property in this.binders) { const binder = this.binders[property]; if (crossfade && binder instanceof CrossFadedCompositeBinder) { const patternVertexBuffer = crossfade.fromScale === 2 ? binder.zoomInPaintVertexBuffer : binder.zoomOutPaintVertexBuffer; if (patternVertexBuffer) this._buffers.push(patternVertexBuffer); } else if ((binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder) && binder.paintVertexBuffer) { this._buffers.push(binder.paintVertexBuffer); } } } upload(context) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) binder.upload(context); } this.updatePaintBuffers(); } destroy() { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) binder.destroy(); } } } class ProgramConfigurationSet { constructor(layers, zoom, filterProperties = () => true) { this.programConfigurations = {}; for (const layer of layers) { this.programConfigurations[layer.id] = new ProgramConfiguration(layer, zoom, filterProperties); } this.needsUpload = false; this._featureMap = new FeaturePositionMap(); this._bufferOffset = 0; } populatePaintArrays(length, feature, index, imagePositions, canonical, formattedSection) { for (const key in this.programConfigurations) { this.programConfigurations[key].populatePaintArrays(length, feature, imagePositions, canonical, formattedSection); } if (feature.id !== undefined) { this._featureMap.add(feature.id, index, this._bufferOffset, length); } this._bufferOffset = length; this.needsUpload = true; } updatePaintArrays(featureStates, vtLayer, layers, imagePositions) { for (const layer of layers) { this.needsUpload = this.programConfigurations[layer.id].updatePaintArrays(featureStates, this._featureMap, vtLayer, layer, imagePositions) || this.needsUpload; } } get(layerId) { return this.programConfigurations[layerId]; } upload(context) { if (!this.needsUpload) return; for (const layerId in this.programConfigurations) { this.programConfigurations[layerId].upload(context); } this.needsUpload = false; } destroy() { for (const layerId in this.programConfigurations) { this.programConfigurations[layerId].destroy(); } } } function paintAttributeNames(property, type) { const attributeNameExceptions = { 'text-opacity': ['opacity'], 'icon-opacity': ['opacity'], 'text-color': ['fill_color'], 'icon-color': ['fill_color'], 'text-halo-color': ['halo_color'], 'icon-halo-color': ['halo_color'], 'text-halo-blur': ['halo_blur'], 'icon-halo-blur': ['halo_blur'], 'text-halo-width': ['halo_width'], 'icon-halo-width': ['halo_width'], 'line-gap-width': ['gapwidth'], 'line-pattern': [ 'pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from' ], 'fill-pattern': [ 'pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from' ], 'fill-extrusion-pattern': [ 'pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from' ] }; return attributeNameExceptions[property] || [property.replace(`${ type }-`, '').replace(/-/g, '_')]; } function getLayoutException(property) { const propertyExceptions = { 'line-pattern': { 'source': StructArrayLayout10ui20, 'composite': StructArrayLayout10ui20 }, 'fill-pattern': { 'source': StructArrayLayout10ui20, 'composite': StructArrayLayout10ui20 }, 'fill-extrusion-pattern': { 'source': StructArrayLayout10ui20, 'composite': StructArrayLayout10ui20 } }; return propertyExceptions[property]; } function layoutType(property, type, binderType) { const defaultLayouts = { 'color': { 'source': StructArrayLayout2f8, 'composite': StructArrayLayout4f16 }, 'number': { 'source': StructArrayLayout1f4, 'composite': StructArrayLayout2f8 } }; const layoutException = getLayoutException(property); return layoutException && layoutException[binderType] || defaultLayouts[type][binderType]; } register('ConstantBinder', ConstantBinder); register('CrossFadedConstantBinder', CrossFadedConstantBinder); register('SourceExpressionBinder', SourceExpressionBinder); register('CrossFadedCompositeBinder', CrossFadedCompositeBinder); register('CompositeExpressionBinder', CompositeExpressionBinder); register('ProgramConfiguration', ProgramConfiguration, { omit: ['_buffers'] }); register('ProgramConfigurationSet', ProgramConfigurationSet); const TRANSITION_SUFFIX = '-transition'; class StyleLayer extends Evented { constructor(layer, properties) { super(); this.id = layer.id; this.type = layer.type; this._featureFilter = { filter: () => true, needGeometry: false }; if (layer.type === 'custom') return; layer = layer; this.metadata = layer.metadata; this.minzoom = layer.minzoom; this.maxzoom = layer.maxzoom; if (layer.type !== 'background' && layer.type !== 'sky') { this.source = layer.source; this.sourceLayer = layer['source-layer']; this.filter = layer.filter; } if (properties.layout) { this._unevaluatedLayout = new Layout(properties.layout); } if (properties.paint) { this._transitionablePaint = new Transitionable(properties.paint); for (const property in layer.paint) { this.setPaintProperty(property, layer.paint[property], { validate: false }); } for (const property in layer.layout) { this.setLayoutProperty(property, layer.layout[property], { validate: false }); } this._transitioningPaint = this._transitionablePaint.untransitioned(); this.paint = new PossiblyEvaluated(properties.paint); } } getCrossfadeParameters() { return this._crossfadeParameters; } getLayoutProperty(name) { if (name === 'visibility') { return this.visibility; } return this._unevaluatedLayout.getValue(name); } setLayoutProperty(name, value, options = {}) { if (value !== null && value !== undefined) { const key = `layers.${ this.id }.layout.${ name }`; if (this._validate(validateLayoutProperty$1, key, name, value, options)) { return; } } if (name === 'visibility') { this.visibility = value; return; } this._unevaluatedLayout.setValue(name, value); } getPaintProperty(name) { if (endsWith(name, TRANSITION_SUFFIX)) { return this._transitionablePaint.getTransition(name.slice(0, -TRANSITION_SUFFIX.length)); } else { return this._transitionablePaint.getValue(name); } } setPaintProperty(name, value, options = {}) { if (value !== null && value !== undefined) { const key = `layers.${ this.id }.paint.${ name }`; if (this._validate(validatePaintProperty$1, key, name, value, options)) { return false; } } if (endsWith(name, TRANSITION_SUFFIX)) { this._transitionablePaint.setTransition(name.slice(0, -TRANSITION_SUFFIX.length), value || undefined); return false; } else { const transitionable = this._transitionablePaint._values[name]; const isCrossFadedProperty = transitionable.property.specification['property-type'] === 'cross-faded-data-driven'; const wasDataDriven = transitionable.value.isDataDriven(); const oldValue = transitionable.value; this._transitionablePaint.setValue(name, value); this._handleSpecialPaintPropertyUpdate(name); const newValue = this._transitionablePaint._values[name].value; const isDataDriven = newValue.isDataDriven(); return isDataDriven || wasDataDriven || isCrossFadedProperty || this._handleOverridablePaintPropertyUpdate(name, oldValue, newValue); } } _handleSpecialPaintPropertyUpdate(_) { } getProgramIds() { return null; } getProgramConfiguration(_) { return null; } _handleOverridablePaintPropertyUpdate(name, oldValue, newValue) { return false; } isHidden(zoom) { if (this.minzoom && zoom < this.minzoom) return true; if (this.maxzoom && zoom >= this.maxzoom) return true; return this.visibility === 'none'; } updateTransitions(parameters) { this._transitioningPaint = this._transitionablePaint.transitioned(parameters, this._transitioningPaint); } hasTransition() { return this._transitioningPaint.hasTransition(); } recalculate(parameters, availableImages) { if (parameters.getCrossfadeParameters) { this._crossfadeParameters = parameters.getCrossfadeParameters(); } if (this._unevaluatedLayout) { this.layout = this._unevaluatedLayout.possiblyEvaluate(parameters, undefined, availableImages); } this.paint = this._transitioningPaint.possiblyEvaluate(parameters, undefined, availableImages); } serialize() { const output = { 'id': this.id, 'type': this.type, 'source': this.source, 'source-layer': this.sourceLayer, 'metadata': this.metadata, 'minzoom': this.minzoom, 'maxzoom': this.maxzoom, 'filter': this.filter, 'layout': this._unevaluatedLayout && this._unevaluatedLayout.serialize(), 'paint': this._transitionablePaint && this._transitionablePaint.serialize() }; if (this.visibility) { output.layout = output.layout || {}; output.layout.visibility = this.visibility; } return filterObject(output, (value, key) => { return value !== undefined && !(key === 'layout' && !Object.keys(value).length) && !(key === 'paint' && !Object.keys(value).length); }); } _validate(validate, key, name, value, options = {}) { if (options && options.validate === false) { return false; } return emitValidationErrors(this, validate.call(validateStyle, { key, layerType: this.type, objectKey: name, value, styleSpec: spec, style: { glyphs: true, sprite: true } })); } is3D() { return false; } isSky() { return false; } isTileClipped() { return false; } hasOffscreenPass() { return false; } resize() { } isStateDependent() { for (const property in this.paint._values) { const value = this.paint.get(property); if (!(value instanceof PossiblyEvaluatedPropertyValue) || !supportsPropertyExpression(value.property.specification)) { continue; } if ((value.value.kind === 'source' || value.value.kind === 'composite') && value.value.isStateDependent) { return true; } } return false; } } const layout$1 = createLayout([{ name: 'a_pos', components: 2, type: 'Int16' }], 4); const {members, size, alignment} = layout$1; class SegmentVector { constructor(segments = []) { this.segments = segments; } prepareSegment(numVertices, layoutVertexArray, indexArray, sortKey) { let segment = this.segments[this.segments.length - 1]; if (numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH) warnOnce(`Max vertices per segment is ${ SegmentVector.MAX_VERTEX_ARRAY_LENGTH }: bucket requested ${ numVertices }`); if (!segment || segment.vertexLength + numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH || segment.sortKey !== sortKey) { segment = { vertexOffset: layoutVertexArray.length, primitiveOffset: indexArray.length, vertexLength: 0, primitiveLength: 0 }; if (sortKey !== undefined) segment.sortKey = sortKey; this.segments.push(segment); } return segment; } get() { return this.segments; } destroy() { for (const segment of this.segments) { for (const k in segment.vaos) { segment.vaos[k].destroy(); } } } static simpleSegment(vertexOffset, primitiveOffset, vertexLength, primitiveLength) { return new SegmentVector([{ vertexOffset, primitiveOffset, vertexLength, primitiveLength, vaos: {}, sortKey: 0 }]); } } SegmentVector.MAX_VERTEX_ARRAY_LENGTH = Math.pow(2, 16) - 1; register('SegmentVector', SegmentVector); var EXTENT$1 = 8192; const BITS = 15; const MAX = Math.pow(2, BITS - 1) - 1; const MIN = -MAX - 1; function loadGeometry(feature) { const scale = EXTENT$1 / feature.extent; const geometry = feature.loadGeometry(); for (let r = 0; r < geometry.length; r++) { const ring = geometry[r]; for (let p = 0; p < ring.length; p++) { const point = ring[p]; const x = Math.round(point.x * scale); const y = Math.round(point.y * scale); point.x = clamp(x, MIN, MAX); point.y = clamp(y, MIN, MAX); if (x < point.x || x > point.x + 1 || y < point.y || y > point.y + 1) { warnOnce('Geometry exceeds allowed extent, reduce your vector tile buffer size'); } } } return geometry; } function toEvaluationFeature(feature, needGeometry) { return { type: feature.type, id: feature.id, properties: feature.properties, geometry: needGeometry ? loadGeometry(feature) : [] }; } function addCircleVertex(layoutVertexArray, x, y, extrudeX, extrudeY) { layoutVertexArray.emplaceBack(x * 2 + (extrudeX + 1) / 2, y * 2 + (extrudeY + 1) / 2); } class CircleBucket { constructor(options) { this.zoom = options.zoom; this.overscaling = options.overscaling; this.layers = options.layers; this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; this.hasPattern = false; this.layoutVertexArray = new StructArrayLayout2i4(); this.indexArray = new StructArrayLayout3ui6(); this.segments = new SegmentVector(); this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.stateDependentLayerIds = this.layers.filter(l => l.isStateDependent()).map(l => l.id); } populate(features, options, canonical) { const styleLayer = this.layers[0]; const bucketFeatures = []; let circleSortKey = null; if (styleLayer.type === 'circle') { circleSortKey = styleLayer.layout.get('circle-sort-key'); } for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = this.layers[0]._featureFilter.needGeometry; const evaluationFeature = toEvaluationFeature(feature, needGeometry); if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; const sortKey = circleSortKey ? circleSortKey.evaluate(evaluationFeature, {}, canonical) : undefined; const bucketFeature = { id, properties: feature.properties, type: feature.type, sourceLayerIndex, index, geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature), patterns: {}, sortKey }; bucketFeatures.push(bucketFeature); } if (circleSortKey) { bucketFeatures.sort((a, b) => { return a.sortKey - b.sortKey; }); } for (const bucketFeature of bucketFeatures) { const {geometry, index, sourceLayerIndex} = bucketFeature; const feature = features[index].feature; this.addFeature(bucketFeature, geometry, index, canonical); options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); } } update(states, vtLayer, imagePositions) { if (!this.stateDependentLayers.length) return; this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); } isEmpty() { return this.layoutVertexArray.length === 0; } uploadPending() { return !this.uploaded || this.programConfigurations.needsUpload; } upload(context) { if (!this.uploaded) { this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, members); this.indexBuffer = context.createIndexBuffer(this.indexArray); } this.programConfigurations.upload(context); this.uploaded = true; } destroy() { if (!this.layoutVertexBuffer) return; this.layoutVertexBuffer.destroy(); this.indexBuffer.destroy(); this.programConfigurations.destroy(); this.segments.destroy(); } addFeature(feature, geometry, index, canonical) { for (const ring of geometry) { for (const point of ring) { const x = point.x; const y = point.y; if (x < 0 || x >= EXTENT$1 || y < 0 || y >= EXTENT$1) continue; const segment = this.segments.prepareSegment(4, this.layoutVertexArray, this.indexArray, feature.sortKey); const index = segment.vertexLength; addCircleVertex(this.layoutVertexArray, x, y, -1, -1); addCircleVertex(this.layoutVertexArray, x, y, 1, -1); addCircleVertex(this.layoutVertexArray, x, y, 1, 1); addCircleVertex(this.layoutVertexArray, x, y, -1, 1); this.indexArray.emplaceBack(index, index + 1, index + 2); this.indexArray.emplaceBack(index, index + 3, index + 2); segment.vertexLength += 4; segment.primitiveLength += 2; } } this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {}, canonical); } } register('CircleBucket', CircleBucket, { omit: ['layers'] }); function polygonIntersectsPolygon(polygonA, polygonB) { for (let i = 0; i < polygonA.length; i++) { if (polygonContainsPoint(polygonB, polygonA[i])) return true; } for (let i = 0; i < polygonB.length; i++) { if (polygonContainsPoint(polygonA, polygonB[i])) return true; } if (lineIntersectsLine(polygonA, polygonB)) return true; return false; } function polygonIntersectsBufferedPoint(polygon, point, radius) { if (polygonContainsPoint(polygon, point)) return true; if (pointIntersectsBufferedLine(point, polygon, radius)) return true; return false; } function polygonIntersectsMultiPolygon(polygon, multiPolygon) { if (polygon.length === 1) { return multiPolygonContainsPoint(multiPolygon, polygon[0]); } for (let m = 0; m < multiPolygon.length; m++) { const ring = multiPolygon[m]; for (let n = 0; n < ring.length; n++) { if (polygonContainsPoint(polygon, ring[n])) return true; } } for (let i = 0; i < polygon.length; i++) { if (multiPolygonContainsPoint(multiPolygon, polygon[i])) return true; } for (let k = 0; k < multiPolygon.length; k++) { if (lineIntersectsLine(polygon, multiPolygon[k])) return true; } return false; } function polygonIntersectsBufferedMultiLine(polygon, multiLine, radius) { for (let i = 0; i < multiLine.length; i++) { const line = multiLine[i]; if (polygon.length >= 3) { for (let k = 0; k < line.length; k++) { if (polygonContainsPoint(polygon, line[k])) return true; } } if (lineIntersectsBufferedLine(polygon, line, radius)) return true; } return false; } function lineIntersectsBufferedLine(lineA, lineB, radius) { if (lineA.length > 1) { if (lineIntersectsLine(lineA, lineB)) return true; for (let j = 0; j < lineB.length; j++) { if (pointIntersectsBufferedLine(lineB[j], lineA, radius)) return true; } } for (let k = 0; k < lineA.length; k++) { if (pointIntersectsBufferedLine(lineA[k], lineB, radius)) return true; } return false; } function lineIntersectsLine(lineA, lineB) { if (lineA.length === 0 || lineB.length === 0) return false; for (let i = 0; i < lineA.length - 1; i++) { const a0 = lineA[i]; const a1 = lineA[i + 1]; for (let j = 0; j < lineB.length - 1; j++) { const b0 = lineB[j]; const b1 = lineB[j + 1]; if (lineSegmentIntersectsLineSegment(a0, a1, b0, b1)) return true; } } return false; } function lineSegmentIntersectsLineSegment(a0, a1, b0, b1) { return isCounterClockwise(a0, b0, b1) !== isCounterClockwise(a1, b0, b1) && isCounterClockwise(a0, a1, b0) !== isCounterClockwise(a0, a1, b1); } function pointIntersectsBufferedLine(p, line, radius) { const radiusSquared = radius * radius; if (line.length === 1) return p.distSqr(line[0]) < radiusSquared; for (let i = 1; i < line.length; i++) { const v = line[i - 1], w = line[i]; if (distToSegmentSquared(p, v, w) < radiusSquared) return true; } return false; } function distToSegmentSquared(p, v, w) { const l2 = v.distSqr(w); if (l2 === 0) return p.distSqr(v); const t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2; if (t < 0) return p.distSqr(v); if (t > 1) return p.distSqr(w); return p.distSqr(w.sub(v)._mult(t)._add(v)); } function multiPolygonContainsPoint(rings, p) { let c = false, ring, p1, p2; for (let k = 0; k < rings.length; k++) { ring = rings[k]; for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { p1 = ring[i]; p2 = ring[j]; if (p1.y > p.y !== p2.y > p.y && p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x) { c = !c; } } } return c; } function polygonContainsPoint(ring, p) { let c = false; for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { const p1 = ring[i]; const p2 = ring[j]; if (p1.y > p.y !== p2.y > p.y && p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x) { c = !c; } } return c; } function polygonIntersectsBox(ring, boxX1, boxY1, boxX2, boxY2) { for (const p of ring) { if (boxX1 <= p.x && boxY1 <= p.y && boxX2 >= p.x && boxY2 >= p.y) return true; } const corners = [ new pointGeometry(boxX1, boxY1), new pointGeometry(boxX1, boxY2), new pointGeometry(boxX2, boxY2), new pointGeometry(boxX2, boxY1) ]; if (ring.length > 2) { for (const corner of corners) { if (polygonContainsPoint(ring, corner)) return true; } } for (let i = 0; i < ring.length - 1; i++) { const p1 = ring[i]; const p2 = ring[i + 1]; if (edgeIntersectsBox(p1, p2, corners)) return true; } return false; } function edgeIntersectsBox(e1, e2, corners) { const tl = corners[0]; const br = corners[2]; if (e1.x < tl.x && e2.x < tl.x || e1.x > br.x && e2.x > br.x || e1.y < tl.y && e2.y < tl.y || e1.y > br.y && e2.y > br.y) return false; const dir = isCounterClockwise(e1, e2, corners[0]); return dir !== isCounterClockwise(e1, e2, corners[1]) || dir !== isCounterClockwise(e1, e2, corners[2]) || dir !== isCounterClockwise(e1, e2, corners[3]); } function getMaximumPaintValue(property, layer, bucket) { const value = layer.paint.get(property).value; if (value.kind === 'constant') { return value.value; } else { return bucket.programConfigurations.get(layer.id).getMaxValue(property); } } function translateDistance(translate) { return Math.sqrt(translate[0] * translate[0] + translate[1] * translate[1]); } function translate(queryGeometry, translate, translateAnchor, bearing, pixelsToTileUnits) { if (!translate[0] && !translate[1]) { return queryGeometry; } const pt = pointGeometry.convert(translate)._mult(pixelsToTileUnits); if (translateAnchor === 'viewport') { pt._rotate(-bearing); } const translated = []; for (let i = 0; i < queryGeometry.length; i++) { const point = queryGeometry[i]; translated.push(point.sub(pt)); } return translated; } function tilespaceTranslate(translate, translateAnchor, bearing, pixelsToTileUnits) { const pt = pointGeometry.convert(translate)._mult(pixelsToTileUnits); if (translateAnchor === 'viewport') { pt._rotate(-bearing); } return pt; } const layout$2 = new Properties({ 'circle-sort-key': new DataDrivenProperty(spec['layout_circle']['circle-sort-key']) }); const paint$1 = new Properties({ 'circle-radius': new DataDrivenProperty(spec['paint_circle']['circle-radius']), 'circle-color': new DataDrivenProperty(spec['paint_circle']['circle-color']), 'circle-blur': new DataDrivenProperty(spec['paint_circle']['circle-blur']), 'circle-opacity': new DataDrivenProperty(spec['paint_circle']['circle-opacity']), 'circle-translate': new DataConstantProperty(spec['paint_circle']['circle-translate']), 'circle-translate-anchor': new DataConstantProperty(spec['paint_circle']['circle-translate-anchor']), 'circle-pitch-scale': new DataConstantProperty(spec['paint_circle']['circle-pitch-scale']), 'circle-pitch-alignment': new DataConstantProperty(spec['paint_circle']['circle-pitch-alignment']), 'circle-stroke-width': new DataDrivenProperty(spec['paint_circle']['circle-stroke-width']), 'circle-stroke-color': new DataDrivenProperty(spec['paint_circle']['circle-stroke-color']), 'circle-stroke-opacity': new DataDrivenProperty(spec['paint_circle']['circle-stroke-opacity']) }); var properties = { paint: paint$1, layout: layout$2 }; var EPSILON = 0.000001; var ARRAY_TYPE = typeof Float32Array !== 'undefined' ? Float32Array : Array; if (!Math.hypot) Math.hypot = function () { var y = 0, i = arguments.length; while (i--) { y += arguments[i] * arguments[i]; } return Math.sqrt(y); }; function create() { var out = new ARRAY_TYPE(4); if (ARRAY_TYPE != Float32Array) { out[1] = 0; out[2] = 0; } out[0] = 1; out[3] = 1; return out; } function rotate(out, a, rad) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; var s = Math.sin(rad); var c = Math.cos(rad); out[0] = a0 * c + a2 * s; out[1] = a1 * c + a3 * s; out[2] = a0 * -s + a2 * c; out[3] = a1 * -s + a3 * c; return out; } function create$1() { var out = new ARRAY_TYPE(9); if (ARRAY_TYPE != Float32Array) { out[1] = 0; out[2] = 0; out[3] = 0; out[5] = 0; out[6] = 0; out[7] = 0; } out[0] = 1; out[4] = 1; out[8] = 1; return out; } function fromMat4(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[4]; out[4] = a[5]; out[5] = a[6]; out[6] = a[8]; out[7] = a[9]; out[8] = a[10]; return out; } function fromRotation(out, rad) { var s = Math.sin(rad), c = Math.cos(rad); out[0] = c; out[1] = s; out[2] = 0; out[3] = -s; out[4] = c; out[5] = 0; out[6] = 0; out[7] = 0; out[8] = 1; return out; } function create$2() { var out = new ARRAY_TYPE(16); if (ARRAY_TYPE != Float32Array) { out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; } out[0] = 1; out[5] = 1; out[10] = 1; out[15] = 1; return out; } function clone$1(a) { var out = new ARRAY_TYPE(16); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; } function identity(out) { out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function invert(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var b00 = a00 * a11 - a01 * a10; var b01 = a00 * a12 - a02 * a10; var b02 = a00 * a13 - a03 * a10; var b03 = a01 * a12 - a02 * a11; var b04 = a01 * a13 - a03 * a11; var b05 = a02 * a13 - a03 * a12; var b06 = a20 * a31 - a21 * a30; var b07 = a20 * a32 - a22 * a30; var b08 = a20 * a33 - a23 * a30; var b09 = a21 * a32 - a22 * a31; var b10 = a21 * a33 - a23 * a31; var b11 = a22 * a33 - a23 * a32; var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; if (!det) { return null; } det = 1 / det; out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; return out; } function multiply(out, a, b) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; return out; } function translate$1(out, a, v) { var x = v[0], y = v[1], z = v[2]; var a00, a01, a02, a03; var a10, a11, a12, a13; var a20, a21, a22, a23; if (a === out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; } else { a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; out[12] = a00 * x + a10 * y + a20 * z + a[12]; out[13] = a01 * x + a11 * y + a21 * z + a[13]; out[14] = a02 * x + a12 * y + a22 * z + a[14]; out[15] = a03 * x + a13 * y + a23 * z + a[15]; } return out; } function scale(out, a, v) { var x = v[0], y = v[1], z = v[2]; out[0] = a[0] * x; out[1] = a[1] * x; out[2] = a[2] * x; out[3] = a[3] * x; out[4] = a[4] * y; out[5] = a[5] * y; out[6] = a[6] * y; out[7] = a[7] * y; out[8] = a[8] * z; out[9] = a[9] * z; out[10] = a[10] * z; out[11] = a[11] * z; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; } function rotateX(out, a, rad) { var s = Math.sin(rad); var c = Math.cos(rad); var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; if (a !== out) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[4] = a10 * c + a20 * s; out[5] = a11 * c + a21 * s; out[6] = a12 * c + a22 * s; out[7] = a13 * c + a23 * s; out[8] = a20 * c - a10 * s; out[9] = a21 * c - a11 * s; out[10] = a22 * c - a12 * s; out[11] = a23 * c - a13 * s; return out; } function rotateY(out, a, rad) { var s = Math.sin(rad); var c = Math.cos(rad); var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; if (a !== out) { out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c - a20 * s; out[1] = a01 * c - a21 * s; out[2] = a02 * c - a22 * s; out[3] = a03 * c - a23 * s; out[8] = a00 * s + a20 * c; out[9] = a01 * s + a21 * c; out[10] = a02 * s + a22 * c; out[11] = a03 * s + a23 * c; return out; } function rotateZ(out, a, rad) { var s = Math.sin(rad); var c = Math.cos(rad); var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; if (a !== out) { out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c + a10 * s; out[1] = a01 * c + a11 * s; out[2] = a02 * c + a12 * s; out[3] = a03 * c + a13 * s; out[4] = a10 * c - a00 * s; out[5] = a11 * c - a01 * s; out[6] = a12 * c - a02 * s; out[7] = a13 * c - a03 * s; return out; } function fromQuat(out, q) { var x = q[0], y = q[1], z = q[2], w = q[3]; var x2 = x + x; var y2 = y + y; var z2 = z + z; var xx = x * x2; var yx = y * x2; var yy = y * y2; var zx = z * x2; var zy = z * y2; var zz = z * z2; var wx = w * x2; var wy = w * y2; var wz = w * z2; out[0] = 1 - yy - zz; out[1] = yx + wz; out[2] = zx - wy; out[3] = 0; out[4] = yx - wz; out[5] = 1 - xx - zz; out[6] = zy + wx; out[7] = 0; out[8] = zx + wy; out[9] = zy - wx; out[10] = 1 - xx - yy; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function perspective(out, fovy, aspect, near, far) { var f = 1 / Math.tan(fovy / 2), nf; out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = -1; out[12] = 0; out[13] = 0; out[15] = 0; if (far != null && far !== Infinity) { nf = 1 / (near - far); out[10] = (far + near) * nf; out[14] = 2 * far * near * nf; } else { out[10] = -1; out[14] = -2 * near; } return out; } function ortho(out, left, right, bottom, top, near, far) { var lr = 1 / (left - right); var bt = 1 / (bottom - top); var nf = 1 / (near - far); out[0] = -2 * lr; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = -2 * bt; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 2 * nf; out[11] = 0; out[12] = (left + right) * lr; out[13] = (top + bottom) * bt; out[14] = (far + near) * nf; out[15] = 1; return out; } var mul = multiply; function create$3() { var out = new ARRAY_TYPE(3); if (ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; } return out; } function clone$2(a) { var out = new ARRAY_TYPE(3); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } function length(a) { var x = a[0]; var y = a[1]; var z = a[2]; return Math.hypot(x, y, z); } function fromValues(x, y, z) { var out = new ARRAY_TYPE(3); out[0] = x; out[1] = y; out[2] = z; return out; } function copy(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } function add(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; out[2] = a[2] + b[2]; return out; } function subtract(out, a, b) { out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; out[2] = a[2] - b[2]; return out; } function multiply$1(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; out[2] = a[2] * b[2]; return out; } function divide(out, a, b) { out[0] = a[0] / b[0]; out[1] = a[1] / b[1]; out[2] = a[2] / b[2]; return out; } function scale$1(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; out[2] = a[2] * b; return out; } function scaleAndAdd(out, a, b, scale) { out[0] = a[0] + b[0] * scale; out[1] = a[1] + b[1] * scale; out[2] = a[2] + b[2] * scale; return out; } function normalize(out, a) { var x = a[0]; var y = a[1]; var z = a[2]; var len = x * x + y * y + z * z; if (len > 0) { len = 1 / Math.sqrt(len); } out[0] = a[0] * len; out[1] = a[1] * len; out[2] = a[2] * len; return out; } function dot(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } function cross(out, a, b) { var ax = a[0], ay = a[1], az = a[2]; var bx = b[0], by = b[1], bz = b[2]; out[0] = ay * bz - az * by; out[1] = az * bx - ax * bz; out[2] = ax * by - ay * bx; return out; } function transformMat4(out, a, m) { var x = a[0], y = a[1], z = a[2]; var w = m[3] * x + m[7] * y + m[11] * z + m[15]; w = w || 1; out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w; out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w; out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w; return out; } function transformMat3(out, a, m) { var x = a[0], y = a[1], z = a[2]; out[0] = x * m[0] + y * m[3] + z * m[6]; out[1] = x * m[1] + y * m[4] + z * m[7]; out[2] = x * m[2] + y * m[5] + z * m[8]; return out; } function transformQuat(out, a, q) { var qx = q[0], qy = q[1], qz = q[2], qw = q[3]; var x = a[0], y = a[1], z = a[2]; var uvx = qy * z - qz * y, uvy = qz * x - qx * z, uvz = qx * y - qy * x; var uuvx = qy * uvz - qz * uvy, uuvy = qz * uvx - qx * uvz, uuvz = qx * uvy - qy * uvx; var w2 = qw * 2; uvx *= w2; uvy *= w2; uvz *= w2; uuvx *= 2; uuvy *= 2; uuvz *= 2; out[0] = x + uvx + uuvx; out[1] = y + uvy + uuvy; out[2] = z + uvz + uuvz; return out; } function exactEquals(a, b) { return a[0] === b[0] && a[1] === b[1] && a[2] === b[2]; } var sub = subtract; var mul$1 = multiply$1; var div = divide; var len = length; var forEach = function () { var vec = create$3(); return function (a, stride, offset, count, fn, arg) { var i, l; if (!stride) { stride = 3; } if (!offset) { offset = 0; } if (count) { l = Math.min(count * stride + offset, a.length); } else { l = a.length; } for (i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; vec[2] = a[i + 2]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; a[i + 2] = vec[2]; } return a; }; }(); function create$4() { var out = new ARRAY_TYPE(4); if (ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0; } return out; } function multiply$2(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; out[2] = a[2] * b[2]; out[3] = a[3] * b[3]; return out; } function scale$2(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; out[2] = a[2] * b; out[3] = a[3] * b; return out; } function length$1(a) { var x = a[0]; var y = a[1]; var z = a[2]; var w = a[3]; return Math.hypot(x, y, z, w); } function normalize$1(out, a) { var x = a[0]; var y = a[1]; var z = a[2]; var w = a[3]; var len = x * x + y * y + z * z + w * w; if (len > 0) { len = 1 / Math.sqrt(len); } out[0] = x * len; out[1] = y * len; out[2] = z * len; out[3] = w * len; return out; } function dot$1(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; } function transformMat4$1(out, a, m) { var x = a[0], y = a[1], z = a[2], w = a[3]; out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w; out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w; out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w; out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w; return out; } function exactEquals$1(a, b) { return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; } var mul$2 = multiply$2; var forEach$1 = function () { var vec = create$4(); return function (a, stride, offset, count, fn, arg) { var i, l; if (!stride) { stride = 4; } if (!offset) { offset = 0; } if (count) { l = Math.min(count * stride + offset, a.length); } else { l = a.length; } for (i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; vec[2] = a[i + 2]; vec[3] = a[i + 3]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; a[i + 2] = vec[2]; a[i + 3] = vec[3]; } return a; }; }(); function create$5() { var out = new ARRAY_TYPE(4); if (ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; } out[3] = 1; return out; } function identity$1(out) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 1; return out; } function setAxisAngle(out, axis, rad) { rad = rad * 0.5; var s = Math.sin(rad); out[0] = s * axis[0]; out[1] = s * axis[1]; out[2] = s * axis[2]; out[3] = Math.cos(rad); return out; } function rotateX$1(out, a, rad) { rad *= 0.5; var ax = a[0], ay = a[1], az = a[2], aw = a[3]; var bx = Math.sin(rad), bw = Math.cos(rad); out[0] = ax * bw + aw * bx; out[1] = ay * bw + az * bx; out[2] = az * bw - ay * bx; out[3] = aw * bw - ax * bx; return out; } function rotateY$1(out, a, rad) { rad *= 0.5; var ax = a[0], ay = a[1], az = a[2], aw = a[3]; var by = Math.sin(rad), bw = Math.cos(rad); out[0] = ax * bw - az * by; out[1] = ay * bw + aw * by; out[2] = az * bw + ax * by; out[3] = aw * bw - ay * by; return out; } function rotateZ$1(out, a, rad) { rad *= 0.5; var ax = a[0], ay = a[1], az = a[2], aw = a[3]; var bz = Math.sin(rad), bw = Math.cos(rad); out[0] = ax * bw + ay * bz; out[1] = ay * bw - ax * bz; out[2] = az * bw + aw * bz; out[3] = aw * bw - az * bz; return out; } function slerp(out, a, b, t) { var ax = a[0], ay = a[1], az = a[2], aw = a[3]; var bx = b[0], by = b[1], bz = b[2], bw = b[3]; var omega, cosom, sinom, scale0, scale1; cosom = ax * bx + ay * by + az * bz + aw * bw; if (cosom < 0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw; } if (1 - cosom > EPSILON) { omega = Math.acos(cosom); sinom = Math.sin(omega); scale0 = Math.sin((1 - t) * omega) / sinom; scale1 = Math.sin(t * omega) / sinom; } else { scale0 = 1 - t; scale1 = t; } out[0] = scale0 * ax + scale1 * bx; out[1] = scale0 * ay + scale1 * by; out[2] = scale0 * az + scale1 * bz; out[3] = scale0 * aw + scale1 * bw; return out; } function conjugate(out, a) { out[0] = -a[0]; out[1] = -a[1]; out[2] = -a[2]; out[3] = a[3]; return out; } function fromMat3(out, m) { var fTrace = m[0] + m[4] + m[8]; var fRoot; if (fTrace > 0) { fRoot = Math.sqrt(fTrace + 1); out[3] = 0.5 * fRoot; fRoot = 0.5 / fRoot; out[0] = (m[5] - m[7]) * fRoot; out[1] = (m[6] - m[2]) * fRoot; out[2] = (m[1] - m[3]) * fRoot; } else { var i = 0; if (m[4] > m[0]) i = 1; if (m[8] > m[i * 3 + i]) i = 2; var j = (i + 1) % 3; var k = (i + 2) % 3; fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1); out[i] = 0.5 * fRoot; fRoot = 0.5 / fRoot; out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot; out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot; out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot; } return out; } var length$2 = length$1; var normalize$2 = normalize$1; var exactEquals$2 = exactEquals$1; var rotationTo = function () { var tmpvec3 = create$3(); var xUnitVec3 = fromValues(1, 0, 0); var yUnitVec3 = fromValues(0, 1, 0); return function (out, a, b) { var dot$1 = dot(a, b); if (dot$1 < -0.999999) { cross(tmpvec3, xUnitVec3, a); if (len(tmpvec3) < 0.000001) cross(tmpvec3, yUnitVec3, a); normalize(tmpvec3, tmpvec3); setAxisAngle(out, tmpvec3, Math.PI); return out; } else if (dot$1 > 0.999999) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 1; return out; } else { cross(tmpvec3, a, b); out[0] = tmpvec3[0]; out[1] = tmpvec3[1]; out[2] = tmpvec3[2]; out[3] = 1 + dot$1; return normalize$2(out, out); } }; }(); var sqlerp = function () { var temp1 = create$5(); var temp2 = create$5(); return function (out, a, b, c, d, t) { slerp(temp1, a, d, t); slerp(temp2, b, c, t); slerp(out, temp1, temp2, 2 * t * (1 - t)); return out; }; }(); var setAxes = function () { var matr = create$1(); return function (out, view, right, up) { matr[0] = right[0]; matr[3] = right[1]; matr[6] = right[2]; matr[1] = up[0]; matr[4] = up[1]; matr[7] = up[2]; matr[2] = -view[0]; matr[5] = -view[1]; matr[8] = -view[2]; return normalize$2(out, fromMat3(out, matr)); }; }(); class Ray { constructor(pos_, dir_) { this.pos = pos_; this.dir = dir_; } intersectsPlane(pt, normal, out) { const D = dot(normal, this.dir); if (Math.abs(D) < 0.000001) { return false; } const t = dot(sub(create$3(), pt, this.pos), normal) / D; const intersection = scaleAndAdd(create$3(), this.pos, this.dir, t); copy(out, intersection); return true; } } class Frustum { constructor(points_, planes_) { this.points = points_; this.planes = planes_; } static fromInvProjectionMatrix(invProj, worldSize, zoom) { const clipSpaceCorners = [ [ -1, 1, -1, 1 ], [ 1, 1, -1, 1 ], [ 1, -1, -1, 1 ], [ -1, -1, -1, 1 ], [ -1, 1, 1, 1 ], [ 1, 1, 1, 1 ], [ 1, -1, 1, 1 ], [ -1, -1, 1, 1 ] ]; const scale = Math.pow(2, zoom); const frustumCoords = clipSpaceCorners.map(v => { const s = transformMat4$1([], v, invProj); const k = 1 / s[3] / worldSize * scale; return mul$2(s, s, [ k, k, 1 / s[3], k ]); }); const frustumPlanePointIndices = [ [ 0, 1, 2 ], [ 6, 5, 4 ], [ 0, 3, 7 ], [ 2, 1, 5 ], [ 3, 2, 6 ], [ 0, 4, 5 ] ]; const frustumPlanes = frustumPlanePointIndices.map(p => { const a = sub([], frustumCoords[p[0]], frustumCoords[p[1]]); const b = sub([], frustumCoords[p[2]], frustumCoords[p[1]]); const n = normalize([], cross([], a, b)); const d = -dot(n, frustumCoords[p[1]]); return n.concat(d); }); return new Frustum(frustumCoords, frustumPlanes); } } class Aabb { constructor(min_, max_) { this.min = min_; this.max = max_; this.center = scale$1([], add([], this.min, this.max), 0.5); } quadrant(index) { const split = [ index % 2 === 0, index < 2 ]; const qMin = clone$2(this.min); const qMax = clone$2(this.max); for (let axis = 0; axis < split.length; axis++) { qMin[axis] = split[axis] ? this.min[axis] : this.center[axis]; qMax[axis] = split[axis] ? this.center[axis] : this.max[axis]; } qMax[2] = this.max[2]; return new Aabb(qMin, qMax); } distanceX(point) { const pointOnAabb = Math.max(Math.min(this.max[0], point[0]), this.min[0]); return pointOnAabb - point[0]; } distanceY(point) { const pointOnAabb = Math.max(Math.min(this.max[1], point[1]), this.min[1]); return pointOnAabb - point[1]; } distanceZ(point) { const pointOnAabb = Math.max(Math.min(this.max[2], point[2]), this.min[2]); return pointOnAabb - point[2]; } intersects(frustum) { const aabbPoints = [ [ this.min[0], this.min[1], this.min[2], 1 ], [ this.max[0], this.min[1], this.min[2], 1 ], [ this.max[0], this.max[1], this.min[2], 1 ], [ this.min[0], this.max[1], this.min[2], 1 ], [ this.min[0], this.min[1], this.max[2], 1 ], [ this.max[0], this.min[1], this.max[2], 1 ], [ this.max[0], this.max[1], this.max[2], 1 ], [ this.min[0], this.max[1], this.max[2], 1 ] ]; let fullyInside = true; for (let p = 0; p < frustum.planes.length; p++) { const plane = frustum.planes[p]; let pointsInside = 0; for (let i = 0; i < aabbPoints.length; i++) { pointsInside += dot$1(plane, aabbPoints[i]) >= 0; } if (pointsInside === 0) return 0; if (pointsInside !== aabbPoints.length) fullyInside = false; } if (fullyInside) return 2; for (let axis = 0; axis < 3; axis++) { let projMin = Number.MAX_VALUE; let projMax = -Number.MAX_VALUE; for (let p = 0; p < frustum.points.length; p++) { const projectedPoint = frustum.points[p][axis] - this.min[axis]; projMin = Math.min(projMin, projectedPoint); projMax = Math.max(projMax, projectedPoint); } if (projMax < 0 || projMin > this.max[axis] - this.min[axis]) return 0; } return 1; } } class CircleStyleLayer extends StyleLayer { constructor(layer) { super(layer, properties); } createBucket(parameters) { return new CircleBucket(parameters); } queryRadius(bucket) { const circleBucket = bucket; return getMaximumPaintValue('circle-radius', this, circleBucket) + getMaximumPaintValue('circle-stroke-width', this, circleBucket) + translateDistance(this.paint.get('circle-translate')); } queryIntersectsFeature(queryGeometry, feature, featureState, geometry, zoom, transform, pixelPosMatrix, elevationHelper) { const alignWithMap = this.paint.get('circle-pitch-alignment') === 'map'; if (alignWithMap && queryGeometry.queryGeometry.isAboveHorizon) return false; const translation = tilespaceTranslate(this.paint.get('circle-translate'), this.paint.get('circle-translate-anchor'), transform.angle, queryGeometry.pixelToTileUnitsFactor); const radius = this.paint.get('circle-radius').evaluate(feature, featureState); const stroke = this.paint.get('circle-stroke-width').evaluate(feature, featureState); const size = radius + stroke; const transformedSize = alignWithMap ? size * queryGeometry.pixelToTileUnitsFactor : size; for (const ring of geometry) { for (const point of ring) { const translatedPoint = point.add(translation); const z = elevationHelper && transform.elevation ? transform.elevation.exaggeration() * elevationHelper.getElevationAt(translatedPoint.x, translatedPoint.y, true) : 0; const transformedPoint = alignWithMap ? translatedPoint : projectPoint(translatedPoint, z, pixelPosMatrix); const transformedPolygon = alignWithMap ? queryGeometry.tilespaceRays.map(r => intersectAtHeight(r, z)) : queryGeometry.queryGeometry.screenGeometry; let adjustedSize = transformedSize; const projectedCenter = transformMat4$1([], [ point.x, point.y, z, 1 ], pixelPosMatrix); if (this.paint.get('circle-pitch-scale') === 'viewport' && this.paint.get('circle-pitch-alignment') === 'map') { adjustedSize *= projectedCenter[3] / transform.cameraToCenterDistance; } else if (this.paint.get('circle-pitch-scale') === 'map' && this.paint.get('circle-pitch-alignment') === 'viewport') { adjustedSize *= transform.cameraToCenterDistance / projectedCenter[3]; } if (polygonIntersectsBufferedPoint(transformedPolygon, transformedPoint, adjustedSize)) return true; } } return false; } getProgramIds() { return ['circle']; } getProgramConfiguration(zoom) { return new ProgramConfiguration(this, zoom); } } function projectPoint(p, z, pixelPosMatrix) { const point = transformMat4$1([], [ p.x, p.y, z, 1 ], pixelPosMatrix); return new pointGeometry(point[0] / point[3], point[1] / point[3]); } const origin = fromValues(0, 0, 0); const up = fromValues(0, 0, 1); function intersectAtHeight(r, z) { const intersectionPt = create$3(); origin[2] = z; const intersects = r.intersectsPlane(origin, up, intersectionPt); return new pointGeometry(intersectionPt[0], intersectionPt[1]); } class HeatmapBucket extends CircleBucket { } register('HeatmapBucket', HeatmapBucket, { omit: ['layers'] }); function createImage(image, {width, height}, channels, data) { if (!data) { data = new Uint8Array(width * height * channels); } else if (data instanceof Uint8ClampedArray) { data = new Uint8Array(data.buffer); } else if (data.length !== width * height * channels) { throw new RangeError('mismatched image size'); } image.width = width; image.height = height; image.data = data; return image; } function resizeImage(image, {width, height}, channels) { if (width === image.width && height === image.height) { return; } const newImage = createImage({}, { width, height }, channels); copyImage(image, newImage, { x: 0, y: 0 }, { x: 0, y: 0 }, { width: Math.min(image.width, width), height: Math.min(image.height, height) }, channels); image.width = width; image.height = height; image.data = newImage.data; } function copyImage(srcImg, dstImg, srcPt, dstPt, size, channels) { if (size.width === 0 || size.height === 0) { return dstImg; } if (size.width > srcImg.width || size.height > srcImg.height || srcPt.x > srcImg.width - size.width || srcPt.y > srcImg.height - size.height) { throw new RangeError('out of range source coordinates for image copy'); } if (size.width > dstImg.width || size.height > dstImg.height || dstPt.x > dstImg.width - size.width || dstPt.y > dstImg.height - size.height) { throw new RangeError('out of range destination coordinates for image copy'); } const srcData = srcImg.data; const dstData = dstImg.data; for (let y = 0; y < size.height; y++) { const srcOffset = ((srcPt.y + y) * srcImg.width + srcPt.x) * channels; const dstOffset = ((dstPt.y + y) * dstImg.width + dstPt.x) * channels; for (let i = 0; i < size.width * channels; i++) { dstData[dstOffset + i] = srcData[srcOffset + i]; } } return dstImg; } class AlphaImage { constructor(size, data) { createImage(this, size, 1, data); } resize(size) { resizeImage(this, size, 1); } clone() { return new AlphaImage({ width: this.width, height: this.height }, new Uint8Array(this.data)); } static copy(srcImg, dstImg, srcPt, dstPt, size) { copyImage(srcImg, dstImg, srcPt, dstPt, size, 1); } } class RGBAImage { constructor(size, data) { createImage(this, size, 4, data); } resize(size) { resizeImage(this, size, 4); } replace(data, copy) { if (copy) { this.data.set(data); } else if (data instanceof Uint8ClampedArray) { this.data = new Uint8Array(data.buffer); } else { this.data = data; } } clone() { return new RGBAImage({ width: this.width, height: this.height }, new Uint8Array(this.data)); } static copy(srcImg, dstImg, srcPt, dstPt, size) { copyImage(srcImg, dstImg, srcPt, dstPt, size, 4); } } register('AlphaImage', AlphaImage); register('RGBAImage', RGBAImage); const paint$2 = new Properties({ 'heatmap-radius': new DataDrivenProperty(spec['paint_heatmap']['heatmap-radius']), 'heatmap-weight': new DataDrivenProperty(spec['paint_heatmap']['heatmap-weight']), 'heatmap-intensity': new DataConstantProperty(spec['paint_heatmap']['heatmap-intensity']), 'heatmap-color': new ColorRampProperty(spec['paint_heatmap']['heatmap-color']), 'heatmap-opacity': new DataConstantProperty(spec['paint_heatmap']['heatmap-opacity']) }); var properties$1 = { paint: paint$2 }; function renderColorRamp(params) { const evaluationGlobals = {}; const width = params.resolution || 256; const height = params.clips ? params.clips.length : 1; const image = params.image || new RGBAImage({ width, height }); const renderPixel = (stride, index, progress) => { evaluationGlobals[params.evaluationKey] = progress; const pxColor = params.expression.evaluate(evaluationGlobals); image.data[stride + index + 0] = Math.floor(pxColor.r * 255 / pxColor.a); image.data[stride + index + 1] = Math.floor(pxColor.g * 255 / pxColor.a); image.data[stride + index + 2] = Math.floor(pxColor.b * 255 / pxColor.a); image.data[stride + index + 3] = Math.floor(pxColor.a * 255); }; if (!params.clips) { for (let i = 0, j = 0; i < width; i++, j += 4) { const progress = i / (width - 1); renderPixel(0, j, progress); } } else { for (let clip = 0, stride = 0; clip < height; ++clip, stride += width * 4) { for (let i = 0, j = 0; i < width; i++, j += 4) { const progress = i / (width - 1); const {start, end} = params.clips[clip]; const evaluationProgress = start * (1 - progress) + end * progress; renderPixel(stride, j, evaluationProgress); } } } return image; } class HeatmapStyleLayer extends StyleLayer { createBucket(options) { return new HeatmapBucket(options); } constructor(layer) { super(layer, properties$1); this._updateColorRamp(); } _handleSpecialPaintPropertyUpdate(name) { if (name === 'heatmap-color') { this._updateColorRamp(); } } _updateColorRamp() { const expression = this._transitionablePaint._values['heatmap-color'].value.expression; this.colorRamp = renderColorRamp({ expression, evaluationKey: 'heatmapDensity', image: this.colorRamp }); this.colorRampTexture = null; } resize() { if (this.heatmapFbo) { this.heatmapFbo.destroy(); this.heatmapFbo = null; } } queryRadius() { return 0; } queryIntersectsFeature() { return false; } hasOffscreenPass() { return this.paint.get('heatmap-opacity') !== 0 && this.visibility !== 'none'; } getProgramIds() { return [ 'heatmap', 'heatmapTexture' ]; } getProgramConfiguration(zoom) { return new ProgramConfiguration(this, zoom); } } const paint$3 = new Properties({ 'hillshade-illumination-direction': new DataConstantProperty(spec['paint_hillshade']['hillshade-illumination-direction']), 'hillshade-illumination-anchor': new DataConstantProperty(spec['paint_hillshade']['hillshade-illumination-anchor']), 'hillshade-exaggeration': new DataConstantProperty(spec['paint_hillshade']['hillshade-exaggeration']), 'hillshade-shadow-color': new DataConstantProperty(spec['paint_hillshade']['hillshade-shadow-color']), 'hillshade-highlight-color': new DataConstantProperty(spec['paint_hillshade']['hillshade-highlight-color']), 'hillshade-accent-color': new DataConstantProperty(spec['paint_hillshade']['hillshade-accent-color']) }); var properties$2 = { paint: paint$3 }; class HillshadeStyleLayer extends StyleLayer { constructor(layer) { super(layer, properties$2); } hasOffscreenPass() { return this.paint.get('hillshade-exaggeration') !== 0 && this.visibility !== 'none'; } getProgramIds() { return [ 'hillshade', 'hillshadePrepare' ]; } getProgramConfiguration(zoom) { return new ProgramConfiguration(this, zoom); } } const layout$3 = createLayout([{ name: 'a_pos', components: 2, type: 'Int16' }], 4); const {members: members$1, size: size$1, alignment: alignment$1} = layout$3; var earcut_1 = earcut; var default_1 = earcut; function earcut(data, holeIndices, dim) { dim = dim || 2; var hasHoles = holeIndices && holeIndices.length, outerLen = hasHoles ? holeIndices[0] * dim : data.length, outerNode = linkedList(data, 0, outerLen, dim, true), triangles = []; if (!outerNode || outerNode.next === outerNode.prev) return triangles; var minX, minY, maxX, maxY, x, y, invSize; if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); if (data.length > 80 * dim) { minX = maxX = data[0]; minY = maxY = data[1]; for (var i = dim; i < outerLen; i += dim) { x = data[i]; y = data[i + 1]; if (x < minX) minX = x; if (y < minY) minY = y; if (x > maxX) maxX = x; if (y > maxY) maxY = y; } invSize = Math.max(maxX - minX, maxY - minY); invSize = invSize !== 0 ? 1 / invSize : 0; } earcutLinked(outerNode, triangles, dim, minX, minY, invSize); return triangles; } function linkedList(data, start, end, dim, clockwise) { var i, last; if (clockwise === signedArea(data, start, end, dim) > 0) { for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last); } else { for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); } if (last && equals(last, last.next)) { removeNode(last); last = last.next; } return last; } function filterPoints(start, end) { if (!start) return start; if (!end) end = start; var p = start, again; do { again = false; if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { removeNode(p); p = end = p.prev; if (p === p.next) break; again = true; } else { p = p.next; } } while (again || p !== end); return end; } function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { if (!ear) return; if (!pass && invSize) indexCurve(ear, minX, minY, invSize); var stop = ear, prev, next; while (ear.prev !== ear.next) { prev = ear.prev; next = ear.next; if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { triangles.push(prev.i / dim); triangles.push(ear.i / dim); triangles.push(next.i / dim); removeNode(ear); ear = next.next; stop = next.next; continue; } ear = next; if (ear === stop) { if (!pass) { earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); } else if (pass === 1) { ear = cureLocalIntersections(filterPoints(ear), triangles, dim); earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); } else if (pass === 2) { splitEarcut(ear, triangles, dim, minX, minY, invSize); } break; } } } function isEar(ear) { var a = ear.prev, b = ear, c = ear.next; if (area(a, b, c) >= 0) return false; var p = ear.next.next; while (p !== ear.prev) { if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.next; } return true; } function isEarHashed(ear, minX, minY, invSize) { var a = ear.prev, b = ear, c = ear.next; if (area(a, b, c) >= 0) return false; var minTX = a.x < b.x ? a.x < c.x ? a.x : c.x : b.x < c.x ? b.x : c.x, minTY = a.y < b.y ? a.y < c.y ? a.y : c.y : b.y < c.y ? b.y : c.y, maxTX = a.x > b.x ? a.x > c.x ? a.x : c.x : b.x > c.x ? b.x : c.x, maxTY = a.y > b.y ? a.y > c.y ? a.y : c.y : b.y > c.y ? b.y : c.y; var minZ = zOrder(minTX, minTY, minX, minY, invSize), maxZ = zOrder(maxTX, maxTY, minX, minY, invSize); var p = ear.prevZ, n = ear.nextZ; while (p && p.z >= minZ && n && n.z <= maxZ) { if (p !== ear.prev && p !== ear.next && pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.prevZ; if (n !== ear.prev && n !== ear.next && pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; n = n.nextZ; } while (p && p.z >= minZ) { if (p !== ear.prev && p !== ear.next && pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.prevZ; } while (n && n.z <= maxZ) { if (n !== ear.prev && n !== ear.next && pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; n = n.nextZ; } return true; } function cureLocalIntersections(start, triangles, dim) { var p = start; do { var a = p.prev, b = p.next.next; if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { triangles.push(a.i / dim); triangles.push(p.i / dim); triangles.push(b.i / dim); removeNode(p); removeNode(p.next); p = start = b; } p = p.next; } while (p !== start); return filterPoints(p); } function splitEarcut(start, triangles, dim, minX, minY, invSize) { var a = start; do { var b = a.next.next; while (b !== a.prev) { if (a.i !== b.i && isValidDiagonal(a, b)) { var c = splitPolygon(a, b); a = filterPoints(a, a.next); c = filterPoints(c, c.next); earcutLinked(a, triangles, dim, minX, minY, invSize); earcutLinked(c, triangles, dim, minX, minY, invSize); return; } b = b.next; } a = a.next; } while (a !== start); } function eliminateHoles(data, holeIndices, outerNode, dim) { var queue = [], i, len, start, end, list; for (i = 0, len = holeIndices.length; i < len; i++) { start = holeIndices[i] * dim; end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; list = linkedList(data, start, end, dim, false); if (list === list.next) list.steiner = true; queue.push(getLeftmost(list)); } queue.sort(compareX); for (i = 0; i < queue.length; i++) { eliminateHole(queue[i], outerNode); outerNode = filterPoints(outerNode, outerNode.next); } return outerNode; } function compareX(a, b) { return a.x - b.x; } function eliminateHole(hole, outerNode) { outerNode = findHoleBridge(hole, outerNode); if (outerNode) { var b = splitPolygon(outerNode, hole); filterPoints(outerNode, outerNode.next); filterPoints(b, b.next); } } function findHoleBridge(hole, outerNode) { var p = outerNode, hx = hole.x, hy = hole.y, qx = -Infinity, m; do { if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); if (x <= hx && x > qx) { qx = x; if (x === hx) { if (hy === p.y) return p; if (hy === p.next.y) return p.next; } m = p.x < p.next.x ? p : p.next; } } p = p.next; } while (p !== outerNode); if (!m) return null; if (hx === qx) return m; var stop = m, mx = m.x, my = m.y, tanMin = Infinity, tan; p = m; do { if (hx >= p.x && p.x >= mx && hx !== p.x && pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { tan = Math.abs(hy - p.y) / (hx - p.x); if (locallyInside(p, hole) && (tan < tanMin || tan === tanMin && (p.x > m.x || p.x === m.x && sectorContainsSector(m, p)))) { m = p; tanMin = tan; } } p = p.next; } while (p !== stop); return m; } function sectorContainsSector(m, p) { return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; } function indexCurve(start, minX, minY, invSize) { var p = start; do { if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize); p.prevZ = p.prev; p.nextZ = p.next; p = p.next; } while (p !== start); p.prevZ.nextZ = null; p.prevZ = null; sortLinked(p); } function sortLinked(list) { var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1; do { p = list; list = null; tail = null; numMerges = 0; while (p) { numMerges++; q = p; pSize = 0; for (i = 0; i < inSize; i++) { pSize++; q = q.nextZ; if (!q) break; } qSize = inSize; while (pSize > 0 || qSize > 0 && q) { if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { e = p; p = p.nextZ; pSize--; } else { e = q; q = q.nextZ; qSize--; } if (tail) tail.nextZ = e; else list = e; e.prevZ = tail; tail = e; } p = q; } tail.nextZ = null; inSize *= 2; } while (numMerges > 1); return list; } function zOrder(x, y, minX, minY, invSize) { x = 32767 * (x - minX) * invSize; y = 32767 * (y - minY) * invSize; x = (x | x << 8) & 16711935; x = (x | x << 4) & 252645135; x = (x | x << 2) & 858993459; x = (x | x << 1) & 1431655765; y = (y | y << 8) & 16711935; y = (y | y << 4) & 252645135; y = (y | y << 2) & 858993459; y = (y | y << 1) & 1431655765; return x | y << 1; } function getLeftmost(start) { var p = start, leftmost = start; do { if (p.x < leftmost.x || p.x === leftmost.x && p.y < leftmost.y) leftmost = p; p = p.next; } while (p !== start); return leftmost; } function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0; } function isValidDiagonal(a, b) { return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && (area(a.prev, a, b.prev) || area(a, b.prev, b)) || equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); } function area(p, q, r) { return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); } function equals(p1, p2) { return p1.x === p2.x && p1.y === p2.y; } function intersects(p1, q1, p2, q2) { var o1 = sign(area(p1, q1, p2)); var o2 = sign(area(p1, q1, q2)); var o3 = sign(area(p2, q2, p1)); var o4 = sign(area(p2, q2, q1)); if (o1 !== o2 && o3 !== o4) return true; if (o1 === 0 && onSegment(p1, p2, q1)) return true; if (o2 === 0 && onSegment(p1, q2, q1)) return true; if (o3 === 0 && onSegment(p2, p1, q2)) return true; if (o4 === 0 && onSegment(p2, q1, q2)) return true; return false; } function onSegment(p, q, r) { return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); } function sign(num) { return num > 0 ? 1 : num < 0 ? -1 : 0; } function intersectsPolygon(a, b) { var p = a; do { if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) return true; p = p.next; } while (p !== a); return false; } function locallyInside(a, b) { return area(a.prev, a, a.next) < 0 ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; } function middleInside(a, b) { var p = a, inside = false, px = (a.x + b.x) / 2, py = (a.y + b.y) / 2; do { if (p.y > py !== p.next.y > py && p.next.y !== p.y && px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x) inside = !inside; p = p.next; } while (p !== a); return inside; } function splitPolygon(a, b) { var a2 = new Node(a.i, a.x, a.y), b2 = new Node(b.i, b.x, b.y), an = a.next, bp = b.prev; a.next = b; b.prev = a; a2.next = an; an.prev = a2; b2.next = a2; a2.prev = b2; bp.next = b2; b2.prev = bp; return b2; } function insertNode(i, x, y, last) { var p = new Node(i, x, y); if (!last) { p.prev = p; p.next = p; } else { p.next = last.next; p.prev = last; last.next.prev = p; last.next = p; } return p; } function removeNode(p) { p.next.prev = p.prev; p.prev.next = p.next; if (p.prevZ) p.prevZ.nextZ = p.nextZ; if (p.nextZ) p.nextZ.prevZ = p.prevZ; } function Node(i, x, y) { this.i = i; this.x = x; this.y = y; this.prev = null; this.next = null; this.z = null; this.prevZ = null; this.nextZ = null; this.steiner = false; } earcut.deviation = function (data, holeIndices, dim, triangles) { var hasHoles = holeIndices && holeIndices.length; var outerLen = hasHoles ? holeIndices[0] * dim : data.length; var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); if (hasHoles) { for (var i = 0, len = holeIndices.length; i < len; i++) { var start = holeIndices[i] * dim; var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; polygonArea -= Math.abs(signedArea(data, start, end, dim)); } } var trianglesArea = 0; for (i = 0; i < triangles.length; i += 3) { var a = triangles[i] * dim; var b = triangles[i + 1] * dim; var c = triangles[i + 2] * dim; trianglesArea += Math.abs((data[a] - data[c]) * (data[b + 1] - data[a + 1]) - (data[a] - data[b]) * (data[c + 1] - data[a + 1])); } return polygonArea === 0 && trianglesArea === 0 ? 0 : Math.abs((trianglesArea - polygonArea) / polygonArea); }; function signedArea(data, start, end, dim) { var sum = 0; for (var i = start, j = end - dim; i < end; i += dim) { sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); j = i; } return sum; } earcut.flatten = function (data) { var dim = data[0][0].length, result = { vertices: [], holes: [], dimensions: dim }, holeIndex = 0; for (var i = 0; i < data.length; i++) { for (var j = 0; j < data[i].length; j++) { for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]); } if (i > 0) { holeIndex += data[i - 1].length; result.holes.push(holeIndex); } } return result; }; earcut_1.default = default_1; function quickselect(arr, k, left, right, compare) { quickselectStep(arr, k, left || 0, right || arr.length - 1, compare || defaultCompare); } function quickselectStep(arr, k, left, right, compare) { while (right > left) { if (right - left > 600) { var n = right - left + 1; var m = k - left + 1; var z = Math.log(n); var s = 0.5 * Math.exp(2 * z / 3); var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); quickselectStep(arr, k, newLeft, newRight, compare); } var t = arr[k]; var i = left; var j = right; swap$1(arr, left, k); if (compare(arr[right], t) > 0) swap$1(arr, left, right); while (i < j) { swap$1(arr, i, j); i++; j--; while (compare(arr[i], t) < 0) i++; while (compare(arr[j], t) > 0) j--; } if (compare(arr[left], t) === 0) swap$1(arr, left, j); else { j++; swap$1(arr, j, right); } if (j <= k) left = j + 1; if (k <= j) right = j - 1; } } function swap$1(arr, i, j) { var tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } function defaultCompare(a, b) { return a < b ? -1 : a > b ? 1 : 0; } function classifyRings(rings, maxRings) { const len = rings.length; if (len <= 1) return [rings]; const polygons = []; let polygon, ccw; for (let i = 0; i < len; i++) { const area = calculateSignedArea(rings[i]); if (area === 0) continue; rings[i].area = Math.abs(area); if (ccw === undefined) ccw = area < 0; if (ccw === area < 0) { if (polygon) polygons.push(polygon); polygon = [rings[i]]; } else { polygon.push(rings[i]); } } if (polygon) polygons.push(polygon); if (maxRings > 1) { for (let j = 0; j < polygons.length; j++) { if (polygons[j].length <= maxRings) continue; quickselect(polygons[j], maxRings, 1, polygons[j].length - 1, compareAreas); polygons[j] = polygons[j].slice(0, maxRings); } } return polygons; } function compareAreas(a, b) { return b.area - a.area; } function hasPattern(type, layers, options) { const patterns = options.patternDependencies; let hasPattern = false; for (const layer of layers) { const patternProperty = layer.paint.get(`${ type }-pattern`); if (!patternProperty.isConstant()) { hasPattern = true; } const constantPattern = patternProperty.constantOr(null); if (constantPattern) { hasPattern = true; patterns[constantPattern.to] = true; patterns[constantPattern.from] = true; } } return hasPattern; } function addPatternDependencies(type, layers, patternFeature, zoom, options) { const patterns = options.patternDependencies; for (const layer of layers) { const patternProperty = layer.paint.get(`${ type }-pattern`); const patternPropertyValue = patternProperty.value; if (patternPropertyValue.kind !== 'constant') { let min = patternPropertyValue.evaluate({ zoom: zoom - 1 }, patternFeature, {}, options.availableImages); let mid = patternPropertyValue.evaluate({ zoom }, patternFeature, {}, options.availableImages); let max = patternPropertyValue.evaluate({ zoom: zoom + 1 }, patternFeature, {}, options.availableImages); min = min && min.name ? min.name : min; mid = mid && mid.name ? mid.name : mid; max = max && max.name ? max.name : max; patterns[min] = true; patterns[mid] = true; patterns[max] = true; patternFeature.patterns[layer.id] = { min, mid, max }; } } return patternFeature; } const EARCUT_MAX_RINGS = 500; class FillBucket { constructor(options) { this.zoom = options.zoom; this.overscaling = options.overscaling; this.layers = options.layers; this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; this.hasPattern = false; this.patternFeatures = []; this.layoutVertexArray = new StructArrayLayout2i4(); this.indexArray = new StructArrayLayout3ui6(); this.indexArray2 = new StructArrayLayout2ui4(); this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.segments2 = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter(l => l.isStateDependent()).map(l => l.id); } populate(features, options, canonical) { this.hasPattern = hasPattern('fill', this.layers, options); const fillSortKey = this.layers[0].layout.get('fill-sort-key'); const bucketFeatures = []; for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = this.layers[0]._featureFilter.needGeometry; const evaluationFeature = toEvaluationFeature(feature, needGeometry); if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; const sortKey = fillSortKey ? fillSortKey.evaluate(evaluationFeature, {}, canonical, options.availableImages) : undefined; const bucketFeature = { id, properties: feature.properties, type: feature.type, sourceLayerIndex, index, geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature), patterns: {}, sortKey }; bucketFeatures.push(bucketFeature); } if (fillSortKey) { bucketFeatures.sort((a, b) => { return a.sortKey - b.sortKey; }); } for (const bucketFeature of bucketFeatures) { const {geometry, index, sourceLayerIndex} = bucketFeature; if (this.hasPattern) { const patternFeature = addPatternDependencies('fill', this.layers, bucketFeature, this.zoom, options); this.patternFeatures.push(patternFeature); } else { this.addFeature(bucketFeature, geometry, index, canonical, {}); } const feature = features[index].feature; options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); } } update(states, vtLayer, imagePositions) { if (!this.stateDependentLayers.length) return; this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); } addFeatures(options, canonical, imagePositions) { for (const feature of this.patternFeatures) { this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions); } } isEmpty() { return this.layoutVertexArray.length === 0; } uploadPending() { return !this.uploaded || this.programConfigurations.needsUpload; } upload(context) { if (!this.uploaded) { this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, members$1); this.indexBuffer = context.createIndexBuffer(this.indexArray); this.indexBuffer2 = context.createIndexBuffer(this.indexArray2); } this.programConfigurations.upload(context); this.uploaded = true; } destroy() { if (!this.layoutVertexBuffer) return; this.layoutVertexBuffer.destroy(); this.indexBuffer.destroy(); this.indexBuffer2.destroy(); this.programConfigurations.destroy(); this.segments.destroy(); this.segments2.destroy(); } addFeature(feature, geometry, index, canonical, imagePositions) { for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) { let numVertices = 0; for (const ring of polygon) { numVertices += ring.length; } const triangleSegment = this.segments.prepareSegment(numVertices, this.layoutVertexArray, this.indexArray); const triangleIndex = triangleSegment.vertexLength; const flattened = []; const holeIndices = []; for (const ring of polygon) { if (ring.length === 0) { continue; } if (ring !== polygon[0]) { holeIndices.push(flattened.length / 2); } const lineSegment = this.segments2.prepareSegment(ring.length, this.layoutVertexArray, this.indexArray2); const lineIndex = lineSegment.vertexLength; this.layoutVertexArray.emplaceBack(ring[0].x, ring[0].y); this.indexArray2.emplaceBack(lineIndex + ring.length - 1, lineIndex); flattened.push(ring[0].x); flattened.push(ring[0].y); for (let i = 1; i < ring.length; i++) { this.layoutVertexArray.emplaceBack(ring[i].x, ring[i].y); this.indexArray2.emplaceBack(lineIndex + i - 1, lineIndex + i); flattened.push(ring[i].x); flattened.push(ring[i].y); } lineSegment.vertexLength += ring.length; lineSegment.primitiveLength += ring.length; } const indices = earcut_1(flattened, holeIndices); for (let i = 0; i < indices.length; i += 3) { this.indexArray.emplaceBack(triangleIndex + indices[i], triangleIndex + indices[i + 1], triangleIndex + indices[i + 2]); } triangleSegment.vertexLength += numVertices; triangleSegment.primitiveLength += indices.length / 3; } this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); } } register('FillBucket', FillBucket, { omit: [ 'layers', 'patternFeatures' ] }); const layout$4 = new Properties({ 'fill-sort-key': new DataDrivenProperty(spec['layout_fill']['fill-sort-key']) }); const paint$4 = new Properties({ 'fill-antialias': new DataConstantProperty(spec['paint_fill']['fill-antialias']), 'fill-opacity': new DataDrivenProperty(spec['paint_fill']['fill-opacity']), 'fill-color': new DataDrivenProperty(spec['paint_fill']['fill-color']), 'fill-outline-color': new DataDrivenProperty(spec['paint_fill']['fill-outline-color']), 'fill-translate': new DataConstantProperty(spec['paint_fill']['fill-translate']), 'fill-translate-anchor': new DataConstantProperty(spec['paint_fill']['fill-translate-anchor']), 'fill-pattern': new CrossFadedDataDrivenProperty(spec['paint_fill']['fill-pattern']) }); var properties$3 = { paint: paint$4, layout: layout$4 }; class FillStyleLayer extends StyleLayer { constructor(layer) { super(layer, properties$3); } getProgramIds() { const pattern = this.paint.get('fill-pattern'); const image = pattern && pattern.constantOr(1); const ids = [image ? 'fillPattern' : 'fill']; if (this.paint.get('fill-antialias')) { ids.push(image && !this.getPaintProperty('fill-outline-color') ? 'fillOutlinePattern' : 'fillOutline'); } return ids; } getProgramConfiguration(zoom) { return new ProgramConfiguration(this, zoom); } recalculate(parameters, availableImages) { super.recalculate(parameters, availableImages); const outlineColor = this.paint._values['fill-outline-color']; if (outlineColor.value.kind === 'constant' && outlineColor.value.value === undefined) { this.paint._values['fill-outline-color'] = this.paint._values['fill-color']; } } createBucket(parameters) { return new FillBucket(parameters); } queryRadius() { return translateDistance(this.paint.get('fill-translate')); } queryIntersectsFeature(queryGeometry, feature, featureState, geometry, zoom, transform) { if (queryGeometry.queryGeometry.isAboveHorizon) return false; const translatedPolygon = translate(queryGeometry.tilespaceGeometry, this.paint.get('fill-translate'), this.paint.get('fill-translate-anchor'), transform.angle, queryGeometry.pixelToTileUnitsFactor); return polygonIntersectsMultiPolygon(translatedPolygon, geometry); } isTileClipped() { return true; } } const fillExtrusionAttributes = createLayout([{ name: 'a_pos_normal_ed', components: 4, type: 'Int16' }]); const centroidAttributes = createLayout([{ name: 'a_centroid_pos', components: 2, type: 'Uint16' }]); const {members: members$2, size: size$2, alignment: alignment$2} = fillExtrusionAttributes; var vectortilefeature = VectorTileFeature; function VectorTileFeature(pbf, end, extent, keys, values) { this.properties = {}; this.extent = extent; this.type = 0; this._pbf = pbf; this._geometry = -1; this._keys = keys; this._values = values; pbf.readFields(readFeature, this, end); } function readFeature(tag, feature, pbf) { if (tag == 1) feature.id = pbf.readVarint(); else if (tag == 2) readTag(pbf, feature); else if (tag == 3) feature.type = pbf.readVarint(); else if (tag == 4) feature._geometry = pbf.pos; } function readTag(pbf, feature) { var end = pbf.readVarint() + pbf.pos; while (pbf.pos < end) { var key = feature._keys[pbf.readVarint()], value = feature._values[pbf.readVarint()]; feature.properties[key] = value; } } VectorTileFeature.types = [ 'Unknown', 'Point', 'LineString', 'Polygon' ]; VectorTileFeature.prototype.loadGeometry = function () { var pbf = this._pbf; pbf.pos = this._geometry; var end = pbf.readVarint() + pbf.pos, cmd = 1, length = 0, x = 0, y = 0, lines = [], line; while (pbf.pos < end) { if (length <= 0) { var cmdLen = pbf.readVarint(); cmd = cmdLen & 7; length = cmdLen >> 3; } length--; if (cmd === 1 || cmd === 2) { x += pbf.readSVarint(); y += pbf.readSVarint(); if (cmd === 1) { if (line) lines.push(line); line = []; } line.push(new pointGeometry(x, y)); } else if (cmd === 7) { if (line) { line.push(line[0].clone()); } } else { throw new Error('unknown command ' + cmd); } } if (line) lines.push(line); return lines; }; VectorTileFeature.prototype.bbox = function () { var pbf = this._pbf; pbf.pos = this._geometry; var end = pbf.readVarint() + pbf.pos, cmd = 1, length = 0, x = 0, y = 0, x1 = Infinity, x2 = -Infinity, y1 = Infinity, y2 = -Infinity; while (pbf.pos < end) { if (length <= 0) { var cmdLen = pbf.readVarint(); cmd = cmdLen & 7; length = cmdLen >> 3; } length--; if (cmd === 1 || cmd === 2) { x += pbf.readSVarint(); y += pbf.readSVarint(); if (x < x1) x1 = x; if (x > x2) x2 = x; if (y < y1) y1 = y; if (y > y2) y2 = y; } else if (cmd !== 7) { throw new Error('unknown command ' + cmd); } } return [ x1, y1, x2, y2 ]; }; VectorTileFeature.prototype.toGeoJSON = function (x, y, z) { var size = this.extent * Math.pow(2, z), x0 = this.extent * x, y0 = this.extent * y, coords = this.loadGeometry(), type = VectorTileFeature.types[this.type], i, j; function project(line) { for (var j = 0; j < line.length; j++) { var p = line[j], y2 = 180 - (p.y + y0) * 360 / size; line[j] = [ (p.x + x0) * 360 / size - 180, 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90 ]; } } switch (this.type) { case 1: var points = []; for (i = 0; i < coords.length; i++) { points[i] = coords[i][0]; } coords = points; project(coords); break; case 2: for (i = 0; i < coords.length; i++) { project(coords[i]); } break; case 3: coords = classifyRings$1(coords); for (i = 0; i < coords.length; i++) { for (j = 0; j < coords[i].length; j++) { project(coords[i][j]); } } break; } if (coords.length === 1) { coords = coords[0]; } else { type = 'Multi' + type; } var result = { type: 'Feature', geometry: { type: type, coordinates: coords }, properties: this.properties }; if ('id' in this) { result.id = this.id; } return result; }; function classifyRings$1(rings) { var len = rings.length; if (len <= 1) return [rings]; var polygons = [], polygon, ccw; for (var i = 0; i < len; i++) { var area = signedArea$1(rings[i]); if (area === 0) continue; if (ccw === undefined) ccw = area < 0; if (ccw === area < 0) { if (polygon) polygons.push(polygon); polygon = [rings[i]]; } else { polygon.push(rings[i]); } } if (polygon) polygons.push(polygon); return polygons; } function signedArea$1(ring) { var sum = 0; for (var 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; } var vectortilelayer = VectorTileLayer; function VectorTileLayer(pbf, end) { this.version = 1; this.name = null; this.extent = 4096; this.length = 0; this._pbf = pbf; this._keys = []; this._values = []; this._features = []; pbf.readFields(readLayer, this, end); this.length = this._features.length; } function readLayer(tag, layer, pbf) { if (tag === 15) layer.version = pbf.readVarint(); else if (tag === 1) layer.name = pbf.readString(); else if (tag === 5) layer.extent = pbf.readVarint(); else if (tag === 2) layer._features.push(pbf.pos); else if (tag === 3) layer._keys.push(pbf.readString()); else if (tag === 4) layer._values.push(readValueMessage(pbf)); } function readValueMessage(pbf) { var value = null, end = pbf.readVarint() + pbf.pos; while (pbf.pos < end) { var tag = pbf.readVarint() >> 3; value = tag === 1 ? pbf.readString() : tag === 2 ? pbf.readFloat() : tag === 3 ? pbf.readDouble() : tag === 4 ? pbf.readVarint64() : tag === 5 ? pbf.readVarint() : tag === 6 ? pbf.readSVarint() : tag === 7 ? pbf.readBoolean() : null; } return value; } VectorTileLayer.prototype.feature = function (i) { if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds'); this._pbf.pos = this._features[i]; var end = this._pbf.readVarint() + this._pbf.pos; return new vectortilefeature(this._pbf, end, this.extent, this._keys, this._values); }; var vectortile = VectorTile; function VectorTile(pbf, end) { this.layers = pbf.readFields(readTile, {}, end); } function readTile(tag, layers, pbf) { if (tag === 3) { var layer = new vectortilelayer(pbf, pbf.readVarint() + pbf.pos); if (layer.length) layers[layer.name] = layer; } } var VectorTile$1 = vectortile; var VectorTileFeature$1 = vectortilefeature; var VectorTileLayer$1 = vectortilelayer; var vectorTile = { VectorTile: VectorTile$1, VectorTileFeature: VectorTileFeature$1, VectorTileLayer: VectorTileLayer$1 }; const vectorTileFeatureTypes = vectorTile.VectorTileFeature.types; const EARCUT_MAX_RINGS$1 = 500; const FACTOR = Math.pow(2, 13); const ELEVATION_SCALE = 7.3; function addVertex(vertexArray, x, y, nxRatio, nySign, normalUp, top, e) { vertexArray.emplaceBack((x << 1) + top, (y << 1) + normalUp, (Math.floor(nxRatio * FACTOR) << 1) + nySign, Math.round(e)); } class PartMetadata { constructor() { this.acc = new pointGeometry(0, 0); this.polyCount = []; } startRing(p) { this.currentPolyCount = { edges: 0, top: 0 }; this.polyCount.push(this.currentPolyCount); if (this.min) return; this.min = new pointGeometry(p.x, p.y); this.max = new pointGeometry(p.x, p.y); } append(p, prev) { this.currentPolyCount.edges++; this.acc._add(p); let checkBorders = !!this.borders; const min = this.min, max = this.max; if (p.x < min.x) { min.x = p.x; checkBorders = true; } else if (p.x > max.x) { max.x = p.x; checkBorders = true; } if (p.y < min.y) { min.y = p.y; checkBorders = true; } else if (p.y > max.y) { max.y = p.y; checkBorders = true; } if (((p.x === 0 || p.x === EXTENT$1) && p.x === prev.x) !== ((p.y === 0 || p.y === EXTENT$1) && p.y === prev.y)) { this.processBorderOverlap(p, prev); } if (checkBorders) this.checkBorderIntersection(p, prev); } checkBorderIntersection(p, prev) { if (prev.x < 0 !== p.x < 0) { this.addBorderIntersection(0, number(prev.y, p.y, (0 - prev.x) / (p.x - prev.x))); } if (prev.x > EXTENT$1 !== p.x > EXTENT$1) { this.addBorderIntersection(1, number(prev.y, p.y, (EXTENT$1 - prev.x) / (p.x - prev.x))); } if (prev.y < 0 !== p.y < 0) { this.addBorderIntersection(2, number(prev.x, p.x, (0 - prev.y) / (p.y - prev.y))); } if (prev.y > EXTENT$1 !== p.y > EXTENT$1) { this.addBorderIntersection(3, number(prev.x, p.x, (EXTENT$1 - prev.y) / (p.y - prev.y))); } } addBorderIntersection(index, i) { if (!this.borders) { this.borders = [ [ Number.MAX_VALUE, -Number.MAX_VALUE ], [ Number.MAX_VALUE, -Number.MAX_VALUE ], [ Number.MAX_VALUE, -Number.MAX_VALUE ], [ Number.MAX_VALUE, -Number.MAX_VALUE ] ]; } const b = this.borders[index]; if (i < b[0]) b[0] = i; if (i > b[1]) b[1] = i; } processBorderOverlap(p, prev) { if (p.x === prev.x) { if (p.y === prev.y) return; const index = p.x === 0 ? 0 : 1; this.addBorderIntersection(index, prev.y); this.addBorderIntersection(index, p.y); } else { const index = p.y === 0 ? 2 : 3; this.addBorderIntersection(index, prev.x); this.addBorderIntersection(index, p.x); } } centroid() { const count = this.polyCount.reduce((acc, p) => acc + p.edges, 0); return count !== 0 ? this.acc.div(count)._round() : new pointGeometry(0, 0); } span() { return new pointGeometry(this.max.x - this.min.x, this.max.y - this.min.y); } intersectsCount() { return this.borders.reduce((acc, p) => acc + +(p[0] !== Number.MAX_VALUE), 0); } } class FillExtrusionBucket { constructor(options) { this.zoom = options.zoom; this.overscaling = options.overscaling; this.layers = options.layers; this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; this.hasPattern = false; this.layoutVertexArray = new StructArrayLayout4i8(); this.centroidVertexArray = new FillExtrusionCentroidArray(); this.indexArray = new StructArrayLayout3ui6(); this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter(l => l.isStateDependent()).map(l => l.id); this.enableTerrain = options.enableTerrain; } populate(features, options, canonical) { this.features = []; this.hasPattern = hasPattern('fill-extrusion', this.layers, options); this.featuresOnBorder = []; this.borders = [ [], [], [], [] ]; this.borderDone = [ false, false, false, false ]; this.tileToMeter = tileToMeter(canonical); for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = this.layers[0]._featureFilter.needGeometry; const evaluationFeature = toEvaluationFeature(feature, needGeometry); if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; const bucketFeature = { id, sourceLayerIndex, index, geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature), properties: feature.properties, type: feature.type, patterns: {} }; const vertexArrayOffset = this.layoutVertexArray.length; if (this.hasPattern) { this.features.push(addPatternDependencies('fill-extrusion', this.layers, bucketFeature, this.zoom, options)); } else { this.addFeature(bucketFeature, bucketFeature.geometry, index, canonical, {}); } options.featureIndex.insert(feature, bucketFeature.geometry, index, sourceLayerIndex, this.index, vertexArrayOffset); } this.sortBorders(); } addFeatures(options, canonical, imagePositions) { for (const feature of this.features) { const {geometry} = feature; this.addFeature(feature, geometry, feature.index, canonical, imagePositions); } this.sortBorders(); } update(states, vtLayer, imagePositions) { if (!this.stateDependentLayers.length) return; this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); } isEmpty() { return this.layoutVertexArray.length === 0; } uploadPending() { return !this.uploaded || this.programConfigurations.needsUpload; } upload(context) { if (!this.uploaded) { this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, members$2); this.indexBuffer = context.createIndexBuffer(this.indexArray); } this.programConfigurations.upload(context); this.uploaded = true; } uploadCentroid(context) { if (this.centroidVertexArray.length === 0) return; if (!this.centroidVertexBuffer) { this.centroidVertexBuffer = context.createVertexBuffer(this.centroidVertexArray, centroidAttributes.members, true); } else if (this.needsCentroidUpdate) { this.centroidVertexBuffer.updateData(this.centroidVertexArray); } this.needsCentroidUpdate = false; } destroy() { if (!this.layoutVertexBuffer) return; this.layoutVertexBuffer.destroy(); if (this.centroidVertexBuffer) this.centroidVertexBuffer.destroy(); this.indexBuffer.destroy(); this.programConfigurations.destroy(); this.segments.destroy(); } addFeature(feature, geometry, index, canonical, imagePositions) { const flatRoof = this.enableTerrain && feature.properties && feature.properties.hasOwnProperty('type') && feature.properties.hasOwnProperty('height') && vectorTileFeatureTypes[feature.type] === 'Polygon'; const metadata = flatRoof ? new PartMetadata() : null; for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS$1)) { let numVertices = 0; let segment = this.segments.prepareSegment(4, this.layoutVertexArray, this.indexArray); if (polygon.length === 0 || isEntirelyOutside(polygon[0])) { continue; } for (let i = 0; i < polygon.length; i++) { const ring = polygon[i]; if (ring.length === 0) { continue; } numVertices += ring.length; let edgeDistance = 0; if (metadata) metadata.startRing(ring[0]); for (let p = 0; p < ring.length; p++) { const p1 = ring[p]; if (p >= 1) { const p2 = ring[p - 1]; if (!isBoundaryEdge(p1, p2)) { if (metadata) metadata.append(p1, p2); if (segment.vertexLength + 4 > SegmentVector.MAX_VERTEX_ARRAY_LENGTH) { segment = this.segments.prepareSegment(4, this.layoutVertexArray, this.indexArray); } const d = p1.sub(p2)._perp(); const nxRatio = d.x / (Math.abs(d.x) + Math.abs(d.y)); const nySign = d.y > 0 ? 1 : 0; const dist = p2.dist(p1); if (edgeDistance + dist > 32768) edgeDistance = 0; addVertex(this.layoutVertexArray, p1.x, p1.y, nxRatio, nySign, 0, 0, edgeDistance); addVertex(this.layoutVertexArray, p1.x, p1.y, nxRatio, nySign, 0, 1, edgeDistance); edgeDistance += dist; addVertex(this.layoutVertexArray, p2.x, p2.y, nxRatio, nySign, 0, 0, edgeDistance); addVertex(this.layoutVertexArray, p2.x, p2.y, nxRatio, nySign, 0, 1, edgeDistance); const bottomRight = segment.vertexLength; this.indexArray.emplaceBack(bottomRight, bottomRight + 2, bottomRight + 1); this.indexArray.emplaceBack(bottomRight + 1, bottomRight + 2, bottomRight + 3); segment.vertexLength += 4; segment.primitiveLength += 2; } } } } if (segment.vertexLength + numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH) { segment = this.segments.prepareSegment(numVertices, this.layoutVertexArray, this.indexArray); } if (vectorTileFeatureTypes[feature.type] !== 'Polygon') continue; const flattened = []; const holeIndices = []; const triangleIndex = segment.vertexLength; for (let i = 0; i < polygon.length; i++) { const ring = polygon[i]; if (ring.length === 0) { continue; } if (ring !== polygon[0]) { holeIndices.push(flattened.length / 2); } for (let i = 0; i < ring.length; i++) { const p = ring[i]; addVertex(this.layoutVertexArray, p.x, p.y, 0, 0, 1, 1, 0); flattened.push(p.x); flattened.push(p.y); if (metadata) metadata.currentPolyCount.top++; } } const indices = earcut_1(flattened, holeIndices); for (let j = 0; j < indices.length; j += 3) { this.indexArray.emplaceBack(triangleIndex + indices[j], triangleIndex + indices[j + 2], triangleIndex + indices[j + 1]); } segment.primitiveLength += indices.length / 3; segment.vertexLength += numVertices; } if (metadata && metadata.polyCount.length > 0) { if (metadata.borders) { metadata.vertexArrayOffset = this.centroidVertexArray.length; const borders = metadata.borders; const index = this.featuresOnBorder.push(metadata) - 1; for (let i = 0; i < 4; i++) { if (borders[i][0] !== Number.MAX_VALUE) { this.borders[i].push(index); } } } this.encodeCentroid(metadata.borders ? undefined : metadata.centroid(), metadata); } this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); } sortBorders() { for (let i = 0; i < 4; i++) { this.borders[i].sort((a, b) => this.featuresOnBorder[a].borders[i][0] - this.featuresOnBorder[b].borders[i][0]); } } encodeCentroid(c, metadata, append = true) { let x, y; if (c) { if (c.y !== 0) { const span = metadata.span()._mult(this.tileToMeter); x = (Math.max(c.x, 1) << 3) + Math.min(7, Math.round(span.x / 10)); y = (Math.max(c.y, 1) << 3) + Math.min(7, Math.round(span.y / 10)); } else { x = Math.ceil(c.x * ELEVATION_SCALE); y = 0; } } else { x = 0; y = +append; } let offset = append ? this.centroidVertexArray.length : metadata.vertexArrayOffset; for (const polyInfo of metadata.polyCount) { if (append) { this.centroidVertexArray.resize(this.centroidVertexArray.length + polyInfo.edges * 4 + polyInfo.top); } for (let i = 0; i < polyInfo.edges * 2; i++) { this.centroidVertexArray.emplace(offset++, 0, y); this.centroidVertexArray.emplace(offset++, x, y); } for (let i = 0; i < polyInfo.top; i++) { this.centroidVertexArray.emplace(offset++, x, y); } } } } register('FillExtrusionBucket', FillExtrusionBucket, { omit: [ 'layers', 'features' ] }); register('PartMetadata', PartMetadata); function isBoundaryEdge(p1, p2) { return p1.x === p2.x && (p1.x < 0 || p1.x > EXTENT$1) || p1.y === p2.y && (p1.y < 0 || p1.y > EXTENT$1); } function isEntirelyOutside(ring) { return ring.every(p => p.x <= 0) || ring.every(p => p.x >= EXTENT$1) || ring.every(p => p.y <= 0) || ring.every(p => p.y >= EXTENT$1); } function tileToMeter(canonical) { const circumferenceAtEquator = 40075017; const mercatorY = canonical.y / (1 << canonical.z); const exp = Math.exp(Math.PI * (1 - 2 * mercatorY)); return circumferenceAtEquator * 2 * exp / (exp * exp + 1) / EXTENT$1 / (1 << canonical.z); } const paint$5 = new Properties({ 'fill-extrusion-opacity': new DataConstantProperty(spec['paint_fill-extrusion']['fill-extrusion-opacity']), 'fill-extrusion-color': new DataDrivenProperty(spec['paint_fill-extrusion']['fill-extrusion-color']), 'fill-extrusion-translate': new DataConstantProperty(spec['paint_fill-extrusion']['fill-extrusion-translate']), 'fill-extrusion-translate-anchor': new DataConstantProperty(spec['paint_fill-extrusion']['fill-extrusion-translate-anchor']), 'fill-extrusion-pattern': new CrossFadedDataDrivenProperty(spec['paint_fill-extrusion']['fill-extrusion-pattern']), 'fill-extrusion-height': new DataDrivenProperty(spec['paint_fill-extrusion']['fill-extrusion-height']), 'fill-extrusion-base': new DataDrivenProperty(spec['paint_fill-extrusion']['fill-extrusion-base']), 'fill-extrusion-vertical-gradient': new DataConstantProperty(spec['paint_fill-extrusion']['fill-extrusion-vertical-gradient']) }); var properties$4 = { paint: paint$5 }; class FillExtrusionStyleLayer extends StyleLayer { constructor(layer) { super(layer, properties$4); } createBucket(parameters) { return new FillExtrusionBucket(parameters); } queryRadius() { return translateDistance(this.paint.get('fill-extrusion-translate')); } is3D() { return true; } getProgramIds() { const patternProperty = this.paint.get('fill-extrusion-pattern'); const image = patternProperty.constantOr(1); return [image ? 'fillExtrusionPattern' : 'fillExtrusion']; } getProgramConfiguration(zoom) { return new ProgramConfiguration(this, zoom); } queryIntersectsFeature(queryGeometry, feature, featureState, geometry, zoom, transform, pixelPosMatrix, elevationHelper, layoutVertexArrayOffset) { const translation = tilespaceTranslate(this.paint.get('fill-extrusion-translate'), this.paint.get('fill-extrusion-translate-anchor'), transform.angle, queryGeometry.pixelToTileUnitsFactor); const height = this.paint.get('fill-extrusion-height').evaluate(feature, featureState); const base = this.paint.get('fill-extrusion-base').evaluate(feature, featureState); const centroid = [ 0, 0 ]; const terrainVisible = elevationHelper && transform.elevation; const exaggeration = transform.elevation ? transform.elevation.exaggeration() : 1; if (terrainVisible) { const centroidVertexArray = queryGeometry.tile.getBucket(this).centroidVertexArray; const centroidOffset = layoutVertexArrayOffset + 1; if (centroidOffset < centroidVertexArray.length) { const centroidVertexObject = centroidVertexArray.get(centroidOffset); centroid[0] = centroidVertexObject.a_centroid_pos0; centroid[1] = centroidVertexObject.a_centroid_pos1; } } const isHidden = centroid[0] === 0 && centroid[1] === 1; if (isHidden) return false; const demSampler = terrainVisible ? elevationHelper : null; const projected = projectExtrusion(geometry, base, height, translation, pixelPosMatrix, demSampler, centroid, exaggeration, transform.center.lat); const projectedBase = projected[0]; const projectedTop = projected[1]; const screenQuery = queryGeometry.queryGeometry; const projectedQueryGeometry = screenQuery.isPointQuery() ? screenQuery.screenBounds : screenQuery.screenGeometry; return checkIntersection(projectedBase, projectedTop, projectedQueryGeometry); } } function dot$2(a, b) { return a.x * b.x + a.y * b.y; } function getIntersectionDistance(projectedQueryGeometry, projectedFace) { if (projectedQueryGeometry.length === 1) { let i = 0; const a = projectedFace[i++]; let b; while (!b || a.equals(b)) { b = projectedFace[i++]; if (!b) return Infinity; } for (; i < projectedFace.length; i++) { const c = projectedFace[i]; const p = projectedQueryGeometry[0]; const ab = b.sub(a); const ac = c.sub(a); const ap = p.sub(a); const dotABAB = dot$2(ab, ab); const dotABAC = dot$2(ab, ac); const dotACAC = dot$2(ac, ac); const dotAPAB = dot$2(ap, ab); const dotAPAC = dot$2(ap, ac); const denom = dotABAB * dotACAC - dotABAC * dotABAC; const v = (dotACAC * dotAPAB - dotABAC * dotAPAC) / denom; const w = (dotABAB * dotAPAC - dotABAC * dotAPAB) / denom; const u = 1 - v - w; const distance = a.z * u + b.z * v + c.z * w; if (isFinite(distance)) return distance; } return Infinity; } else { let closestDistance = Infinity; for (const p of projectedFace) { closestDistance = Math.min(closestDistance, p.z); } return closestDistance; } } function checkIntersection(projectedBase, projectedTop, projectedQueryGeometry) { let closestDistance = Infinity; if (polygonIntersectsMultiPolygon(projectedQueryGeometry, projectedTop)) { closestDistance = getIntersectionDistance(projectedQueryGeometry, projectedTop[0]); } for (let r = 0; r < projectedTop.length; r++) { const ringTop = projectedTop[r]; const ringBase = projectedBase[r]; for (let p = 0; p < ringTop.length - 1; p++) { const topA = ringTop[p]; const topB = ringTop[p + 1]; const baseA = ringBase[p]; const baseB = ringBase[p + 1]; const face = [ topA, topB, baseB, baseA, topA ]; if (polygonIntersectsPolygon(projectedQueryGeometry, face)) { closestDistance = Math.min(closestDistance, getIntersectionDistance(projectedQueryGeometry, face)); } } } return closestDistance === Infinity ? false : closestDistance; } function projectExtrusion(geometry, zBase, zTop, translation, m, demSampler, centroid, exaggeration, lat) { if (demSampler) { return projectExtrusion3D(geometry, zBase, zTop, translation, m, demSampler, centroid, exaggeration, lat); } else { return projectExtrusion2D(geometry, zBase, zTop, translation, m); } } function projectExtrusion2D(geometry, zBase, zTop, translation, m) { const projectedBase = []; const projectedTop = []; const baseXZ = m[8] * zBase; const baseYZ = m[9] * zBase; const baseZZ = m[10] * zBase; const baseWZ = m[11] * zBase; const topXZ = m[8] * zTop; const topYZ = m[9] * zTop; const topZZ = m[10] * zTop; const topWZ = m[11] * zTop; for (const r of geometry) { const ringBase = []; const ringTop = []; for (const p of r) { const x = p.x + translation.x; const y = p.y + translation.y; const sX = m[0] * x + m[4] * y + m[12]; const sY = m[1] * x + m[5] * y + m[13]; const sZ = m[2] * x + m[6] * y + m[14]; const sW = m[3] * x + m[7] * y + m[15]; const baseX = sX + baseXZ; const baseY = sY + baseYZ; const baseZ = sZ + baseZZ; const baseW = sW + baseWZ; const topX = sX + topXZ; const topY = sY + topYZ; const topZ = sZ + topZZ; const topW = sW + topWZ; const b = new pointGeometry(baseX / baseW, baseY / baseW); b.z = baseZ / baseW; ringBase.push(b); const t = new pointGeometry(topX / topW, topY / topW); t.z = topZ / topW; ringTop.push(t); } projectedBase.push(ringBase); projectedTop.push(ringTop); } return [ projectedBase, projectedTop ]; } function projectExtrusion3D(geometry, zBase, zTop, translation, m, demSampler, centroid, exaggeration, lat) { const projectedBase = []; const projectedTop = []; const v = [ 0, 0, 0 ]; for (const r of geometry) { const ringBase = []; const ringTop = []; for (const p of r) { const x = p.x + translation.x; const y = p.y + translation.y; const heightOffset = getTerrainHeightOffset(x, y, zBase, zTop, demSampler, centroid, exaggeration, lat); v[0] = x; v[1] = y; v[2] = heightOffset.base; const base = toPoint(transformMat4(v, v, m)); v[0] = x; v[1] = y; v[2] = heightOffset.top; const top = toPoint(transformMat4(v, v, m)); ringBase.push(base); ringTop.push(top); } projectedBase.push(ringBase); projectedTop.push(ringTop); } return [ projectedBase, projectedTop ]; } function toPoint(v) { const p = new pointGeometry(v[0], v[1]); p.z = v[2]; return p; } function getTerrainHeightOffset(x, y, zBase, zTop, demSampler, centroid, exaggeration, lat) { const ele = exaggeration * demSampler.getElevationAt(x, y, true, true); const flatRoof = centroid[0] !== 0; const centroidElevation = flatRoof ? centroid[1] === 0 ? exaggeration * elevationFromUint16(centroid[0]) : exaggeration * flatElevation(demSampler, centroid, lat) : ele; return { base: ele + (zBase === 0) ? -1 : zBase, top: flatRoof ? Math.max(centroidElevation + zTop, ele + zBase + 2) : ele + zTop }; } function elevationFromUint16(n) { return n / ELEVATION_SCALE; } function flatElevation(demSampler, centroid, lat) { const posX = Math.floor(centroid[0] / 8); const posY = Math.floor(centroid[1] / 8); const spanX = 10 * (centroid[0] - posX * 8); const spanY = 10 * (centroid[1] - posY * 8); const z = demSampler.getElevationAt(posX, posY, true, true); const meterToDEM = demSampler.getMeterToDEM(lat); const wX = Math.floor(0.5 * (spanX * meterToDEM - 1)); const wY = Math.floor(0.5 * (spanY * meterToDEM - 1)); const posPx = demSampler.tileCoordToPixel(posX, posY); const offsetX = 2 * wX + 1; const offsetY = 2 * wY + 1; const corners = fourSample(demSampler, posPx.x - wX, posPx.y - wY, offsetX, offsetY); const diffX = Math.abs(corners[0] - corners[1]); const diffY = Math.abs(corners[2] - corners[3]); const diffZ = Math.abs(corners[0] - corners[2]); const diffW = Math.abs(corners[1] - corners[3]); const diffSumX = diffX + diffY; const diffSumY = diffZ + diffW; const slopeX = Math.min(0.25, meterToDEM * 0.5 * diffSumX / offsetX); const slopeY = Math.min(0.25, meterToDEM * 0.5 * diffSumY / offsetY); return z + Math.max(slopeX * spanX, slopeY * spanY); } function fourSample(demSampler, posX, posY, offsetX, offsetY) { return [ demSampler.getElevationAtPixel(posX, posY, true), demSampler.getElevationAtPixel(posX + offsetY, posY, true), demSampler.getElevationAtPixel(posX, posY + offsetY, true), demSampler.getElevationAtPixel(posX + offsetX, posY + offsetY, true) ]; } const lineLayoutAttributes = createLayout([ { name: 'a_pos_normal', components: 2, type: 'Int16' }, { name: 'a_data', components: 4, type: 'Uint8' }, { name: 'a_linesofar', components: 1, type: 'Float32' } ], 4); const {members: members$3, size: size$3, alignment: alignment$3} = lineLayoutAttributes; const lineLayoutAttributesExt = createLayout([ { name: 'a_uv_x', components: 1, type: 'Float32' }, { name: 'a_split_index', components: 1, type: 'Float32' } ]); const {members: members$4, size: size$4, alignment: alignment$4} = lineLayoutAttributesExt; const vectorTileFeatureTypes$1 = vectorTile.VectorTileFeature.types; const EXTRUDE_SCALE = 63; const COS_HALF_SHARP_CORNER = Math.cos(75 / 2 * (Math.PI / 180)); const SHARP_CORNER_OFFSET = 15; const DEG_PER_TRIANGLE = 20; class LineBucket { constructor(options) { this.zoom = options.zoom; this.overscaling = options.overscaling; this.layers = options.layers; this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; this.hasPattern = false; this.patternFeatures = []; this.lineClipsArray = []; this.gradients = {}; this.layers.forEach(layer => { this.gradients[layer.id] = {}; }); this.layoutVertexArray = new StructArrayLayout2i4ub1f12(); this.layoutVertexArray2 = new StructArrayLayout2f8(); this.indexArray = new StructArrayLayout3ui6(); this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.maxLineLength = 0; this.stateDependentLayerIds = this.layers.filter(l => l.isStateDependent()).map(l => l.id); } populate(features, options, canonical) { this.hasPattern = hasPattern('line', this.layers, options); const lineSortKey = this.layers[0].layout.get('line-sort-key'); const bucketFeatures = []; for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = this.layers[0]._featureFilter.needGeometry; const evaluationFeature = toEvaluationFeature(feature, needGeometry); if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; const sortKey = lineSortKey ? lineSortKey.evaluate(evaluationFeature, {}, canonical) : undefined; const bucketFeature = { id, properties: feature.properties, type: feature.type, sourceLayerIndex, index, geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature), patterns: {}, sortKey }; bucketFeatures.push(bucketFeature); } if (lineSortKey) { bucketFeatures.sort((a, b) => { return a.sortKey - b.sortKey; }); } for (const bucketFeature of bucketFeatures) { const {geometry, index, sourceLayerIndex} = bucketFeature; if (this.hasPattern) { const patternBucketFeature = addPatternDependencies('line', this.layers, bucketFeature, this.zoom, options); this.patternFeatures.push(patternBucketFeature); } else { this.addFeature(bucketFeature, geometry, index, canonical, {}); } const feature = features[index].feature; options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); } } update(states, vtLayer, imagePositions) { if (!this.stateDependentLayers.length) return; this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); } addFeatures(options, canonical, imagePositions) { for (const feature of this.patternFeatures) { this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions); } } isEmpty() { return this.layoutVertexArray.length === 0; } uploadPending() { return !this.uploaded || this.programConfigurations.needsUpload; } upload(context) { if (!this.uploaded) { if (this.layoutVertexArray2.length !== 0) { this.layoutVertexBuffer2 = context.createVertexBuffer(this.layoutVertexArray2, members$4); } this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, members$3); this.indexBuffer = context.createIndexBuffer(this.indexArray); } this.programConfigurations.upload(context); this.uploaded = true; } destroy() { if (!this.layoutVertexBuffer) return; this.layoutVertexBuffer.destroy(); this.indexBuffer.destroy(); this.programConfigurations.destroy(); this.segments.destroy(); } lineFeatureClips(feature) { if (!!feature.properties && feature.properties.hasOwnProperty('mapbox_clip_start') && feature.properties.hasOwnProperty('mapbox_clip_end')) { const start = +feature.properties['mapbox_clip_start']; const end = +feature.properties['mapbox_clip_end']; return { start, end }; } } addFeature(feature, geometry, index, canonical, imagePositions) { const layout = this.layers[0].layout; const join = layout.get('line-join').evaluate(feature, {}); const cap = layout.get('line-cap'); const miterLimit = layout.get('line-miter-limit'); const roundLimit = layout.get('line-round-limit'); this.lineClips = this.lineFeatureClips(feature); for (const line of geometry) { this.addLine(line, feature, join, cap, miterLimit, roundLimit); } this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); } addLine(vertices, feature, join, cap, miterLimit, roundLimit) { this.distance = 0; this.scaledDistance = 0; this.totalDistance = 0; this.lineSoFar = 0; if (this.lineClips) { this.lineClipsArray.push(this.lineClips); for (let i = 0; i < vertices.length - 1; i++) { this.totalDistance += vertices[i].dist(vertices[i + 1]); } this.updateScaledDistance(); this.maxLineLength = Math.max(this.maxLineLength, this.totalDistance); } const isPolygon = vectorTileFeatureTypes$1[feature.type] === 'Polygon'; let len = vertices.length; while (len >= 2 && vertices[len - 1].equals(vertices[len - 2])) { len--; } let first = 0; while (first < len - 1 && vertices[first].equals(vertices[first + 1])) { first++; } if (len < (isPolygon ? 3 : 2)) return; if (join === 'bevel') miterLimit = 1.05; const sharpCornerOffset = this.overscaling <= 16 ? SHARP_CORNER_OFFSET * EXTENT$1 / (512 * this.overscaling) : 0; const segment = this.segments.prepareSegment(len * 10, this.layoutVertexArray, this.indexArray); let currentVertex; let prevVertex = undefined; let nextVertex = undefined; let prevNormal = undefined; let nextNormal = undefined; this.e1 = this.e2 = -1; if (isPolygon) { currentVertex = vertices[len - 2]; nextNormal = vertices[first].sub(currentVertex)._unit()._perp(); } for (let i = first; i < len; i++) { nextVertex = i === len - 1 ? isPolygon ? vertices[first + 1] : undefined : vertices[i + 1]; if (nextVertex && vertices[i].equals(nextVertex)) continue; if (nextNormal) prevNormal = nextNormal; if (currentVertex) prevVertex = currentVertex; currentVertex = vertices[i]; nextNormal = nextVertex ? nextVertex.sub(currentVertex)._unit()._perp() : prevNormal; prevNormal = prevNormal || nextNormal; let joinNormal = prevNormal.add(nextNormal); if (joinNormal.x !== 0 || joinNormal.y !== 0) { joinNormal._unit(); } const cosAngle = prevNormal.x * nextNormal.x + prevNormal.y * nextNormal.y; const cosHalfAngle = joinNormal.x * nextNormal.x + joinNormal.y * nextNormal.y; const miterLength = cosHalfAngle !== 0 ? 1 / cosHalfAngle : Infinity; const approxAngle = 2 * Math.sqrt(2 - 2 * cosHalfAngle); const isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevVertex && nextVertex; const lineTurnsLeft = prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x > 0; if (isSharpCorner && i > first) { const prevSegmentLength = currentVertex.dist(prevVertex); if (prevSegmentLength > 2 * sharpCornerOffset) { const newPrevVertex = currentVertex.sub(currentVertex.sub(prevVertex)._mult(sharpCornerOffset / prevSegmentLength)._round()); this.updateDistance(prevVertex, newPrevVertex); this.addCurrentVertex(newPrevVertex, prevNormal, 0, 0, segment); prevVertex = newPrevVertex; } } const middleVertex = prevVertex && nextVertex; let currentJoin = middleVertex ? join : isPolygon ? 'butt' : cap; if (middleVertex && currentJoin === 'round') { if (miterLength < roundLimit) { currentJoin = 'miter'; } else if (miterLength <= 2) { currentJoin = 'fakeround'; } } if (currentJoin === 'miter' && miterLength > miterLimit) { currentJoin = 'bevel'; } if (currentJoin === 'bevel') { if (miterLength > 2) currentJoin = 'flipbevel'; if (miterLength < miterLimit) currentJoin = 'miter'; } if (prevVertex) this.updateDistance(prevVertex, currentVertex); if (currentJoin === 'miter') { joinNormal._mult(miterLength); this.addCurrentVertex(currentVertex, joinNormal, 0, 0, segment); } else if (currentJoin === 'flipbevel') { if (miterLength > 100) { joinNormal = nextNormal.mult(-1); } else { const bevelLength = miterLength * prevNormal.add(nextNormal).mag() / prevNormal.sub(nextNormal).mag(); joinNormal._perp()._mult(bevelLength * (lineTurnsLeft ? -1 : 1)); } this.addCurrentVertex(currentVertex, joinNormal, 0, 0, segment); this.addCurrentVertex(currentVertex, joinNormal.mult(-1), 0, 0, segment); } else if (currentJoin === 'bevel' || currentJoin === 'fakeround') { const offset = -Math.sqrt(miterLength * miterLength - 1); const offsetA = lineTurnsLeft ? offset : 0; const offsetB = lineTurnsLeft ? 0 : offset; if (prevVertex) { this.addCurrentVertex(currentVertex, prevNormal, offsetA, offsetB, segment); } if (currentJoin === 'fakeround') { const n = Math.round(approxAngle * 180 / Math.PI / DEG_PER_TRIANGLE); for (let m = 1; m < n; m++) { let t = m / n; if (t !== 0.5) { const t2 = t - 0.5; const A = 1.0904 + cosAngle * (-3.2452 + cosAngle * (3.55645 - cosAngle * 1.43519)); const B = 0.848013 + cosAngle * (-1.06021 + cosAngle * 0.215638); t = t + t * t2 * (t - 1) * (A * t2 * t2 + B); } const extrude = nextNormal.sub(prevNormal)._mult(t)._add(prevNormal)._unit()._mult(lineTurnsLeft ? -1 : 1); this.addHalfVertex(currentVertex, extrude.x, extrude.y, false, lineTurnsLeft, 0, segment); } } if (nextVertex) { this.addCurrentVertex(currentVertex, nextNormal, -offsetA, -offsetB, segment); } } else if (currentJoin === 'butt') { this.addCurrentVertex(currentVertex, joinNormal, 0, 0, segment); } else if (currentJoin === 'square') { const offset = prevVertex ? 1 : -1; this.addCurrentVertex(currentVertex, joinNormal, offset, offset, segment); } else if (currentJoin === 'round') { if (prevVertex) { this.addCurrentVertex(currentVertex, prevNormal, 0, 0, segment); this.addCurrentVertex(currentVertex, prevNormal, 1, 1, segment, true); } if (nextVertex) { this.addCurrentVertex(currentVertex, nextNormal, -1, -1, segment, true); this.addCurrentVertex(currentVertex, nextNormal, 0, 0, segment); } } if (isSharpCorner && i < len - 1) { const nextSegmentLength = currentVertex.dist(nextVertex); if (nextSegmentLength > 2 * sharpCornerOffset) { const newCurrentVertex = currentVertex.add(nextVertex.sub(currentVertex)._mult(sharpCornerOffset / nextSegmentLength)._round()); this.updateDistance(currentVertex, newCurrentVertex); this.addCurrentVertex(newCurrentVertex, nextNormal, 0, 0, segment); currentVertex = newCurrentVertex; } } } } addCurrentVertex(p, normal, endLeft, endRight, segment, round = false) { const leftX = normal.x + normal.y * endLeft; const leftY = normal.y - normal.x * endLeft; const rightX = -normal.x + normal.y * endRight; const rightY = -normal.y - normal.x * endRight; this.addHalfVertex(p, leftX, leftY, round, false, endLeft, segment); this.addHalfVertex(p, rightX, rightY, round, true, -endRight, segment); } addHalfVertex({x, y}, extrudeX, extrudeY, round, up, dir, segment) { this.layoutVertexArray.emplaceBack((x << 1) + (round ? 1 : 0), (y << 1) + (up ? 1 : 0), Math.round(EXTRUDE_SCALE * extrudeX) + 128, Math.round(EXTRUDE_SCALE * extrudeY) + 128, (dir === 0 ? 0 : dir < 0 ? -1 : 1) + 1, 0, this.lineSoFar); if (this.lineClips) { this.layoutVertexArray2.emplaceBack(this.scaledDistance, this.lineClipsArray.length); } const e = segment.vertexLength++; if (this.e1 >= 0 && this.e2 >= 0) { this.indexArray.emplaceBack(this.e1, this.e2, e); segment.primitiveLength++; } if (up) { this.e2 = e; } else { this.e1 = e; } } updateScaledDistance() { if (this.lineClips) { const featureShare = this.lineClips.end - this.lineClips.start; const totalFeatureLength = this.totalDistance / featureShare; this.scaledDistance = this.distance / this.totalDistance; this.lineSoFar = totalFeatureLength * this.lineClips.start + this.distance; } else { this.lineSoFar = this.distance; } } updateDistance(prev, next) { this.distance += prev.dist(next); this.updateScaledDistance(); } } register('LineBucket', LineBucket, { omit: [ 'layers', 'patternFeatures' ] }); const layout$5 = new Properties({ 'line-cap': new DataConstantProperty(spec['layout_line']['line-cap']), 'line-join': new DataDrivenProperty(spec['layout_line']['line-join']), 'line-miter-limit': new DataConstantProperty(spec['layout_line']['line-miter-limit']), 'line-round-limit': new DataConstantProperty(spec['layout_line']['line-round-limit']), 'line-sort-key': new DataDrivenProperty(spec['layout_line']['line-sort-key']) }); const paint$6 = new Properties({ 'line-opacity': new DataDrivenProperty(spec['paint_line']['line-opacity']), 'line-color': new DataDrivenProperty(spec['paint_line']['line-color']), 'line-translate': new DataConstantProperty(spec['paint_line']['line-translate']), 'line-translate-anchor': new DataConstantProperty(spec['paint_line']['line-translate-anchor']), 'line-width': new DataDrivenProperty(spec['paint_line']['line-width']), 'line-gap-width': new DataDrivenProperty(spec['paint_line']['line-gap-width']), 'line-offset': new DataDrivenProperty(spec['paint_line']['line-offset']), 'line-blur': new DataDrivenProperty(spec['paint_line']['line-blur']), 'line-dasharray': new CrossFadedProperty(spec['paint_line']['line-dasharray']), 'line-pattern': new CrossFadedDataDrivenProperty(spec['paint_line']['line-pattern']), 'line-gradient': new ColorRampProperty(spec['paint_line']['line-gradient']) }); var properties$5 = { paint: paint$6, layout: layout$5 }; class LineFloorwidthProperty extends DataDrivenProperty { possiblyEvaluate(value, parameters) { parameters = new EvaluationParameters(Math.floor(parameters.zoom), { now: parameters.now, fadeDuration: parameters.fadeDuration, zoomHistory: parameters.zoomHistory, transition: parameters.transition }); return super.possiblyEvaluate(value, parameters); } evaluate(value, globals, feature, featureState) { globals = extend({}, globals, { zoom: Math.floor(globals.zoom) }); return super.evaluate(value, globals, feature, featureState); } } const lineFloorwidthProperty = new LineFloorwidthProperty(properties$5.paint.properties['line-width'].specification); lineFloorwidthProperty.useIntegerZoom = true; class LineStyleLayer extends StyleLayer { constructor(layer) { super(layer, properties$5); this.gradientVersion = 0; } _handleSpecialPaintPropertyUpdate(name) { if (name === 'line-gradient') { const expression = this._transitionablePaint._values['line-gradient'].value.expression; this.stepInterpolant = expression._styleExpression.expression instanceof Step; this.gradientVersion = (this.gradientVersion + 1) % MAX_SAFE_INTEGER; } } gradientExpression() { return this._transitionablePaint._values['line-gradient'].value.expression; } recalculate(parameters, availableImages) { super.recalculate(parameters, availableImages); this.paint._values['line-floorwidth'] = lineFloorwidthProperty.possiblyEvaluate(this._transitioningPaint._values['line-width'].value, parameters); } createBucket(parameters) { return new LineBucket(parameters); } getProgramIds() { const dasharray = this.paint.get('line-dasharray'); const patternProperty = this.paint.get('line-pattern'); const image = patternProperty.constantOr(1); const gradient = this.paint.get('line-gradient'); const programId = image ? 'linePattern' : dasharray ? 'lineSDF' : gradient ? 'lineGradient' : 'line'; return [programId]; } getProgramConfiguration(zoom) { return new ProgramConfiguration(this, zoom); } queryRadius(bucket) { const lineBucket = bucket; const width = getLineWidth(getMaximumPaintValue('line-width', this, lineBucket), getMaximumPaintValue('line-gap-width', this, lineBucket)); const offset = getMaximumPaintValue('line-offset', this, lineBucket); return width / 2 + Math.abs(offset) + translateDistance(this.paint.get('line-translate')); } queryIntersectsFeature(queryGeometry, feature, featureState, geometry, zoom, transform) { if (queryGeometry.queryGeometry.isAboveHorizon) return false; const translatedPolygon = translate(queryGeometry.tilespaceGeometry, this.paint.get('line-translate'), this.paint.get('line-translate-anchor'), transform.angle, queryGeometry.pixelToTileUnitsFactor); const halfWidth = queryGeometry.pixelToTileUnitsFactor / 2 * getLineWidth(this.paint.get('line-width').evaluate(feature, featureState), this.paint.get('line-gap-width').evaluate(feature, featureState)); const lineOffset = this.paint.get('line-offset').evaluate(feature, featureState); if (lineOffset) { geometry = offsetLine(geometry, lineOffset * queryGeometry.pixelToTileUnitsFactor); } return polygonIntersectsBufferedMultiLine(translatedPolygon, geometry, halfWidth); } isTileClipped() { return true; } } function getLineWidth(lineWidth, lineGapWidth) { if (lineGapWidth > 0) { return lineGapWidth + 2 * lineWidth; } else { return lineWidth; } } function offsetLine(rings, offset) { const newRings = []; const zero = new pointGeometry(0, 0); for (let k = 0; k < rings.length; k++) { const ring = rings[k]; const newRing = []; for (let i = 0; i < ring.length; i++) { const a = ring[i - 1]; const b = ring[i]; const c = ring[i + 1]; const aToB = i === 0 ? zero : b.sub(a)._unit()._perp(); const bToC = i === ring.length - 1 ? zero : c.sub(b)._unit()._perp(); const extrude = aToB._add(bToC)._unit(); const cosHalfAngle = extrude.x * bToC.x + extrude.y * bToC.y; extrude._mult(1 / cosHalfAngle); newRing.push(extrude._mult(offset)._add(b)); } newRings.push(newRing); } return newRings; } const symbolLayoutAttributes = createLayout([ { name: 'a_pos_offset', components: 4, type: 'Int16' }, { name: 'a_data', components: 4, type: 'Uint16' }, { name: 'a_pixeloffset', components: 4, type: 'Int16' } ], 4); const dynamicLayoutAttributes = createLayout([{ name: 'a_projected_pos', components: 3, type: 'Float32' }], 4); const placementOpacityAttributes = createLayout([{ name: 'a_fade_opacity', components: 1, type: 'Uint32' }], 4); const collisionVertexAttributes = createLayout([ { name: 'a_placed', components: 2, type: 'Uint8' }, { name: 'a_shift', components: 2, type: 'Float32' } ]); const collisionVertexAttributesExt = createLayout([ { name: 'a_size_scale', components: 1, type: 'Float32' }, { name: 'a_padding', components: 2, type: 'Float32' } ]); const collisionBox = createLayout([ { type: 'Int16', name: 'anchorPointX' }, { type: 'Int16', name: 'anchorPointY' }, { type: 'Float32', name: 'x1' }, { type: 'Float32', name: 'y1' }, { type: 'Float32', name: 'x2' }, { type: 'Float32', name: 'y2' }, { type: 'Int16', name: 'padding' }, { type: 'Uint32', name: 'featureIndex' }, { type: 'Uint16', name: 'sourceLayerIndex' }, { type: 'Uint16', name: 'bucketIndex' } ]); const collisionBoxLayout = createLayout([ { name: 'a_pos', components: 2, type: 'Int16' }, { name: 'a_anchor_pos', components: 2, type: 'Int16' }, { name: 'a_extrude', components: 2, type: 'Int16' } ], 4); const collisionCircleLayout = createLayout([ { name: 'a_pos_2f', components: 2, type: 'Float32' }, { name: 'a_radius', components: 1, type: 'Float32' }, { name: 'a_flags', components: 2, type: 'Int16' } ], 4); const quadTriangle = createLayout([{ name: 'triangle', components: 3, type: 'Uint16' }]); const placement = createLayout([ { type: 'Int16', name: 'anchorX' }, { type: 'Int16', name: 'anchorY' }, { type: 'Uint16', name: 'glyphStartIndex' }, { type: 'Uint16', name: 'numGlyphs' }, { type: 'Uint32', name: 'vertexStartIndex' }, { type: 'Uint32', name: 'lineStartIndex' }, { type: 'Uint32', name: 'lineLength' }, { type: 'Uint16', name: 'segment' }, { type: 'Uint16', name: 'lowerSize' }, { type: 'Uint16', name: 'upperSize' }, { type: 'Float32', name: 'lineOffsetX' }, { type: 'Float32', name: 'lineOffsetY' }, { type: 'Uint8', name: 'writingMode' }, { type: 'Uint8', name: 'placedOrientation' }, { type: 'Uint8', name: 'hidden' }, { type: 'Uint32', name: 'crossTileID' }, { type: 'Int16', name: 'associatedIconIndex' } ]); const symbolInstance = createLayout([ { type: 'Int16', name: 'anchorX' }, { type: 'Int16', name: 'anchorY' }, { type: 'Int16', name: 'rightJustifiedTextSymbolIndex' }, { type: 'Int16', name: 'centerJustifiedTextSymbolIndex' }, { type: 'Int16', name: 'leftJustifiedTextSymbolIndex' }, { type: 'Int16', name: 'verticalPlacedTextSymbolIndex' }, { type: 'Int16', name: 'placedIconSymbolIndex' }, { type: 'Int16', name: 'verticalPlacedIconSymbolIndex' }, { type: 'Uint16', name: 'key' }, { type: 'Uint16', name: 'textBoxStartIndex' }, { type: 'Uint16', name: 'textBoxEndIndex' }, { type: 'Uint16', name: 'verticalTextBoxStartIndex' }, { type: 'Uint16', name: 'verticalTextBoxEndIndex' }, { type: 'Uint16', name: 'iconBoxStartIndex' }, { type: 'Uint16', name: 'iconBoxEndIndex' }, { type: 'Uint16', name: 'verticalIconBoxStartIndex' }, { type: 'Uint16', name: 'verticalIconBoxEndIndex' }, { type: 'Uint16', name: 'featureIndex' }, { type: 'Uint16', name: 'numHorizontalGlyphVertices' }, { type: 'Uint16', name: 'numVerticalGlyphVertices' }, { type: 'Uint16', name: 'numIconVertices' }, { type: 'Uint16', name: 'numVerticalIconVertices' }, { type: 'Uint16', name: 'useRuntimeCollisionCircles' }, { type: 'Uint32', name: 'crossTileID' }, { type: 'Float32', name: 'textBoxScale' }, { type: 'Float32', components: 2, name: 'textOffset' }, { type: 'Float32', name: 'collisionCircleDiameter' } ]); const glyphOffset = createLayout([{ type: 'Float32', name: 'offsetX' }]); const lineVertex = createLayout([ { type: 'Int16', name: 'x' }, { type: 'Int16', name: 'y' }, { type: 'Int16', name: 'tileUnitDistanceFromAnchor' } ]); var ONE_EM = 24; const SIZE_PACK_FACTOR = 128; function getSizeData(tileZoom, value) { const {expression} = value; if (expression.kind === 'constant') { const layoutSize = expression.evaluate(new EvaluationParameters(tileZoom + 1)); return { kind: 'constant', layoutSize }; } else if (expression.kind === 'source') { return { kind: 'source' }; } else { const {zoomStops, interpolationType} = expression; let lower = 0; while (lower < zoomStops.length && zoomStops[lower] <= tileZoom) lower++; lower = Math.max(0, lower - 1); let upper = lower; while (upper < zoomStops.length && zoomStops[upper] < tileZoom + 1) upper++; upper = Math.min(zoomStops.length - 1, upper); const minZoom = zoomStops[lower]; const maxZoom = zoomStops[upper]; if (expression.kind === 'composite') { return { kind: 'composite', minZoom, maxZoom, interpolationType }; } const minSize = expression.evaluate(new EvaluationParameters(minZoom)); const maxSize = expression.evaluate(new EvaluationParameters(maxZoom)); return { kind: 'camera', minZoom, maxZoom, minSize, maxSize, interpolationType }; } } function evaluateSizeForFeature(sizeData, {uSize, uSizeT}, {lowerSize, upperSize}) { if (sizeData.kind === 'source') { return lowerSize / SIZE_PACK_FACTOR; } else if (sizeData.kind === 'composite') { return number(lowerSize / SIZE_PACK_FACTOR, upperSize / SIZE_PACK_FACTOR, uSizeT); } return uSize; } function evaluateSizeForZoom(sizeData, zoom) { let uSizeT = 0; let uSize = 0; if (sizeData.kind === 'constant') { uSize = sizeData.layoutSize; } else if (sizeData.kind !== 'source') { const {interpolationType, minZoom, maxZoom} = sizeData; const t = !interpolationType ? 0 : clamp(Interpolate.interpolationFactor(interpolationType, zoom, minZoom, maxZoom), 0, 1); if (sizeData.kind === 'camera') { uSize = number(sizeData.minSize, sizeData.maxSize, t); } else { uSizeT = t; } } return { uSizeT, uSize }; } var symbolSize = /*#__PURE__*/Object.freeze({ __proto__: null, getSizeData: getSizeData, evaluateSizeForFeature: evaluateSizeForFeature, evaluateSizeForZoom: evaluateSizeForZoom, SIZE_PACK_FACTOR: SIZE_PACK_FACTOR }); function transformText(text, layer, feature) { const transform = layer.layout.get('text-transform').evaluate(feature, {}); if (transform === 'uppercase') { text = text.toLocaleUpperCase(); } else if (transform === 'lowercase') { text = text.toLocaleLowerCase(); } if (plugin.applyArabicShaping) { text = plugin.applyArabicShaping(text); } return text; } function transformText$1 (text, layer, feature) { text.sections.forEach(section => { section.text = transformText(section.text, layer, feature); }); return text; } function mergeLines (features) { const leftIndex = {}; const rightIndex = {}; const mergedFeatures = []; let mergedIndex = 0; function add(k) { mergedFeatures.push(features[k]); mergedIndex++; } function mergeFromRight(leftKey, rightKey, geom) { const i = rightIndex[leftKey]; delete rightIndex[leftKey]; rightIndex[rightKey] = i; mergedFeatures[i].geometry[0].pop(); mergedFeatures[i].geometry[0] = mergedFeatures[i].geometry[0].concat(geom[0]); return i; } function mergeFromLeft(leftKey, rightKey, geom) { const i = leftIndex[rightKey]; delete leftIndex[rightKey]; leftIndex[leftKey] = i; mergedFeatures[i].geometry[0].shift(); mergedFeatures[i].geometry[0] = geom[0].concat(mergedFeatures[i].geometry[0]); return i; } function getKey(text, geom, onRight) { const point = onRight ? geom[0][geom[0].length - 1] : geom[0][0]; return `${ text }:${ point.x }:${ point.y }`; } for (let k = 0; k < features.length; k++) { const feature = features[k]; const geom = feature.geometry; const text = feature.text ? feature.text.toString() : null; if (!text) { add(k); continue; } const leftKey = getKey(text, geom), rightKey = getKey(text, geom, true); if (leftKey in rightIndex && rightKey in leftIndex && rightIndex[leftKey] !== leftIndex[rightKey]) { const j = mergeFromLeft(leftKey, rightKey, geom); const i = mergeFromRight(leftKey, rightKey, mergedFeatures[j].geometry); delete leftIndex[leftKey]; delete rightIndex[rightKey]; rightIndex[getKey(text, mergedFeatures[i].geometry, true)] = i; mergedFeatures[j].geometry = null; } else if (leftKey in rightIndex) { mergeFromRight(leftKey, rightKey, geom); } else if (rightKey in leftIndex) { mergeFromLeft(leftKey, rightKey, geom); } else { add(k); leftIndex[leftKey] = mergedIndex - 1; rightIndex[rightKey] = mergedIndex - 1; } } return mergedFeatures.filter(f => f.geometry); } const verticalizedCharacterMap = { '!': '\uFE15', '#': '\uFF03', '$': '\uFF04', '%': '\uFF05', '&': '\uFF06', '(': '\uFE35', ')': '\uFE36', '*': '\uFF0A', '+': '\uFF0B', ',': '\uFE10', '-': '\uFE32', '.': '\u30FB', '/': '\uFF0F', ':': '\uFE13', ';': '\uFE14', '<': '\uFE3F', '=': '\uFF1D', '>': '\uFE40', '?': '\uFE16', '@': '\uFF20', '[': '\uFE47', '\\': '\uFF3C', ']': '\uFE48', '^': '\uFF3E', '_': '︳', '`': '\uFF40', '{': '\uFE37', '|': '\u2015', '}': '\uFE38', '~': '\uFF5E', '\xA2': '\uFFE0', '\xA3': '\uFFE1', '\xA5': '\uFFE5', '\xA6': '\uFFE4', '\xAC': '\uFFE2', '\xAF': '\uFFE3', '\u2013': '\uFE32', '\u2014': '\uFE31', '\u2018': '\uFE43', '\u2019': '\uFE44', '\u201C': '\uFE41', '\u201D': '\uFE42', '\u2026': '\uFE19', '\u2027': '\u30FB', '\u20A9': '\uFFE6', '\u3001': '\uFE11', '\u3002': '\uFE12', '\u3008': '\uFE3F', '\u3009': '\uFE40', '\u300A': '\uFE3D', '\u300B': '\uFE3E', '\u300C': '\uFE41', '\u300D': '\uFE42', '\u300E': '\uFE43', '\u300F': '\uFE44', '\u3010': '\uFE3B', '\u3011': '\uFE3C', '\u3014': '\uFE39', '\u3015': '\uFE3A', '\u3016': '\uFE17', '\u3017': '\uFE18', '\uFF01': '\uFE15', '\uFF08': '\uFE35', '\uFF09': '\uFE36', '\uFF0C': '\uFE10', '\uFF0D': '\uFE32', '\uFF0E': '\u30FB', '\uFF1A': '\uFE13', '\uFF1B': '\uFE14', '\uFF1C': '\uFE3F', '\uFF1E': '\uFE40', '\uFF1F': '\uFE16', '\uFF3B': '\uFE47', '\uFF3D': '\uFE48', '_': '︳', '\uFF5B': '\uFE37', '\uFF5C': '\u2015', '\uFF5D': '\uFE38', '\uFF5F': '\uFE35', '\uFF60': '\uFE36', '\uFF61': '\uFE12', '\uFF62': '\uFE41', '\uFF63': '\uFE42' }; function verticalizePunctuation(input) { let output = ''; for (let i = 0; i < input.length; i++) { const nextCharCode = input.charCodeAt(i + 1) || null; const prevCharCode = input.charCodeAt(i - 1) || null; const canReplacePunctuation = (!nextCharCode || !charHasRotatedVerticalOrientation(nextCharCode) || verticalizedCharacterMap[input[i + 1]]) && (!prevCharCode || !charHasRotatedVerticalOrientation(prevCharCode) || verticalizedCharacterMap[input[i - 1]]); if (canReplacePunctuation && verticalizedCharacterMap[input[i]]) { output += verticalizedCharacterMap[input[i]]; } else { output += input[i]; } } return output; } var read = function (buffer, offset, isLE, mLen, nBytes) { var e, m; var eLen = nBytes * 8 - mLen - 1; var eMax = (1 << eLen) - 1; var eBias = eMax >> 1; var nBits = -7; var i = isLE ? nBytes - 1 : 0; var d = isLE ? -1 : 1; var s = buffer[offset + i]; i += d; e = s & (1 << -nBits) - 1; s >>= -nBits; nBits += eLen; for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) { } m = e & (1 << -nBits) - 1; e >>= -nBits; nBits += mLen; for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) { } if (e === 0) { e = 1 - eBias; } else if (e === eMax) { return m ? NaN : (s ? -1 : 1) * Infinity; } else { m = m + Math.pow(2, mLen); e = e - eBias; } return (s ? -1 : 1) * m * Math.pow(2, e - mLen); }; var write = function (buffer, value, offset, isLE, mLen, nBytes) { var e, m, c; var eLen = nBytes * 8 - mLen - 1; var eMax = (1 << eLen) - 1; var eBias = eMax >> 1; var rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0; var i = isLE ? 0 : nBytes - 1; var d = isLE ? 1 : -1; var s = value < 0 || value === 0 && 1 / value < 0 ? 1 : 0; value = Math.abs(value); if (isNaN(value) || value === Infinity) { m = isNaN(value) ? 1 : 0; e = eMax; } else { e = Math.floor(Math.log(value) / Math.LN2); if (value * (c = Math.pow(2, -e)) < 1) { e--; c *= 2; } if (e + eBias >= 1) { value += rt / c; } else { value += rt * Math.pow(2, 1 - eBias); } if (value * c >= 2) { e++; c /= 2; } if (e + eBias >= eMax) { m = 0; e = eMax; } else if (e + eBias >= 1) { m = (value * c - 1) * Math.pow(2, mLen); e = e + eBias; } else { m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); e = 0; } } for (; mLen >= 8; buffer[offset + i] = m & 255, i += d, m /= 256, mLen -= 8) { } e = e << mLen | m; eLen += mLen; for (; eLen > 0; buffer[offset + i] = e & 255, i += d, e /= 256, eLen -= 8) { } buffer[offset + i - d] |= s * 128; }; var ieee754 = { read: read, write: write }; var pbf = Pbf; function Pbf(buf) { this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0); this.pos = 0; this.type = 0; this.length = this.buf.length; } Pbf.Varint = 0; Pbf.Fixed64 = 1; Pbf.Bytes = 2; Pbf.Fixed32 = 5; var SHIFT_LEFT_32 = (1 << 16) * (1 << 16), SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32; var TEXT_DECODER_MIN_LENGTH = 12; var utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf8'); Pbf.prototype = { destroy: function () { this.buf = null; }, readFields: function (readField, result, end) { end = end || this.length; while (this.pos < end) { var val = this.readVarint(), tag = val >> 3, startPos = this.pos; this.type = val & 7; readField(tag, result, this); if (this.pos === startPos) this.skip(val); } return result; }, readMessage: function (readField, result) { return this.readFields(readField, result, this.readVarint() + this.pos); }, readFixed32: function () { var val = readUInt32(this.buf, this.pos); this.pos += 4; return val; }, readSFixed32: function () { var val = readInt32(this.buf, this.pos); this.pos += 4; return val; }, readFixed64: function () { var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; this.pos += 8; return val; }, readSFixed64: function () { var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; this.pos += 8; return val; }, readFloat: function () { var val = ieee754.read(this.buf, this.pos, true, 23, 4); this.pos += 4; return val; }, readDouble: function () { var val = ieee754.read(this.buf, this.pos, true, 52, 8); this.pos += 8; return val; }, readVarint: function (isSigned) { var buf = this.buf, val, b; b = buf[this.pos++]; val = b & 127; if (b < 128) return val; b = buf[this.pos++]; val |= (b & 127) << 7; if (b < 128) return val; b = buf[this.pos++]; val |= (b & 127) << 14; if (b < 128) return val; b = buf[this.pos++]; val |= (b & 127) << 21; if (b < 128) return val; b = buf[this.pos]; val |= (b & 15) << 28; return readVarintRemainder(val, isSigned, this); }, readVarint64: function () { return this.readVarint(true); }, readSVarint: function () { var num = this.readVarint(); return num % 2 === 1 ? (num + 1) / -2 : num / 2; }, readBoolean: function () { return Boolean(this.readVarint()); }, readString: function () { var end = this.readVarint() + this.pos; var pos = this.pos; this.pos = end; if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) { return readUtf8TextDecoder(this.buf, pos, end); } return readUtf8(this.buf, pos, end); }, readBytes: function () { var end = this.readVarint() + this.pos, buffer = this.buf.subarray(this.pos, end); this.pos = end; return buffer; }, readPackedVarint: function (arr, isSigned) { if (this.type !== Pbf.Bytes) return arr.push(this.readVarint(isSigned)); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readVarint(isSigned)); return arr; }, readPackedSVarint: function (arr) { if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint()); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readSVarint()); return arr; }, readPackedBoolean: function (arr) { if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean()); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readBoolean()); return arr; }, readPackedFloat: function (arr) { if (this.type !== Pbf.Bytes) return arr.push(this.readFloat()); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readFloat()); return arr; }, readPackedDouble: function (arr) { if (this.type !== Pbf.Bytes) return arr.push(this.readDouble()); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readDouble()); return arr; }, readPackedFixed32: function (arr) { if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32()); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readFixed32()); return arr; }, readPackedSFixed32: function (arr) { if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32()); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readSFixed32()); return arr; }, readPackedFixed64: function (arr) { if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64()); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readFixed64()); return arr; }, readPackedSFixed64: function (arr) { if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64()); var end = readPackedEnd(this); arr = arr || []; while (this.pos < end) arr.push(this.readSFixed64()); return arr; }, skip: function (val) { var type = val & 7; if (type === Pbf.Varint) while (this.buf[this.pos++] > 127) { } else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos; else if (type === Pbf.Fixed32) this.pos += 4; else if (type === Pbf.Fixed64) this.pos += 8; else throw new Error('Unimplemented type: ' + type); }, writeTag: function (tag, type) { this.writeVarint(tag << 3 | type); }, realloc: function (min) { var length = this.length || 16; while (length < this.pos + min) length *= 2; if (length !== this.length) { var buf = new Uint8Array(length); buf.set(this.buf); this.buf = buf; this.length = length; } }, finish: function () { this.length = this.pos; this.pos = 0; return this.buf.subarray(0, this.length); }, writeFixed32: function (val) { this.realloc(4); writeInt32(this.buf, val, this.pos); this.pos += 4; }, writeSFixed32: function (val) { this.realloc(4); writeInt32(this.buf, val, this.pos); this.pos += 4; }, writeFixed64: function (val) { this.realloc(8); writeInt32(this.buf, val & -1, this.pos); writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); this.pos += 8; }, writeSFixed64: function (val) { this.realloc(8); writeInt32(this.buf, val & -1, this.pos); writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); this.pos += 8; }, writeVarint: function (val) { val = +val || 0; if (val > 268435455 || val < 0) { writeBigVarint(val, this); return; } this.realloc(4); this.buf[this.pos++] = val & 127 | (val > 127 ? 128 : 0); if (val <= 127) return; this.buf[this.pos++] = (val >>>= 7) & 127 | (val > 127 ? 128 : 0); if (val <= 127) return; this.buf[this.pos++] = (val >>>= 7) & 127 | (val > 127 ? 128 : 0); if (val <= 127) return; this.buf[this.pos++] = val >>> 7 & 127; }, writeSVarint: function (val) { this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2); }, writeBoolean: function (val) { this.writeVarint(Boolean(val)); }, writeString: function (str) { str = String(str); this.realloc(str.length * 4); this.pos++; var startPos = this.pos; this.pos = writeUtf8(this.buf, str, this.pos); var len = this.pos - startPos; if (len >= 128) makeRoomForExtraLength(startPos, len, this); this.pos = startPos - 1; this.writeVarint(len); this.pos += len; }, writeFloat: function (val) { this.realloc(4); ieee754.write(this.buf, val, this.pos, true, 23, 4); this.pos += 4; }, writeDouble: function (val) { this.realloc(8); ieee754.write(this.buf, val, this.pos, true, 52, 8); this.pos += 8; }, writeBytes: function (buffer) { var len = buffer.length; this.writeVarint(len); this.realloc(len); for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i]; }, writeRawMessage: function (fn, obj) { this.pos++; var startPos = this.pos; fn(obj, this); var len = this.pos - startPos; if (len >= 128) makeRoomForExtraLength(startPos, len, this); this.pos = startPos - 1; this.writeVarint(len); this.pos += len; }, writeMessage: function (tag, fn, obj) { this.writeTag(tag, Pbf.Bytes); this.writeRawMessage(fn, obj); }, writePackedVarint: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedVarint, arr); }, writePackedSVarint: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSVarint, arr); }, writePackedBoolean: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedBoolean, arr); }, writePackedFloat: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFloat, arr); }, writePackedDouble: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedDouble, arr); }, writePackedFixed32: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed32, arr); }, writePackedSFixed32: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed32, arr); }, writePackedFixed64: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed64, arr); }, writePackedSFixed64: function (tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed64, arr); }, writeBytesField: function (tag, buffer) { this.writeTag(tag, Pbf.Bytes); this.writeBytes(buffer); }, writeFixed32Field: function (tag, val) { this.writeTag(tag, Pbf.Fixed32); this.writeFixed32(val); }, writeSFixed32Field: function (tag, val) { this.writeTag(tag, Pbf.Fixed32); this.writeSFixed32(val); }, writeFixed64Field: function (tag, val) { this.writeTag(tag, Pbf.Fixed64); this.writeFixed64(val); }, writeSFixed64Field: function (tag, val) { this.writeTag(tag, Pbf.Fixed64); this.writeSFixed64(val); }, writeVarintField: function (tag, val) { this.writeTag(tag, Pbf.Varint); this.writeVarint(val); }, writeSVarintField: function (tag, val) { this.writeTag(tag, Pbf.Varint); this.writeSVarint(val); }, writeStringField: function (tag, str) { this.writeTag(tag, Pbf.Bytes); this.writeString(str); }, writeFloatField: function (tag, val) { this.writeTag(tag, Pbf.Fixed32); this.writeFloat(val); }, writeDoubleField: function (tag, val) { this.writeTag(tag, Pbf.Fixed64); this.writeDouble(val); }, writeBooleanField: function (tag, val) { this.writeVarintField(tag, Boolean(val)); } }; function readVarintRemainder(l, s, p) { var buf = p.buf, h, b; b = buf[p.pos++]; h = (b & 112) >> 4; if (b < 128) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 127) << 3; if (b < 128) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 127) << 10; if (b < 128) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 127) << 17; if (b < 128) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 127) << 24; if (b < 128) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 1) << 31; if (b < 128) return toNum(l, h, s); throw new Error('Expected varint not more than 10 bytes'); } function readPackedEnd(pbf) { return pbf.type === Pbf.Bytes ? pbf.readVarint() + pbf.pos : pbf.pos + 1; } function toNum(low, high, isSigned) { if (isSigned) { return high * 4294967296 + (low >>> 0); } return (high >>> 0) * 4294967296 + (low >>> 0); } function writeBigVarint(val, pbf) { var low, high; if (val >= 0) { low = val % 4294967296 | 0; high = val / 4294967296 | 0; } else { low = ~(-val % 4294967296); high = ~(-val / 4294967296); if (low ^ 4294967295) { low = low + 1 | 0; } else { low = 0; high = high + 1 | 0; } } if (val >= 18446744073709552000 || val < -18446744073709552000) { throw new Error('Given varint doesn\'t fit into 10 bytes'); } pbf.realloc(10); writeBigVarintLow(low, high, pbf); writeBigVarintHigh(high, pbf); } function writeBigVarintLow(low, high, pbf) { pbf.buf[pbf.pos++] = low & 127 | 128; low >>>= 7; pbf.buf[pbf.pos++] = low & 127 | 128; low >>>= 7; pbf.buf[pbf.pos++] = low & 127 | 128; low >>>= 7; pbf.buf[pbf.pos++] = low & 127 | 128; low >>>= 7; pbf.buf[pbf.pos] = low & 127; } function writeBigVarintHigh(high, pbf) { var lsb = (high & 7) << 4; pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 128 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 127 | ((high >>>= 7) ? 128 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 127 | ((high >>>= 7) ? 128 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 127 | ((high >>>= 7) ? 128 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 127 | ((high >>>= 7) ? 128 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 127; } function makeRoomForExtraLength(startPos, len, pbf) { var extraLen = len <= 16383 ? 1 : len <= 2097151 ? 2 : len <= 268435455 ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7)); pbf.realloc(extraLen); for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i]; } function writePackedVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]); } function writePackedSVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]); } function writePackedFloat(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]); } function writePackedDouble(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]); } function writePackedBoolean(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]); } function writePackedFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]); } function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); } function writePackedFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]); } function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); } function readUInt32(buf, pos) { return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + buf[pos + 3] * 16777216; } function writeInt32(buf, val, pos) { buf[pos] = val; buf[pos + 1] = val >>> 8; buf[pos + 2] = val >>> 16; buf[pos + 3] = val >>> 24; } function readInt32(buf, pos) { return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + (buf[pos + 3] << 24); } function readUtf8(buf, pos, end) { var str = ''; var i = pos; while (i < end) { var b0 = buf[i]; var c = null; var bytesPerSequence = b0 > 239 ? 4 : b0 > 223 ? 3 : b0 > 191 ? 2 : 1; if (i + bytesPerSequence > end) break; var b1, b2, b3; if (bytesPerSequence === 1) { if (b0 < 128) { c = b0; } } else if (bytesPerSequence === 2) { b1 = buf[i + 1]; if ((b1 & 192) === 128) { c = (b0 & 31) << 6 | b1 & 63; if (c <= 127) { c = null; } } } else if (bytesPerSequence === 3) { b1 = buf[i + 1]; b2 = buf[i + 2]; if ((b1 & 192) === 128 && (b2 & 192) === 128) { c = (b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63; if (c <= 2047 || c >= 55296 && c <= 57343) { c = null; } } } else if (bytesPerSequence === 4) { b1 = buf[i + 1]; b2 = buf[i + 2]; b3 = buf[i + 3]; if ((b1 & 192) === 128 && (b2 & 192) === 128 && (b3 & 192) === 128) { c = (b0 & 15) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63; if (c <= 65535 || c >= 1114112) { c = null; } } } if (c === null) { c = 65533; bytesPerSequence = 1; } else if (c > 65535) { c -= 65536; str += String.fromCharCode(c >>> 10 & 1023 | 55296); c = 56320 | c & 1023; } str += String.fromCharCode(c); i += bytesPerSequence; } return str; } function readUtf8TextDecoder(buf, pos, end) { return utf8TextDecoder.decode(buf.subarray(pos, end)); } function writeUtf8(buf, str, pos) { for (var i = 0, c, lead; i < str.length; i++) { c = str.charCodeAt(i); if (c > 55295 && c < 57344) { if (lead) { if (c < 56320) { buf[pos++] = 239; buf[pos++] = 191; buf[pos++] = 189; lead = c; continue; } else { c = lead - 55296 << 10 | c - 56320 | 65536; lead = null; } } else { if (c > 56319 || i + 1 === str.length) { buf[pos++] = 239; buf[pos++] = 191; buf[pos++] = 189; } else { lead = c; } continue; } } else if (lead) { buf[pos++] = 239; buf[pos++] = 191; buf[pos++] = 189; lead = null; } if (c < 128) { buf[pos++] = c; } else { if (c < 2048) { buf[pos++] = c >> 6 | 192; } else { if (c < 65536) { buf[pos++] = c >> 12 | 224; } else { buf[pos++] = c >> 18 | 240; buf[pos++] = c >> 12 & 63 | 128; } buf[pos++] = c >> 6 & 63 | 128; } buf[pos++] = c & 63 | 128; } } return pos; } const border = 3; function readFontstacks(tag, glyphs, pbf) { if (tag === 1) { pbf.readMessage(readFontstack, glyphs); } } function readFontstack(tag, glyphs, pbf) { if (tag === 3) { const {id, bitmap, width, height, left, top, advance} = pbf.readMessage(readGlyph, {}); glyphs.push({ id, bitmap: new AlphaImage({ width: width + 2 * border, height: height + 2 * border }, bitmap), metrics: { width, height, left, top, advance } }); } } function readGlyph(tag, glyph, pbf) { if (tag === 1) glyph.id = pbf.readVarint(); else if (tag === 2) glyph.bitmap = pbf.readBytes(); else if (tag === 3) glyph.width = pbf.readVarint(); else if (tag === 4) glyph.height = pbf.readVarint(); else if (tag === 5) glyph.left = pbf.readSVarint(); else if (tag === 6) glyph.top = pbf.readSVarint(); else if (tag === 7) glyph.advance = pbf.readVarint(); } function parseGlyphPBF (data) { return new pbf(data).readFields(readFontstacks, []); } const GLYPH_PBF_BORDER = border; function potpack(boxes) { // calculate total box area and maximum box width let area = 0; let maxWidth = 0; for (const box of boxes) { area += box.w * box.h; maxWidth = Math.max(maxWidth, box.w); } // sort the boxes for insertion by height, descending boxes.sort((a, b) => b.h - a.h); // aim for a squarish resulting container, // slightly adjusted for sub-100% space utilization const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth); // start with a single empty space, unbounded at the bottom const spaces = [{x: 0, y: 0, w: startWidth, h: Infinity}]; let width = 0; let height = 0; for (const box of boxes) { // look through spaces backwards so that we check smaller spaces first for (let i = spaces.length - 1; i >= 0; i--) { const space = spaces[i]; // look for empty spaces that can accommodate the current box if (box.w > space.w || box.h > space.h) continue; // found the space; add the box to its top-left corner // |-------|-------| // | box | | // |_______| | // | space | // |_______________| box.x = space.x; box.y = space.y; height = Math.max(height, box.y + box.h); width = Math.max(width, box.x + box.w); if (box.w === space.w && box.h === space.h) { // space matches the box exactly; remove it const last = spaces.pop(); if (i < spaces.length) spaces[i] = last; } else if (box.h === space.h) { // space matches the box height; update it accordingly // |-------|---------------| // | box | updated space | // |_______|_______________| space.x += box.w; space.w -= box.w; } else if (box.w === space.w) { // space matches the box width; update it accordingly // |---------------| // | box | // |_______________| // | updated space | // |_______________| space.y += box.h; space.h -= box.h; } else { // otherwise the box splits the space into two spaces // |-------|-----------| // | box | new space | // |_______|___________| // | updated space | // |___________________| spaces.push({ x: space.x + box.w, y: space.y, w: space.w - box.w, h: box.h }); space.y += box.h; space.h -= box.h; } break; } } return { w: width, // container width h: height, // container height fill: (area / (width * height)) || 0 // space utilization }; } const IMAGE_PADDING = 1; class ImagePosition { constructor(paddedRect, {pixelRatio, version, stretchX, stretchY, content}) { this.paddedRect = paddedRect; this.pixelRatio = pixelRatio; this.stretchX = stretchX; this.stretchY = stretchY; this.content = content; this.version = version; } get tl() { return [ this.paddedRect.x + IMAGE_PADDING, this.paddedRect.y + IMAGE_PADDING ]; } get br() { return [ this.paddedRect.x + this.paddedRect.w - IMAGE_PADDING, this.paddedRect.y + this.paddedRect.h - IMAGE_PADDING ]; } get tlbr() { return this.tl.concat(this.br); } get displaySize() { return [ (this.paddedRect.w - IMAGE_PADDING * 2) / this.pixelRatio, (this.paddedRect.h - IMAGE_PADDING * 2) / this.pixelRatio ]; } } class ImageAtlas { constructor(icons, patterns) { const iconPositions = {}, patternPositions = {}; this.haveRenderCallbacks = []; const bins = []; this.addImages(icons, iconPositions, bins); this.addImages(patterns, patternPositions, bins); const {w, h} = potpack(bins); const image = new RGBAImage({ width: w || 1, height: h || 1 }); for (const id in icons) { const src = icons[id]; const bin = iconPositions[id].paddedRect; RGBAImage.copy(src.data, image, { x: 0, y: 0 }, { x: bin.x + IMAGE_PADDING, y: bin.y + IMAGE_PADDING }, src.data); } for (const id in patterns) { const src = patterns[id]; const bin = patternPositions[id].paddedRect; const x = bin.x + IMAGE_PADDING, y = bin.y + IMAGE_PADDING, w = src.data.width, h = src.data.height; RGBAImage.copy(src.data, image, { x: 0, y: 0 }, { x, y }, src.data); RGBAImage.copy(src.data, image, { x: 0, y: h - 1 }, { x, y: y - 1 }, { width: w, height: 1 }); RGBAImage.copy(src.data, image, { x: 0, y: 0 }, { x, y: y + h }, { width: w, height: 1 }); RGBAImage.copy(src.data, image, { x: w - 1, y: 0 }, { x: x - 1, y }, { width: 1, height: h }); RGBAImage.copy(src.data, image, { x: 0, y: 0 }, { x: x + w, y }, { width: 1, height: h }); } this.image = image; this.iconPositions = iconPositions; this.patternPositions = patternPositions; } addImages(images, positions, bins) { for (const id in images) { const src = images[id]; const bin = { x: 0, y: 0, w: src.data.width + 2 * IMAGE_PADDING, h: src.data.height + 2 * IMAGE_PADDING }; bins.push(bin); positions[id] = new ImagePosition(bin, src); if (src.hasRenderCallback) { this.haveRenderCallbacks.push(id); } } } patchUpdatedImages(imageManager, texture) { imageManager.dispatchRenderCallbacks(this.haveRenderCallbacks); for (const name in imageManager.updatedImages) { this.patchUpdatedImage(this.iconPositions[name], imageManager.getImage(name), texture); this.patchUpdatedImage(this.patternPositions[name], imageManager.getImage(name), texture); } } patchUpdatedImage(position, image, texture) { if (!position || !image) return; if (position.version === image.version) return; position.version = image.version; const [x, y] = position.tl; texture.update(image.data, undefined, { x, y }); } } register('ImagePosition', ImagePosition); register('ImageAtlas', ImageAtlas); const WritingMode = { horizontal: 1, vertical: 2, horizontalOnly: 3 }; const SHAPING_DEFAULT_OFFSET = -17; function isEmpty(positionedLines) { for (const line of positionedLines) { if (line.positionedGlyphs.length !== 0) { return false; } } return true; } const PUAbegin = 57344; const PUAend = 63743; class SectionOptions { constructor() { this.scale = 1; this.fontStack = ''; this.imageName = null; } static forText(scale, fontStack) { const textOptions = new SectionOptions(); textOptions.scale = scale || 1; textOptions.fontStack = fontStack; return textOptions; } static forImage(imageName) { const imageOptions = new SectionOptions(); imageOptions.imageName = imageName; return imageOptions; } } class TaggedString { constructor() { this.text = ''; this.sectionIndex = []; this.sections = []; this.imageSectionID = null; } static fromFeature(text, defaultFontStack) { const result = new TaggedString(); for (let i = 0; i < text.sections.length; i++) { const section = text.sections[i]; if (!section.image) { result.addTextSection(section, defaultFontStack); } else { result.addImageSection(section); } } return result; } length() { return this.text.length; } getSection(index) { return this.sections[this.sectionIndex[index]]; } getSectionIndex(index) { return this.sectionIndex[index]; } getCharCode(index) { return this.text.charCodeAt(index); } verticalizePunctuation() { this.text = verticalizePunctuation(this.text); } trim() { let beginningWhitespace = 0; for (let i = 0; i < this.text.length && whitespace[this.text.charCodeAt(i)]; i++) { beginningWhitespace++; } let trailingWhitespace = this.text.length; for (let i = this.text.length - 1; i >= 0 && i >= beginningWhitespace && whitespace[this.text.charCodeAt(i)]; i--) { trailingWhitespace--; } this.text = this.text.substring(beginningWhitespace, trailingWhitespace); this.sectionIndex = this.sectionIndex.slice(beginningWhitespace, trailingWhitespace); } substring(start, end) { const substring = new TaggedString(); substring.text = this.text.substring(start, end); substring.sectionIndex = this.sectionIndex.slice(start, end); substring.sections = this.sections; return substring; } toString() { return this.text; } getMaxScale() { return this.sectionIndex.reduce((max, index) => Math.max(max, this.sections[index].scale), 0); } addTextSection(section, defaultFontStack) { this.text += section.text; this.sections.push(SectionOptions.forText(section.scale, section.fontStack || defaultFontStack)); const index = this.sections.length - 1; for (let i = 0; i < section.text.length; ++i) { this.sectionIndex.push(index); } } addImageSection(section) { const imageName = section.image ? section.image.name : ''; if (imageName.length === 0) { warnOnce(`Can't add FormattedSection with an empty image.`); return; } const nextImageSectionCharCode = this.getNextImageSectionCharCode(); if (!nextImageSectionCharCode) { warnOnce(`Reached maximum number of images ${ PUAend - PUAbegin + 2 }`); return; } this.text += String.fromCharCode(nextImageSectionCharCode); this.sections.push(SectionOptions.forImage(imageName)); this.sectionIndex.push(this.sections.length - 1); } getNextImageSectionCharCode() { if (!this.imageSectionID) { this.imageSectionID = PUAbegin; return this.imageSectionID; } if (this.imageSectionID >= PUAend) return null; return ++this.imageSectionID; } } function breakLines(input, lineBreakPoints) { const lines = []; const text = input.text; let start = 0; for (const lineBreak of lineBreakPoints) { lines.push(input.substring(start, lineBreak)); start = lineBreak; } if (start < text.length) { lines.push(input.substring(start, text.length)); } return lines; } function shapeText(text, glyphMap, glyphPositions, imagePositions, defaultFontStack, maxWidth, lineHeight, textAnchor, textJustify, spacing, translate, writingMode, allowVerticalPlacement, symbolPlacement, layoutTextSize, layoutTextSizeThisZoom) { const logicalInput = TaggedString.fromFeature(text, defaultFontStack); if (writingMode === WritingMode.vertical) { logicalInput.verticalizePunctuation(); } let lines; const {processBidirectionalText, processStyledBidirectionalText} = plugin; if (processBidirectionalText && logicalInput.sections.length === 1) { lines = []; const untaggedLines = processBidirectionalText(logicalInput.toString(), determineLineBreaks(logicalInput, spacing, maxWidth, glyphMap, imagePositions, symbolPlacement, layoutTextSize)); for (const line of untaggedLines) { const taggedLine = new TaggedString(); taggedLine.text = line; taggedLine.sections = logicalInput.sections; for (let i = 0; i < line.length; i++) { taggedLine.sectionIndex.push(0); } lines.push(taggedLine); } } else if (processStyledBidirectionalText) { lines = []; const processedLines = processStyledBidirectionalText(logicalInput.text, logicalInput.sectionIndex, determineLineBreaks(logicalInput, spacing, maxWidth, glyphMap, imagePositions, symbolPlacement, layoutTextSize)); for (const line of processedLines) { const taggedLine = new TaggedString(); taggedLine.text = line[0]; taggedLine.sectionIndex = line[1]; taggedLine.sections = logicalInput.sections; lines.push(taggedLine); } } else { lines = breakLines(logicalInput, determineLineBreaks(logicalInput, spacing, maxWidth, glyphMap, imagePositions, symbolPlacement, layoutTextSize)); } const positionedLines = []; const shaping = { positionedLines, text: logicalInput.toString(), top: translate[1], bottom: translate[1], left: translate[0], right: translate[0], writingMode, iconsInText: false, verticalizable: false }; shapeLines(shaping, glyphMap, glyphPositions, imagePositions, lines, lineHeight, textAnchor, textJustify, writingMode, spacing, allowVerticalPlacement, layoutTextSizeThisZoom); if (isEmpty(positionedLines)) return false; return shaping; } const whitespace = { [9]: true, [10]: true, [11]: true, [12]: true, [13]: true, [32]: true }; const breakable = { [10]: true, [32]: true, [38]: true, [40]: true, [41]: true, [43]: true, [45]: true, [47]: true, [173]: true, [183]: true, [8203]: true, [8208]: true, [8211]: true, [8231]: true }; function getGlyphAdvance(codePoint, section, glyphMap, imagePositions, spacing, layoutTextSize) { if (!section.imageName) { const positions = glyphMap[section.fontStack]; const glyph = positions && positions[codePoint]; if (!glyph) return 0; return glyph.metrics.advance * section.scale + spacing; } else { const imagePosition = imagePositions[section.imageName]; if (!imagePosition) return 0; return imagePosition.displaySize[0] * section.scale * ONE_EM / layoutTextSize + spacing; } } function determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphMap, imagePositions, layoutTextSize) { let totalWidth = 0; for (let index = 0; index < logicalInput.length(); index++) { const section = logicalInput.getSection(index); totalWidth += getGlyphAdvance(logicalInput.getCharCode(index), section, glyphMap, imagePositions, spacing, layoutTextSize); } const lineCount = Math.max(1, Math.ceil(totalWidth / maxWidth)); return totalWidth / lineCount; } function calculateBadness(lineWidth, targetWidth, penalty, isLastBreak) { const raggedness = Math.pow(lineWidth - targetWidth, 2); if (isLastBreak) { if (lineWidth < targetWidth) { return raggedness / 2; } else { return raggedness * 2; } } return raggedness + Math.abs(penalty) * penalty; } function calculatePenalty(codePoint, nextCodePoint, penalizableIdeographicBreak) { let penalty = 0; if (codePoint === 10) { penalty -= 10000; } if (penalizableIdeographicBreak) { penalty += 150; } if (codePoint === 40 || codePoint === 65288) { penalty += 50; } if (nextCodePoint === 41 || nextCodePoint === 65289) { penalty += 50; } return penalty; } function evaluateBreak(breakIndex, breakX, targetWidth, potentialBreaks, penalty, isLastBreak) { let bestPriorBreak = null; let bestBreakBadness = calculateBadness(breakX, targetWidth, penalty, isLastBreak); for (const potentialBreak of potentialBreaks) { const lineWidth = breakX - potentialBreak.x; const breakBadness = calculateBadness(lineWidth, targetWidth, penalty, isLastBreak) + potentialBreak.badness; if (breakBadness <= bestBreakBadness) { bestPriorBreak = potentialBreak; bestBreakBadness = breakBadness; } } return { index: breakIndex, x: breakX, priorBreak: bestPriorBreak, badness: bestBreakBadness }; } function leastBadBreaks(lastLineBreak) { if (!lastLineBreak) { return []; } return leastBadBreaks(lastLineBreak.priorBreak).concat(lastLineBreak.index); } function determineLineBreaks(logicalInput, spacing, maxWidth, glyphMap, imagePositions, symbolPlacement, layoutTextSize) { if (symbolPlacement !== 'point') return []; if (!logicalInput) return []; const potentialLineBreaks = []; const targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphMap, imagePositions, layoutTextSize); const hasServerSuggestedBreakpoints = logicalInput.text.indexOf('\u200B') >= 0; let currentX = 0; for (let i = 0; i < logicalInput.length(); i++) { const section = logicalInput.getSection(i); const codePoint = logicalInput.getCharCode(i); if (!whitespace[codePoint]) currentX += getGlyphAdvance(codePoint, section, glyphMap, imagePositions, spacing, layoutTextSize); if (i < logicalInput.length() - 1) { const ideographicBreak = charAllowsIdeographicBreaking(codePoint); if (breakable[codePoint] || ideographicBreak || section.imageName) { potentialLineBreaks.push(evaluateBreak(i + 1, currentX, targetWidth, potentialLineBreaks, calculatePenalty(codePoint, logicalInput.getCharCode(i + 1), ideographicBreak && hasServerSuggestedBreakpoints), false)); } } } return leastBadBreaks(evaluateBreak(logicalInput.length(), currentX, targetWidth, potentialLineBreaks, 0, true)); } function getAnchorAlignment(anchor) { let horizontalAlign = 0.5, verticalAlign = 0.5; switch (anchor) { case 'right': case 'top-right': case 'bottom-right': horizontalAlign = 1; break; case 'left': case 'top-left': case 'bottom-left': horizontalAlign = 0; break; } switch (anchor) { case 'bottom': case 'bottom-right': case 'bottom-left': verticalAlign = 1; break; case 'top': case 'top-right': case 'top-left': verticalAlign = 0; break; } return { horizontalAlign, verticalAlign }; } function shapeLines(shaping, glyphMap, glyphPositions, imagePositions, lines, lineHeight, textAnchor, textJustify, writingMode, spacing, allowVerticalPlacement, layoutTextSizeThisZoom) { let x = 0; let y = SHAPING_DEFAULT_OFFSET; let maxLineLength = 0; let maxLineHeight = 0; const justify = textJustify === 'right' ? 1 : textJustify === 'left' ? 0 : 0.5; let lineIndex = 0; for (const line of lines) { line.trim(); const lineMaxScale = line.getMaxScale(); const maxLineOffset = (lineMaxScale - 1) * ONE_EM; const positionedLine = { positionedGlyphs: [], lineOffset: 0 }; shaping.positionedLines[lineIndex] = positionedLine; const positionedGlyphs = positionedLine.positionedGlyphs; let lineOffset = 0; if (!line.length()) { y += lineHeight; ++lineIndex; continue; } for (let i = 0; i < line.length(); i++) { const section = line.getSection(i); const sectionIndex = line.getSectionIndex(i); const codePoint = line.getCharCode(i); let baselineOffset = 0; let metrics = null; let rect = null; let imageName = null; let verticalAdvance = ONE_EM; const vertical = !(writingMode === WritingMode.horizontal || !allowVerticalPlacement && !charHasUprightVerticalOrientation(codePoint) || allowVerticalPlacement && (whitespace[codePoint] || charInComplexShapingScript(codePoint))); if (!section.imageName) { const positions = glyphPositions[section.fontStack]; const glyphPosition = positions && positions[codePoint]; if (glyphPosition && glyphPosition.rect) { rect = glyphPosition.rect; metrics = glyphPosition.metrics; } else { const glyphs = glyphMap[section.fontStack]; const glyph = glyphs && glyphs[codePoint]; if (!glyph) continue; metrics = glyph.metrics; } baselineOffset = (lineMaxScale - section.scale) * ONE_EM; } else { const imagePosition = imagePositions[section.imageName]; if (!imagePosition) continue; imageName = section.imageName; shaping.iconsInText = shaping.iconsInText || true; rect = imagePosition.paddedRect; const size = imagePosition.displaySize; section.scale = section.scale * ONE_EM / layoutTextSizeThisZoom; metrics = { width: size[0], height: size[1], left: IMAGE_PADDING, top: -GLYPH_PBF_BORDER, advance: vertical ? size[1] : size[0], localGlyph: false }; const imageOffset = ONE_EM - size[1] * section.scale; baselineOffset = maxLineOffset + imageOffset; verticalAdvance = metrics.advance; const offset = vertical ? size[0] * section.scale - ONE_EM * lineMaxScale : size[1] * section.scale - ONE_EM * lineMaxScale; if (offset > 0 && offset > lineOffset) { lineOffset = offset; } } if (!vertical) { positionedGlyphs.push({ glyph: codePoint, imageName, x, y: y + baselineOffset, vertical, scale: section.scale, localGlyph: metrics.localGlyph, fontStack: section.fontStack, sectionIndex, metrics, rect }); x += metrics.advance * section.scale + spacing; } else { shaping.verticalizable = true; positionedGlyphs.push({ glyph: codePoint, imageName, x, y: y + baselineOffset, vertical, scale: section.scale, localGlyph: metrics.localGlyph, fontStack: section.fontStack, sectionIndex, metrics, rect }); x += verticalAdvance * section.scale + spacing; } } if (positionedGlyphs.length !== 0) { const lineLength = x - spacing; maxLineLength = Math.max(lineLength, maxLineLength); justifyLine(positionedGlyphs, 0, positionedGlyphs.length - 1, justify, lineOffset); } x = 0; const currentLineHeight = lineHeight * lineMaxScale + lineOffset; positionedLine.lineOffset = Math.max(lineOffset, maxLineOffset); y += currentLineHeight; maxLineHeight = Math.max(currentLineHeight, maxLineHeight); ++lineIndex; } const height = y - SHAPING_DEFAULT_OFFSET; const {horizontalAlign, verticalAlign} = getAnchorAlignment(textAnchor); align$1(shaping.positionedLines, justify, horizontalAlign, verticalAlign, maxLineLength, maxLineHeight, lineHeight, height, lines.length); shaping.top += -verticalAlign * height; shaping.bottom = shaping.top + height; shaping.left += -horizontalAlign * maxLineLength; shaping.right = shaping.left + maxLineLength; } function justifyLine(positionedGlyphs, start, end, justify, lineOffset) { if (!justify && !lineOffset) return; const lastPositionedGlyph = positionedGlyphs[end]; const lastAdvance = lastPositionedGlyph.metrics.advance * lastPositionedGlyph.scale; const lineIndent = (positionedGlyphs[end].x + lastAdvance) * justify; for (let j = start; j <= end; j++) { positionedGlyphs[j].x -= lineIndent; positionedGlyphs[j].y += lineOffset; } } function align$1(positionedLines, justify, horizontalAlign, verticalAlign, maxLineLength, maxLineHeight, lineHeight, blockHeight, lineCount) { const shiftX = (justify - horizontalAlign) * maxLineLength; let shiftY = 0; if (maxLineHeight !== lineHeight) { shiftY = -blockHeight * verticalAlign - SHAPING_DEFAULT_OFFSET; } else { shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight; } for (const line of positionedLines) { for (const positionedGlyph of line.positionedGlyphs) { positionedGlyph.x += shiftX; positionedGlyph.y += shiftY; } } } function shapeIcon(image, iconOffset, iconAnchor) { const {horizontalAlign, verticalAlign} = getAnchorAlignment(iconAnchor); const dx = iconOffset[0]; const dy = iconOffset[1]; const x1 = dx - image.displaySize[0] * horizontalAlign; const x2 = x1 + image.displaySize[0]; const y1 = dy - image.displaySize[1] * verticalAlign; const y2 = y1 + image.displaySize[1]; return { image, top: y1, bottom: y2, left: x1, right: x2 }; } function fitIconToText(shapedIcon, shapedText, textFit, padding, iconOffset, fontScale) { const image = shapedIcon.image; let collisionPadding; if (image.content) { const content = image.content; const pixelRatio = image.pixelRatio || 1; collisionPadding = [ content[0] / pixelRatio, content[1] / pixelRatio, image.displaySize[0] - content[2] / pixelRatio, image.displaySize[1] - content[3] / pixelRatio ]; } const textLeft = shapedText.left * fontScale; const textRight = shapedText.right * fontScale; let top, right, bottom, left; if (textFit === 'width' || textFit === 'both') { left = iconOffset[0] + textLeft - padding[3]; right = iconOffset[0] + textRight + padding[1]; } else { left = iconOffset[0] + (textLeft + textRight - image.displaySize[0]) / 2; right = left + image.displaySize[0]; } const textTop = shapedText.top * fontScale; const textBottom = shapedText.bottom * fontScale; if (textFit === 'height' || textFit === 'both') { top = iconOffset[1] + textTop - padding[0]; bottom = iconOffset[1] + textBottom + padding[2]; } else { top = iconOffset[1] + (textTop + textBottom - image.displaySize[1]) / 2; bottom = top + image.displaySize[1]; } return { image, top, right, bottom, left, collisionPadding }; } class Anchor extends pointGeometry { constructor(x, y, angle, segment) { super(x, y); this.angle = angle; if (segment !== undefined) { this.segment = segment; } } clone() { return new Anchor(this.x, this.y, this.angle, this.segment); } } register('Anchor', Anchor); function checkMaxAngle(line, anchor, labelLength, windowSize, maxAngle) { if (anchor.segment === undefined) return true; let p = anchor; let index = anchor.segment + 1; let anchorDistance = 0; while (anchorDistance > -labelLength / 2) { index--; if (index < 0) return false; anchorDistance -= line[index].dist(p); p = line[index]; } anchorDistance += line[index].dist(line[index + 1]); index++; const recentCorners = []; let recentAngleDelta = 0; while (anchorDistance < labelLength / 2) { const prev = line[index - 1]; const current = line[index]; const next = line[index + 1]; if (!next) return false; let angleDelta = prev.angleTo(current) - current.angleTo(next); angleDelta = Math.abs((angleDelta + 3 * Math.PI) % (Math.PI * 2) - Math.PI); recentCorners.push({ distance: anchorDistance, angleDelta }); recentAngleDelta += angleDelta; while (anchorDistance - recentCorners[0].distance > windowSize) { recentAngleDelta -= recentCorners.shift().angleDelta; } if (recentAngleDelta > maxAngle) return false; index++; anchorDistance += current.dist(next); } return true; } function getLineLength(line) { let lineLength = 0; for (let k = 0; k < line.length - 1; k++) { lineLength += line[k].dist(line[k + 1]); } return lineLength; } function getAngleWindowSize(shapedText, glyphSize, boxScale) { return shapedText ? 3 / 5 * glyphSize * boxScale : 0; } function getShapedLabelLength(shapedText, shapedIcon) { return Math.max(shapedText ? shapedText.right - shapedText.left : 0, shapedIcon ? shapedIcon.right - shapedIcon.left : 0); } function getCenterAnchor(line, maxAngle, shapedText, shapedIcon, glyphSize, boxScale) { const angleWindowSize = getAngleWindowSize(shapedText, glyphSize, boxScale); const labelLength = getShapedLabelLength(shapedText, shapedIcon) * boxScale; let prevDistance = 0; const centerDistance = getLineLength(line) / 2; for (let i = 0; i < line.length - 1; i++) { const a = line[i], b = line[i + 1]; const segmentDistance = a.dist(b); if (prevDistance + segmentDistance > centerDistance) { const t = (centerDistance - prevDistance) / segmentDistance, x = number(a.x, b.x, t), y = number(a.y, b.y, t); const anchor = new Anchor(x, y, b.angleTo(a), i); anchor._round(); if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) { return anchor; } else { return; } } prevDistance += segmentDistance; } } function getAnchors(line, spacing, maxAngle, shapedText, shapedIcon, glyphSize, boxScale, overscaling, tileExtent) { const angleWindowSize = getAngleWindowSize(shapedText, glyphSize, boxScale); const shapedLabelLength = getShapedLabelLength(shapedText, shapedIcon); const labelLength = shapedLabelLength * boxScale; const isLineContinued = line[0].x === 0 || line[0].x === tileExtent || line[0].y === 0 || line[0].y === tileExtent; if (spacing - labelLength < spacing / 4) { spacing = labelLength + spacing / 4; } const fixedExtraOffset = glyphSize * 2; const offset = !isLineContinued ? (shapedLabelLength / 2 + fixedExtraOffset) * boxScale * overscaling % spacing : spacing / 2 * overscaling % spacing; return resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength, isLineContinued, false, tileExtent); } function resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength, isLineContinued, placeAtMiddle, tileExtent) { const halfLabelLength = labelLength / 2; const lineLength = getLineLength(line); let distance = 0, markedDistance = offset - spacing; let anchors = []; for (let i = 0; i < line.length - 1; i++) { const a = line[i], b = line[i + 1]; const segmentDist = a.dist(b), angle = b.angleTo(a); while (markedDistance + spacing < distance + segmentDist) { markedDistance += spacing; const t = (markedDistance - distance) / segmentDist, x = number(a.x, b.x, t), y = number(a.y, b.y, t); if (x >= 0 && x < tileExtent && y >= 0 && y < tileExtent && markedDistance - halfLabelLength >= 0 && markedDistance + halfLabelLength <= lineLength) { const anchor = new Anchor(x, y, angle, i); anchor._round(); if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) { anchors.push(anchor); } } } distance += segmentDist; } if (!placeAtMiddle && !anchors.length && !isLineContinued) { anchors = resample(line, distance / 2, spacing, angleWindowSize, maxAngle, labelLength, isLineContinued, true, tileExtent); } return anchors; } function clipLine(lines, x1, y1, x2, y2) { const clippedLines = []; for (let l = 0; l < lines.length; l++) { const line = lines[l]; let clippedLine; for (let i = 0; i < line.length - 1; i++) { let p0 = line[i]; let p1 = line[i + 1]; if (p0.x < x1 && p1.x < x1) { continue; } else if (p0.x < x1) { p0 = new pointGeometry(x1, p0.y + (p1.y - p0.y) * ((x1 - p0.x) / (p1.x - p0.x)))._round(); } else if (p1.x < x1) { p1 = new pointGeometry(x1, p0.y + (p1.y - p0.y) * ((x1 - p0.x) / (p1.x - p0.x)))._round(); } if (p0.y < y1 && p1.y < y1) { continue; } else if (p0.y < y1) { p0 = new pointGeometry(p0.x + (p1.x - p0.x) * ((y1 - p0.y) / (p1.y - p0.y)), y1)._round(); } else if (p1.y < y1) { p1 = new pointGeometry(p0.x + (p1.x - p0.x) * ((y1 - p0.y) / (p1.y - p0.y)), y1)._round(); } if (p0.x >= x2 && p1.x >= x2) { continue; } else if (p0.x >= x2) { p0 = new pointGeometry(x2, p0.y + (p1.y - p0.y) * ((x2 - p0.x) / (p1.x - p0.x)))._round(); } else if (p1.x >= x2) { p1 = new pointGeometry(x2, p0.y + (p1.y - p0.y) * ((x2 - p0.x) / (p1.x - p0.x)))._round(); } if (p0.y >= y2 && p1.y >= y2) { continue; } else if (p0.y >= y2) { p0 = new pointGeometry(p0.x + (p1.x - p0.x) * ((y2 - p0.y) / (p1.y - p0.y)), y2)._round(); } else if (p1.y >= y2) { p1 = new pointGeometry(p0.x + (p1.x - p0.x) * ((y2 - p0.y) / (p1.y - p0.y)), y2)._round(); } if (!clippedLine || !p0.equals(clippedLine[clippedLine.length - 1])) { clippedLine = [p0]; clippedLines.push(clippedLine); } clippedLine.push(p1); } } return clippedLines; } function loadGlyphRange (fontstack, range, urlTemplate, requestManager, callback) { const begin = range * 256; const end = begin + 255; const request = requestManager.transformRequest(requestManager.normalizeGlyphsURL(urlTemplate).replace('{fontstack}', fontstack).replace('{range}', `${ begin }-${ end }`), ResourceType.Glyphs); getArrayBuffer(request, (err, data) => { if (err) { callback(err); } else if (data) { const glyphs = {}; for (const glyph of parseGlyphPBF(data)) { glyphs[glyph.id] = glyph; } callback(null, glyphs); } }); } var tinySdf = TinySDF; var default_1$1 = TinySDF; var INF = 100000000000000000000; function TinySDF(fontSize, buffer, radius, cutoff, fontFamily, fontWeight) { this.fontSize = fontSize || 24; this.buffer = buffer === undefined ? 3 : buffer; this.cutoff = cutoff || 0.25; this.fontFamily = fontFamily || 'sans-serif'; this.fontWeight = fontWeight || 'normal'; this.radius = radius || 8; var size = this.size = this.fontSize + this.buffer * 2; var gridSize = size + this.buffer * 2; this.canvas = document.createElement('canvas'); this.canvas.width = this.canvas.height = size; this.ctx = this.canvas.getContext('2d'); this.ctx.font = this.fontWeight + ' ' + this.fontSize + 'px ' + this.fontFamily; this.ctx.textBaseline = 'middle'; this.ctx.textAlign = 'left'; this.ctx.fillStyle = 'black'; this.gridOuter = new Float64Array(gridSize * gridSize); this.gridInner = new Float64Array(gridSize * gridSize); this.f = new Float64Array(size); this.z = new Float64Array(size + 1); this.v = new Uint16Array(size); this.middle = Math.round(size / 2 * (navigator.userAgent.indexOf('Gecko/') >= 0 ? 1.2 : 1)); } function prepareGrids(imgData, width, height, glyphWidth, glyphHeight, gridOuter, gridInner) { gridOuter.fill(INF, 0, width * height); gridInner.fill(0, 0, width * height); var offset = (width - glyphWidth) / 2; for (var y = 0; y < glyphHeight; y++) { for (var x = 0; x < glyphWidth; x++) { var j = (y + offset) * width + x + offset; var a = imgData.data[4 * (y * glyphWidth + x) + 3] / 255; if (a === 1) { gridOuter[j] = 0; gridInner[j] = INF; } else if (a === 0) { gridOuter[j] = INF; gridInner[j] = 0; } else { var b = Math.max(0, 0.5 - a); var c = Math.max(0, a - 0.5); gridOuter[j] = b * b; gridInner[j] = c * c; } } } } function extractAlpha(alphaChannel, width, height, gridOuter, gridInner, radius, cutoff) { for (var i = 0; i < width * height; i++) { var d = Math.sqrt(gridOuter[i]) - Math.sqrt(gridInner[i]); alphaChannel[i] = Math.round(255 - 255 * (d / radius + cutoff)); } } TinySDF.prototype._draw = function (char, getMetrics) { var textMetrics = this.ctx.measureText(char); var advance = textMetrics.width; var doubleBuffer = 2 * this.buffer; var width, glyphWidth, height, glyphHeight, top; var imgTop, imgLeft; if (getMetrics && textMetrics.actualBoundingBoxLeft !== undefined) { top = Math.floor(textMetrics.actualBoundingBoxAscent) - this.middle; imgTop = Math.max(0, this.middle - Math.ceil(textMetrics.actualBoundingBoxAscent)); imgLeft = this.buffer; glyphWidth = Math.min(this.size, Math.ceil(textMetrics.actualBoundingBoxRight - textMetrics.actualBoundingBoxLeft)); glyphHeight = Math.min(this.size - imgTop, Math.ceil(textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent)); width = glyphWidth + doubleBuffer; height = glyphHeight + doubleBuffer; } else { width = glyphWidth = this.size; height = glyphHeight = this.size; top = 0; imgTop = imgLeft = 0; } var imgData; if (glyphWidth && glyphHeight) { this.ctx.clearRect(imgLeft, imgTop, glyphWidth, glyphHeight); this.ctx.fillText(char, this.buffer, this.middle); imgData = this.ctx.getImageData(imgLeft, imgTop, glyphWidth, glyphHeight); } var alphaChannel = new Uint8ClampedArray(width * height); prepareGrids(imgData, width, height, glyphWidth, glyphHeight, this.gridOuter, this.gridInner); edt(this.gridOuter, width, height, this.f, this.v, this.z); edt(this.gridInner, width, height, this.f, this.v, this.z); extractAlpha(alphaChannel, width, height, this.gridOuter, this.gridInner, this.radius, this.cutoff); return { data: alphaChannel, metrics: { width: glyphWidth, height: glyphHeight, sdfWidth: width, sdfHeight: height, top: top, left: 0, advance: advance, fontAscent: textMetrics.fontBoundingBoxAscent } }; }; TinySDF.prototype.draw = function (char) { return this._draw(char, false).data; }; TinySDF.prototype.drawWithMetrics = function (char) { return this._draw(char, true); }; function edt(data, width, height, f, v, z) { for (var x = 0; x < width; x++) edt1d(data, x, width, height, f, v, z); for (var y = 0; y < height; y++) edt1d(data, y * width, 1, width, f, v, z); } function edt1d(grid, offset, stride, length, f, v, z) { var q, k, s, r; v[0] = 0; z[0] = -INF; z[1] = INF; for (q = 0; q < length; q++) f[q] = grid[offset + q * stride]; for (q = 1, k = 0, s = 0; q < length; q++) { do { r = v[k]; s = (f[q] - f[r] + q * q - r * r) / (q - r) / 2; } while (s <= z[k] && --k > -1); k++; v[k] = q; z[k] = s; z[k + 1] = INF; } for (q = 0, k = 0; q < length; q++) { while (z[k + 1] < q) k++; r = v[k]; grid[offset + q * stride] = f[r] + (q - r) * (q - r); } } tinySdf.default = default_1$1; const SDF_SCALE = 2; const LocalGlyphMode = { none: 0, ideographs: 1, all: 2 }; class GlyphManager { constructor(requestManager, localGlyphMode, localFontFamily) { this.requestManager = requestManager; this.localGlyphMode = localGlyphMode; this.localFontFamily = localFontFamily; this.entries = {}; this.localGlyphs = { '200': {}, '400': {}, '500': {}, '900': {} }; } setURL(url) { this.url = url; } getGlyphs(glyphs, callback) { const all = []; for (const stack in glyphs) { for (const id of glyphs[stack]) { all.push({ stack, id }); } } asyncAll(all, ({stack, id}, callback) => { let entry = this.entries[stack]; if (!entry) { entry = this.entries[stack] = { glyphs: {}, requests: {}, ranges: {} }; } let glyph = entry.glyphs[id]; if (glyph !== undefined) { callback(null, { stack, id, glyph }); return; } glyph = this._tinySDF(entry, stack, id); if (glyph) { entry.glyphs[id] = glyph; callback(null, { stack, id, glyph }); return; } const range = Math.floor(id / 256); if (range * 256 > 65535) { callback(new Error('glyphs > 65535 not supported')); return; } if (entry.ranges[range]) { callback(null, { stack, id, glyph }); return; } let requests = entry.requests[range]; if (!requests) { requests = entry.requests[range] = []; GlyphManager.loadGlyphRange(stack, range, this.url, this.requestManager, (err, response) => { if (response) { for (const id in response) { if (!this._doesCharSupportLocalGlyph(+id)) { entry.glyphs[+id] = response[+id]; } } entry.ranges[range] = true; } for (const cb of requests) { cb(err, response); } delete entry.requests[range]; }); } requests.push((err, result) => { if (err) { callback(err); } else if (result) { callback(null, { stack, id, glyph: result[id] || null }); } }); }, (err, glyphs) => { if (err) { callback(err); } else if (glyphs) { const result = {}; for (const {stack, id, glyph} of glyphs) { (result[stack] || (result[stack] = {}))[id] = glyph && { id: glyph.id, bitmap: glyph.bitmap.clone(), metrics: glyph.metrics }; } callback(null, result); } }); } _doesCharSupportLocalGlyph(id) { if (this.localGlyphMode === LocalGlyphMode.none) { return false; } else if (this.localGlyphMode === LocalGlyphMode.all) { return !!this.localFontFamily; } else { return !!this.localFontFamily && (unicodeBlockLookup['CJK Unified Ideographs'](id) || unicodeBlockLookup['Hangul Syllables'](id) || unicodeBlockLookup['Hiragana'](id) || unicodeBlockLookup['Katakana'](id)); } } _tinySDF(entry, stack, id) { const family = this.localFontFamily; if (!family) { return; } if (!this._doesCharSupportLocalGlyph(id)) { return; } let tinySDF = entry.tinySDF; if (!tinySDF) { let fontWeight = '400'; if (/bold/i.test(stack)) { fontWeight = '900'; } else if (/medium/i.test(stack)) { fontWeight = '500'; } else if (/light/i.test(stack)) { fontWeight = '200'; } tinySDF = entry.tinySDF = new GlyphManager.TinySDF(24 * SDF_SCALE, 3 * SDF_SCALE, 8 * SDF_SCALE, 0.25, family, fontWeight); } if (this.localGlyphs[tinySDF.fontWeight][id]) { return this.localGlyphs[tinySDF.fontWeight][id]; } const {data, metrics} = tinySDF.drawWithMetrics(String.fromCharCode(id)); const {fontAscent, sdfWidth, sdfHeight, width, height, left, top, advance} = metrics; const ascent = fontAscent ? fontAscent / SDF_SCALE : 17; const baselineAdjustment = ascent - 9; const glyph = this.localGlyphs[tinySDF.fontWeight][id] = { id, bitmap: new AlphaImage({ width: sdfWidth, height: sdfHeight }, data), metrics: { width: width / SDF_SCALE, height: height / SDF_SCALE, left: left / SDF_SCALE, top: top / SDF_SCALE - baselineAdjustment, advance: advance / SDF_SCALE, localGlyph: true } }; return glyph; } } GlyphManager.loadGlyphRange = loadGlyphRange; GlyphManager.TinySDF = tinySdf; const border$1 = IMAGE_PADDING; function getIconQuads(shapedIcon, iconRotate, isSDFIcon, hasIconTextFit) { const quads = []; const image = shapedIcon.image; const pixelRatio = image.pixelRatio; const imageWidth = image.paddedRect.w - 2 * border$1; const imageHeight = image.paddedRect.h - 2 * border$1; const iconWidth = shapedIcon.right - shapedIcon.left; const iconHeight = shapedIcon.bottom - shapedIcon.top; const stretchX = image.stretchX || [[ 0, imageWidth ]]; const stretchY = image.stretchY || [[ 0, imageHeight ]]; const reduceRanges = (sum, range) => sum + range[1] - range[0]; const stretchWidth = stretchX.reduce(reduceRanges, 0); const stretchHeight = stretchY.reduce(reduceRanges, 0); const fixedWidth = imageWidth - stretchWidth; const fixedHeight = imageHeight - stretchHeight; let stretchOffsetX = 0; let stretchContentWidth = stretchWidth; let stretchOffsetY = 0; let stretchContentHeight = stretchHeight; let fixedOffsetX = 0; let fixedContentWidth = fixedWidth; let fixedOffsetY = 0; let fixedContentHeight = fixedHeight; if (image.content && hasIconTextFit) { const content = image.content; stretchOffsetX = sumWithinRange(stretchX, 0, content[0]); stretchOffsetY = sumWithinRange(stretchY, 0, content[1]); stretchContentWidth = sumWithinRange(stretchX, content[0], content[2]); stretchContentHeight = sumWithinRange(stretchY, content[1], content[3]); fixedOffsetX = content[0] - stretchOffsetX; fixedOffsetY = content[1] - stretchOffsetY; fixedContentWidth = content[2] - content[0] - stretchContentWidth; fixedContentHeight = content[3] - content[1] - stretchContentHeight; } const makeBox = (left, top, right, bottom) => { const leftEm = getEmOffset(left.stretch - stretchOffsetX, stretchContentWidth, iconWidth, shapedIcon.left); const leftPx = getPxOffset(left.fixed - fixedOffsetX, fixedContentWidth, left.stretch, stretchWidth); const topEm = getEmOffset(top.stretch - stretchOffsetY, stretchContentHeight, iconHeight, shapedIcon.top); const topPx = getPxOffset(top.fixed - fixedOffsetY, fixedContentHeight, top.stretch, stretchHeight); const rightEm = getEmOffset(right.stretch - stretchOffsetX, stretchContentWidth, iconWidth, shapedIcon.left); const rightPx = getPxOffset(right.fixed - fixedOffsetX, fixedContentWidth, right.stretch, stretchWidth); const bottomEm = getEmOffset(bottom.stretch - stretchOffsetY, stretchContentHeight, iconHeight, shapedIcon.top); const bottomPx = getPxOffset(bottom.fixed - fixedOffsetY, fixedContentHeight, bottom.stretch, stretchHeight); const tl = new pointGeometry(leftEm, topEm); const tr = new pointGeometry(rightEm, topEm); const br = new pointGeometry(rightEm, bottomEm); const bl = new pointGeometry(leftEm, bottomEm); const pixelOffsetTL = new pointGeometry(leftPx / pixelRatio, topPx / pixelRatio); const pixelOffsetBR = new pointGeometry(rightPx / pixelRatio, bottomPx / pixelRatio); const angle = iconRotate * Math.PI / 180; if (angle) { const sin = Math.sin(angle), cos = Math.cos(angle), matrix = [ cos, -sin, sin, cos ]; tl._matMult(matrix); tr._matMult(matrix); bl._matMult(matrix); br._matMult(matrix); } const x1 = left.stretch + left.fixed; const x2 = right.stretch + right.fixed; const y1 = top.stretch + top.fixed; const y2 = bottom.stretch + bottom.fixed; const subRect = { x: image.paddedRect.x + border$1 + x1, y: image.paddedRect.y + border$1 + y1, w: x2 - x1, h: y2 - y1 }; const minFontScaleX = fixedContentWidth / pixelRatio / iconWidth; const minFontScaleY = fixedContentHeight / pixelRatio / iconHeight; return { tl, tr, bl, br, tex: subRect, writingMode: undefined, glyphOffset: [ 0, 0 ], sectionIndex: 0, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, isSDF: isSDFIcon }; }; if (!hasIconTextFit || !image.stretchX && !image.stretchY) { quads.push(makeBox({ fixed: 0, stretch: -1 }, { fixed: 0, stretch: -1 }, { fixed: 0, stretch: imageWidth + 1 }, { fixed: 0, stretch: imageHeight + 1 })); } else { const xCuts = stretchZonesToCuts(stretchX, fixedWidth, stretchWidth); const yCuts = stretchZonesToCuts(stretchY, fixedHeight, stretchHeight); for (let xi = 0; xi < xCuts.length - 1; xi++) { const x1 = xCuts[xi]; const x2 = xCuts[xi + 1]; for (let yi = 0; yi < yCuts.length - 1; yi++) { const y1 = yCuts[yi]; const y2 = yCuts[yi + 1]; quads.push(makeBox(x1, y1, x2, y2)); } } } return quads; } function sumWithinRange(ranges, min, max) { let sum = 0; for (const range of ranges) { sum += Math.max(min, Math.min(max, range[1])) - Math.max(min, Math.min(max, range[0])); } return sum; } function stretchZonesToCuts(stretchZones, fixedSize, stretchSize) { const cuts = [{ fixed: -border$1, stretch: 0 }]; for (const [c1, c2] of stretchZones) { const last = cuts[cuts.length - 1]; cuts.push({ fixed: c1 - last.stretch, stretch: last.stretch }); cuts.push({ fixed: c1 - last.stretch, stretch: last.stretch + (c2 - c1) }); } cuts.push({ fixed: fixedSize + border$1, stretch: stretchSize }); return cuts; } function getEmOffset(stretchOffset, stretchSize, iconSize, iconOffset) { return stretchOffset / stretchSize * iconSize + iconOffset; } function getPxOffset(fixedOffset, fixedSize, stretchOffset, stretchSize) { return fixedOffset - fixedSize * stretchOffset / stretchSize; } function getGlyphQuads(anchor, shaping, textOffset, layer, alongLine, feature, imageMap, allowVerticalPlacement) { const textRotate = layer.layout.get('text-rotate').evaluate(feature, {}) * Math.PI / 180; const quads = []; for (const line of shaping.positionedLines) { for (const positionedGlyph of line.positionedGlyphs) { if (!positionedGlyph.rect) continue; const textureRect = positionedGlyph.rect || {}; const glyphPadding = 1; let rectBuffer = GLYPH_PBF_BORDER + glyphPadding; let isSDF = true; let pixelRatio = 1; let lineOffset = 0; const rotateVerticalGlyph = (alongLine || allowVerticalPlacement) && positionedGlyph.vertical; const halfAdvance = positionedGlyph.metrics.advance * positionedGlyph.scale / 2; if (allowVerticalPlacement && shaping.verticalizable) { const scaledGlyphOffset = (positionedGlyph.scale - 1) * ONE_EM; const imageOffset = (ONE_EM - positionedGlyph.metrics.width * positionedGlyph.scale) / 2; lineOffset = line.lineOffset / 2 - (positionedGlyph.imageName ? -imageOffset : scaledGlyphOffset); } if (positionedGlyph.imageName) { const image = imageMap[positionedGlyph.imageName]; isSDF = image.sdf; pixelRatio = image.pixelRatio; rectBuffer = IMAGE_PADDING / pixelRatio; } const glyphOffset = alongLine ? [ positionedGlyph.x + halfAdvance, positionedGlyph.y ] : [ 0, 0 ]; let builtInOffset = alongLine ? [ 0, 0 ] : [ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] - lineOffset ]; let verticalizedLabelOffset = [ 0, 0 ]; if (rotateVerticalGlyph) { verticalizedLabelOffset = builtInOffset; builtInOffset = [ 0, 0 ]; } const x1 = (positionedGlyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset[0]; const y1 = (-positionedGlyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset[1]; const x2 = x1 + textureRect.w * positionedGlyph.scale / (pixelRatio * (positionedGlyph.localGlyph ? SDF_SCALE : 1)); const y2 = y1 + textureRect.h * positionedGlyph.scale / (pixelRatio * (positionedGlyph.localGlyph ? SDF_SCALE : 1)); const tl = new pointGeometry(x1, y1); const tr = new pointGeometry(x2, y1); const bl = new pointGeometry(x1, y2); const br = new pointGeometry(x2, y2); if (rotateVerticalGlyph) { const center = new pointGeometry(-halfAdvance, halfAdvance - SHAPING_DEFAULT_OFFSET); const verticalRotation = -Math.PI / 2; const xHalfWidthOffsetCorrection = ONE_EM / 2 - halfAdvance; const yImageOffsetCorrection = positionedGlyph.imageName ? xHalfWidthOffsetCorrection : 0; const halfWidthOffsetCorrection = new pointGeometry(5 - SHAPING_DEFAULT_OFFSET - xHalfWidthOffsetCorrection, -yImageOffsetCorrection); const verticalOffsetCorrection = new pointGeometry(...verticalizedLabelOffset); tl._rotateAround(verticalRotation, center)._add(halfWidthOffsetCorrection)._add(verticalOffsetCorrection); tr._rotateAround(verticalRotation, center)._add(halfWidthOffsetCorrection)._add(verticalOffsetCorrection); bl._rotateAround(verticalRotation, center)._add(halfWidthOffsetCorrection)._add(verticalOffsetCorrection); br._rotateAround(verticalRotation, center)._add(halfWidthOffsetCorrection)._add(verticalOffsetCorrection); } if (textRotate) { const sin = Math.sin(textRotate), cos = Math.cos(textRotate), matrix = [ cos, -sin, sin, cos ]; tl._matMult(matrix); tr._matMult(matrix); bl._matMult(matrix); br._matMult(matrix); } const pixelOffsetTL = new pointGeometry(0, 0); const pixelOffsetBR = new pointGeometry(0, 0); const minFontScaleX = 0; const minFontScaleY = 0; quads.push({ tl, tr, bl, br, tex: textureRect, writingMode: shaping.writingMode, glyphOffset, sectionIndex: positionedGlyph.sectionIndex, isSDF, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY }); } } return quads; } class TinyQueue { constructor(data = [], compare = defaultCompare$1) { this.data = data; this.length = this.data.length; this.compare = compare; if (this.length > 0) { for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i); } } push(item) { this.data.push(item); this.length++; this._up(this.length - 1); } pop() { if (this.length === 0) return undefined; const top = this.data[0]; const bottom = this.data.pop(); this.length--; if (this.length > 0) { this.data[0] = bottom; this._down(0); } return top; } peek() { return this.data[0]; } _up(pos) { const {data, compare} = this; const item = data[pos]; while (pos > 0) { const parent = pos - 1 >> 1; const current = data[parent]; if (compare(item, current) >= 0) break; data[pos] = current; pos = parent; } data[pos] = item; } _down(pos) { const {data, compare} = this; const halfLength = this.length >> 1; const item = data[pos]; while (pos < halfLength) { let left = (pos << 1) + 1; let best = data[left]; const right = left + 1; if (right < this.length && compare(data[right], best) < 0) { left = right; best = data[right]; } if (compare(best, item) >= 0) break; data[pos] = best; pos = left; } data[pos] = item; } } function defaultCompare$1(a, b) { return a < b ? -1 : a > b ? 1 : 0; } function findPoleOfInaccessibility (polygonRings, precision = 1, debug = false) { let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; const outerRing = polygonRings[0]; for (let i = 0; i < outerRing.length; i++) { const p = outerRing[i]; if (!i || p.x < minX) minX = p.x; if (!i || p.y < minY) minY = p.y; if (!i || p.x > maxX) maxX = p.x; if (!i || p.y > maxY) maxY = p.y; } const width = maxX - minX; const height = maxY - minY; const cellSize = Math.min(width, height); let h = cellSize / 2; const cellQueue = new TinyQueue([], compareMax); if (cellSize === 0) return new pointGeometry(minX, minY); for (let x = minX; x < maxX; x += cellSize) { for (let y = minY; y < maxY; y += cellSize) { cellQueue.push(new Cell(x + h, y + h, h, polygonRings)); } } let bestCell = getCentroidCell(polygonRings); let numProbes = cellQueue.length; while (cellQueue.length) { const cell = cellQueue.pop(); if (cell.d > bestCell.d || !bestCell.d) { bestCell = cell; if (debug) console.log('found best %d after %d probes', Math.round(10000 * cell.d) / 10000, numProbes); } if (cell.max - bestCell.d <= precision) continue; h = cell.h / 2; cellQueue.push(new Cell(cell.p.x - h, cell.p.y - h, h, polygonRings)); cellQueue.push(new Cell(cell.p.x + h, cell.p.y - h, h, polygonRings)); cellQueue.push(new Cell(cell.p.x - h, cell.p.y + h, h, polygonRings)); cellQueue.push(new Cell(cell.p.x + h, cell.p.y + h, h, polygonRings)); numProbes += 4; } if (debug) { console.log(`num probes: ${ numProbes }`); console.log(`best distance: ${ bestCell.d }`); } return bestCell.p; } function compareMax(a, b) { return b.max - a.max; } function Cell(x, y, h, polygon) { this.p = new pointGeometry(x, y); this.h = h; this.d = pointToPolygonDist(this.p, polygon); this.max = this.d + this.h * Math.SQRT2; } function pointToPolygonDist(p, polygon) { let inside = false; let minDistSq = Infinity; for (let k = 0; k < polygon.length; k++) { const ring = polygon[k]; for (let i = 0, len = ring.length, j = len - 1; i < len; j = i++) { const a = ring[i]; const b = ring[j]; if (a.y > p.y !== b.y > p.y && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x) inside = !inside; minDistSq = Math.min(minDistSq, distToSegmentSquared(p, a, b)); } } return (inside ? 1 : -1) * Math.sqrt(minDistSq); } function getCentroidCell(polygon) { let area = 0; let x = 0; let y = 0; const points = polygon[0]; for (let i = 0, len = points.length, j = len - 1; i < len; j = i++) { const a = points[i]; const b = points[j]; const f = a.x * b.y - b.x * a.y; x += (a.x + b.x) * f; y += (a.y + b.y) * f; area += f * 3; } return new Cell(x / area, y / area, 0, polygon); } const baselineOffset = 7; const INVALID_TEXT_OFFSET = Number.POSITIVE_INFINITY; function evaluateVariableOffset(anchor, offset) { function fromRadialOffset(anchor, radialOffset) { let x = 0, y = 0; if (radialOffset < 0) radialOffset = 0; const hypotenuse = radialOffset / Math.sqrt(2); switch (anchor) { case 'top-right': case 'top-left': y = hypotenuse - baselineOffset; break; case 'bottom-right': case 'bottom-left': y = -hypotenuse + baselineOffset; break; case 'bottom': y = -radialOffset + baselineOffset; break; case 'top': y = radialOffset - baselineOffset; break; } switch (anchor) { case 'top-right': case 'bottom-right': x = -hypotenuse; break; case 'top-left': case 'bottom-left': x = hypotenuse; break; case 'left': x = radialOffset; break; case 'right': x = -radialOffset; break; } return [ x, y ]; } function fromTextOffset(anchor, offsetX, offsetY) { let x = 0, y = 0; offsetX = Math.abs(offsetX); offsetY = Math.abs(offsetY); switch (anchor) { case 'top-right': case 'top-left': case 'top': y = offsetY - baselineOffset; break; case 'bottom-right': case 'bottom-left': case 'bottom': y = -offsetY + baselineOffset; break; } switch (anchor) { case 'top-right': case 'bottom-right': case 'right': x = -offsetX; break; case 'top-left': case 'bottom-left': case 'left': x = offsetX; break; } return [ x, y ]; } return offset[1] !== INVALID_TEXT_OFFSET ? fromTextOffset(anchor, offset[0], offset[1]) : fromRadialOffset(anchor, offset[0]); } function performSymbolLayout(bucket, glyphMap, glyphPositions, imageMap, imagePositions, showCollisionBoxes, canonical, tileZoom) { bucket.createArrays(); const tileSize = 512 * bucket.overscaling; bucket.tilePixelRatio = EXTENT$1 / tileSize; bucket.compareText = {}; bucket.iconsNeedLinear = false; const layout = bucket.layers[0].layout; const unevaluatedLayoutValues = bucket.layers[0]._unevaluatedLayout._values; const sizes = {}; if (bucket.textSizeData.kind === 'composite') { const {minZoom, maxZoom} = bucket.textSizeData; sizes.compositeTextSizes = [ unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(minZoom), canonical), unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(maxZoom), canonical) ]; } if (bucket.iconSizeData.kind === 'composite') { const {minZoom, maxZoom} = bucket.iconSizeData; sizes.compositeIconSizes = [ unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(minZoom), canonical), unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(maxZoom), canonical) ]; } sizes.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(tileZoom + 1), canonical); sizes.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(tileZoom + 1), canonical); sizes.textMaxSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(18), canonical); const lineHeight = layout.get('text-line-height') * ONE_EM; const textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point'; const keepUpright = layout.get('text-keep-upright'); const textSize = layout.get('text-size'); for (const feature of bucket.features) { const fontstack = layout.get('text-font').evaluate(feature, {}, canonical).join(','); const layoutTextSizeThisZoom = textSize.evaluate(feature, {}, canonical); const layoutTextSize = sizes.layoutTextSize.evaluate(feature, {}, canonical); const layoutIconSize = sizes.layoutIconSize.evaluate(feature, {}, canonical); const shapedTextOrientations = { horizontal: {}, vertical: undefined }; const text = feature.text; let textOffset = [ 0, 0 ]; if (text) { const unformattedText = text.toString(); const spacing = layout.get('text-letter-spacing').evaluate(feature, {}, canonical) * ONE_EM; const spacingIfAllowed = allowsLetterSpacing(unformattedText) ? spacing : 0; const textAnchor = layout.get('text-anchor').evaluate(feature, {}, canonical); const variableTextAnchor = layout.get('text-variable-anchor'); if (!variableTextAnchor) { const radialOffset = layout.get('text-radial-offset').evaluate(feature, {}, canonical); if (radialOffset) { textOffset = evaluateVariableOffset(textAnchor, [ radialOffset * ONE_EM, INVALID_TEXT_OFFSET ]); } else { textOffset = layout.get('text-offset').evaluate(feature, {}, canonical).map(t => t * ONE_EM); } } let textJustify = textAlongLine ? 'center' : layout.get('text-justify').evaluate(feature, {}, canonical); const symbolPlacement = layout.get('symbol-placement'); const maxWidth = symbolPlacement === 'point' ? layout.get('text-max-width').evaluate(feature, {}, canonical) * ONE_EM : 0; const addVerticalShapingForPointLabelIfNeeded = () => { if (bucket.allowVerticalPlacement && allowsVerticalWritingMode(unformattedText)) { shapedTextOrientations.vertical = shapeText(text, glyphMap, glyphPositions, imagePositions, fontstack, maxWidth, lineHeight, textAnchor, 'left', spacingIfAllowed, textOffset, WritingMode.vertical, true, symbolPlacement, layoutTextSize, layoutTextSizeThisZoom); } }; if (!textAlongLine && variableTextAnchor) { const justifications = textJustify === 'auto' ? variableTextAnchor.map(a => getAnchorJustification(a)) : [textJustify]; let singleLine = false; for (let i = 0; i < justifications.length; i++) { const justification = justifications[i]; if (shapedTextOrientations.horizontal[justification]) continue; if (singleLine) { shapedTextOrientations.horizontal[justification] = shapedTextOrientations.horizontal[0]; } else { const shaping = shapeText(text, glyphMap, glyphPositions, imagePositions, fontstack, maxWidth, lineHeight, 'center', justification, spacingIfAllowed, textOffset, WritingMode.horizontal, false, symbolPlacement, layoutTextSize, layoutTextSizeThisZoom); if (shaping) { shapedTextOrientations.horizontal[justification] = shaping; singleLine = shaping.positionedLines.length === 1; } } } addVerticalShapingForPointLabelIfNeeded(); } else { if (textJustify === 'auto') { textJustify = getAnchorJustification(textAnchor); } const shaping = shapeText(text, glyphMap, glyphPositions, imagePositions, fontstack, maxWidth, lineHeight, textAnchor, textJustify, spacingIfAllowed, textOffset, WritingMode.horizontal, false, symbolPlacement, layoutTextSize, layoutTextSizeThisZoom); if (shaping) shapedTextOrientations.horizontal[textJustify] = shaping; addVerticalShapingForPointLabelIfNeeded(); if (allowsVerticalWritingMode(unformattedText) && textAlongLine && keepUpright) { shapedTextOrientations.vertical = shapeText(text, glyphMap, glyphPositions, imagePositions, fontstack, maxWidth, lineHeight, textAnchor, textJustify, spacingIfAllowed, textOffset, WritingMode.vertical, false, symbolPlacement, layoutTextSize, layoutTextSizeThisZoom); } } } let shapedIcon; let isSDFIcon = false; if (feature.icon && feature.icon.name) { const image = imageMap[feature.icon.name]; if (image) { shapedIcon = shapeIcon(imagePositions[feature.icon.name], layout.get('icon-offset').evaluate(feature, {}, canonical), layout.get('icon-anchor').evaluate(feature, {}, canonical)); isSDFIcon = image.sdf; if (bucket.sdfIcons === undefined) { bucket.sdfIcons = image.sdf; } else if (bucket.sdfIcons !== image.sdf) { warnOnce('Style sheet warning: Cannot mix SDF and non-SDF icons in one buffer'); } if (image.pixelRatio !== bucket.pixelRatio) { bucket.iconsNeedLinear = true; } else if (layout.get('icon-rotate').constantOr(1) !== 0) { bucket.iconsNeedLinear = true; } } } const shapedText = getDefaultHorizontalShaping(shapedTextOrientations.horizontal) || shapedTextOrientations.vertical; bucket.iconsInText = shapedText ? shapedText.iconsInText : false; if (shapedText || shapedIcon) { addFeature(bucket, feature, shapedTextOrientations, shapedIcon, imageMap, sizes, layoutTextSize, layoutIconSize, textOffset, isSDFIcon, canonical); } } if (showCollisionBoxes) { bucket.generateCollisionDebugBuffers(tileZoom, bucket.collisionBoxArray); } } function getAnchorJustification(anchor) { switch (anchor) { case 'right': case 'top-right': case 'bottom-right': return 'right'; case 'left': case 'top-left': case 'bottom-left': return 'left'; } return 'center'; } function addFeature(bucket, feature, shapedTextOrientations, shapedIcon, imageMap, sizes, layoutTextSize, layoutIconSize, textOffset, isSDFIcon, canonical) { let textMaxSize = sizes.textMaxSize.evaluate(feature, {}, canonical); if (textMaxSize === undefined) { textMaxSize = layoutTextSize; } const layout = bucket.layers[0].layout; const iconOffset = layout.get('icon-offset').evaluate(feature, {}, canonical); const defaultHorizontalShaping = getDefaultHorizontalShaping(shapedTextOrientations.horizontal); const glyphSize = ONE_EM, fontScale = layoutTextSize / glyphSize, textBoxScale = bucket.tilePixelRatio * fontScale, textMaxBoxScale = bucket.tilePixelRatio * textMaxSize / glyphSize, iconBoxScale = bucket.tilePixelRatio * layoutIconSize, symbolMinDistance = bucket.tilePixelRatio * layout.get('symbol-spacing'), textPadding = layout.get('text-padding') * bucket.tilePixelRatio, iconPadding = layout.get('icon-padding') * bucket.tilePixelRatio, textMaxAngle = degToRad(layout.get('text-max-angle')), textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point', iconAlongLine = layout.get('icon-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point', symbolPlacement = layout.get('symbol-placement'), textRepeatDistance = symbolMinDistance / 2; const iconTextFit = layout.get('icon-text-fit'); let verticallyShapedIcon; if (shapedIcon && iconTextFit !== 'none') { if (bucket.allowVerticalPlacement && shapedTextOrientations.vertical) { verticallyShapedIcon = fitIconToText(shapedIcon, shapedTextOrientations.vertical, iconTextFit, layout.get('icon-text-fit-padding'), iconOffset, fontScale); } if (defaultHorizontalShaping) { shapedIcon = fitIconToText(shapedIcon, defaultHorizontalShaping, iconTextFit, layout.get('icon-text-fit-padding'), iconOffset, fontScale); } } const addSymbolAtAnchor = (line, anchor) => { if (anchor.x < 0 || anchor.x >= EXTENT$1 || anchor.y < 0 || anchor.y >= EXTENT$1) { return; } addSymbol(bucket, anchor, line, shapedTextOrientations, shapedIcon, imageMap, verticallyShapedIcon, bucket.layers[0], bucket.collisionBoxArray, feature.index, feature.sourceLayerIndex, bucket.index, textBoxScale, textPadding, textAlongLine, textOffset, iconBoxScale, iconPadding, iconAlongLine, iconOffset, feature, sizes, isSDFIcon, canonical, layoutTextSize); }; if (symbolPlacement === 'line') { for (const line of clipLine(feature.geometry, 0, 0, EXTENT$1, EXTENT$1)) { const anchors = getAnchors(line, symbolMinDistance, textMaxAngle, shapedTextOrientations.vertical || defaultHorizontalShaping, shapedIcon, glyphSize, textMaxBoxScale, bucket.overscaling, EXTENT$1); for (const anchor of anchors) { const shapedText = defaultHorizontalShaping; if (!shapedText || !anchorIsTooClose(bucket, shapedText.text, textRepeatDistance, anchor)) { addSymbolAtAnchor(line, anchor); } } } } else if (symbolPlacement === 'line-center') { for (const line of feature.geometry) { if (line.length > 1) { const anchor = getCenterAnchor(line, textMaxAngle, shapedTextOrientations.vertical || defaultHorizontalShaping, shapedIcon, glyphSize, textMaxBoxScale); if (anchor) { addSymbolAtAnchor(line, anchor); } } } } else if (feature.type === 'Polygon') { for (const polygon of classifyRings(feature.geometry, 0)) { const poi = findPoleOfInaccessibility(polygon, 16); addSymbolAtAnchor(polygon[0], new Anchor(poi.x, poi.y, 0)); } } else if (feature.type === 'LineString') { for (const line of feature.geometry) { addSymbolAtAnchor(line, new Anchor(line[0].x, line[0].y, 0)); } } else if (feature.type === 'Point') { for (const points of feature.geometry) { for (const point of points) { addSymbolAtAnchor([point], new Anchor(point.x, point.y, 0)); } } } } const MAX_GLYPH_ICON_SIZE = 255; const MAX_PACKED_SIZE = MAX_GLYPH_ICON_SIZE * SIZE_PACK_FACTOR; function addTextVertices(bucket, anchor, shapedText, imageMap, layer, textAlongLine, feature, textOffset, lineArray, writingMode, placementTypes, placedTextSymbolIndices, placedIconIndex, sizes, canonical) { const glyphQuads = getGlyphQuads(anchor, shapedText, textOffset, layer, textAlongLine, feature, imageMap, bucket.allowVerticalPlacement); const sizeData = bucket.textSizeData; let textSizeData = null; if (sizeData.kind === 'source') { textSizeData = [SIZE_PACK_FACTOR * layer.layout.get('text-size').evaluate(feature, {}, canonical)]; if (textSizeData[0] > MAX_PACKED_SIZE) { warnOnce(`${ bucket.layerIds[0] }: Value for "text-size" is >= ${ MAX_GLYPH_ICON_SIZE }. Reduce your "text-size".`); } } else if (sizeData.kind === 'composite') { textSizeData = [ SIZE_PACK_FACTOR * sizes.compositeTextSizes[0].evaluate(feature, {}, canonical), SIZE_PACK_FACTOR * sizes.compositeTextSizes[1].evaluate(feature, {}, canonical) ]; if (textSizeData[0] > MAX_PACKED_SIZE || textSizeData[1] > MAX_PACKED_SIZE) { warnOnce(`${ bucket.layerIds[0] }: Value for "text-size" is >= ${ MAX_GLYPH_ICON_SIZE }. Reduce your "text-size".`); } } bucket.addSymbols(bucket.text, glyphQuads, textSizeData, textOffset, textAlongLine, feature, writingMode, anchor, lineArray.lineStartIndex, lineArray.lineLength, placedIconIndex, canonical); for (const placementType of placementTypes) { placedTextSymbolIndices[placementType] = bucket.text.placedSymbolArray.length - 1; } return glyphQuads.length * 4; } function getDefaultHorizontalShaping(horizontalShaping) { for (const justification in horizontalShaping) { return horizontalShaping[justification]; } return null; } function evaluateBoxCollisionFeature(collisionBoxArray, anchor, featureIndex, sourceLayerIndex, bucketIndex, shaped, boxScale, padding, rotate) { let y1 = shaped.top; let y2 = shaped.bottom; let x1 = shaped.left; let x2 = shaped.right; const collisionPadding = shaped.collisionPadding; if (collisionPadding) { x1 -= collisionPadding[0]; y1 -= collisionPadding[1]; x2 += collisionPadding[2]; y2 += collisionPadding[3]; } if (rotate) { const tl = new pointGeometry(x1, y1); const tr = new pointGeometry(x2, y1); const bl = new pointGeometry(x1, y2); const br = new pointGeometry(x2, y2); const rotateRadians = degToRad(rotate); tl._rotate(rotateRadians); tr._rotate(rotateRadians); bl._rotate(rotateRadians); br._rotate(rotateRadians); x1 = Math.min(tl.x, tr.x, bl.x, br.x); x2 = Math.max(tl.x, tr.x, bl.x, br.x); y1 = Math.min(tl.y, tr.y, bl.y, br.y); y2 = Math.max(tl.y, tr.y, bl.y, br.y); } collisionBoxArray.emplaceBack(anchor.x, anchor.y, x1, y1, x2, y2, padding, featureIndex, sourceLayerIndex, bucketIndex); return collisionBoxArray.length - 1; } function evaluateCircleCollisionFeature(shaped) { if (shaped.collisionPadding) { shaped.top -= shaped.collisionPadding[1]; shaped.bottom += shaped.collisionPadding[3]; } const height = shaped.bottom - shaped.top; return height > 0 ? Math.max(10, height) : null; } function addSymbol(bucket, anchor, line, shapedTextOrientations, shapedIcon, imageMap, verticallyShapedIcon, layer, collisionBoxArray, featureIndex, sourceLayerIndex, bucketIndex, textBoxScale, textPadding, textAlongLine, textOffset, iconBoxScale, iconPadding, iconAlongLine, iconOffset, feature, sizes, isSDFIcon, canonical, layoutTextSize) { const lineArray = bucket.addToLineVertexArray(anchor, line); let textBoxIndex, iconBoxIndex, verticalTextBoxIndex, verticalIconBoxIndex; let textCircle, verticalTextCircle, verticalIconCircle; let numIconVertices = 0; let numVerticalIconVertices = 0; let numHorizontalGlyphVertices = 0; let numVerticalGlyphVertices = 0; let placedIconSymbolIndex = -1; let verticalPlacedIconSymbolIndex = -1; const placedTextSymbolIndices = {}; let key = murmurhashJs(''); let textOffset0 = 0; let textOffset1 = 0; if (layer._unevaluatedLayout.getValue('text-radial-offset') === undefined) { [textOffset0, textOffset1] = layer.layout.get('text-offset').evaluate(feature, {}, canonical).map(t => t * ONE_EM); } else { textOffset0 = layer.layout.get('text-radial-offset').evaluate(feature, {}, canonical) * ONE_EM; textOffset1 = INVALID_TEXT_OFFSET; } if (bucket.allowVerticalPlacement && shapedTextOrientations.vertical) { const verticalShaping = shapedTextOrientations.vertical; if (textAlongLine) { verticalTextCircle = evaluateCircleCollisionFeature(verticalShaping); if (verticallyShapedIcon) { verticalIconCircle = evaluateCircleCollisionFeature(verticallyShapedIcon); } } else { const textRotation = layer.layout.get('text-rotate').evaluate(feature, {}, canonical); const verticalTextRotation = textRotation + 90; verticalTextBoxIndex = evaluateBoxCollisionFeature(collisionBoxArray, anchor, featureIndex, sourceLayerIndex, bucketIndex, verticalShaping, textBoxScale, textPadding, verticalTextRotation); if (verticallyShapedIcon) { verticalIconBoxIndex = evaluateBoxCollisionFeature(collisionBoxArray, anchor, featureIndex, sourceLayerIndex, bucketIndex, verticallyShapedIcon, iconBoxScale, iconPadding, verticalTextRotation); } } } if (shapedIcon) { const iconRotate = layer.layout.get('icon-rotate').evaluate(feature, {}, canonical); const hasIconTextFit = layer.layout.get('icon-text-fit') !== 'none'; const iconQuads = getIconQuads(shapedIcon, iconRotate, isSDFIcon, hasIconTextFit); const verticalIconQuads = verticallyShapedIcon ? getIconQuads(verticallyShapedIcon, iconRotate, isSDFIcon, hasIconTextFit) : undefined; iconBoxIndex = evaluateBoxCollisionFeature(collisionBoxArray, anchor, featureIndex, sourceLayerIndex, bucketIndex, shapedIcon, iconBoxScale, iconPadding, iconRotate); numIconVertices = iconQuads.length * 4; const sizeData = bucket.iconSizeData; let iconSizeData = null; if (sizeData.kind === 'source') { iconSizeData = [SIZE_PACK_FACTOR * layer.layout.get('icon-size').evaluate(feature, {}, canonical)]; if (iconSizeData[0] > MAX_PACKED_SIZE) { warnOnce(`${ bucket.layerIds[0] }: Value for "icon-size" is >= ${ MAX_GLYPH_ICON_SIZE }. Reduce your "icon-size".`); } } else if (sizeData.kind === 'composite') { iconSizeData = [ SIZE_PACK_FACTOR * sizes.compositeIconSizes[0].evaluate(feature, {}, canonical), SIZE_PACK_FACTOR * sizes.compositeIconSizes[1].evaluate(feature, {}, canonical) ]; if (iconSizeData[0] > MAX_PACKED_SIZE || iconSizeData[1] > MAX_PACKED_SIZE) { warnOnce(`${ bucket.layerIds[0] }: Value for "icon-size" is >= ${ MAX_GLYPH_ICON_SIZE }. Reduce your "icon-size".`); } } bucket.addSymbols(bucket.icon, iconQuads, iconSizeData, iconOffset, iconAlongLine, feature, false, anchor, lineArray.lineStartIndex, lineArray.lineLength, -1, canonical); placedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1; if (verticalIconQuads) { numVerticalIconVertices = verticalIconQuads.length * 4; bucket.addSymbols(bucket.icon, verticalIconQuads, iconSizeData, iconOffset, iconAlongLine, feature, WritingMode.vertical, anchor, lineArray.lineStartIndex, lineArray.lineLength, -1, canonical); verticalPlacedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1; } } for (const justification in shapedTextOrientations.horizontal) { const shaping = shapedTextOrientations.horizontal[justification]; if (!textBoxIndex) { key = murmurhashJs(shaping.text); if (textAlongLine) { textCircle = evaluateCircleCollisionFeature(shaping); } else { const textRotate = layer.layout.get('text-rotate').evaluate(feature, {}, canonical); textBoxIndex = evaluateBoxCollisionFeature(collisionBoxArray, anchor, featureIndex, sourceLayerIndex, bucketIndex, shaping, textBoxScale, textPadding, textRotate); } } const singleLine = shaping.positionedLines.length === 1; numHorizontalGlyphVertices += addTextVertices(bucket, anchor, shaping, imageMap, layer, textAlongLine, feature, textOffset, lineArray, shapedTextOrientations.vertical ? WritingMode.horizontal : WritingMode.horizontalOnly, singleLine ? Object.keys(shapedTextOrientations.horizontal) : [justification], placedTextSymbolIndices, placedIconSymbolIndex, sizes, canonical); if (singleLine) { break; } } if (shapedTextOrientations.vertical) { numVerticalGlyphVertices += addTextVertices(bucket, anchor, shapedTextOrientations.vertical, imageMap, layer, textAlongLine, feature, textOffset, lineArray, WritingMode.vertical, ['vertical'], placedTextSymbolIndices, verticalPlacedIconSymbolIndex, sizes, canonical); } let collisionCircleDiameter = -1; const getCollisionCircleHeight = (diameter, prevHeight) => { return diameter ? Math.max(diameter, prevHeight) : prevHeight; }; collisionCircleDiameter = getCollisionCircleHeight(textCircle, collisionCircleDiameter); collisionCircleDiameter = getCollisionCircleHeight(verticalTextCircle, collisionCircleDiameter); collisionCircleDiameter = getCollisionCircleHeight(verticalIconCircle, collisionCircleDiameter); const useRuntimeCollisionCircles = collisionCircleDiameter > -1 ? 1 : 0; if (useRuntimeCollisionCircles) collisionCircleDiameter *= layoutTextSize / ONE_EM; if (bucket.glyphOffsetArray.length >= SymbolBucket.MAX_GLYPHS) warnOnce('Too many glyphs being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907'); if (feature.sortKey !== undefined) { bucket.addToSortKeyRanges(bucket.symbolInstances.length, feature.sortKey); } bucket.symbolInstances.emplaceBack(anchor.x, anchor.y, placedTextSymbolIndices.right >= 0 ? placedTextSymbolIndices.right : -1, placedTextSymbolIndices.center >= 0 ? placedTextSymbolIndices.center : -1, placedTextSymbolIndices.left >= 0 ? placedTextSymbolIndices.left : -1, placedTextSymbolIndices.vertical || -1, placedIconSymbolIndex, verticalPlacedIconSymbolIndex, key, textBoxIndex !== undefined ? textBoxIndex : bucket.collisionBoxArray.length, textBoxIndex !== undefined ? textBoxIndex + 1 : bucket.collisionBoxArray.length, verticalTextBoxIndex !== undefined ? verticalTextBoxIndex : bucket.collisionBoxArray.length, verticalTextBoxIndex !== undefined ? verticalTextBoxIndex + 1 : bucket.collisionBoxArray.length, iconBoxIndex !== undefined ? iconBoxIndex : bucket.collisionBoxArray.length, iconBoxIndex !== undefined ? iconBoxIndex + 1 : bucket.collisionBoxArray.length, verticalIconBoxIndex ? verticalIconBoxIndex : bucket.collisionBoxArray.length, verticalIconBoxIndex ? verticalIconBoxIndex + 1 : bucket.collisionBoxArray.length, featureIndex, numHorizontalGlyphVertices, numVerticalGlyphVertices, numIconVertices, numVerticalIconVertices, useRuntimeCollisionCircles, 0, textBoxScale, textOffset0, textOffset1, collisionCircleDiameter); } function anchorIsTooClose(bucket, text, repeatDistance, anchor) { const compareText = bucket.compareText; if (!(text in compareText)) { compareText[text] = []; } else { const otherAnchors = compareText[text]; for (let k = otherAnchors.length - 1; k >= 0; k--) { if (anchor.dist(otherAnchors[k]) < repeatDistance) { return true; } } } compareText[text].push(anchor); return false; } const vectorTileFeatureTypes$2 = vectorTile.VectorTileFeature.types; const shaderOpacityAttributes = [{ name: 'a_fade_opacity', components: 1, type: 'Uint8', offset: 0 }]; function addVertex$1(array, anchorX, anchorY, ox, oy, tx, ty, sizeVertex, isSDF, pixelOffsetX, pixelOffsetY, minFontScaleX, minFontScaleY) { const aSizeX = sizeVertex ? Math.min(MAX_PACKED_SIZE, Math.round(sizeVertex[0])) : 0; const aSizeY = sizeVertex ? Math.min(MAX_PACKED_SIZE, Math.round(sizeVertex[1])) : 0; array.emplaceBack(anchorX, anchorY, Math.round(ox * 32), Math.round(oy * 32), tx, ty, (aSizeX << 1) + (isSDF ? 1 : 0), aSizeY, pixelOffsetX * 16, pixelOffsetY * 16, minFontScaleX * 256, minFontScaleY * 256); } function addDynamicAttributes(dynamicLayoutVertexArray, p, angle) { dynamicLayoutVertexArray.emplaceBack(p.x, p.y, angle); dynamicLayoutVertexArray.emplaceBack(p.x, p.y, angle); dynamicLayoutVertexArray.emplaceBack(p.x, p.y, angle); dynamicLayoutVertexArray.emplaceBack(p.x, p.y, angle); } function containsRTLText(formattedText) { for (const section of formattedText.sections) { if (stringContainsRTLText(section.text)) { return true; } } return false; } class SymbolBuffers { constructor(programConfigurations) { this.layoutVertexArray = new StructArrayLayout4i4ui4i24(); this.indexArray = new StructArrayLayout3ui6(); this.programConfigurations = programConfigurations; this.segments = new SegmentVector(); this.dynamicLayoutVertexArray = new StructArrayLayout3f12(); this.opacityVertexArray = new StructArrayLayout1ul4(); this.placedSymbolArray = new PlacedSymbolArray(); } isEmpty() { return this.layoutVertexArray.length === 0 && this.indexArray.length === 0 && this.dynamicLayoutVertexArray.length === 0 && this.opacityVertexArray.length === 0; } upload(context, dynamicIndexBuffer, upload, update) { if (this.isEmpty()) { return; } if (upload) { this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, symbolLayoutAttributes.members); this.indexBuffer = context.createIndexBuffer(this.indexArray, dynamicIndexBuffer); this.dynamicLayoutVertexBuffer = context.createVertexBuffer(this.dynamicLayoutVertexArray, dynamicLayoutAttributes.members, true); this.opacityVertexBuffer = context.createVertexBuffer(this.opacityVertexArray, shaderOpacityAttributes, true); this.opacityVertexBuffer.itemSize = 1; } if (upload || update) { this.programConfigurations.upload(context); } } destroy() { if (!this.layoutVertexBuffer) return; this.layoutVertexBuffer.destroy(); this.indexBuffer.destroy(); this.programConfigurations.destroy(); this.segments.destroy(); this.dynamicLayoutVertexBuffer.destroy(); this.opacityVertexBuffer.destroy(); } } register('SymbolBuffers', SymbolBuffers); class CollisionBuffers { constructor(LayoutArray, layoutAttributes, IndexArray) { this.layoutVertexArray = new LayoutArray(); this.layoutAttributes = layoutAttributes; this.indexArray = new IndexArray(); this.segments = new SegmentVector(); this.collisionVertexArray = new StructArrayLayout2ub2f12(); this.collisionVertexArrayExt = new StructArrayLayout3f12(); } upload(context) { this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, this.layoutAttributes); this.indexBuffer = context.createIndexBuffer(this.indexArray); this.collisionVertexBuffer = context.createVertexBuffer(this.collisionVertexArray, collisionVertexAttributes.members, true); this.collisionVertexBufferExt = context.createVertexBuffer(this.collisionVertexArrayExt, collisionVertexAttributesExt.members, true); } destroy() { if (!this.layoutVertexBuffer) return; this.layoutVertexBuffer.destroy(); this.indexBuffer.destroy(); this.segments.destroy(); this.collisionVertexBuffer.destroy(); this.collisionVertexBufferExt.destroy(); } } register('CollisionBuffers', CollisionBuffers); class SymbolBucket { constructor(options) { this.collisionBoxArray = options.collisionBoxArray; this.zoom = options.zoom; this.overscaling = options.overscaling; this.layers = options.layers; this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; this.pixelRatio = options.pixelRatio; this.sourceLayerIndex = options.sourceLayerIndex; this.hasPattern = false; this.hasRTLText = false; this.sortKeyRanges = []; this.collisionCircleArray = []; this.placementInvProjMatrix = identity([]); this.placementViewportMatrix = identity([]); const layer = this.layers[0]; const unevaluatedLayoutValues = layer._unevaluatedLayout._values; this.textSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['text-size']); this.iconSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['icon-size']); const layout = this.layers[0].layout; const sortKey = layout.get('symbol-sort-key'); const zOrder = layout.get('symbol-z-order'); this.canOverlap = layout.get('text-allow-overlap') || layout.get('icon-allow-overlap') || layout.get('text-ignore-placement') || layout.get('icon-ignore-placement'); this.sortFeaturesByKey = zOrder !== 'viewport-y' && sortKey.constantOr(1) !== undefined; const zOrderByViewportY = zOrder === 'viewport-y' || zOrder === 'auto' && !this.sortFeaturesByKey; this.sortFeaturesByY = zOrderByViewportY && this.canOverlap; if (layout.get('symbol-placement') === 'point') { this.writingModes = layout.get('text-writing-mode').map(wm => WritingMode[wm]); } this.stateDependentLayerIds = this.layers.filter(l => l.isStateDependent()).map(l => l.id); this.sourceID = options.sourceID; } createArrays() { this.text = new SymbolBuffers(new ProgramConfigurationSet(this.layers, this.zoom, property => /^text/.test(property))); this.icon = new SymbolBuffers(new ProgramConfigurationSet(this.layers, this.zoom, property => /^icon/.test(property))); this.glyphOffsetArray = new GlyphOffsetArray(); this.lineVertexArray = new SymbolLineVertexArray(); this.symbolInstances = new SymbolInstanceArray(); } calculateGlyphDependencies(text, stack, textAlongLine, allowVerticalPlacement, doesAllowVerticalWritingMode) { for (let i = 0; i < text.length; i++) { stack[text.charCodeAt(i)] = true; if ((textAlongLine || allowVerticalPlacement) && doesAllowVerticalWritingMode) { const verticalChar = verticalizedCharacterMap[text.charAt(i)]; if (verticalChar) { stack[verticalChar.charCodeAt(0)] = true; } } } } populate(features, options, canonical) { const layer = this.layers[0]; const layout = layer.layout; const textFont = layout.get('text-font'); const textField = layout.get('text-field'); const iconImage = layout.get('icon-image'); const hasText = (textField.value.kind !== 'constant' || textField.value.value instanceof Formatted && !textField.value.value.isEmpty() || textField.value.value.toString().length > 0) && (textFont.value.kind !== 'constant' || textFont.value.value.length > 0); const hasIcon = iconImage.value.kind !== 'constant' || !!iconImage.value.value || Object.keys(iconImage.parameters).length > 0; const symbolSortKey = layout.get('symbol-sort-key'); this.features = []; if (!hasText && !hasIcon) { return; } const icons = options.iconDependencies; const stacks = options.glyphDependencies; const availableImages = options.availableImages; const globalProperties = new EvaluationParameters(this.zoom); for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = layer._featureFilter.needGeometry; const evaluationFeature = toEvaluationFeature(feature, needGeometry); if (!layer._featureFilter.filter(globalProperties, evaluationFeature, canonical)) { continue; } if (!needGeometry) evaluationFeature.geometry = loadGeometry(feature); let text; if (hasText) { const resolvedTokens = layer.getValueAndResolveTokens('text-field', evaluationFeature, canonical, availableImages); const formattedText = Formatted.factory(resolvedTokens); if (containsRTLText(formattedText)) { this.hasRTLText = true; } if (!this.hasRTLText || getRTLTextPluginStatus() === 'unavailable' || this.hasRTLText && plugin.isParsed()) { text = transformText$1(formattedText, layer, evaluationFeature); } } let icon; if (hasIcon) { const resolvedTokens = layer.getValueAndResolveTokens('icon-image', evaluationFeature, canonical, availableImages); if (resolvedTokens instanceof ResolvedImage) { icon = resolvedTokens; } else { icon = ResolvedImage.fromString(resolvedTokens); } } if (!text && !icon) { continue; } const sortKey = this.sortFeaturesByKey ? symbolSortKey.evaluate(evaluationFeature, {}, canonical) : undefined; const symbolFeature = { id, text, icon, index, sourceLayerIndex, geometry: evaluationFeature.geometry, properties: feature.properties, type: vectorTileFeatureTypes$2[feature.type], sortKey }; this.features.push(symbolFeature); if (icon) { icons[icon.name] = true; } if (text) { const fontStack = textFont.evaluate(evaluationFeature, {}, canonical).join(','); const textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point'; this.allowVerticalPlacement = this.writingModes && this.writingModes.indexOf(WritingMode.vertical) >= 0; for (const section of text.sections) { if (!section.image) { const doesAllowVerticalWritingMode = allowsVerticalWritingMode(text.toString()); const sectionFont = section.fontStack || fontStack; const sectionStack = stacks[sectionFont] = stacks[sectionFont] || {}; this.calculateGlyphDependencies(section.text, sectionStack, textAlongLine, this.allowVerticalPlacement, doesAllowVerticalWritingMode); } else { icons[section.image.name] = true; } } } } if (layout.get('symbol-placement') === 'line') { this.features = mergeLines(this.features); } if (this.sortFeaturesByKey) { this.features.sort((a, b) => { return a.sortKey - b.sortKey; }); } } update(states, vtLayer, imagePositions) { if (!this.stateDependentLayers.length) return; this.text.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, imagePositions); this.icon.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, imagePositions); } isEmpty() { return this.symbolInstances.length === 0 && !this.hasRTLText; } uploadPending() { return !this.uploaded || this.text.programConfigurations.needsUpload || this.icon.programConfigurations.needsUpload; } upload(context) { if (!this.uploaded && this.hasDebugData()) { this.textCollisionBox.upload(context); this.iconCollisionBox.upload(context); } this.text.upload(context, this.sortFeaturesByY, !this.uploaded, this.text.programConfigurations.needsUpload); this.icon.upload(context, this.sortFeaturesByY, !this.uploaded, this.icon.programConfigurations.needsUpload); this.uploaded = true; } destroyDebugData() { this.textCollisionBox.destroy(); this.iconCollisionBox.destroy(); } destroy() { this.text.destroy(); this.icon.destroy(); if (this.hasDebugData()) { this.destroyDebugData(); } } addToLineVertexArray(anchor, line) { const lineStartIndex = this.lineVertexArray.length; if (anchor.segment !== undefined) { let sumForwardLength = anchor.dist(line[anchor.segment + 1]); let sumBackwardLength = anchor.dist(line[anchor.segment]); const vertices = {}; for (let i = anchor.segment + 1; i < line.length; i++) { vertices[i] = { x: line[i].x, y: line[i].y, tileUnitDistanceFromAnchor: sumForwardLength }; if (i < line.length - 1) { sumForwardLength += line[i + 1].dist(line[i]); } } for (let i = anchor.segment || 0; i >= 0; i--) { vertices[i] = { x: line[i].x, y: line[i].y, tileUnitDistanceFromAnchor: sumBackwardLength }; if (i > 0) { sumBackwardLength += line[i - 1].dist(line[i]); } } for (let i = 0; i < line.length; i++) { const vertex = vertices[i]; this.lineVertexArray.emplaceBack(vertex.x, vertex.y, vertex.tileUnitDistanceFromAnchor); } } return { lineStartIndex, lineLength: this.lineVertexArray.length - lineStartIndex }; } addSymbols(arrays, quads, sizeVertex, lineOffset, alongLine, feature, writingMode, labelAnchor, lineStartIndex, lineLength, associatedIconIndex, canonical) { const indexArray = arrays.indexArray; const layoutVertexArray = arrays.layoutVertexArray; const segment = arrays.segments.prepareSegment(4 * quads.length, layoutVertexArray, indexArray, this.canOverlap ? feature.sortKey : undefined); const glyphOffsetArrayStart = this.glyphOffsetArray.length; const vertexStartIndex = segment.vertexLength; const angle = this.allowVerticalPlacement && writingMode === WritingMode.vertical ? Math.PI / 2 : 0; const sections = feature.text && feature.text.sections; for (let i = 0; i < quads.length; i++) { const {tl, tr, bl, br, tex, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, glyphOffset, isSDF, sectionIndex} = quads[i]; const index = segment.vertexLength; const y = glyphOffset[1]; addVertex$1(layoutVertexArray, labelAnchor.x, labelAnchor.y, tl.x, y + tl.y, tex.x, tex.y, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY); addVertex$1(layoutVertexArray, labelAnchor.x, labelAnchor.y, tr.x, y + tr.y, tex.x + tex.w, tex.y, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY); addVertex$1(layoutVertexArray, labelAnchor.x, labelAnchor.y, bl.x, y + bl.y, tex.x, tex.y + tex.h, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY); addVertex$1(layoutVertexArray, labelAnchor.x, labelAnchor.y, br.x, y + br.y, tex.x + tex.w, tex.y + tex.h, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY); addDynamicAttributes(arrays.dynamicLayoutVertexArray, labelAnchor, angle); indexArray.emplaceBack(index, index + 1, index + 2); indexArray.emplaceBack(index + 1, index + 2, index + 3); segment.vertexLength += 4; segment.primitiveLength += 2; this.glyphOffsetArray.emplaceBack(glyphOffset[0]); if (i === quads.length - 1 || sectionIndex !== quads[i + 1].sectionIndex) { arrays.programConfigurations.populatePaintArrays(layoutVertexArray.length, feature, feature.index, {}, canonical, sections && sections[sectionIndex]); } } arrays.placedSymbolArray.emplaceBack(labelAnchor.x, labelAnchor.y, glyphOffsetArrayStart, this.glyphOffsetArray.length - glyphOffsetArrayStart, vertexStartIndex, lineStartIndex, lineLength, labelAnchor.segment, sizeVertex ? sizeVertex[0] : 0, sizeVertex ? sizeVertex[1] : 0, lineOffset[0], lineOffset[1], writingMode, 0, false, 0, associatedIconIndex); } _commitLayoutVertex(array, point, anchorX, anchorY, extrude) { array.emplaceBack(point.x, point.y, anchorX, anchorY, Math.round(extrude.x), Math.round(extrude.y)); } _addCollisionDebugVertices(box, scale, arrays, boxAnchorPoint, symbolInstance) { const segment = arrays.segments.prepareSegment(4, arrays.layoutVertexArray, arrays.indexArray); const index = segment.vertexLength; const anchorX = symbolInstance.anchorX; const anchorY = symbolInstance.anchorY; for (let i = 0; i < 4; i++) { arrays.collisionVertexArray.emplaceBack(0, 0, 0, 0); } arrays.collisionVertexArrayExt.emplaceBack(scale, -box.padding, -box.padding); arrays.collisionVertexArrayExt.emplaceBack(scale, box.padding, -box.padding); arrays.collisionVertexArrayExt.emplaceBack(scale, box.padding, box.padding); arrays.collisionVertexArrayExt.emplaceBack(scale, -box.padding, box.padding); this._commitLayoutVertex(arrays.layoutVertexArray, boxAnchorPoint, anchorX, anchorY, new pointGeometry(box.x1, box.y1)); this._commitLayoutVertex(arrays.layoutVertexArray, boxAnchorPoint, anchorX, anchorY, new pointGeometry(box.x2, box.y1)); this._commitLayoutVertex(arrays.layoutVertexArray, boxAnchorPoint, anchorX, anchorY, new pointGeometry(box.x2, box.y2)); this._commitLayoutVertex(arrays.layoutVertexArray, boxAnchorPoint, anchorX, anchorY, new pointGeometry(box.x1, box.y2)); segment.vertexLength += 4; const indexArray = arrays.indexArray; indexArray.emplaceBack(index, index + 1); indexArray.emplaceBack(index + 1, index + 2); indexArray.emplaceBack(index + 2, index + 3); indexArray.emplaceBack(index + 3, index); segment.primitiveLength += 4; } _addTextDebugCollisionBoxes(size, zoom, collisionBoxArray, startIndex, endIndex, instance) { for (let b = startIndex; b < endIndex; b++) { const box = collisionBoxArray.get(b); const scale = this.getSymbolInstanceTextSize(size, instance, zoom, b); this._addCollisionDebugVertices(box, scale, this.textCollisionBox, box.anchorPoint, instance); } } _addIconDebugCollisionBoxes(size, zoom, collisionBoxArray, startIndex, endIndex, instance) { for (let b = startIndex; b < endIndex; b++) { const box = collisionBoxArray.get(b); const scale = this.getSymbolInstanceIconSize(size, zoom, b); this._addCollisionDebugVertices(box, scale, this.iconCollisionBox, box.anchorPoint, instance); } } generateCollisionDebugBuffers(zoom, collisionBoxArray) { if (this.hasDebugData()) { this.destroyDebugData(); } this.textCollisionBox = new CollisionBuffers(StructArrayLayout2i2i2i12, collisionBoxLayout.members, StructArrayLayout2ui4); this.iconCollisionBox = new CollisionBuffers(StructArrayLayout2i2i2i12, collisionBoxLayout.members, StructArrayLayout2ui4); const iconSize = evaluateSizeForZoom(this.iconSizeData, zoom); const textSize = evaluateSizeForZoom(this.textSizeData, zoom); for (let i = 0; i < this.symbolInstances.length; i++) { const symbolInstance = this.symbolInstances.get(i); this._addTextDebugCollisionBoxes(textSize, zoom, collisionBoxArray, symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, symbolInstance); this._addTextDebugCollisionBoxes(textSize, zoom, collisionBoxArray, symbolInstance.verticalTextBoxStartIndex, symbolInstance.verticalTextBoxEndIndex, symbolInstance); this._addIconDebugCollisionBoxes(iconSize, zoom, collisionBoxArray, symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex, symbolInstance); this._addIconDebugCollisionBoxes(iconSize, zoom, collisionBoxArray, symbolInstance.verticalIconBoxStartIndex, symbolInstance.verticalIconBoxEndIndex, symbolInstance); } } getSymbolInstanceTextSize(textSize, instance, zoom, boxIndex) { const symbolIndex = instance.rightJustifiedTextSymbolIndex >= 0 ? instance.rightJustifiedTextSymbolIndex : instance.centerJustifiedTextSymbolIndex >= 0 ? instance.centerJustifiedTextSymbolIndex : instance.leftJustifiedTextSymbolIndex >= 0 ? instance.leftJustifiedTextSymbolIndex : instance.verticalPlacedTextSymbolIndex >= 0 ? instance.verticalPlacedTextSymbolIndex : boxIndex; const symbol = this.text.placedSymbolArray.get(symbolIndex); const featureSize = evaluateSizeForFeature(this.textSizeData, textSize, symbol) / ONE_EM; return this.tilePixelRatio * featureSize; } getSymbolInstanceIconSize(iconSize, zoom, index) { const symbol = this.icon.placedSymbolArray.get(index); const featureSize = evaluateSizeForFeature(this.iconSizeData, iconSize, symbol); return this.tilePixelRatio * featureSize; } _commitDebugCollisionVertexUpdate(array, scale, padding) { array.emplaceBack(scale, -padding, -padding); array.emplaceBack(scale, padding, -padding); array.emplaceBack(scale, padding, padding); array.emplaceBack(scale, -padding, padding); } _updateTextDebugCollisionBoxes(size, zoom, collisionBoxArray, startIndex, endIndex, instance) { for (let b = startIndex; b < endIndex; b++) { const box = collisionBoxArray.get(b); const scale = this.getSymbolInstanceTextSize(size, instance, zoom, b); const array = this.textCollisionBox.collisionVertexArrayExt; this._commitDebugCollisionVertexUpdate(array, scale, box.padding); } } _updateIconDebugCollisionBoxes(size, zoom, collisionBoxArray, startIndex, endIndex) { for (let b = startIndex; b < endIndex; b++) { const box = collisionBoxArray.get(b); const scale = this.getSymbolInstanceIconSize(size, zoom, b); const array = this.iconCollisionBox.collisionVertexArrayExt; this._commitDebugCollisionVertexUpdate(array, scale, box.padding); } } updateCollisionDebugBuffers(zoom, collisionBoxArray) { if (!this.hasDebugData()) { return; } if (this.hasTextCollisionBoxData()) this.textCollisionBox.collisionVertexArrayExt.clear(); if (this.hasIconCollisionBoxData()) this.iconCollisionBox.collisionVertexArrayExt.clear(); const iconSize = evaluateSizeForZoom(this.iconSizeData, zoom); const textSize = evaluateSizeForZoom(this.textSizeData, zoom); for (let i = 0; i < this.symbolInstances.length; i++) { const symbolInstance = this.symbolInstances.get(i); this._updateTextDebugCollisionBoxes(textSize, zoom, collisionBoxArray, symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, symbolInstance); this._updateTextDebugCollisionBoxes(textSize, zoom, collisionBoxArray, symbolInstance.verticalTextBoxStartIndex, symbolInstance.verticalTextBoxEndIndex, symbolInstance); this._updateIconDebugCollisionBoxes(iconSize, zoom, collisionBoxArray, symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex); this._updateIconDebugCollisionBoxes(iconSize, zoom, collisionBoxArray, symbolInstance.verticalIconBoxStartIndex, symbolInstance.verticalIconBoxEndIndex); } if (this.hasTextCollisionBoxData() && this.textCollisionBox.collisionVertexBufferExt) { this.textCollisionBox.collisionVertexBufferExt.updateData(this.textCollisionBox.collisionVertexArrayExt); } if (this.hasIconCollisionBoxData() && this.iconCollisionBox.collisionVertexBufferExt) { this.iconCollisionBox.collisionVertexBufferExt.updateData(this.iconCollisionBox.collisionVertexArrayExt); } } _deserializeCollisionBoxesForSymbol(collisionBoxArray, textStartIndex, textEndIndex, verticalTextStartIndex, verticalTextEndIndex, iconStartIndex, iconEndIndex, verticalIconStartIndex, verticalIconEndIndex) { const collisionArrays = {}; for (let k = textStartIndex; k < textEndIndex; k++) { const box = collisionBoxArray.get(k); collisionArrays.textBox = { x1: box.x1, y1: box.y1, x2: box.x2, y2: box.y2, padding: box.padding, anchorPointX: box.anchorPointX, anchorPointY: box.anchorPointY }; collisionArrays.textFeatureIndex = box.featureIndex; break; } for (let k = verticalTextStartIndex; k < verticalTextEndIndex; k++) { const box = collisionBoxArray.get(k); collisionArrays.verticalTextBox = { x1: box.x1, y1: box.y1, x2: box.x2, y2: box.y2, padding: box.padding, anchorPointX: box.anchorPointX, anchorPointY: box.anchorPointY }; collisionArrays.verticalTextFeatureIndex = box.featureIndex; break; } for (let k = iconStartIndex; k < iconEndIndex; k++) { const box = collisionBoxArray.get(k); collisionArrays.iconBox = { x1: box.x1, y1: box.y1, x2: box.x2, y2: box.y2, padding: box.padding, anchorPointX: box.anchorPointX, anchorPointY: box.anchorPointY }; collisionArrays.iconFeatureIndex = box.featureIndex; break; } for (let k = verticalIconStartIndex; k < verticalIconEndIndex; k++) { const box = collisionBoxArray.get(k); collisionArrays.verticalIconBox = { x1: box.x1, y1: box.y1, x2: box.x2, y2: box.y2, padding: box.padding, anchorPointX: box.anchorPointX, anchorPointY: box.anchorPointY }; collisionArrays.verticalIconFeatureIndex = box.featureIndex; break; } return collisionArrays; } deserializeCollisionBoxes(collisionBoxArray) { this.collisionArrays = []; for (let i = 0; i < this.symbolInstances.length; i++) { const symbolInstance = this.symbolInstances.get(i); this.collisionArrays.push(this._deserializeCollisionBoxesForSymbol(collisionBoxArray, symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, symbolInstance.verticalTextBoxStartIndex, symbolInstance.verticalTextBoxEndIndex, symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex, symbolInstance.verticalIconBoxStartIndex, symbolInstance.verticalIconBoxEndIndex)); } } hasTextData() { return this.text.segments.get().length > 0; } hasIconData() { return this.icon.segments.get().length > 0; } hasDebugData() { return this.textCollisionBox && this.iconCollisionBox; } hasTextCollisionBoxData() { return this.hasDebugData() && this.textCollisionBox.segments.get().length > 0; } hasIconCollisionBoxData() { return this.hasDebugData() && this.iconCollisionBox.segments.get().length > 0; } addIndicesForPlacedSymbol(iconOrText, placedSymbolIndex) { const placedSymbol = iconOrText.placedSymbolArray.get(placedSymbolIndex); const endIndex = placedSymbol.vertexStartIndex + placedSymbol.numGlyphs * 4; for (let vertexIndex = placedSymbol.vertexStartIndex; vertexIndex < endIndex; vertexIndex += 4) { iconOrText.indexArray.emplaceBack(vertexIndex, vertexIndex + 1, vertexIndex + 2); iconOrText.indexArray.emplaceBack(vertexIndex + 1, vertexIndex + 2, vertexIndex + 3); } } getSortedSymbolIndexes(angle) { if (this.sortedAngle === angle && this.symbolInstanceIndexes !== undefined) { return this.symbolInstanceIndexes; } const sin = Math.sin(angle); const cos = Math.cos(angle); const rotatedYs = []; const featureIndexes = []; const result = []; for (let i = 0; i < this.symbolInstances.length; ++i) { result.push(i); const symbolInstance = this.symbolInstances.get(i); rotatedYs.push(Math.round(sin * symbolInstance.anchorX + cos * symbolInstance.anchorY) | 0); featureIndexes.push(symbolInstance.featureIndex); } result.sort((aIndex, bIndex) => { return rotatedYs[aIndex] - rotatedYs[bIndex] || featureIndexes[bIndex] - featureIndexes[aIndex]; }); return result; } addToSortKeyRanges(symbolInstanceIndex, sortKey) { const last = this.sortKeyRanges[this.sortKeyRanges.length - 1]; if (last && last.sortKey === sortKey) { last.symbolInstanceEnd = symbolInstanceIndex + 1; } else { this.sortKeyRanges.push({ sortKey, symbolInstanceStart: symbolInstanceIndex, symbolInstanceEnd: symbolInstanceIndex + 1 }); } } sortFeatures(angle) { if (!this.sortFeaturesByY) return; if (this.sortedAngle === angle) return; if (this.text.segments.get().length > 1 || this.icon.segments.get().length > 1) return; this.symbolInstanceIndexes = this.getSortedSymbolIndexes(angle); this.sortedAngle = angle; this.text.indexArray.clear(); this.icon.indexArray.clear(); this.featureSortOrder = []; for (const i of this.symbolInstanceIndexes) { const symbolInstance = this.symbolInstances.get(i); this.featureSortOrder.push(symbolInstance.featureIndex); [ symbolInstance.rightJustifiedTextSymbolIndex, symbolInstance.centerJustifiedTextSymbolIndex, symbolInstance.leftJustifiedTextSymbolIndex ].forEach((index, i, array) => { if (index >= 0 && array.indexOf(index) === i) { this.addIndicesForPlacedSymbol(this.text, index); } }); if (symbolInstance.verticalPlacedTextSymbolIndex >= 0) { this.addIndicesForPlacedSymbol(this.text, symbolInstance.verticalPlacedTextSymbolIndex); } if (symbolInstance.placedIconSymbolIndex >= 0) { this.addIndicesForPlacedSymbol(this.icon, symbolInstance.placedIconSymbolIndex); } if (symbolInstance.verticalPlacedIconSymbolIndex >= 0) { this.addIndicesForPlacedSymbol(this.icon, symbolInstance.verticalPlacedIconSymbolIndex); } } if (this.text.indexBuffer) this.text.indexBuffer.updateData(this.text.indexArray); if (this.icon.indexBuffer) this.icon.indexBuffer.updateData(this.icon.indexArray); } } register('SymbolBucket', SymbolBucket, { omit: [ 'layers', 'collisionBoxArray', 'features', 'compareText' ] }); SymbolBucket.MAX_GLYPHS = 65535; SymbolBucket.addDynamicAttributes = addDynamicAttributes; function resolveTokens(properties, text) { return text.replace(/{([^{}]+)}/g, (match, key) => { return key in properties ? String(properties[key]) : ''; }); } const layout$6 = new Properties({ 'symbol-placement': new DataConstantProperty(spec['layout_symbol']['symbol-placement']), 'symbol-spacing': new DataConstantProperty(spec['layout_symbol']['symbol-spacing']), 'symbol-avoid-edges': new DataConstantProperty(spec['layout_symbol']['symbol-avoid-edges']), 'symbol-sort-key': new DataDrivenProperty(spec['layout_symbol']['symbol-sort-key']), 'symbol-z-order': new DataConstantProperty(spec['layout_symbol']['symbol-z-order']), 'icon-allow-overlap': new DataConstantProperty(spec['layout_symbol']['icon-allow-overlap']), 'icon-ignore-placement': new DataConstantProperty(spec['layout_symbol']['icon-ignore-placement']), 'icon-optional': new DataConstantProperty(spec['layout_symbol']['icon-optional']), 'icon-rotation-alignment': new DataConstantProperty(spec['layout_symbol']['icon-rotation-alignment']), 'icon-size': new DataDrivenProperty(spec['layout_symbol']['icon-size']), 'icon-text-fit': new DataConstantProperty(spec['layout_symbol']['icon-text-fit']), 'icon-text-fit-padding': new DataConstantProperty(spec['layout_symbol']['icon-text-fit-padding']), 'icon-image': new DataDrivenProperty(spec['layout_symbol']['icon-image']), 'icon-rotate': new DataDrivenProperty(spec['layout_symbol']['icon-rotate']), 'icon-padding': new DataConstantProperty(spec['layout_symbol']['icon-padding']), 'icon-keep-upright': new DataConstantProperty(spec['layout_symbol']['icon-keep-upright']), 'icon-offset': new DataDrivenProperty(spec['layout_symbol']['icon-offset']), 'icon-anchor': new DataDrivenProperty(spec['layout_symbol']['icon-anchor']), 'icon-pitch-alignment': new DataConstantProperty(spec['layout_symbol']['icon-pitch-alignment']), 'text-pitch-alignment': new DataConstantProperty(spec['layout_symbol']['text-pitch-alignment']), 'text-rotation-alignment': new DataConstantProperty(spec['layout_symbol']['text-rotation-alignment']), 'text-field': new DataDrivenProperty(spec['layout_symbol']['text-field']), 'text-font': new DataDrivenProperty(spec['layout_symbol']['text-font']), 'text-size': new DataDrivenProperty(spec['layout_symbol']['text-size']), 'text-max-width': new DataDrivenProperty(spec['layout_symbol']['text-max-width']), 'text-line-height': new DataConstantProperty(spec['layout_symbol']['text-line-height']), 'text-letter-spacing': new DataDrivenProperty(spec['layout_symbol']['text-letter-spacing']), 'text-justify': new DataDrivenProperty(spec['layout_symbol']['text-justify']), 'text-radial-offset': new DataDrivenProperty(spec['layout_symbol']['text-radial-offset']), 'text-variable-anchor': new DataConstantProperty(spec['layout_symbol']['text-variable-anchor']), 'text-anchor': new DataDrivenProperty(spec['layout_symbol']['text-anchor']), 'text-max-angle': new DataConstantProperty(spec['layout_symbol']['text-max-angle']), 'text-writing-mode': new DataConstantProperty(spec['layout_symbol']['text-writing-mode']), 'text-rotate': new DataDrivenProperty(spec['layout_symbol']['text-rotate']), 'text-padding': new DataConstantProperty(spec['layout_symbol']['text-padding']), 'text-keep-upright': new DataConstantProperty(spec['layout_symbol']['text-keep-upright']), 'text-transform': new DataDrivenProperty(spec['layout_symbol']['text-transform']), 'text-offset': new DataDrivenProperty(spec['layout_symbol']['text-offset']), 'text-allow-overlap': new DataConstantProperty(spec['layout_symbol']['text-allow-overlap']), 'text-ignore-placement': new DataConstantProperty(spec['layout_symbol']['text-ignore-placement']), 'text-optional': new DataConstantProperty(spec['layout_symbol']['text-optional']) }); const paint$7 = new Properties({ 'icon-opacity': new DataDrivenProperty(spec['paint_symbol']['icon-opacity']), 'icon-color': new DataDrivenProperty(spec['paint_symbol']['icon-color']), 'icon-halo-color': new DataDrivenProperty(spec['paint_symbol']['icon-halo-color']), 'icon-halo-width': new DataDrivenProperty(spec['paint_symbol']['icon-halo-width']), 'icon-halo-blur': new DataDrivenProperty(spec['paint_symbol']['icon-halo-blur']), 'icon-translate': new DataConstantProperty(spec['paint_symbol']['icon-translate']), 'icon-translate-anchor': new DataConstantProperty(spec['paint_symbol']['icon-translate-anchor']), 'text-opacity': new DataDrivenProperty(spec['paint_symbol']['text-opacity']), 'text-color': new DataDrivenProperty(spec['paint_symbol']['text-color'], { runtimeType: ColorType, getOverride: o => o.textColor, hasOverride: o => !!o.textColor }), 'text-halo-color': new DataDrivenProperty(spec['paint_symbol']['text-halo-color']), 'text-halo-width': new DataDrivenProperty(spec['paint_symbol']['text-halo-width']), 'text-halo-blur': new DataDrivenProperty(spec['paint_symbol']['text-halo-blur']), 'text-translate': new DataConstantProperty(spec['paint_symbol']['text-translate']), 'text-translate-anchor': new DataConstantProperty(spec['paint_symbol']['text-translate-anchor']) }); var properties$6 = { paint: paint$7, layout: layout$6 }; class FormatSectionOverride { constructor(defaultValue) { this.type = defaultValue.property.overrides ? defaultValue.property.overrides.runtimeType : NullType; this.defaultValue = defaultValue; } evaluate(ctx) { if (ctx.formattedSection) { const overrides = this.defaultValue.property.overrides; if (overrides && overrides.hasOverride(ctx.formattedSection)) { return overrides.getOverride(ctx.formattedSection); } } if (ctx.feature && ctx.featureState) { return this.defaultValue.evaluate(ctx.feature, ctx.featureState); } return this.defaultValue.property.specification.default; } eachChild(fn) { if (!this.defaultValue.isConstant()) { const expr = this.defaultValue.value; fn(expr._styleExpression.expression); } } outputDefined() { return false; } serialize() { return null; } } register('FormatSectionOverride', FormatSectionOverride, { omit: ['defaultValue'] }); class SymbolStyleLayer extends StyleLayer { constructor(layer) { super(layer, properties$6); } recalculate(parameters, availableImages) { super.recalculate(parameters, availableImages); if (this.layout.get('icon-rotation-alignment') === 'auto') { if (this.layout.get('symbol-placement') !== 'point') { this.layout._values['icon-rotation-alignment'] = 'map'; } else { this.layout._values['icon-rotation-alignment'] = 'viewport'; } } if (this.layout.get('text-rotation-alignment') === 'auto') { if (this.layout.get('symbol-placement') !== 'point') { this.layout._values['text-rotation-alignment'] = 'map'; } else { this.layout._values['text-rotation-alignment'] = 'viewport'; } } if (this.layout.get('text-pitch-alignment') === 'auto') { this.layout._values['text-pitch-alignment'] = this.layout.get('text-rotation-alignment'); } if (this.layout.get('icon-pitch-alignment') === 'auto') { this.layout._values['icon-pitch-alignment'] = this.layout.get('icon-rotation-alignment'); } if (this.layout.get('symbol-placement') === 'point') { const writingModes = this.layout.get('text-writing-mode'); if (writingModes) { const deduped = []; for (const m of writingModes) { if (deduped.indexOf(m) < 0) deduped.push(m); } this.layout._values['text-writing-mode'] = deduped; } else { this.layout._values['text-writing-mode'] = ['horizontal']; } } this._setPaintOverrides(); } getValueAndResolveTokens(name, feature, canonical, availableImages) { const value = this.layout.get(name).evaluate(feature, {}, canonical, availableImages); const unevaluated = this._unevaluatedLayout._values[name]; if (!unevaluated.isDataDriven() && !isExpression(unevaluated.value) && value) { return resolveTokens(feature.properties, value); } return value; } createBucket(parameters) { return new SymbolBucket(parameters); } queryRadius() { return 0; } queryIntersectsFeature() { return false; } _setPaintOverrides() { for (const overridable of properties$6.paint.overridableProperties) { if (!SymbolStyleLayer.hasPaintOverride(this.layout, overridable)) { continue; } const overriden = this.paint.get(overridable); const override = new FormatSectionOverride(overriden); const styleExpression = new StyleExpression(override, overriden.property.specification); let expression = null; if (overriden.value.kind === 'constant' || overriden.value.kind === 'source') { expression = new ZoomConstantExpression('source', styleExpression); } else { expression = new ZoomDependentExpression('composite', styleExpression, overriden.value.zoomStops, overriden.value._interpolationType); } this.paint._values[overridable] = new PossiblyEvaluatedPropertyValue(overriden.property, expression, overriden.parameters); } } _handleOverridablePaintPropertyUpdate(name, oldValue, newValue) { if (!this.layout || oldValue.isDataDriven() || newValue.isDataDriven()) { return false; } return SymbolStyleLayer.hasPaintOverride(this.layout, name); } static hasPaintOverride(layout, propertyName) { const textField = layout.get('text-field'); const property = properties$6.paint.properties[propertyName]; let hasOverrides = false; const checkSections = sections => { for (const section of sections) { if (property.overrides && property.overrides.hasOverride(section)) { hasOverrides = true; return; } } }; if (textField.value.kind === 'constant' && textField.value.value instanceof Formatted) { checkSections(textField.value.value.sections); } else if (textField.value.kind === 'source') { const checkExpression = expression => { if (hasOverrides) return; if (expression instanceof Literal && typeOf(expression.value) === FormattedType) { const formatted = expression.value; checkSections(formatted.sections); } else if (expression instanceof FormatExpression) { checkSections(expression.sections); } else { expression.eachChild(checkExpression); } }; const expr = textField.value; if (expr._styleExpression) { checkExpression(expr._styleExpression.expression); } } return hasOverrides; } getProgramConfiguration(zoom) { return new ProgramConfiguration(this, zoom); } } const paint$8 = new Properties({ 'background-color': new DataConstantProperty(spec['paint_background']['background-color']), 'background-pattern': new CrossFadedProperty(spec['paint_background']['background-pattern']), 'background-opacity': new DataConstantProperty(spec['paint_background']['background-opacity']) }); var properties$7 = { paint: paint$8 }; class BackgroundStyleLayer extends StyleLayer { constructor(layer) { super(layer, properties$7); } getProgramIds() { const image = this.paint.get('background-pattern'); return [image ? 'backgroundPattern' : 'background']; } } const paint$9 = new Properties({ 'raster-opacity': new DataConstantProperty(spec['paint_raster']['raster-opacity']), 'raster-hue-rotate': new DataConstantProperty(spec['paint_raster']['raster-hue-rotate']), 'raster-brightness-min': new DataConstantProperty(spec['paint_raster']['raster-brightness-min']), 'raster-brightness-max': new DataConstantProperty(spec['paint_raster']['raster-brightness-max']), 'raster-saturation': new DataConstantProperty(spec['paint_raster']['raster-saturation']), 'raster-contrast': new DataConstantProperty(spec['paint_raster']['raster-contrast']), 'raster-resampling': new DataConstantProperty(spec['paint_raster']['raster-resampling']), 'raster-fade-duration': new DataConstantProperty(spec['paint_raster']['raster-fade-duration']) }); var properties$8 = { paint: paint$9 }; class RasterStyleLayer extends StyleLayer { constructor(layer) { super(layer, properties$8); } getProgramIds() { return ['raster']; } } function validateCustomStyleLayer(layerObject) { const errors = []; const id = layerObject.id; if (id === undefined) { errors.push({ message: `layers.${ id }: missing required property "id"` }); } if (layerObject.render === undefined) { errors.push({ message: `layers.${ id }: missing required method "render"` }); } if (layerObject.renderingMode && layerObject.renderingMode !== '2d' && layerObject.renderingMode !== '3d') { errors.push({ message: `layers.${ id }: property "renderingMode" must be either "2d" or "3d"` }); } return errors; } class CustomStyleLayer extends StyleLayer { constructor(implementation) { super(implementation, {}); this.implementation = implementation; } is3D() { return this.implementation.renderingMode === '3d'; } hasOffscreenPass() { return this.implementation.prerender !== undefined; } recalculate() { } updateTransitions() { } hasTransition() { } serialize() { } onAdd(map) { if (this.implementation.onAdd) { this.implementation.onAdd(map, map.painter.context.gl); } } onRemove(map) { if (this.implementation.onRemove) { this.implementation.onRemove(map, map.painter.context.gl); } } } const paint$a = new Properties({ 'sky-type': new DataConstantProperty(spec['paint_sky']['sky-type']), 'sky-atmosphere-sun': new DataConstantProperty(spec['paint_sky']['sky-atmosphere-sun']), 'sky-atmosphere-sun-intensity': new DataConstantProperty(spec['paint_sky']['sky-atmosphere-sun-intensity']), 'sky-gradient-center': new DataConstantProperty(spec['paint_sky']['sky-gradient-center']), 'sky-gradient-radius': new DataConstantProperty(spec['paint_sky']['sky-gradient-radius']), 'sky-gradient': new ColorRampProperty(spec['paint_sky']['sky-gradient']), 'sky-atmosphere-halo-color': new DataConstantProperty(spec['paint_sky']['sky-atmosphere-halo-color']), 'sky-atmosphere-color': new DataConstantProperty(spec['paint_sky']['sky-atmosphere-color']), 'sky-opacity': new DataConstantProperty(spec['paint_sky']['sky-opacity']) }); var properties$9 = { paint: paint$a }; function getCelestialDirection(azimuth, altitude, leftHanded) { const up = fromValues(0, 0, 1); const rotation = identity$1(create$5()); rotateY$1(rotation, rotation, leftHanded ? -degToRad(azimuth) + Math.PI : degToRad(azimuth)); rotateX$1(rotation, rotation, -degToRad(altitude)); transformQuat(up, up, rotation); return normalize(up, up); } class SkyLayer extends StyleLayer { constructor(layer) { super(layer, properties$9); this._updateColorRamp(); } _handleSpecialPaintPropertyUpdate(name) { if (name === 'sky-gradient') { this._updateColorRamp(); } else if (name === 'sky-atmosphere-sun' || name === 'sky-atmosphere-halo-color' || name === 'sky-atmosphere-color' || name === 'sky-atmosphere-sun-intensity') { this._skyboxInvalidated = true; } } _updateColorRamp() { const expression = this._transitionablePaint._values['sky-gradient'].value.expression; this.colorRamp = renderColorRamp({ expression, evaluationKey: 'skyRadialProgress' }); if (this.colorRampTexture) { this.colorRampTexture.destroy(); this.colorRampTexture = null; } } needsSkyboxCapture(painter) { if (!!this._skyboxInvalidated || !this.skyboxTexture || !this.skyboxGeometry) { return true; } if (!this.paint.get('sky-atmosphere-sun')) { const lightPosition = painter.style.light.properties.get('position'); return this._lightPosition.azimuthal !== lightPosition.azimuthal || this._lightPosition.polar !== lightPosition.polar; } } getCenter(painter, leftHanded) { const type = this.paint.get('sky-type'); if (type === 'atmosphere') { const sunPosition = this.paint.get('sky-atmosphere-sun'); const useLightPosition = !sunPosition; const light = painter.style.light; const lightPosition = light.properties.get('position'); if (useLightPosition && light.properties.get('anchor') === 'viewport') { warnOnce('The sun direction is attached to a light with viewport anchor, lighting may behave unexpectedly.'); } return useLightPosition ? getCelestialDirection(lightPosition.azimuthal, -lightPosition.polar + 90, leftHanded) : getCelestialDirection(sunPosition[0], -sunPosition[1] + 90, leftHanded); } else if (type === 'gradient') { const direction = this.paint.get('sky-gradient-center'); return getCelestialDirection(direction[0], -direction[1] + 90, leftHanded); } } is3D() { return false; } isSky() { return true; } markSkyboxValid(painter) { this._skyboxInvalidated = false; this._lightPosition = painter.style.light.properties.get('position'); } hasOffscreenPass() { return true; } getProgramIds() { const type = this.paint.get('sky-type'); if (type === 'atmosphere') { return [ 'skyboxCapture', 'skybox' ]; } else if (type === 'gradient') { return ['skyboxGradient']; } return null; } } const subclasses = { circle: CircleStyleLayer, heatmap: HeatmapStyleLayer, hillshade: HillshadeStyleLayer, fill: FillStyleLayer, 'fill-extrusion': FillExtrusionStyleLayer, line: LineStyleLayer, symbol: SymbolStyleLayer, background: BackgroundStyleLayer, raster: RasterStyleLayer, sky: SkyLayer }; function createStyleLayer(layer) { if (layer.type === 'custom') { return new CustomStyleLayer(layer); } else { return new subclasses[layer.type](layer); } } const {HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, ImageData: ImageData$1, ImageBitmap: ImageBitmap$1} = window$1; class Texture { constructor(context, image, format, options) { this.context = context; this.format = format; this.texture = context.gl.createTexture(); this.update(image, options); } update(image, options, position) { const {width, height} = image; const resize = (!this.size || this.size[0] !== width || this.size[1] !== height) && !position; const {context} = this; const {gl} = context; this.useMipmap = Boolean(options && options.useMipmap); gl.bindTexture(gl.TEXTURE_2D, this.texture); context.pixelStoreUnpackFlipY.set(false); context.pixelStoreUnpack.set(1); context.pixelStoreUnpackPremultiplyAlpha.set(this.format === gl.RGBA && (!options || options.premultiply !== false)); if (resize) { this.size = [ width, height ]; if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof HTMLVideoElement || image instanceof ImageData$1 || ImageBitmap$1 && image instanceof ImageBitmap$1) { gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, gl.UNSIGNED_BYTE, image); } else { gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, gl.UNSIGNED_BYTE, image.data); } } else { const {x, y} = position || { x: 0, y: 0 }; if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof HTMLVideoElement || image instanceof ImageData$1 || ImageBitmap$1 && image instanceof ImageBitmap$1) { gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, gl.RGBA, gl.UNSIGNED_BYTE, image); } else { gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image.data); } } if (this.useMipmap && this.isSizePowerOfTwo()) { gl.generateMipmap(gl.TEXTURE_2D); } } bind(filter, wrap, minFilter) { const {context} = this; const {gl} = context; gl.bindTexture(gl.TEXTURE_2D, this.texture); if (minFilter === gl.LINEAR_MIPMAP_NEAREST && !this.isSizePowerOfTwo()) { minFilter = gl.LINEAR; } if (filter !== this.filter) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter || filter); this.filter = filter; } if (wrap !== this.wrap) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap); this.wrap = wrap; } } isSizePowerOfTwo() { return this.size[0] === this.size[1] && Math.log(this.size[0]) / Math.LN2 % 1 === 0; } destroy() { const {gl} = this.context; gl.deleteTexture(this.texture); this.texture = null; } } class ThrottledInvoker { constructor(callback) { this._callback = callback; this._triggered = false; if (typeof MessageChannel !== 'undefined') { this._channel = new MessageChannel(); this._channel.port2.onmessage = () => { this._triggered = false; this._callback(); }; } } trigger() { if (!this._triggered) { this._triggered = true; if (this._channel) { this._channel.port1.postMessage(true); } else { setTimeout(() => { this._triggered = false; this._callback(); }, 0); } } } remove() { delete this._channel; this._callback = () => { }; } } const performance = window$1.performance; class RequestPerformance { constructor(request) { this._marks = { start: [ request.url, 'start' ].join('#'), end: [ request.url, 'end' ].join('#'), measure: request.url.toString() }; performance.mark(this._marks.start); } finish() { performance.mark(this._marks.end); let resourceTimingData = performance.getEntriesByName(this._marks.measure); if (resourceTimingData.length === 0) { performance.measure(this._marks.measure, this._marks.start, this._marks.end); resourceTimingData = performance.getEntriesByName(this._marks.measure); performance.clearMarks(this._marks.start); performance.clearMarks(this._marks.end); performance.clearMeasures(this._marks.measure); } return resourceTimingData; } } class Scheduler { constructor() { this.tasks = {}; this.taskQueue = []; bindAll(['process'], this); this.invoker = new ThrottledInvoker(this.process); this.nextId = 0; } add(fn, metadata) { const id = this.nextId++; this.tasks[id] = { fn, metadata, priority: getPriority(metadata), id }; this.taskQueue.push(id); this.invoker.trigger(); return { cancel: () => { delete this.tasks[id]; } }; } process() { const m = isWorker() ? void 0 : undefined; try { this.taskQueue = this.taskQueue.filter(id => !!this.tasks[id]); if (!this.taskQueue.length) { return; } const id = this.pick(); if (id === null) return; const task = this.tasks[id]; delete this.tasks[id]; if (this.taskQueue.length) { this.invoker.trigger(); } if (!task) { return; } task.fn(); } finally { } } pick() { let minIndex = null; let minPriority = Infinity; for (let i = 0; i < this.taskQueue.length; i++) { const id = this.taskQueue[i]; const task = this.tasks[id]; if (task.priority < minPriority) { minPriority = task.priority; minIndex = i; } } if (minIndex === null) return null; const id = this.taskQueue[minIndex]; this.taskQueue.splice(minIndex, 1); return id; } remove() { this.invoker.remove(); } } function getPriority({type, isSymbolTile, zoom}) { zoom = zoom || 0; if (type === 'message') return 0; if (type === 'maybePrepare' && !isSymbolTile) return 100 - zoom; if (type === 'parseTile' && !isSymbolTile) return 200 - zoom; if (type === 'parseTile' && isSymbolTile) return 300 - zoom; if (type === 'maybePrepare' && isSymbolTile) return 400 - zoom; return 500; } class Actor { constructor(target, parent, mapId) { this.target = target; this.parent = parent; this.mapId = mapId; this.callbacks = {}; this.cancelCallbacks = {}; bindAll(['receive'], this); this.target.addEventListener('message', this.receive, false); this.globalScope = isWorker() ? target : window$1; this.scheduler = new Scheduler(); } send(type, data, callback, targetMapId, mustQueue = false, callbackMetadata) { const id = Math.round(Math.random() * 1000000000000000000).toString(36).substring(0, 10); if (callback) { callback.metadata = callbackMetadata; this.callbacks[id] = callback; } const buffers = isSafari(this.globalScope) ? undefined : []; this.target.postMessage({ id, type, hasCallback: !!callback, targetMapId, mustQueue, sourceMapId: this.mapId, data: serialize(data, buffers) }, buffers); return { cancel: () => { if (callback) { delete this.callbacks[id]; } this.target.postMessage({ id, type: '', targetMapId, sourceMapId: this.mapId }); } }; } receive(message) { const data = message.data, id = data.id; if (!id) { return; } if (data.targetMapId && this.mapId !== data.targetMapId) { return; } if (data.type === '') { const cancel = this.cancelCallbacks[id]; delete this.cancelCallbacks[id]; if (cancel) { cancel.cancel(); } } else { if (isWorker() || data.mustQueue) { const callback = this.callbacks[id]; const metadata = callback && callback.metadata || { type: 'message' }; this.cancelCallbacks[id] = this.scheduler.add(() => this.processTask(id, data), metadata); } else { this.processTask(id, data); } } } processTask(id, task) { if (task.type === '') { const callback = this.callbacks[id]; delete this.callbacks[id]; if (callback) { if (task.error) { callback(deserialize(task.error)); } else { callback(null, deserialize(task.data)); } } } else { const buffers = isSafari(this.globalScope) ? undefined : []; const done = task.hasCallback ? (err, data) => { delete this.cancelCallbacks[id]; this.target.postMessage({ id, type: '', sourceMapId: this.mapId, error: err ? serialize(err) : null, data: serialize(data, buffers) }, buffers); } : _ => { }; const params = deserialize(task.data); if (this.parent[task.type]) { this.parent[task.type](task.sourceMapId, params, done); } else if (this.parent.getWorkerSource) { const keys = task.type.split('.'); const scope = this.parent.getWorkerSource(task.sourceMapId, keys[0], params.source); scope[keys[1]](params, done); } else { done(new Error(`Could not find function ${ task.type }`)); } } } remove() { this.scheduler.remove(); this.target.removeEventListener('message', this.receive, false); } } class LngLatBounds { constructor(sw, ne) { if (!sw) ; else if (ne) { this.setSouthWest(sw).setNorthEast(ne); } else if (sw.length === 4) { this.setSouthWest([ sw[0], sw[1] ]).setNorthEast([ sw[2], sw[3] ]); } else { this.setSouthWest(sw[0]).setNorthEast(sw[1]); } } setNorthEast(ne) { this._ne = ne instanceof LngLat ? new LngLat(ne.lng, ne.lat) : LngLat.convert(ne); return this; } setSouthWest(sw) { this._sw = sw instanceof LngLat ? new LngLat(sw.lng, sw.lat) : LngLat.convert(sw); return this; } extend(obj) { const sw = this._sw, ne = this._ne; let sw2, ne2; if (obj instanceof LngLat) { sw2 = obj; ne2 = obj; } else if (obj instanceof LngLatBounds) { sw2 = obj._sw; ne2 = obj._ne; if (!sw2 || !ne2) return this; } else { if (Array.isArray(obj)) { if (obj.length === 4 || obj.every(Array.isArray)) { const lngLatBoundsObj = obj; return this.extend(LngLatBounds.convert(lngLatBoundsObj)); } else { const lngLatObj = obj; return this.extend(LngLat.convert(lngLatObj)); } } return this; } if (!sw && !ne) { this._sw = new LngLat(sw2.lng, sw2.lat); this._ne = new LngLat(ne2.lng, ne2.lat); } else { sw.lng = Math.min(sw2.lng, sw.lng); sw.lat = Math.min(sw2.lat, sw.lat); ne.lng = Math.max(ne2.lng, ne.lng); ne.lat = Math.max(ne2.lat, ne.lat); } return this; } getCenter() { return new LngLat((this._sw.lng + this._ne.lng) / 2, (this._sw.lat + this._ne.lat) / 2); } getSouthWest() { return this._sw; } getNorthEast() { return this._ne; } getNorthWest() { return new LngLat(this.getWest(), this.getNorth()); } getSouthEast() { return new LngLat(this.getEast(), this.getSouth()); } getWest() { return this._sw.lng; } getSouth() { return this._sw.lat; } getEast() { return this._ne.lng; } getNorth() { return this._ne.lat; } toArray() { return [ this._sw.toArray(), this._ne.toArray() ]; } toString() { return `LngLatBounds(${ this._sw.toString() }, ${ this._ne.toString() })`; } isEmpty() { return !(this._sw && this._ne); } contains(lnglat) { const {lng, lat} = LngLat.convert(lnglat); const containsLatitude = this._sw.lat <= lat && lat <= this._ne.lat; let containsLongitude = this._sw.lng <= lng && lng <= this._ne.lng; if (this._sw.lng > this._ne.lng) { containsLongitude = this._sw.lng >= lng && lng >= this._ne.lng; } return containsLatitude && containsLongitude; } static convert(input) { if (!input || input instanceof LngLatBounds) return input; return new LngLatBounds(input); } } const earthRadius = 6371008.8; class LngLat { constructor(lng, lat) { if (isNaN(lng) || isNaN(lat)) { throw new Error(`Invalid LngLat object: (${ lng }, ${ lat })`); } this.lng = +lng; this.lat = +lat; if (this.lat > 90 || this.lat < -90) { throw new Error('Invalid LngLat latitude value: must be between -90 and 90'); } } wrap() { return new LngLat(wrap(this.lng, -180, 180), this.lat); } toArray() { return [ this.lng, this.lat ]; } toString() { return `LngLat(${ this.lng }, ${ this.lat })`; } distanceTo(lngLat) { const rad = Math.PI / 180; const lat1 = this.lat * rad; const lat2 = lngLat.lat * rad; const a = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos((lngLat.lng - this.lng) * rad); const maxMeters = earthRadius * Math.acos(Math.min(a, 1)); return maxMeters; } toBounds(radius = 0) { const earthCircumferenceInMetersAtEquator = 40075017; const latAccuracy = 360 * radius / earthCircumferenceInMetersAtEquator, lngAccuracy = latAccuracy / Math.cos(Math.PI / 180 * this.lat); return new LngLatBounds(new LngLat(this.lng - lngAccuracy, this.lat - latAccuracy), new LngLat(this.lng + lngAccuracy, this.lat + latAccuracy)); } static convert(input) { if (input instanceof LngLat) { return input; } if (Array.isArray(input) && (input.length === 2 || input.length === 3)) { return new LngLat(Number(input[0]), Number(input[1])); } if (!Array.isArray(input) && typeof input === 'object' && input !== null) { return new LngLat(Number('lng' in input ? input.lng : input.lon), Number(input.lat)); } throw new Error('`LngLatLike` argument must be specified as a LngLat instance, an object {lng: , lat: }, an object {lon: , lat: }, or an array of [, ]'); } } const earthCircumfrence = 2 * Math.PI * earthRadius; function circumferenceAtLatitude(latitude) { return earthCircumfrence * Math.cos(latitude * Math.PI / 180); } function mercatorXfromLng$1(lng) { return (180 + lng) / 360; } function mercatorYfromLat$1(lat) { return (180 - 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360))) / 360; } function mercatorZfromAltitude(altitude, lat) { return altitude / circumferenceAtLatitude(lat); } function lngFromMercatorX(x) { return x * 360 - 180; } function latFromMercatorY(y) { const y2 = 180 - y * 360; return 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90; } function altitudeFromMercatorZ(z, y) { return z * circumferenceAtLatitude(latFromMercatorY(y)); } function mercatorScale(lat) { return 1 / Math.cos(lat * Math.PI / 180); } class MercatorCoordinate { constructor(x, y, z = 0) { this.x = +x; this.y = +y; this.z = +z; } static fromLngLat(lngLatLike, altitude = 0) { const lngLat = LngLat.convert(lngLatLike); return new MercatorCoordinate(mercatorXfromLng$1(lngLat.lng), mercatorYfromLat$1(lngLat.lat), mercatorZfromAltitude(altitude, lngLat.lat)); } toLngLat() { return new LngLat(lngFromMercatorX(this.x), latFromMercatorY(this.y)); } toAltitude() { return altitudeFromMercatorZ(this.z, this.y); } meterInMercatorCoordinateUnits() { return 1 / earthCircumfrence * mercatorScale(latFromMercatorY(this.y)); } } /** * getTileBBox * * @param {Number} x Tile coordinate x * @param {Number} y Tile coordinate y * @param {Number} z Tile zoom * @returns {String} String of the bounding box */ function getTileBBox(x, y, z) { // for Google/OSM tile scheme we need to alter the y y = (Math.pow(2, z) - y - 1); var min = getMercCoords(x * 256, y * 256, z), max = getMercCoords((x + 1) * 256, (y + 1) * 256, z); return min[0] + ',' + min[1] + ',' + max[0] + ',' + max[1]; } /** * getMercCoords * * @param {Number} x Pixel coordinate x * @param {Number} y Pixel coordinate y * @param {Number} z Tile zoom * @returns {Array} [x, y] */ function getMercCoords(x, y, z) { var resolution = (2 * Math.PI * 6378137 / 256) / Math.pow(2, z), merc_x = (x * resolution - 2 * Math.PI * 6378137 / 2.0), merc_y = (y * resolution - 2 * Math.PI * 6378137 / 2.0); return [merc_x, merc_y]; } class CanonicalTileID { constructor(z, x, y) { this.z = z; this.x = x; this.y = y; this.key = calculateKey(0, z, z, x, y); } equals(id) { return this.z === id.z && this.x === id.x && this.y === id.y; } url(urls, scheme) { const bbox = getTileBBox(this.x, this.y, this.z); const quadkey = getQuadkey(this.z, this.x, this.y); return urls[(this.x + this.y) % urls.length].replace('{prefix}', (this.x % 16).toString(16) + (this.y % 16).toString(16)).replace('{z}', String(this.z)).replace('{x}', String(this.x)).replace('{y}', String(scheme === 'tms' ? Math.pow(2, this.z) - this.y - 1 : this.y)).replace('{quadkey}', quadkey).replace('{bbox-epsg-3857}', bbox); } getTilePoint(coord) { const tilesAtZoom = Math.pow(2, this.z); return new pointGeometry((coord.x * tilesAtZoom - this.x) * EXTENT$1, (coord.y * tilesAtZoom - this.y) * EXTENT$1); } getTileVec3(coord) { const tilesAtZoom = Math.pow(2, this.z); const x = (coord.x * tilesAtZoom - this.x) * EXTENT$1; const y = (coord.y * tilesAtZoom - this.y) * EXTENT$1; return fromValues(x, y, altitudeFromMercatorZ(coord.z, coord.y)); } toString() { return `${ this.z }/${ this.x }/${ this.y }`; } } class UnwrappedTileID { constructor(wrap, canonical) { this.wrap = wrap; this.canonical = canonical; this.key = calculateKey(wrap, canonical.z, canonical.z, canonical.x, canonical.y); } } class OverscaledTileID { constructor(overscaledZ, wrap, z, x, y) { this.overscaledZ = overscaledZ; this.wrap = wrap; this.canonical = new CanonicalTileID(z, +x, +y); this.key = wrap === 0 && overscaledZ === z ? this.canonical.key : calculateKey(wrap, overscaledZ, z, x, y); } equals(id) { return this.overscaledZ === id.overscaledZ && this.wrap === id.wrap && this.canonical.equals(id.canonical); } scaledTo(targetZ) { const zDifference = this.canonical.z - targetZ; if (targetZ > this.canonical.z) { return new OverscaledTileID(targetZ, this.wrap, this.canonical.z, this.canonical.x, this.canonical.y); } else { return new OverscaledTileID(targetZ, this.wrap, targetZ, this.canonical.x >> zDifference, this.canonical.y >> zDifference); } } calculateScaledKey(targetZ, withWrap = true) { if (this.overscaledZ === targetZ && withWrap) return this.key; if (targetZ > this.canonical.z) { return calculateKey(this.wrap * +withWrap, targetZ, this.canonical.z, this.canonical.x, this.canonical.y); } else { const zDifference = this.canonical.z - targetZ; return calculateKey(this.wrap * +withWrap, targetZ, targetZ, this.canonical.x >> zDifference, this.canonical.y >> zDifference); } } isChildOf(parent) { if (parent.wrap !== this.wrap) { return false; } const zDifference = this.canonical.z - parent.canonical.z; return parent.overscaledZ === 0 || parent.overscaledZ < this.overscaledZ && parent.canonical.x === this.canonical.x >> zDifference && parent.canonical.y === this.canonical.y >> zDifference; } children(sourceMaxZoom) { if (this.overscaledZ >= sourceMaxZoom) { return [new OverscaledTileID(this.overscaledZ + 1, this.wrap, this.canonical.z, this.canonical.x, this.canonical.y)]; } const z = this.canonical.z + 1; const x = this.canonical.x * 2; const y = this.canonical.y * 2; return [ new OverscaledTileID(z, this.wrap, z, x, y), new OverscaledTileID(z, this.wrap, z, x + 1, y), new OverscaledTileID(z, this.wrap, z, x, y + 1), new OverscaledTileID(z, this.wrap, z, x + 1, y + 1) ]; } isLessThan(rhs) { if (this.wrap < rhs.wrap) return true; if (this.wrap > rhs.wrap) return false; if (this.overscaledZ < rhs.overscaledZ) return true; if (this.overscaledZ > rhs.overscaledZ) return false; if (this.canonical.x < rhs.canonical.x) return true; if (this.canonical.x > rhs.canonical.x) return false; if (this.canonical.y < rhs.canonical.y) return true; return false; } wrapped() { return new OverscaledTileID(this.overscaledZ, 0, this.canonical.z, this.canonical.x, this.canonical.y); } unwrapTo(wrap) { return new OverscaledTileID(this.overscaledZ, wrap, this.canonical.z, this.canonical.x, this.canonical.y); } overscaleFactor() { return Math.pow(2, this.overscaledZ - this.canonical.z); } toUnwrapped() { return new UnwrappedTileID(this.wrap, this.canonical); } toString() { return `${ this.overscaledZ }/${ this.canonical.x }/${ this.canonical.y }`; } getTilePoint(coord) { return this.canonical.getTilePoint(new MercatorCoordinate(coord.x - this.wrap, coord.y)); } getTileVec3(coord) { return this.canonical.getTileVec3(new MercatorCoordinate(coord.x - this.wrap, coord.y, coord.z)); } } function calculateKey(wrap, overscaledZ, z, x, y) { const dim = 1 << Math.min(z, 22); let xy = dim * (y % dim) + x % dim; if (wrap && z < 22) { const bitsAvailable = 2 * (22 - z); xy += dim * dim * ((wrap < 0 ? -2 * wrap - 1 : 2 * wrap) % (1 << bitsAvailable)); } const key = (xy * 32 + z) * 16 + (overscaledZ - z); return key; } function getQuadkey(z, x, y) { let quadkey = '', mask; for (let i = z; i > 0; i--) { mask = 1 << i - 1; quadkey += (x & mask ? 1 : 0) + (y & mask ? 2 : 0); } return quadkey; } register('CanonicalTileID', CanonicalTileID); register('OverscaledTileID', OverscaledTileID, { omit: ['posMatrix'] }); class DictionaryCoder { constructor(strings) { this._stringToNumber = {}; this._numberToString = []; for (let i = 0; i < strings.length; i++) { const string = strings[i]; this._stringToNumber[string] = i; this._numberToString[i] = string; } } encode(string) { return this._stringToNumber[string]; } decode(n) { return this._numberToString[n]; } } class Feature { constructor(vectorTileFeature, z, x, y, id) { this.type = 'Feature'; this._vectorTileFeature = vectorTileFeature; vectorTileFeature._z = z; vectorTileFeature._x = x; vectorTileFeature._y = y; this.properties = vectorTileFeature.properties; this.id = id; } get geometry() { if (this._geometry === undefined) { this._geometry = this._vectorTileFeature.toGeoJSON(this._vectorTileFeature._x, this._vectorTileFeature._y, this._vectorTileFeature._z).geometry; } return this._geometry; } set geometry(g) { this._geometry = g; } toJSON() { const json = { geometry: this.geometry }; for (const i in this) { if (i === '_geometry' || i === '_vectorTileFeature') continue; json[i] = this[i]; } return json; } } function deserialize$1(input, style) { const output = {}; if (!style) return output; for (const bucket of input) { const layers = bucket.layerIds.map(id => style.getLayer(id)).filter(Boolean); if (layers.length === 0) { continue; } bucket.layers = layers; if (bucket.stateDependentLayerIds) { bucket.stateDependentLayers = bucket.stateDependentLayerIds.map(lId => layers.filter(l => l.id === lId)[0]); } for (const layer of layers) { output[layer.id] = bucket; } } return output; } var posAttributes = createLayout([{ name: 'a_pos', type: 'Int16', components: 2 }]); const CLOCK_SKEW_RETRY_TIMEOUT = 30000; class Tile { constructor(tileID, size, tileZoom) { this.tileID = tileID; this.uid = uniqueId(); this.uses = 0; this.tileSize = size; this.tileZoom = tileZoom; this.buckets = {}; this.expirationTime = null; this.queryPadding = 0; this.hasSymbolBuckets = false; this.hasRTLText = false; this.dependencies = {}; this.expiredRequestCount = 0; this.state = 'loading'; } registerFadeDuration(duration) { const fadeEndTime = duration + this.timeAdded; if (fadeEndTime < exported.now()) return; if (this.fadeEndTime && fadeEndTime < this.fadeEndTime) return; this.fadeEndTime = fadeEndTime; } wasRequested() { return this.state === 'errored' || this.state === 'loaded' || this.state === 'reloading'; } loadVectorData(data, painter, justReloaded) { if (this.hasData()) { this.unloadVectorData(); } this.state = 'loaded'; if (!data) { this.collisionBoxArray = new CollisionBoxArray(); return; } if (data.featureIndex) { this.latestFeatureIndex = data.featureIndex; if (data.rawTileData) { this.latestRawTileData = data.rawTileData; this.latestFeatureIndex.rawTileData = data.rawTileData; } else if (this.latestRawTileData) { this.latestFeatureIndex.rawTileData = this.latestRawTileData; } } this.collisionBoxArray = data.collisionBoxArray; this.buckets = deserialize$1(data.buckets, painter.style); this.hasSymbolBuckets = false; for (const id in this.buckets) { const bucket = this.buckets[id]; if (bucket instanceof SymbolBucket) { this.hasSymbolBuckets = true; if (justReloaded) { bucket.justReloaded = true; } else { break; } } } this.hasRTLText = false; if (this.hasSymbolBuckets) { for (const id in this.buckets) { const bucket = this.buckets[id]; if (bucket instanceof SymbolBucket) { if (bucket.hasRTLText) { this.hasRTLText = true; lazyLoadRTLTextPlugin(); break; } } } } this.queryPadding = 0; for (const id in this.buckets) { const bucket = this.buckets[id]; this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(id).queryRadius(bucket)); } if (data.imageAtlas) { this.imageAtlas = data.imageAtlas; } if (data.glyphAtlasImage) { this.glyphAtlasImage = data.glyphAtlasImage; } } unloadVectorData() { for (const id in this.buckets) { this.buckets[id].destroy(); } this.buckets = {}; if (this.imageAtlasTexture) { this.imageAtlasTexture.destroy(); } if (this.imageAtlas) { this.imageAtlas = null; } if (this.glyphAtlasTexture) { this.glyphAtlasTexture.destroy(); } this.latestFeatureIndex = null; this.state = 'unloaded'; } getBucket(layer) { return this.buckets[layer.id]; } upload(context) { for (const id in this.buckets) { const bucket = this.buckets[id]; if (bucket.uploadPending()) { bucket.upload(context); } } const gl = context.gl; if (this.imageAtlas && !this.imageAtlas.uploaded) { this.imageAtlasTexture = new Texture(context, this.imageAtlas.image, gl.RGBA); this.imageAtlas.uploaded = true; } if (this.glyphAtlasImage) { this.glyphAtlasTexture = new Texture(context, this.glyphAtlasImage, gl.ALPHA); this.glyphAtlasImage = null; } } prepare(imageManager) { if (this.imageAtlas) { this.imageAtlas.patchUpdatedImages(imageManager, this.imageAtlasTexture); } } queryRenderedFeatures(layers, serializedLayers, sourceFeatureState, tileResult, params, transform, pixelPosMatrix, visualizeQueryGeometry) { if (!this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData) return {}; return this.latestFeatureIndex.query({ tileResult, pixelPosMatrix, transform, params }, layers, serializedLayers, sourceFeatureState); } querySourceFeatures(result, params) { const featureIndex = this.latestFeatureIndex; if (!featureIndex || !featureIndex.rawTileData) return; const vtLayers = featureIndex.loadVTLayers(); const sourceLayer = params ? params.sourceLayer : ''; const layer = vtLayers._geojsonTileLayer || vtLayers[sourceLayer]; if (!layer) return; const filter = createFilter(params && params.filter); const {z, x, y} = this.tileID.canonical; const coord = { z, x, y }; for (let i = 0; i < layer.length; i++) { const feature = layer.feature(i); if (filter.needGeometry) { const evaluationFeature = toEvaluationFeature(feature, true); if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), evaluationFeature, this.tileID.canonical)) continue; } else if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) { continue; } const id = featureIndex.getId(feature, sourceLayer); const geojsonFeature = new Feature(feature, z, x, y, id); geojsonFeature.tile = coord; result.push(geojsonFeature); } } hasData() { return this.state === 'loaded' || this.state === 'reloading' || this.state === 'expired'; } patternsLoaded() { return this.imageAtlas && !!Object.keys(this.imageAtlas.patternPositions).length; } setExpiryData(data) { const prior = this.expirationTime; if (data.cacheControl) { const parsedCC = parseCacheControl(data.cacheControl); if (parsedCC['max-age']) this.expirationTime = Date.now() + parsedCC['max-age'] * 1000; } else if (data.expires) { this.expirationTime = new Date(data.expires).getTime(); } if (this.expirationTime) { const now = Date.now(); let isExpired = false; if (this.expirationTime > now) { isExpired = false; } else if (!prior) { isExpired = true; } else if (this.expirationTime < prior) { isExpired = true; } else { const delta = this.expirationTime - prior; if (!delta) { isExpired = true; } else { this.expirationTime = now + Math.max(delta, CLOCK_SKEW_RETRY_TIMEOUT); } } if (isExpired) { this.expiredRequestCount++; this.state = 'expired'; } else { this.expiredRequestCount = 0; } } } getExpiryTimeout() { if (this.expirationTime) { if (this.expiredRequestCount) { return 1000 * (1 << Math.min(this.expiredRequestCount - 1, 31)); } else { return Math.min(this.expirationTime - new Date().getTime(), Math.pow(2, 31) - 1); } } } setFeatureState(states, painter) { if (!this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData || Object.keys(states).length === 0) { return; } const vtLayers = this.latestFeatureIndex.loadVTLayers(); for (const id in this.buckets) { if (!painter.style.hasLayer(id)) continue; const bucket = this.buckets[id]; const sourceLayerId = bucket.layers[0]['sourceLayer'] || '_geojsonTileLayer'; const sourceLayer = vtLayers[sourceLayerId]; const sourceLayerStates = states[sourceLayerId]; if (!sourceLayer || !sourceLayerStates || Object.keys(sourceLayerStates).length === 0) continue; bucket.update(sourceLayerStates, sourceLayer, this.imageAtlas && this.imageAtlas.patternPositions || {}); const layer = painter && painter.style && painter.style.getLayer(id); if (layer) { this.queryPadding = Math.max(this.queryPadding, layer.queryRadius(bucket)); } } } holdingForFade() { return this.symbolFadeHoldUntil !== undefined; } symbolFadeFinished() { return !this.symbolFadeHoldUntil || this.symbolFadeHoldUntil < exported.now(); } clearFadeHold() { this.symbolFadeHoldUntil = undefined; } setHoldDuration(duration) { this.symbolFadeHoldUntil = exported.now() + duration; } setDependencies(namespace, dependencies) { const index = {}; for (const dep of dependencies) { index[dep] = true; } this.dependencies[namespace] = index; } hasDependency(namespaces, keys) { for (const namespace of namespaces) { const dependencies = this.dependencies[namespace]; if (dependencies) { for (const key of keys) { if (dependencies[key]) { return true; } } } } return false; } clearQueryDebugViz() { } } class SourceFeatureState { constructor() { this.state = {}; this.stateChanges = {}; this.deletedStates = {}; } updateState(sourceLayer, featureId, newState) { const feature = String(featureId); this.stateChanges[sourceLayer] = this.stateChanges[sourceLayer] || {}; this.stateChanges[sourceLayer][feature] = this.stateChanges[sourceLayer][feature] || {}; extend(this.stateChanges[sourceLayer][feature], newState); if (this.deletedStates[sourceLayer] === null) { this.deletedStates[sourceLayer] = {}; for (const ft in this.state[sourceLayer]) { if (ft !== feature) this.deletedStates[sourceLayer][ft] = null; } } else { const featureDeletionQueued = this.deletedStates[sourceLayer] && this.deletedStates[sourceLayer][feature] === null; if (featureDeletionQueued) { this.deletedStates[sourceLayer][feature] = {}; for (const prop in this.state[sourceLayer][feature]) { if (!newState[prop]) this.deletedStates[sourceLayer][feature][prop] = null; } } else { for (const key in newState) { const deletionInQueue = this.deletedStates[sourceLayer] && this.deletedStates[sourceLayer][feature] && this.deletedStates[sourceLayer][feature][key] === null; if (deletionInQueue) delete this.deletedStates[sourceLayer][feature][key]; } } } } removeFeatureState(sourceLayer, featureId, key) { const sourceLayerDeleted = this.deletedStates[sourceLayer] === null; if (sourceLayerDeleted) return; const feature = String(featureId); this.deletedStates[sourceLayer] = this.deletedStates[sourceLayer] || {}; if (key && featureId !== undefined) { if (this.deletedStates[sourceLayer][feature] !== null) { this.deletedStates[sourceLayer][feature] = this.deletedStates[sourceLayer][feature] || {}; this.deletedStates[sourceLayer][feature][key] = null; } } else if (featureId !== undefined) { const updateInQueue = this.stateChanges[sourceLayer] && this.stateChanges[sourceLayer][feature]; if (updateInQueue) { this.deletedStates[sourceLayer][feature] = {}; for (key in this.stateChanges[sourceLayer][feature]) this.deletedStates[sourceLayer][feature][key] = null; } else { this.deletedStates[sourceLayer][feature] = null; } } else { this.deletedStates[sourceLayer] = null; } } getState(sourceLayer, featureId) { const feature = String(featureId); const base = this.state[sourceLayer] || {}; const changes = this.stateChanges[sourceLayer] || {}; const reconciledState = extend({}, base[feature], changes[feature]); if (this.deletedStates[sourceLayer] === null) return {}; else if (this.deletedStates[sourceLayer]) { const featureDeletions = this.deletedStates[sourceLayer][featureId]; if (featureDeletions === null) return {}; for (const prop in featureDeletions) delete reconciledState[prop]; } return reconciledState; } initializeTileState(tile, painter) { tile.setFeatureState(this.state, painter); } coalesceChanges(tiles, painter) { const featuresChanged = {}; for (const sourceLayer in this.stateChanges) { this.state[sourceLayer] = this.state[sourceLayer] || {}; const layerStates = {}; for (const feature in this.stateChanges[sourceLayer]) { if (!this.state[sourceLayer][feature]) this.state[sourceLayer][feature] = {}; extend(this.state[sourceLayer][feature], this.stateChanges[sourceLayer][feature]); layerStates[feature] = this.state[sourceLayer][feature]; } featuresChanged[sourceLayer] = layerStates; } for (const sourceLayer in this.deletedStates) { this.state[sourceLayer] = this.state[sourceLayer] || {}; const layerStates = {}; if (this.deletedStates[sourceLayer] === null) { for (const ft in this.state[sourceLayer]) { layerStates[ft] = {}; this.state[sourceLayer][ft] = {}; } } else { for (const feature in this.deletedStates[sourceLayer]) { const deleteWholeFeatureState = this.deletedStates[sourceLayer][feature] === null; if (deleteWholeFeatureState) this.state[sourceLayer][feature] = {}; else { for (const key of Object.keys(this.deletedStates[sourceLayer][feature])) { delete this.state[sourceLayer][feature][key]; } } layerStates[feature] = this.state[sourceLayer][feature]; } } featuresChanged[sourceLayer] = featuresChanged[sourceLayer] || {}; extend(featuresChanged[sourceLayer], layerStates); } this.stateChanges = {}; this.deletedStates = {}; if (Object.keys(featuresChanged).length === 0) return; for (const id in tiles) { const tile = tiles[id]; tile.setFeatureState(featuresChanged, painter); } } } class MipLevel { constructor(size_) { this.size = size_; this.minimums = []; this.maximums = []; this.leaves = []; } getElevation(x, y) { const idx = this.toIdx(x, y); return { min: this.minimums[idx], max: this.maximums[idx] }; } isLeaf(x, y) { return this.leaves[this.toIdx(x, y)]; } toIdx(x, y) { return y * this.size + x; } } function aabbRayIntersect(min, max, pos, dir) { let tMin = 0; let tMax = Number.MAX_VALUE; const epsilon = 1e-15; for (let i = 0; i < 3; i++) { if (Math.abs(dir[i]) < epsilon) { if (pos[i] < min[i] || pos[i] > max[i]) return null; } else { const ood = 1 / dir[i]; let t1 = (min[i] - pos[i]) * ood; let t2 = (max[i] - pos[i]) * ood; if (t1 > t2) { const temp = t1; t1 = t2; t2 = temp; } if (t1 > tMin) tMin = t1; if (t2 < tMax) tMax = t2; if (tMin > tMax) return null; } } return tMin; } function triangleRayIntersect(ax, ay, az, bx, by, bz, cx, cy, cz, pos, dir) { const abX = bx - ax; const abY = by - ay; const abZ = bz - az; const acX = cx - ax; const acY = cy - ay; const acZ = cz - az; const pvecX = dir[1] * acZ - dir[2] * acY; const pvecY = dir[2] * acX - dir[0] * acZ; const pvecZ = dir[0] * acY - dir[1] * acX; const det = abX * pvecX + abY * pvecY + abZ * pvecZ; if (Math.abs(det) < 1e-15) return null; const invDet = 1 / det; const tvecX = pos[0] - ax; const tvecY = pos[1] - ay; const tvecZ = pos[2] - az; const u = (tvecX * pvecX + tvecY * pvecY + tvecZ * pvecZ) * invDet; if (u < 0 || u > 1) return null; const qvecX = tvecY * abZ - tvecZ * abY; const qvecY = tvecZ * abX - tvecX * abZ; const qvecZ = tvecX * abY - tvecY * abX; const v = (dir[0] * qvecX + dir[1] * qvecY + dir[2] * qvecZ) * invDet; if (v < 0 || u + v > 1) return null; return (acX * qvecX + acY * qvecY + acZ * qvecZ) * invDet; } function frac(v, lo, hi) { return (v - lo) / (hi - lo); } function decodeBounds(x, y, depth, boundsMinx, boundsMiny, boundsMaxx, boundsMaxy, outMin, outMax) { const scale = 1 << depth; const rangex = boundsMaxx - boundsMinx; const rangey = boundsMaxy - boundsMiny; const minX = (x + 0) / scale * rangex + boundsMinx; const maxX = (x + 1) / scale * rangex + boundsMinx; const minY = (y + 0) / scale * rangey + boundsMiny; const maxY = (y + 1) / scale * rangey + boundsMiny; outMin[0] = minX; outMin[1] = minY; outMax[0] = maxX; outMax[1] = maxY; } const aabbSkirtPadding = 100; class DemMinMaxQuadTree { constructor(dem_) { this.maximums = []; this.minimums = []; this.leaves = []; this.childOffsets = []; this.nodeCount = 0; this.dem = dem_; this._siblingOffset = [ [ 0, 0 ], [ 1, 0 ], [ 0, 1 ], [ 1, 1 ] ]; if (!this.dem) return; const mips = buildDemMipmap(this.dem); const maxLvl = mips.length - 1; const rootMip = mips[maxLvl]; const min = rootMip.minimums; const max = rootMip.maximums; const leaves = rootMip.leaves; this._addNode(min[0], max[0], leaves[0]); this._construct(mips, 0, 0, maxLvl, 0); } raycastRoot(minx, miny, maxx, maxy, p, d, exaggeration = 1) { const min = [ minx, miny, -aabbSkirtPadding ]; const max = [ maxx, maxy, this.maximums[0] * exaggeration ]; return aabbRayIntersect(min, max, p, d); } raycast(rootMinx, rootMiny, rootMaxx, rootMaxy, p, d, exaggeration = 1) { if (!this.nodeCount) return null; const t = this.raycastRoot(rootMinx, rootMiny, rootMaxx, rootMaxy, p, d, exaggeration); if (t == null) return null; const tHits = []; const sortedHits = []; const boundsMin = []; const boundsMax = []; const stack = [{ idx: 0, t, nodex: 0, nodey: 0, depth: 0 }]; while (stack.length > 0) { const {idx, t, nodex, nodey, depth} = stack.pop(); if (this.leaves[idx]) { decodeBounds(nodex, nodey, depth, rootMinx, rootMiny, rootMaxx, rootMaxy, boundsMin, boundsMax); const scale = 1 << depth; const minxUv = (nodex + 0) / scale; const maxxUv = (nodex + 1) / scale; const minyUv = (nodey + 0) / scale; const maxyUv = (nodey + 1) / scale; const az = sampleElevation(minxUv, minyUv, this.dem) * exaggeration; const bz = sampleElevation(maxxUv, minyUv, this.dem) * exaggeration; const cz = sampleElevation(maxxUv, maxyUv, this.dem) * exaggeration; const dz = sampleElevation(minxUv, maxyUv, this.dem) * exaggeration; const t0 = triangleRayIntersect(boundsMin[0], boundsMin[1], az, boundsMax[0], boundsMin[1], bz, boundsMax[0], boundsMax[1], cz, p, d); const t1 = triangleRayIntersect(boundsMax[0], boundsMax[1], cz, boundsMin[0], boundsMax[1], dz, boundsMin[0], boundsMin[1], az, p, d); const tMin = Math.min(t0 !== null ? t0 : Number.MAX_VALUE, t1 !== null ? t1 : Number.MAX_VALUE); if (tMin === Number.MAX_VALUE) { const hitPos = scaleAndAdd([], p, d, t); const fracx = frac(hitPos[0], boundsMin[0], boundsMax[0]); const fracy = frac(hitPos[1], boundsMin[1], boundsMax[1]); if (bilinearLerp(az, bz, dz, cz, fracx, fracy) >= hitPos[2]) return t; } else { return tMin; } continue; } let hitCount = 0; for (let i = 0; i < this._siblingOffset.length; i++) { const childNodeX = (nodex << 1) + this._siblingOffset[i][0]; const childNodeY = (nodey << 1) + this._siblingOffset[i][1]; decodeBounds(childNodeX, childNodeY, depth + 1, rootMinx, rootMiny, rootMaxx, rootMaxy, boundsMin, boundsMax); boundsMin[2] = -aabbSkirtPadding; boundsMax[2] = this.maximums[this.childOffsets[idx] + i] * exaggeration; const result = aabbRayIntersect(boundsMin, boundsMax, p, d); if (result != null) { const tHit = result; tHits[i] = tHit; let added = false; for (let j = 0; j < hitCount && !added; j++) { if (tHit >= tHits[sortedHits[j]]) { sortedHits.splice(j, 0, i); added = true; } } if (!added) sortedHits[hitCount] = i; hitCount++; } } for (let i = 0; i < hitCount; i++) { const hitIdx = sortedHits[i]; stack.push({ idx: this.childOffsets[idx] + hitIdx, t: tHits[hitIdx], nodex: (nodex << 1) + this._siblingOffset[hitIdx][0], nodey: (nodey << 1) + this._siblingOffset[hitIdx][1], depth: depth + 1 }); } } return null; } _addNode(min, max, leaf) { this.minimums.push(min); this.maximums.push(max); this.leaves.push(leaf); this.childOffsets.push(0); return this.nodeCount++; } _construct(mips, x, y, lvl, parentIdx) { if (mips[lvl].isLeaf(x, y) === 1) { return; } if (!this.childOffsets[parentIdx]) this.childOffsets[parentIdx] = this.nodeCount; const childLvl = lvl - 1; const childMip = mips[childLvl]; let leafMask = 0; let firstNodeIdx; for (let i = 0; i < this._siblingOffset.length; i++) { const childX = x * 2 + this._siblingOffset[i][0]; const childY = y * 2 + this._siblingOffset[i][1]; const elevation = childMip.getElevation(childX, childY); const leaf = childMip.isLeaf(childX, childY); const nodeIdx = this._addNode(elevation.min, elevation.max, leaf); if (leaf) leafMask |= 1 << i; if (!firstNodeIdx) firstNodeIdx = nodeIdx; } for (let i = 0; i < this._siblingOffset.length; i++) { if (!(leafMask & 1 << i)) { this._construct(mips, x * 2 + this._siblingOffset[i][0], y * 2 + this._siblingOffset[i][1], childLvl, firstNodeIdx + i); } } } } function bilinearLerp(p00, p10, p01, p11, x, y) { return number(number(p00, p01, y), number(p10, p11, y), x); } function sampleElevation(fx, fy, dem) { const demSize = dem.dim; const x = clamp(fx * demSize - 0.5, 0, demSize - 1); const y = clamp(fy * demSize - 0.5, 0, demSize - 1); const ixMin = Math.floor(x); const iyMin = Math.floor(y); const ixMax = Math.min(ixMin + 1, demSize - 1); const iyMax = Math.min(iyMin + 1, demSize - 1); const e00 = dem.get(ixMin, iyMin); const e10 = dem.get(ixMax, iyMin); const e01 = dem.get(ixMin, iyMax); const e11 = dem.get(ixMax, iyMax); return bilinearLerp(e00, e10, e01, e11, x - ixMin, y - iyMin); } function buildDemMipmap(dem) { const demSize = dem.dim; const elevationDiffThreshold = 5; const texelSizeOfMip0 = 8; const levelCount = Math.ceil(Math.log2(demSize / texelSizeOfMip0)); const mips = []; let blockCount = Math.ceil(Math.pow(2, levelCount)); const blockSize = 1 / blockCount; const blockSamples = (x, y, size, exclusive, outBounds) => { const padding = exclusive ? 1 : 0; const minx = x * size; const maxx = (x + 1) * size - padding; const miny = y * size; const maxy = (y + 1) * size - padding; outBounds[0] = minx; outBounds[1] = miny; outBounds[2] = maxx; outBounds[3] = maxy; }; let mip = new MipLevel(blockCount); const blockBounds = []; for (let idx = 0; idx < blockCount * blockCount; idx++) { const y = Math.floor(idx / blockCount); const x = idx % blockCount; blockSamples(x, y, blockSize, false, blockBounds); const e0 = sampleElevation(blockBounds[0], blockBounds[1], dem); const e1 = sampleElevation(blockBounds[2], blockBounds[1], dem); const e2 = sampleElevation(blockBounds[2], blockBounds[3], dem); const e3 = sampleElevation(blockBounds[0], blockBounds[3], dem); mip.minimums.push(Math.min(e0, e1, e2, e3)); mip.maximums.push(Math.max(e0, e1, e2, e3)); mip.leaves.push(1); } mips.push(mip); for (blockCount /= 2; blockCount >= 1; blockCount /= 2) { const prevMip = mips[mips.length - 1]; mip = new MipLevel(blockCount); for (let idx = 0; idx < blockCount * blockCount; idx++) { const y = Math.floor(idx / blockCount); const x = idx % blockCount; blockSamples(x, y, 2, true, blockBounds); const e0 = prevMip.getElevation(blockBounds[0], blockBounds[1]); const e1 = prevMip.getElevation(blockBounds[2], blockBounds[1]); const e2 = prevMip.getElevation(blockBounds[2], blockBounds[3]); const e3 = prevMip.getElevation(blockBounds[0], blockBounds[3]); const l0 = prevMip.isLeaf(blockBounds[0], blockBounds[1]); const l1 = prevMip.isLeaf(blockBounds[2], blockBounds[1]); const l2 = prevMip.isLeaf(blockBounds[2], blockBounds[3]); const l3 = prevMip.isLeaf(blockBounds[0], blockBounds[3]); const minElevation = Math.min(e0.min, e1.min, e2.min, e3.min); const maxElevation = Math.max(e0.max, e1.max, e2.max, e3.max); const canConcatenate = l0 && l1 && l2 && l3; mip.maximums.push(maxElevation); mip.minimums.push(minElevation); if (maxElevation - minElevation <= elevationDiffThreshold && canConcatenate) { mip.leaves.push(1); } else { mip.leaves.push(0); } } mips.push(mip); } return mips; } const unpackVectors = { mapbox: [ 6553.6, 25.6, 0.1, 10000 ], terrarium: [ 256, 1, 1 / 256, 32768 ] }; class DEMData { get tree() { if (!this._tree) this._buildQuadTree(); return this._tree; } constructor(uid, data, encoding, borderReady = false, buildQuadTree = false) { this.uid = uid; if (data.height !== data.width) throw new RangeError('DEM tiles must be square'); if (encoding && encoding !== 'mapbox' && encoding !== 'terrarium') return warnOnce(`"${ encoding }" is not a valid encoding type. Valid types include "mapbox" and "terrarium".`); this.stride = data.height; const dim = this.dim = data.height - 2; this.data = new Uint32Array(data.data.buffer); this.encoding = encoding || 'mapbox'; this.borderReady = borderReady; if (borderReady) return; for (let x = 0; x < dim; x++) { this.data[this._idx(-1, x)] = this.data[this._idx(0, x)]; this.data[this._idx(dim, x)] = this.data[this._idx(dim - 1, x)]; this.data[this._idx(x, -1)] = this.data[this._idx(x, 0)]; this.data[this._idx(x, dim)] = this.data[this._idx(x, dim - 1)]; } this.data[this._idx(-1, -1)] = this.data[this._idx(0, 0)]; this.data[this._idx(dim, -1)] = this.data[this._idx(dim - 1, 0)]; this.data[this._idx(-1, dim)] = this.data[this._idx(0, dim - 1)]; this.data[this._idx(dim, dim)] = this.data[this._idx(dim - 1, dim - 1)]; if (buildQuadTree) this._buildQuadTree(); } _buildQuadTree() { this._tree = new DemMinMaxQuadTree(this); } get(x, y, clampToEdge = false) { const pixels = new Uint8Array(this.data.buffer); if (clampToEdge) { x = clamp(x, -1, this.dim); y = clamp(y, -1, this.dim); } const index = this._idx(x, y) * 4; const unpack = this.encoding === 'terrarium' ? this._unpackTerrarium : this._unpackMapbox; return unpack(pixels[index], pixels[index + 1], pixels[index + 2]); } static getUnpackVector(encoding) { return unpackVectors[encoding]; } get unpackVector() { return unpackVectors[this.encoding]; } _idx(x, y) { if (x < -1 || x >= this.dim + 1 || y < -1 || y >= this.dim + 1) throw new RangeError('out of range source coordinates for DEM data'); return (y + 1) * this.stride + (x + 1); } _unpackMapbox(r, g, b) { return (r * 256 * 256 + g * 256 + b) / 10 - 10000; } _unpackTerrarium(r, g, b) { return r * 256 + g + b / 256 - 32768; } static pack(altitude, encoding) { const color = [ 0, 0, 0, 0 ]; const vector = DEMData.getUnpackVector(encoding); let v = Math.floor((altitude + vector[3]) / vector[2]); color[2] = v % 256; v = Math.floor(v / 256); color[1] = v % 256; v = Math.floor(v / 256); color[0] = v; return color; } getPixels() { return new RGBAImage({ width: this.stride, height: this.stride }, new Uint8Array(this.data.buffer)); } backfillBorder(borderTile, dx, dy) { if (this.dim !== borderTile.dim) throw new Error('dem dimension mismatch'); let xMin = dx * this.dim, xMax = dx * this.dim + this.dim, yMin = dy * this.dim, yMax = dy * this.dim + this.dim; switch (dx) { case -1: xMin = xMax - 1; break; case 1: xMax = xMin + 1; break; } switch (dy) { case -1: yMin = yMax - 1; break; case 1: yMax = yMin + 1; break; } const ox = -dx * this.dim; const oy = -dy * this.dim; for (let y = yMin; y < yMax; y++) { for (let x = xMin; x < xMax; x++) { this.data[this._idx(x, y)] = borderTile.data[this._idx(x + ox, y + oy)]; } } } onDeserialize() { if (this._tree) this._tree.dem = this; } } register('DEMData', DEMData); register('DemMinMaxQuadTree', DemMinMaxQuadTree, { omit: ['dem'] }); class TileCache { constructor(max, onRemove) { this.max = max; this.onRemove = onRemove; this.reset(); } reset() { for (const key in this.data) { for (const removedData of this.data[key]) { if (removedData.timeout) clearTimeout(removedData.timeout); this.onRemove(removedData.value); } } this.data = {}; this.order = []; return this; } add(tileID, data, expiryTimeout) { const key = tileID.wrapped().key; if (this.data[key] === undefined) { this.data[key] = []; } const dataWrapper = { value: data, timeout: undefined }; if (expiryTimeout !== undefined) { dataWrapper.timeout = setTimeout(() => { this.remove(tileID, dataWrapper); }, expiryTimeout); } this.data[key].push(dataWrapper); this.order.push(key); if (this.order.length > this.max) { const removedData = this._getAndRemoveByKey(this.order[0]); if (removedData) this.onRemove(removedData); } return this; } has(tileID) { return tileID.wrapped().key in this.data; } getAndRemove(tileID) { if (!this.has(tileID)) { return null; } return this._getAndRemoveByKey(tileID.wrapped().key); } _getAndRemoveByKey(key) { const data = this.data[key].shift(); if (data.timeout) clearTimeout(data.timeout); if (this.data[key].length === 0) { delete this.data[key]; } this.order.splice(this.order.indexOf(key), 1); return data.value; } getByKey(key) { const data = this.data[key]; return data ? data[0].value : null; } get(tileID) { if (!this.has(tileID)) { return null; } const data = this.data[tileID.wrapped().key][0]; return data.value; } remove(tileID, value) { if (!this.has(tileID)) { return this; } const key = tileID.wrapped().key; const dataIndex = value === undefined ? 0 : this.data[key].indexOf(value); const data = this.data[key][dataIndex]; this.data[key].splice(dataIndex, 1); if (data.timeout) clearTimeout(data.timeout); if (this.data[key].length === 0) { delete this.data[key]; } this.onRemove(data.value); this.order.splice(this.order.indexOf(key), 1); return this; } setMaxSize(max) { this.max = max; while (this.order.length > this.max) { const removedData = this._getAndRemoveByKey(this.order[0]); if (removedData) this.onRemove(removedData); } return this; } filter(filterFn) { const removed = []; for (const key in this.data) { for (const entry of this.data[key]) { if (!filterFn(entry.value)) { removed.push(entry); } } } for (const r of removed) { this.remove(r.value.tileID, r); } } } class IndexBuffer { constructor(context, array, dynamicDraw) { this.context = context; const gl = context.gl; this.buffer = gl.createBuffer(); this.dynamicDraw = Boolean(dynamicDraw); this.context.unbindVAO(); context.bindElementBuffer.set(this.buffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, array.arrayBuffer, this.dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); if (!this.dynamicDraw) { delete array.arrayBuffer; } } bind() { this.context.bindElementBuffer.set(this.buffer); } updateData(array) { const gl = this.context.gl; this.context.unbindVAO(); this.bind(); gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, array.arrayBuffer); } destroy() { const gl = this.context.gl; if (this.buffer) { gl.deleteBuffer(this.buffer); delete this.buffer; } } } const AttributeType = { Int8: 'BYTE', Uint8: 'UNSIGNED_BYTE', Int16: 'SHORT', Uint16: 'UNSIGNED_SHORT', Int32: 'INT', Uint32: 'UNSIGNED_INT', Float32: 'FLOAT' }; class VertexBuffer { constructor(context, array, attributes, dynamicDraw) { this.length = array.length; this.attributes = attributes; this.itemSize = array.bytesPerElement; this.dynamicDraw = dynamicDraw; this.context = context; const gl = context.gl; this.buffer = gl.createBuffer(); context.bindVertexBuffer.set(this.buffer); gl.bufferData(gl.ARRAY_BUFFER, array.arrayBuffer, this.dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); if (!this.dynamicDraw) { delete array.arrayBuffer; } } bind() { this.context.bindVertexBuffer.set(this.buffer); } updateData(array) { const gl = this.context.gl; this.bind(); gl.bufferSubData(gl.ARRAY_BUFFER, 0, array.arrayBuffer); } enableAttributes(gl, program) { for (let j = 0; j < this.attributes.length; j++) { const member = this.attributes[j]; const attribIndex = program.attributes[member.name]; if (attribIndex !== undefined) { gl.enableVertexAttribArray(attribIndex); } } } setVertexAttribPointers(gl, program, vertexOffset) { for (let j = 0; j < this.attributes.length; j++) { const member = this.attributes[j]; const attribIndex = program.attributes[member.name]; if (attribIndex !== undefined) { gl.vertexAttribPointer(attribIndex, member.components, gl[AttributeType[member.type]], false, this.itemSize, member.offset + this.itemSize * (vertexOffset || 0)); } } } destroy() { const gl = this.context.gl; if (this.buffer) { gl.deleteBuffer(this.buffer); delete this.buffer; } } } class BaseValue { constructor(context) { this.gl = context.gl; this.default = this.getDefault(); this.current = this.default; this.dirty = false; } get() { return this.current; } set(value) { } getDefault() { return this.default; } setDefault() { this.set(this.default); } } class ClearColor extends BaseValue { getDefault() { return Color.transparent; } set(v) { const c = this.current; if (v.r === c.r && v.g === c.g && v.b === c.b && v.a === c.a && !this.dirty) return; this.gl.clearColor(v.r, v.g, v.b, v.a); this.current = v; this.dirty = false; } } class ClearDepth extends BaseValue { getDefault() { return 1; } set(v) { if (v === this.current && !this.dirty) return; this.gl.clearDepth(v); this.current = v; this.dirty = false; } } class ClearStencil extends BaseValue { getDefault() { return 0; } set(v) { if (v === this.current && !this.dirty) return; this.gl.clearStencil(v); this.current = v; this.dirty = false; } } class ColorMask extends BaseValue { getDefault() { return [ true, true, true, true ]; } set(v) { const c = this.current; if (v[0] === c[0] && v[1] === c[1] && v[2] === c[2] && v[3] === c[3] && !this.dirty) return; this.gl.colorMask(v[0], v[1], v[2], v[3]); this.current = v; this.dirty = false; } } class DepthMask extends BaseValue { getDefault() { return true; } set(v) { if (v === this.current && !this.dirty) return; this.gl.depthMask(v); this.current = v; this.dirty = false; } } class StencilMask extends BaseValue { getDefault() { return 255; } set(v) { if (v === this.current && !this.dirty) return; this.gl.stencilMask(v); this.current = v; this.dirty = false; } } class StencilFunc extends BaseValue { getDefault() { return { func: this.gl.ALWAYS, ref: 0, mask: 255 }; } set(v) { const c = this.current; if (v.func === c.func && v.ref === c.ref && v.mask === c.mask && !this.dirty) return; this.gl.stencilFunc(v.func, v.ref, v.mask); this.current = v; this.dirty = false; } } class StencilOp extends BaseValue { getDefault() { const gl = this.gl; return [ gl.KEEP, gl.KEEP, gl.KEEP ]; } set(v) { const c = this.current; if (v[0] === c[0] && v[1] === c[1] && v[2] === c[2] && !this.dirty) return; this.gl.stencilOp(v[0], v[1], v[2]); this.current = v; this.dirty = false; } } class StencilTest extends BaseValue { getDefault() { return false; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; if (v) { gl.enable(gl.STENCIL_TEST); } else { gl.disable(gl.STENCIL_TEST); } this.current = v; this.dirty = false; } } class DepthRange extends BaseValue { getDefault() { return [ 0, 1 ]; } set(v) { const c = this.current; if (v[0] === c[0] && v[1] === c[1] && !this.dirty) return; this.gl.depthRange(v[0], v[1]); this.current = v; this.dirty = false; } } class DepthTest extends BaseValue { getDefault() { return false; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; if (v) { gl.enable(gl.DEPTH_TEST); } else { gl.disable(gl.DEPTH_TEST); } this.current = v; this.dirty = false; } } class DepthFunc extends BaseValue { getDefault() { return this.gl.LESS; } set(v) { if (v === this.current && !this.dirty) return; this.gl.depthFunc(v); this.current = v; this.dirty = false; } } class Blend extends BaseValue { getDefault() { return false; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; if (v) { gl.enable(gl.BLEND); } else { gl.disable(gl.BLEND); } this.current = v; this.dirty = false; } } class BlendFunc extends BaseValue { getDefault() { const gl = this.gl; return [ gl.ONE, gl.ZERO ]; } set(v) { const c = this.current; if (v[0] === c[0] && v[1] === c[1] && !this.dirty) return; this.gl.blendFunc(v[0], v[1]); this.current = v; this.dirty = false; } } class BlendColor extends BaseValue { getDefault() { return Color.transparent; } set(v) { const c = this.current; if (v.r === c.r && v.g === c.g && v.b === c.b && v.a === c.a && !this.dirty) return; this.gl.blendColor(v.r, v.g, v.b, v.a); this.current = v; this.dirty = false; } } class BlendEquation extends BaseValue { getDefault() { return this.gl.FUNC_ADD; } set(v) { if (v === this.current && !this.dirty) return; this.gl.blendEquation(v); this.current = v; this.dirty = false; } } class CullFace extends BaseValue { getDefault() { return false; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; if (v) { gl.enable(gl.CULL_FACE); } else { gl.disable(gl.CULL_FACE); } this.current = v; this.dirty = false; } } class CullFaceSide extends BaseValue { getDefault() { return this.gl.BACK; } set(v) { if (v === this.current && !this.dirty) return; this.gl.cullFace(v); this.current = v; this.dirty = false; } } class FrontFace extends BaseValue { getDefault() { return this.gl.CCW; } set(v) { if (v === this.current && !this.dirty) return; this.gl.frontFace(v); this.current = v; this.dirty = false; } } class Program extends BaseValue { getDefault() { return null; } set(v) { if (v === this.current && !this.dirty) return; this.gl.useProgram(v); this.current = v; this.dirty = false; } } class ActiveTextureUnit extends BaseValue { getDefault() { return this.gl.TEXTURE0; } set(v) { if (v === this.current && !this.dirty) return; this.gl.activeTexture(v); this.current = v; this.dirty = false; } } class Viewport extends BaseValue { getDefault() { const gl = this.gl; return [ 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight ]; } set(v) { const c = this.current; if (v[0] === c[0] && v[1] === c[1] && v[2] === c[2] && v[3] === c[3] && !this.dirty) return; this.gl.viewport(v[0], v[1], v[2], v[3]); this.current = v; this.dirty = false; } } class BindFramebuffer extends BaseValue { getDefault() { return null; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; gl.bindFramebuffer(gl.FRAMEBUFFER, v); this.current = v; this.dirty = false; } } class BindRenderbuffer extends BaseValue { getDefault() { return null; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; gl.bindRenderbuffer(gl.RENDERBUFFER, v); this.current = v; this.dirty = false; } } class BindTexture extends BaseValue { getDefault() { return null; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; gl.bindTexture(gl.TEXTURE_2D, v); this.current = v; this.dirty = false; } } class BindVertexBuffer extends BaseValue { getDefault() { return null; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; gl.bindBuffer(gl.ARRAY_BUFFER, v); this.current = v; this.dirty = false; } } class BindElementBuffer extends BaseValue { getDefault() { return null; } set(v) { const gl = this.gl; gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, v); this.current = v; this.dirty = false; } } class BindVertexArrayOES extends BaseValue { constructor(context) { super(context); this.vao = context.extVertexArrayObject; } getDefault() { return null; } set(v) { if (!this.vao || v === this.current && !this.dirty) return; this.vao.bindVertexArrayOES(v); this.current = v; this.dirty = false; } } class PixelStoreUnpack extends BaseValue { getDefault() { return 4; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; gl.pixelStorei(gl.UNPACK_ALIGNMENT, v); this.current = v; this.dirty = false; } } class PixelStoreUnpackPremultiplyAlpha extends BaseValue { getDefault() { return false; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, v); this.current = v; this.dirty = false; } } class PixelStoreUnpackFlipY extends BaseValue { getDefault() { return false; } set(v) { if (v === this.current && !this.dirty) return; const gl = this.gl; gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, v); this.current = v; this.dirty = false; } } class FramebufferAttachment extends BaseValue { constructor(context, parent) { super(context); this.context = context; this.parent = parent; } getDefault() { return null; } } class ColorAttachment extends FramebufferAttachment { setDirty() { this.dirty = true; } set(v) { if (v === this.current && !this.dirty) return; this.context.bindFramebuffer.set(this.parent); const gl = this.gl; gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, v, 0); this.current = v; this.dirty = false; } } class DepthAttachment extends FramebufferAttachment { attachment() { return this.gl.DEPTH_ATTACHMENT; } set(v) { if (v === this.current && !this.dirty) return; this.context.bindFramebuffer.set(this.parent); const gl = this.gl; gl.framebufferRenderbuffer(gl.FRAMEBUFFER, this.attachment(), gl.RENDERBUFFER, v); this.current = v; this.dirty = false; } } class DepthStencilAttachment extends DepthAttachment { attachment() { return this.gl.DEPTH_STENCIL_ATTACHMENT; } } class Framebuffer { constructor(context, width, height, hasDepth) { this.context = context; this.width = width; this.height = height; const gl = context.gl; const fbo = this.framebuffer = gl.createFramebuffer(); this.colorAttachment = new ColorAttachment(context, fbo); if (hasDepth) { this.depthAttachment = new DepthAttachment(context, fbo); } } destroy() { const gl = this.context.gl; const texture = this.colorAttachment.get(); if (texture) gl.deleteTexture(texture); if (this.depthAttachment) { const renderbuffer = this.depthAttachment.get(); if (renderbuffer) gl.deleteRenderbuffer(renderbuffer); } gl.deleteFramebuffer(this.framebuffer); } } const ALWAYS = 519; class DepthMode { constructor(depthFunc, depthMask, depthRange) { this.func = depthFunc; this.mask = depthMask; this.range = depthRange; } } DepthMode.ReadOnly = false; DepthMode.ReadWrite = true; DepthMode.disabled = new DepthMode(ALWAYS, DepthMode.ReadOnly, [ 0, 1 ]); const ALWAYS$1 = 519; const KEEP = 7680; class StencilMode { constructor(test, ref, mask, fail, depthFail, pass) { this.test = test; this.ref = ref; this.mask = mask; this.fail = fail; this.depthFail = depthFail; this.pass = pass; } } StencilMode.disabled = new StencilMode({ func: ALWAYS$1, mask: 0 }, 0, 0, KEEP, KEEP, KEEP); const ZERO = 0; const ONE = 1; const ONE_MINUS_SRC_ALPHA = 771; class ColorMode { constructor(blendFunction, blendColor, mask) { this.blendFunction = blendFunction; this.blendColor = blendColor; this.mask = mask; } } ColorMode.Replace = [ ONE, ZERO ]; ColorMode.disabled = new ColorMode(ColorMode.Replace, Color.transparent, [ false, false, false, false ]); ColorMode.unblended = new ColorMode(ColorMode.Replace, Color.transparent, [ true, true, true, true ]); ColorMode.alphaBlended = new ColorMode([ ONE, ONE_MINUS_SRC_ALPHA ], Color.transparent, [ true, true, true, true ]); const BACK = 1029; const FRONT = 1028; const CCW = 2305; const CW = 2304; class CullFaceMode { constructor(enable, mode, frontFace) { this.enable = enable; this.mode = mode; this.frontFace = frontFace; } } CullFaceMode.disabled = new CullFaceMode(false, BACK, CCW); CullFaceMode.backCCW = new CullFaceMode(true, BACK, CCW); CullFaceMode.backCW = new CullFaceMode(true, BACK, CW); CullFaceMode.frontCW = new CullFaceMode(true, FRONT, CW); CullFaceMode.frontCCW = new CullFaceMode(true, FRONT, CCW); class Context { constructor(gl) { this.gl = gl; this.extVertexArrayObject = this.gl.getExtension('OES_vertex_array_object'); this.clearColor = new ClearColor(this); this.clearDepth = new ClearDepth(this); this.clearStencil = new ClearStencil(this); this.colorMask = new ColorMask(this); this.depthMask = new DepthMask(this); this.stencilMask = new StencilMask(this); this.stencilFunc = new StencilFunc(this); this.stencilOp = new StencilOp(this); this.stencilTest = new StencilTest(this); this.depthRange = new DepthRange(this); this.depthTest = new DepthTest(this); this.depthFunc = new DepthFunc(this); this.blend = new Blend(this); this.blendFunc = new BlendFunc(this); this.blendColor = new BlendColor(this); this.blendEquation = new BlendEquation(this); this.cullFace = new CullFace(this); this.cullFaceSide = new CullFaceSide(this); this.frontFace = new FrontFace(this); this.program = new Program(this); this.activeTexture = new ActiveTextureUnit(this); this.viewport = new Viewport(this); this.bindFramebuffer = new BindFramebuffer(this); this.bindRenderbuffer = new BindRenderbuffer(this); this.bindTexture = new BindTexture(this); this.bindVertexBuffer = new BindVertexBuffer(this); this.bindElementBuffer = new BindElementBuffer(this); this.bindVertexArrayOES = this.extVertexArrayObject && new BindVertexArrayOES(this); this.pixelStoreUnpack = new PixelStoreUnpack(this); this.pixelStoreUnpackPremultiplyAlpha = new PixelStoreUnpackPremultiplyAlpha(this); this.pixelStoreUnpackFlipY = new PixelStoreUnpackFlipY(this); this.extTextureFilterAnisotropic = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic'); if (this.extTextureFilterAnisotropic) { this.extTextureFilterAnisotropicMax = gl.getParameter(this.extTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT); } this.extTextureFilterAnisotropicForceOff = false; this.extTextureHalfFloat = gl.getExtension('OES_texture_half_float'); if (this.extTextureHalfFloat) { gl.getExtension('OES_texture_half_float_linear'); this.extRenderToTextureHalfFloat = gl.getExtension('EXT_color_buffer_half_float'); } this.extTimerQuery = gl.getExtension('EXT_disjoint_timer_query'); this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); } setDefault() { this.unbindVAO(); this.clearColor.setDefault(); this.clearDepth.setDefault(); this.clearStencil.setDefault(); this.colorMask.setDefault(); this.depthMask.setDefault(); this.stencilMask.setDefault(); this.stencilFunc.setDefault(); this.stencilOp.setDefault(); this.stencilTest.setDefault(); this.depthRange.setDefault(); this.depthTest.setDefault(); this.depthFunc.setDefault(); this.blend.setDefault(); this.blendFunc.setDefault(); this.blendColor.setDefault(); this.blendEquation.setDefault(); this.cullFace.setDefault(); this.cullFaceSide.setDefault(); this.frontFace.setDefault(); this.program.setDefault(); this.activeTexture.setDefault(); this.bindFramebuffer.setDefault(); this.pixelStoreUnpack.setDefault(); this.pixelStoreUnpackPremultiplyAlpha.setDefault(); this.pixelStoreUnpackFlipY.setDefault(); } setDirty() { this.clearColor.dirty = true; this.clearDepth.dirty = true; this.clearStencil.dirty = true; this.colorMask.dirty = true; this.depthMask.dirty = true; this.stencilMask.dirty = true; this.stencilFunc.dirty = true; this.stencilOp.dirty = true; this.stencilTest.dirty = true; this.depthRange.dirty = true; this.depthTest.dirty = true; this.depthFunc.dirty = true; this.blend.dirty = true; this.blendFunc.dirty = true; this.blendColor.dirty = true; this.blendEquation.dirty = true; this.cullFace.dirty = true; this.cullFaceSide.dirty = true; this.frontFace.dirty = true; this.program.dirty = true; this.activeTexture.dirty = true; this.viewport.dirty = true; this.bindFramebuffer.dirty = true; this.bindRenderbuffer.dirty = true; this.bindTexture.dirty = true; this.bindVertexBuffer.dirty = true; this.bindElementBuffer.dirty = true; if (this.extVertexArrayObject) { this.bindVertexArrayOES.dirty = true; } this.pixelStoreUnpack.dirty = true; this.pixelStoreUnpackPremultiplyAlpha.dirty = true; this.pixelStoreUnpackFlipY.dirty = true; } createIndexBuffer(array, dynamicDraw) { return new IndexBuffer(this, array, dynamicDraw); } createVertexBuffer(array, attributes, dynamicDraw) { return new VertexBuffer(this, array, attributes, dynamicDraw); } createRenderbuffer(storageFormat, width, height) { const gl = this.gl; const rbo = gl.createRenderbuffer(); this.bindRenderbuffer.set(rbo); gl.renderbufferStorage(gl.RENDERBUFFER, storageFormat, width, height); this.bindRenderbuffer.set(null); return rbo; } createFramebuffer(width, height, hasDepth) { return new Framebuffer(this, width, height, hasDepth); } clear({color, depth, stencil}) { const gl = this.gl; let mask = 0; if (color) { mask |= gl.COLOR_BUFFER_BIT; this.clearColor.set(color); this.colorMask.set([ true, true, true, true ]); } if (typeof depth !== 'undefined') { mask |= gl.DEPTH_BUFFER_BIT; this.depthRange.set([ 0, 1 ]); this.clearDepth.set(depth); this.depthMask.set(true); } if (typeof stencil !== 'undefined') { mask |= gl.STENCIL_BUFFER_BIT; this.clearStencil.set(stencil); this.stencilMask.set(255); } gl.clear(mask); } setCullFace(cullFaceMode) { if (cullFaceMode.enable === false) { this.cullFace.set(false); } else { this.cullFace.set(true); this.cullFaceSide.set(cullFaceMode.mode); this.frontFace.set(cullFaceMode.frontFace); } } setDepthMode(depthMode) { if (depthMode.func === this.gl.ALWAYS && !depthMode.mask) { this.depthTest.set(false); } else { this.depthTest.set(true); this.depthFunc.set(depthMode.func); this.depthMask.set(depthMode.mask); this.depthRange.set(depthMode.range); } } setStencilMode(stencilMode) { if (stencilMode.test.func === this.gl.ALWAYS && !stencilMode.mask) { this.stencilTest.set(false); } else { this.stencilTest.set(true); this.stencilMask.set(stencilMode.mask); this.stencilOp.set([ stencilMode.fail, stencilMode.depthFail, stencilMode.pass ]); this.stencilFunc.set({ func: stencilMode.test.func, ref: stencilMode.ref, mask: stencilMode.test.mask }); } } setColorMode(colorMode) { if (deepEqual(colorMode.blendFunction, ColorMode.Replace)) { this.blend.set(false); } else { this.blend.set(true); this.blendFunc.set(colorMode.blendFunction); this.blendColor.set(colorMode.blendColor); } this.colorMask.set(colorMode.mask); } unbindVAO() { if (this.extVertexArrayObject) { this.bindVertexArrayOES.set(null); } } } class SourceCache extends Evented { constructor(id, source, onlySymbols) { super(); this.id = id; this._onlySymbols = onlySymbols; source.on('data', e => { if (e.dataType === 'source' && e.sourceDataType === 'metadata') this._sourceLoaded = true; if (this._sourceLoaded && !this._paused && e.dataType === 'source' && e.sourceDataType === 'content') { this.reload(); if (this.transform) { this.update(this.transform); } } }); source.on('error', () => { this._sourceErrored = true; }); this._source = source; this._tiles = {}; this._cache = new TileCache(0, this._unloadTile.bind(this)); this._timers = {}; this._cacheTimers = {}; this._maxTileCacheSize = null; this._loadedParentTiles = {}; this._coveredTiles = {}; this._state = new SourceFeatureState(); } onAdd(map) { this.map = map; this._maxTileCacheSize = map ? map._maxTileCacheSize : null; } loaded() { if (this._sourceErrored) { return true; } if (!this._sourceLoaded) { return false; } if (!this._source.loaded()) { return false; } for (const t in this._tiles) { const tile = this._tiles[t]; if (tile.state !== 'loaded' && tile.state !== 'errored') return false; } return true; } getSource() { return this._source; } pause() { this._paused = true; } resume() { if (!this._paused) return; const shouldReload = this._shouldReloadOnResume; this._paused = false; this._shouldReloadOnResume = false; if (shouldReload) this.reload(); if (this.transform) this.update(this.transform); } _loadTile(tile, callback) { tile.isSymbolTile = this._onlySymbols; return this._source.loadTile(tile, callback); } _unloadTile(tile) { if (this._source.unloadTile) return this._source.unloadTile(tile, () => { }); } _abortTile(tile) { if (this._source.abortTile) return this._source.abortTile(tile, () => { }); } serialize() { return this._source.serialize(); } prepare(context) { if (this._source.prepare) { this._source.prepare(); } this._state.coalesceChanges(this._tiles, this.map ? this.map.painter : null); for (const i in this._tiles) { const tile = this._tiles[i]; tile.upload(context); tile.prepare(this.map.style.imageManager); } } getIds() { return values(this._tiles).map(tile => tile.tileID).sort(compareTileId).map(id => id.key); } getRenderableIds(symbolLayer) { const renderables = []; for (const id in this._tiles) { if (this._isIdRenderable(+id, symbolLayer)) renderables.push(this._tiles[id]); } if (symbolLayer) { return renderables.sort((a_, b_) => { const a = a_.tileID; const b = b_.tileID; const rotatedA = new pointGeometry(a.canonical.x, a.canonical.y)._rotate(this.transform.angle); const rotatedB = new pointGeometry(b.canonical.x, b.canonical.y)._rotate(this.transform.angle); return a.overscaledZ - b.overscaledZ || rotatedB.y - rotatedA.y || rotatedB.x - rotatedA.x; }).map(tile => tile.tileID.key); } return renderables.map(tile => tile.tileID).sort(compareTileId).map(id => id.key); } hasRenderableParent(tileID) { const parentTile = this.findLoadedParent(tileID, 0); if (parentTile) { return this._isIdRenderable(parentTile.tileID.key); } return false; } _isIdRenderable(id, symbolLayer) { return this._tiles[id] && this._tiles[id].hasData() && !this._coveredTiles[id] && (symbolLayer || !this._tiles[id].holdingForFade()); } reload() { if (this._paused) { this._shouldReloadOnResume = true; return; } this._cache.reset(); for (const i in this._tiles) { if (this._tiles[i].state !== 'errored') this._reloadTile(+i, 'reloading'); } } _reloadTile(id, state) { const tile = this._tiles[id]; if (!tile) return; if (tile.state !== 'loading') { tile.state = state; } this._loadTile(tile, this._tileLoaded.bind(this, tile, id, state)); } _tileLoaded(tile, id, previousState, err) { if (err) { tile.state = 'errored'; if (err.status !== 404) this._source.fire(new ErrorEvent(err, { tile })); else this.update(this.transform); return; } tile.timeAdded = exported.now(); if (previousState === 'expired') tile.refreshedUponExpiration = true; this._setTileReloadTimer(id, tile); if (this.getSource().type === 'raster-dem' && tile.dem) this._backfillDEM(tile); this._state.initializeTileState(tile, this.map ? this.map.painter : null); this._source.fire(new Event('data', { dataType: 'source', tile, coord: tile.tileID, 'sourceCacheId': this.id })); } _backfillDEM(tile) { const renderables = this.getRenderableIds(); for (let i = 0; i < renderables.length; i++) { const borderId = renderables[i]; if (tile.neighboringTiles && tile.neighboringTiles[borderId]) { const borderTile = this.getTileByID(borderId); fillBorder(tile, borderTile); fillBorder(borderTile, tile); } } function fillBorder(tile, borderTile) { if (!tile.dem || tile.dem.borderReady) return; tile.needsHillshadePrepare = true; tile.needsDEMTextureUpload = true; let dx = borderTile.tileID.canonical.x - tile.tileID.canonical.x; const dy = borderTile.tileID.canonical.y - tile.tileID.canonical.y; const dim = Math.pow(2, tile.tileID.canonical.z); const borderId = borderTile.tileID.key; if (dx === 0 && dy === 0) return; if (Math.abs(dy) > 1) { return; } if (Math.abs(dx) > 1) { if (Math.abs(dx + dim) === 1) { dx += dim; } else if (Math.abs(dx - dim) === 1) { dx -= dim; } } if (!borderTile.dem || !tile.dem) return; tile.dem.backfillBorder(borderTile.dem, dx, dy); if (tile.neighboringTiles && tile.neighboringTiles[borderId]) tile.neighboringTiles[borderId].backfilled = true; } } getTile(tileID) { return this.getTileByID(tileID.key); } getTileByID(id) { return this._tiles[id]; } _retainLoadedChildren(idealTiles, zoom, maxCoveringZoom, retain) { for (const id in this._tiles) { let tile = this._tiles[id]; if (retain[id] || !tile.hasData() || tile.tileID.overscaledZ <= zoom || tile.tileID.overscaledZ > maxCoveringZoom) continue; let topmostLoadedID = tile.tileID; while (tile && tile.tileID.overscaledZ > zoom + 1) { const parentID = tile.tileID.scaledTo(tile.tileID.overscaledZ - 1); tile = this._tiles[parentID.key]; if (tile && tile.hasData()) { topmostLoadedID = parentID; } } let tileID = topmostLoadedID; while (tileID.overscaledZ > zoom) { tileID = tileID.scaledTo(tileID.overscaledZ - 1); if (idealTiles[tileID.key]) { retain[topmostLoadedID.key] = topmostLoadedID; break; } } } } findLoadedParent(tileID, minCoveringZoom) { if (tileID.key in this._loadedParentTiles) { const parent = this._loadedParentTiles[tileID.key]; if (parent && parent.tileID.overscaledZ >= minCoveringZoom) { return parent; } else { return null; } } for (let z = tileID.overscaledZ - 1; z >= minCoveringZoom; z--) { const parentTileID = tileID.scaledTo(z); const tile = this._getLoadedTile(parentTileID); if (tile) { return tile; } } } _getLoadedTile(tileID) { const tile = this._tiles[tileID.key]; if (tile && tile.hasData()) { return tile; } const cachedTile = this._cache.getByKey(this._source.reparseOverscaled ? tileID.wrapped().key : tileID.canonical.key); return cachedTile; } updateCacheSize(transform, tileSize) { tileSize = tileSize || this._source.tileSize; const widthInTiles = Math.ceil(transform.width / tileSize) + 1; const heightInTiles = Math.ceil(transform.height / tileSize) + 1; const approxTilesInView = widthInTiles * heightInTiles; const commonZoomRange = 5; const viewDependentMaxSize = Math.floor(approxTilesInView * commonZoomRange); const maxSize = typeof this._maxTileCacheSize === 'number' ? Math.min(this._maxTileCacheSize, viewDependentMaxSize) : viewDependentMaxSize; this._cache.setMaxSize(maxSize); } handleWrapJump(lng) { const prevLng = this._prevLng === undefined ? lng : this._prevLng; const lngDifference = lng - prevLng; const worldDifference = lngDifference / 360; const wrapDelta = Math.round(worldDifference); this._prevLng = lng; if (wrapDelta) { const tiles = {}; for (const key in this._tiles) { const tile = this._tiles[key]; tile.tileID = tile.tileID.unwrapTo(tile.tileID.wrap + wrapDelta); tiles[tile.tileID.key] = tile; } this._tiles = tiles; for (const id in this._timers) { clearTimeout(this._timers[id]); delete this._timers[id]; } for (const id in this._tiles) { const tile = this._tiles[id]; this._setTileReloadTimer(+id, tile); } } } update(transform, tileSize, updateForTerrain) { this.transform = transform; if (!this._sourceLoaded || this._paused || this.transform.freezeTileCoverage) { return; } if (this.usedForTerrain && !updateForTerrain) { return; } this.updateCacheSize(transform, tileSize); this.handleWrapJump(this.transform.center.lng); this._coveredTiles = {}; let idealTileIDs; if (!this.used && !this.usedForTerrain) { idealTileIDs = []; } else if (this._source.tileID) { idealTileIDs = transform.getVisibleUnwrappedCoordinates(this._source.tileID).map(unwrapped => new OverscaledTileID(unwrapped.canonical.z, unwrapped.wrap, unwrapped.canonical.z, unwrapped.canonical.x, unwrapped.canonical.y)); } else { idealTileIDs = transform.coveringTiles({ tileSize: tileSize || this._source.tileSize, minzoom: this._source.minzoom, maxzoom: this._source.maxzoom, roundZoom: this._source.roundZoom && !updateForTerrain, reparseOverscaled: this._source.reparseOverscaled, useElevationData: !!this.transform.elevation && !this.usedForTerrain }); if (this._source.hasTile) { idealTileIDs = idealTileIDs.filter(coord => this._source.hasTile(coord)); } } const retain = this._updateRetainedTiles(idealTileIDs); if (isRasterType(this._source.type) && idealTileIDs.length !== 0) { const parentsForFading = {}; const fadingTiles = {}; const ids = Object.keys(retain); for (const id of ids) { const tileID = retain[id]; const tile = this._tiles[id]; if (!tile || tile.fadeEndTime && tile.fadeEndTime <= exported.now()) continue; const parentTile = this.findLoadedParent(tileID, Math.max(tileID.overscaledZ - SourceCache.maxOverzooming, this._source.minzoom)); if (parentTile) { this._addTile(parentTile.tileID); parentsForFading[parentTile.tileID.key] = parentTile.tileID; } fadingTiles[id] = tileID; } const minZoom = idealTileIDs[idealTileIDs.length - 1].overscaledZ; for (const id in this._tiles) { const childTile = this._tiles[id]; if (retain[id] || !childTile.hasData()) { continue; } let parentID = childTile.tileID; while (parentID.overscaledZ > minZoom) { parentID = parentID.scaledTo(parentID.overscaledZ - 1); const tile = this._tiles[parentID.key]; if (tile && tile.hasData() && fadingTiles[parentID.key]) { retain[id] = childTile.tileID; break; } } } for (const id in parentsForFading) { if (!retain[id]) { this._coveredTiles[id] = true; retain[id] = parentsForFading[id]; } } } for (const retainedId in retain) { this._tiles[retainedId].clearFadeHold(); } const remove = keysDifference(this._tiles, retain); for (const tileID of remove) { const tile = this._tiles[tileID]; if (tile.hasSymbolBuckets && !tile.holdingForFade()) { tile.setHoldDuration(this.map._fadeDuration); } else if (!tile.hasSymbolBuckets || tile.symbolFadeFinished()) { this._removeTile(+tileID); } } this._updateLoadedParentTileCache(); if (this._onlySymbols && this._source.afterUpdate) { this._source.afterUpdate(); } } releaseSymbolFadeTiles() { for (const id in this._tiles) { if (this._tiles[id].holdingForFade()) { this._removeTile(+id); } } } _updateRetainedTiles(idealTileIDs) { const retain = {}; if (idealTileIDs.length === 0) { return retain; } const checked = {}; const minZoom = idealTileIDs[idealTileIDs.length - 1].overscaledZ; const maxZoom = idealTileIDs[0].overscaledZ; const minCoveringZoom = Math.max(maxZoom - SourceCache.maxOverzooming, this._source.minzoom); const maxCoveringZoom = Math.max(maxZoom + SourceCache.maxUnderzooming, this._source.minzoom); const missingTiles = {}; for (const tileID of idealTileIDs) { const tile = this._addTile(tileID); retain[tileID.key] = tileID; if (tile.hasData()) continue; if (minZoom < this._source.maxzoom) { missingTiles[tileID.key] = tileID; } } this._retainLoadedChildren(missingTiles, minZoom, maxCoveringZoom, retain); for (const tileID of idealTileIDs) { let tile = this._tiles[tileID.key]; if (tile.hasData()) continue; if (tileID.canonical.z >= this._source.maxzoom) { const childCoord = tileID.children(this._source.maxzoom)[0]; const childTile = this.getTile(childCoord); if (!!childTile && childTile.hasData()) { retain[childCoord.key] = childCoord; continue; } } else { const children = tileID.children(this._source.maxzoom); if (retain[children[0].key] && retain[children[1].key] && retain[children[2].key] && retain[children[3].key]) continue; } let parentWasRequested = tile.wasRequested(); for (let overscaledZ = tileID.overscaledZ - 1; overscaledZ >= minCoveringZoom; --overscaledZ) { const parentId = tileID.scaledTo(overscaledZ); if (checked[parentId.key]) break; checked[parentId.key] = true; tile = this.getTile(parentId); if (!tile && parentWasRequested) { tile = this._addTile(parentId); } if (tile) { retain[parentId.key] = parentId; parentWasRequested = tile.wasRequested(); if (tile.hasData()) break; } } } return retain; } _updateLoadedParentTileCache() { this._loadedParentTiles = {}; for (const tileKey in this._tiles) { const path = []; let parentTile; let currentId = this._tiles[tileKey].tileID; while (currentId.overscaledZ > 0) { if (currentId.key in this._loadedParentTiles) { parentTile = this._loadedParentTiles[currentId.key]; break; } path.push(currentId.key); const parentId = currentId.scaledTo(currentId.overscaledZ - 1); parentTile = this._getLoadedTile(parentId); if (parentTile) { break; } currentId = parentId; } for (const key of path) { this._loadedParentTiles[key] = parentTile; } } } _addTile(tileID) { let tile = this._tiles[tileID.key]; if (tile) return tile; tile = this._cache.getAndRemove(tileID); if (tile) { this._setTileReloadTimer(tileID.key, tile); tile.tileID = tileID; this._state.initializeTileState(tile, this.map ? this.map.painter : null); if (this._cacheTimers[tileID.key]) { clearTimeout(this._cacheTimers[tileID.key]); delete this._cacheTimers[tileID.key]; this._setTileReloadTimer(tileID.key, tile); } } const cached = Boolean(tile); if (!cached) { tile = new Tile(tileID, this._source.tileSize * tileID.overscaleFactor(), this.transform.tileZoom); this._loadTile(tile, this._tileLoaded.bind(this, tile, tileID.key, tile.state)); } if (!tile) return null; tile.uses++; this._tiles[tileID.key] = tile; if (!cached) this._source.fire(new Event('dataloading', { tile, coord: tile.tileID, dataType: 'source' })); return tile; } _setTileReloadTimer(id, tile) { if (id in this._timers) { clearTimeout(this._timers[id]); delete this._timers[id]; } const expiryTimeout = tile.getExpiryTimeout(); if (expiryTimeout) { this._timers[id] = setTimeout(() => { this._reloadTile(id, 'expired'); delete this._timers[id]; }, expiryTimeout); } } _removeTile(id) { const tile = this._tiles[id]; if (!tile) return; tile.uses--; delete this._tiles[id]; if (this._timers[id]) { clearTimeout(this._timers[id]); delete this._timers[id]; } if (tile.uses > 0) return; if (tile.hasData() && tile.state !== 'reloading') { this._cache.add(tile.tileID, tile, tile.getExpiryTimeout()); } else { tile.aborted = true; this._abortTile(tile); this._unloadTile(tile); } } clearTiles() { this._shouldReloadOnResume = false; this._paused = false; for (const id in this._tiles) this._removeTile(+id); this._cache.reset(); } tilesIn(queryGeometry, use3DQuery, visualizeQueryGeometry) { const tileResults = []; const transform = this.transform; if (!transform) return tileResults; for (const tileID in this._tiles) { const tile = this._tiles[tileID]; if (visualizeQueryGeometry) { tile.clearQueryDebugViz(); } if (tile.holdingForFade()) { continue; } const tileResult = queryGeometry.containsTile(tile, transform, use3DQuery); if (tileResult) { tileResults.push(tileResult); } } return tileResults; } getVisibleCoordinates(symbolLayer) { const coords = this.getRenderableIds(symbolLayer).map(id => this._tiles[id].tileID); for (const coord of coords) { coord.posMatrix = this.transform.calculatePosMatrix(coord.toUnwrapped()); } return coords; } hasTransition() { if (this._source.hasTransition()) { return true; } if (isRasterType(this._source.type)) { for (const id in this._tiles) { const tile = this._tiles[id]; if (tile.fadeEndTime !== undefined && tile.fadeEndTime >= exported.now()) { return true; } } } return false; } setFeatureState(sourceLayer, featureId, state) { sourceLayer = sourceLayer || '_geojsonTileLayer'; this._state.updateState(sourceLayer, featureId, state); } removeFeatureState(sourceLayer, featureId, key) { sourceLayer = sourceLayer || '_geojsonTileLayer'; this._state.removeFeatureState(sourceLayer, featureId, key); } getFeatureState(sourceLayer, featureId) { sourceLayer = sourceLayer || '_geojsonTileLayer'; return this._state.getState(sourceLayer, featureId); } setDependencies(tileKey, namespace, dependencies) { const tile = this._tiles[tileKey]; if (tile) { tile.setDependencies(namespace, dependencies); } } reloadTilesForDependencies(namespaces, keys) { for (const id in this._tiles) { const tile = this._tiles[id]; if (tile.hasDependency(namespaces, keys)) { this._reloadTile(+id, 'reloading'); } } this._cache.filter(tile => !tile.hasDependency(namespaces, keys)); } } SourceCache.maxOverzooming = 10; SourceCache.maxUnderzooming = 3; function compareTileId(a, b) { const aWrap = Math.abs(a.wrap * 2) - +(a.wrap < 0); const bWrap = Math.abs(b.wrap * 2) - +(b.wrap < 0); return a.overscaledZ - b.overscaledZ || bWrap - aWrap || b.canonical.y - a.canonical.y || b.canonical.x - a.canonical.x; } function isRasterType(type) { return type === 'raster' || type === 'image' || type === 'video'; } class Elevation { getAtPoint(point, defaultIfNotLoaded = 0) { const src = this._source(); if (!src) return defaultIfNotLoaded; if (point.y < 0 || point.y > 1) { return defaultIfNotLoaded; } const cache = src; const z = cache.getSource().maxzoom; const tiles = 1 << z; const wrap = Math.floor(point.x); const px = point.x - wrap; const tileID = new OverscaledTileID(z, wrap, z, Math.floor(px * tiles), Math.floor(point.y * tiles)); const demTile = this.findDEMTileFor(tileID); if (!(demTile && demTile.dem)) { return defaultIfNotLoaded; } const dem = demTile.dem; const tilesAtTileZoom = 1 << demTile.tileID.canonical.z; const x = (px * tilesAtTileZoom - demTile.tileID.canonical.x) * dem.dim; const y = (point.y * tilesAtTileZoom - demTile.tileID.canonical.y) * dem.dim; const i = Math.floor(x); const j = Math.floor(y); return this.exaggeration() * number(number(dem.get(i, j), dem.get(i, j + 1), y - j), number(dem.get(i + 1, j), dem.get(i + 1, j + 1), y - j), x - i); } getAtTileOffset(tileID, x, y) { const tilesAtTileZoom = 1 << tileID.canonical.z; return this.getAtPoint(new MercatorCoordinate(tileID.wrap + (tileID.canonical.x + x / EXTENT$1) / tilesAtTileZoom, (tileID.canonical.y + y / EXTENT$1) / tilesAtTileZoom)); } getForTilePoints(tileID, points, interpolated, useDemTile) { const helper = DEMSampler.create(this, tileID, useDemTile); if (!helper) { return false; } points.forEach(p => { p[2] = this.exaggeration() * helper.getElevationAt(p[0], p[1], interpolated); }); return true; } getMinMaxForTile(tileID) { const demTile = this.findDEMTileFor(tileID); if (!(demTile && demTile.dem)) { return null; } const dem = demTile.dem; const tree = dem.tree; const demTileID = demTile.tileID; const scale = 1 << tileID.canonical.z - demTileID.canonical.z; let xOffset = tileID.canonical.x / scale - demTileID.canonical.x; let yOffset = tileID.canonical.y / scale - demTileID.canonical.y; let index = 0; for (let i = 0; i < tileID.canonical.z - demTileID.canonical.z; i++) { if (tree.leaves[index]) break; xOffset *= 2; yOffset *= 2; const childOffset = 2 * Math.floor(yOffset) + Math.floor(xOffset); index = tree.childOffsets[index] + childOffset; xOffset = xOffset % 1; yOffset = yOffset % 1; } return { min: this.exaggeration() * tree.minimums[index], max: this.exaggeration() * tree.maximums[index] }; } raycast(position, dir, exaggeration) { throw new Error('Pure virtual method called.'); } pointCoordinate(screenPoint) { throw new Error('Pure virtual method called.'); } _source() { throw new Error('Pure virtual method called.'); } exaggeration() { throw new Error('Pure virtual method called.'); } findDEMTileFor(_) { throw new Error('Pure virtual method called.'); } get visibleDemTiles() { throw new Error('Getter must be implemented in subclass.'); } } class DEMSampler { constructor(demTile, scale, offset) { this._demTile = demTile; this._dem = this._demTile.dem; this._scale = scale; this._offset = offset; } static create(elevation, tileID, useDemTile) { const demTile = useDemTile || elevation.findDEMTileFor(tileID); if (!(demTile && demTile.dem)) { return; } const dem = demTile.dem; const demTileID = demTile.tileID; const scale = 1 << tileID.canonical.z - demTileID.canonical.z; const xOffset = (tileID.canonical.x / scale - demTileID.canonical.x) * dem.dim; const yOffset = (tileID.canonical.y / scale - demTileID.canonical.y) * dem.dim; const k = demTile.tileSize / EXTENT$1 / scale; return new DEMSampler(demTile, k, [ xOffset, yOffset ]); } tileCoordToPixel(x, y) { const px = x * this._scale + this._offset[0]; const py = y * this._scale + this._offset[1]; const i = Math.floor(px); const j = Math.floor(py); return new pointGeometry(i, j); } getElevationAt(x, y, interpolated, clampToEdge) { const px = x * this._scale + this._offset[0]; const py = y * this._scale + this._offset[1]; const i = Math.floor(px); const j = Math.floor(py); const dem = this._dem; clampToEdge = !!clampToEdge; return interpolated ? number(number(dem.get(i, j, clampToEdge), dem.get(i, j + 1, clampToEdge), py - j), number(dem.get(i + 1, j, clampToEdge), dem.get(i + 1, j + 1, clampToEdge), py - j), px - i) : dem.get(i, j, clampToEdge); } getElevationAtPixel(x, y, clampToEdge) { return this._dem.get(x, y, !!clampToEdge); } getMeterToDEM(lat) { return (1 << this._demTile.tileID.canonical.z) * mercatorZfromAltitude(1, lat) * this._dem.stride; } } class FeatureIndex { constructor(tileID, promoteId) { this.tileID = tileID; this.x = tileID.canonical.x; this.y = tileID.canonical.y; this.z = tileID.canonical.z; this.grid = new gridIndex(EXTENT$1, 16, 0); this.featureIndexArray = new FeatureIndexArray(); this.promoteId = promoteId; } insert(feature, geometry, featureIndex, sourceLayerIndex, bucketIndex, layoutVertexArrayOffset = 0) { const key = this.featureIndexArray.length; this.featureIndexArray.emplaceBack(featureIndex, sourceLayerIndex, bucketIndex, layoutVertexArrayOffset); const grid = this.grid; for (let r = 0; r < geometry.length; r++) { const ring = geometry[r]; const bbox = [ Infinity, Infinity, -Infinity, -Infinity ]; for (let i = 0; i < ring.length; i++) { const p = ring[i]; bbox[0] = Math.min(bbox[0], p.x); bbox[1] = Math.min(bbox[1], p.y); bbox[2] = Math.max(bbox[2], p.x); bbox[3] = Math.max(bbox[3], p.y); } if (bbox[0] < EXTENT$1 && bbox[1] < EXTENT$1 && bbox[2] >= 0 && bbox[3] >= 0) { grid.insert(key, bbox[0], bbox[1], bbox[2], bbox[3]); } } } loadVTLayers() { if (!this.vtLayers) { this.vtLayers = new vectorTile.VectorTile(new pbf(this.rawTileData)).layers; this.sourceLayerCoder = new DictionaryCoder(this.vtLayers ? Object.keys(this.vtLayers).sort() : ['_geojsonTileLayer']); } return this.vtLayers; } query(args, styleLayers, serializedLayers, sourceFeatureState) { this.loadVTLayers(); const params = args.params || {}, filter = createFilter(params.filter); const tilespaceGeometry = args.tileResult; const transform = args.transform; const bounds = tilespaceGeometry.bufferedTilespaceBounds; const queryPredicate = (bx1, by1, bx2, by2) => { return polygonIntersectsBox(tilespaceGeometry.bufferedTilespaceGeometry, bx1, by1, bx2, by2); }; const matching = this.grid.query(bounds.min.x, bounds.min.y, bounds.max.x, bounds.max.y, queryPredicate); matching.sort(topDownFeatureComparator); let elevationHelper = null; if (transform.elevation && matching.length > 0) { elevationHelper = DEMSampler.create(transform.elevation, this.tileID); } const result = {}; let previousIndex; for (let k = 0; k < matching.length; k++) { const index = matching[k]; if (index === previousIndex) continue; previousIndex = index; const match = this.featureIndexArray.get(index); let featureGeometry = null; this.loadMatchingFeature(result, match, filter, params.layers, params.availableImages, styleLayers, serializedLayers, sourceFeatureState, (feature, styleLayer, featureState, layoutVertexArrayOffset = 0) => { if (!featureGeometry) { featureGeometry = loadGeometry(feature); } return styleLayer.queryIntersectsFeature(tilespaceGeometry, feature, featureState, featureGeometry, this.z, args.transform, args.pixelPosMatrix, elevationHelper, layoutVertexArrayOffset); }); } return result; } loadMatchingFeature(result, featureIndexData, filter, filterLayerIDs, availableImages, styleLayers, serializedLayers, sourceFeatureState, intersectionTest) { const {featureIndex, bucketIndex, sourceLayerIndex, layoutVertexArrayOffset} = featureIndexData; const layerIDs = this.bucketLayerIDs[bucketIndex]; if (filterLayerIDs && !arraysIntersect(filterLayerIDs, layerIDs)) return; const sourceLayerName = this.sourceLayerCoder.decode(sourceLayerIndex); const sourceLayer = this.vtLayers[sourceLayerName]; const feature = sourceLayer.feature(featureIndex); if (filter.needGeometry) { const evaluationFeature = toEvaluationFeature(feature, true); if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), evaluationFeature, this.tileID.canonical)) { return; } } else if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) { return; } const id = this.getId(feature, sourceLayerName); for (let l = 0; l < layerIDs.length; l++) { const layerID = layerIDs[l]; if (filterLayerIDs && filterLayerIDs.indexOf(layerID) < 0) { continue; } const styleLayer = styleLayers[layerID]; if (!styleLayer) continue; let featureState = {}; if (id !== undefined && sourceFeatureState) { featureState = sourceFeatureState.getState(styleLayer.sourceLayer || '_geojsonTileLayer', id); } const serializedLayer = extend({}, serializedLayers[layerID]); serializedLayer.paint = evaluateProperties(serializedLayer.paint, styleLayer.paint, feature, featureState, availableImages); serializedLayer.layout = evaluateProperties(serializedLayer.layout, styleLayer.layout, feature, featureState, availableImages); const intersectionZ = !intersectionTest || intersectionTest(feature, styleLayer, featureState, layoutVertexArrayOffset); if (!intersectionZ) { continue; } const geojsonFeature = new Feature(feature, this.z, this.x, this.y, id); geojsonFeature.layer = serializedLayer; let layerResult = result[layerID]; if (layerResult === undefined) { layerResult = result[layerID] = []; } layerResult.push({ featureIndex, feature: geojsonFeature, intersectionZ }); } } lookupSymbolFeatures(symbolFeatureIndexes, serializedLayers, bucketIndex, sourceLayerIndex, filterSpec, filterLayerIDs, availableImages, styleLayers) { const result = {}; this.loadVTLayers(); const filter = createFilter(filterSpec); for (const symbolFeatureIndex of symbolFeatureIndexes) { this.loadMatchingFeature(result, { bucketIndex, sourceLayerIndex, featureIndex: symbolFeatureIndex, layoutVertexArrayOffset: 0 }, filter, filterLayerIDs, availableImages, styleLayers, serializedLayers); } return result; } hasLayer(id) { for (const layerIDs of this.bucketLayerIDs) { for (const layerID of layerIDs) { if (id === layerID) return true; } } return false; } getId(feature, sourceLayerId) { let id = feature.id; if (this.promoteId) { const propName = typeof this.promoteId === 'string' ? this.promoteId : this.promoteId[sourceLayerId]; id = feature.properties[propName]; if (typeof id === 'boolean') id = Number(id); } return id; } } register('FeatureIndex', FeatureIndex, { omit: [ 'rawTileData', 'sourceLayerCoder' ] }); function evaluateProperties(serializedProperties, styleLayerProperties, feature, featureState, availableImages) { return mapObject(serializedProperties, (property, key) => { const prop = styleLayerProperties instanceof PossiblyEvaluated ? styleLayerProperties.get(key) : null; return prop && prop.evaluate ? prop.evaluate(feature, featureState, availableImages) : prop; }); } function topDownFeatureComparator(a, b) { return b - a; } const glyphPadding = 1; const localGlyphPadding = glyphPadding * SDF_SCALE; class GlyphAtlas { constructor(stacks) { const positions = {}; const bins = []; for (const stack in stacks) { const glyphs = stacks[stack]; const stackPositions = positions[stack] = {}; for (const id in glyphs) { const src = glyphs[+id]; if (!src || src.bitmap.width === 0 || src.bitmap.height === 0) continue; const padding = src.metrics.localGlyph ? localGlyphPadding : glyphPadding; const bin = { x: 0, y: 0, w: src.bitmap.width + 2 * padding, h: src.bitmap.height + 2 * padding }; bins.push(bin); stackPositions[id] = { rect: bin, metrics: src.metrics }; } } const {w, h} = potpack(bins); const image = new AlphaImage({ width: w || 1, height: h || 1 }); for (const stack in stacks) { const glyphs = stacks[stack]; for (const id in glyphs) { const src = glyphs[+id]; if (!src || src.bitmap.width === 0 || src.bitmap.height === 0) continue; const bin = positions[stack][id].rect; const padding = src.metrics.localGlyph ? localGlyphPadding : glyphPadding; AlphaImage.copy(src.bitmap, image, { x: 0, y: 0 }, { x: bin.x + padding, y: bin.y + padding }, src.bitmap); } } this.image = image; this.positions = positions; } } register('GlyphAtlas', GlyphAtlas); class WorkerTile { constructor(params) { this.tileID = new OverscaledTileID(params.tileID.overscaledZ, params.tileID.wrap, params.tileID.canonical.z, params.tileID.canonical.x, params.tileID.canonical.y); this.tileZoom = params.tileZoom; this.uid = params.uid; this.zoom = params.zoom; this.pixelRatio = params.pixelRatio; this.tileSize = params.tileSize; this.source = params.source; this.overscaling = this.tileID.overscaleFactor(); this.showCollisionBoxes = params.showCollisionBoxes; this.collectResourceTiming = !!params.collectResourceTiming; this.returnDependencies = !!params.returnDependencies; this.promoteId = params.promoteId; this.enableTerrain = !!params.enableTerrain; this.isSymbolTile = params.isSymbolTile; } parse(data, layerIndex, availableImages, actor, callback) { this.status = 'parsing'; this.data = data; this.collisionBoxArray = new CollisionBoxArray(); const sourceLayerCoder = new DictionaryCoder(Object.keys(data.layers).sort()); const featureIndex = new FeatureIndex(this.tileID, this.promoteId); featureIndex.bucketLayerIDs = []; const buckets = {}; const options = { featureIndex, iconDependencies: {}, patternDependencies: {}, glyphDependencies: {}, availableImages }; const layerFamilies = layerIndex.familiesBySource[this.source]; for (const sourceLayerId in layerFamilies) { const sourceLayer = data.layers[sourceLayerId]; if (!sourceLayer) { continue; } let anySymbolLayers = false; let anyOtherLayers = false; for (const family of layerFamilies[sourceLayerId]) { if (family[0].type === 'symbol') { anySymbolLayers = true; } else { anyOtherLayers = true; } } if (this.isSymbolTile === true && !anySymbolLayers) { continue; } else if (this.isSymbolTile === false && !anyOtherLayers) { continue; } if (sourceLayer.version === 1) { warnOnce(`Vector tile source "${ this.source }" layer "${ sourceLayerId }" ` + `does not use vector tile spec v2 and therefore may have some rendering errors.`); } const sourceLayerIndex = sourceLayerCoder.encode(sourceLayerId); const features = []; for (let index = 0; index < sourceLayer.length; index++) { const feature = sourceLayer.feature(index); const id = featureIndex.getId(feature, sourceLayerId); features.push({ feature, id, index, sourceLayerIndex }); } for (const family of layerFamilies[sourceLayerId]) { const layer = family[0]; if (this.isSymbolTile !== undefined && layer.type === 'symbol' !== this.isSymbolTile) continue; if (layer.minzoom && this.zoom < Math.floor(layer.minzoom)) continue; if (layer.maxzoom && this.zoom >= layer.maxzoom) continue; if (layer.visibility === 'none') continue; recalculateLayers(family, this.zoom, availableImages); const bucket = buckets[layer.id] = layer.createBucket({ index: featureIndex.bucketLayerIDs.length, layers: family, zoom: this.zoom, pixelRatio: this.pixelRatio, overscaling: this.overscaling, collisionBoxArray: this.collisionBoxArray, sourceLayerIndex, sourceID: this.source, enableTerrain: this.enableTerrain }); bucket.populate(features, options, this.tileID.canonical); featureIndex.bucketLayerIDs.push(family.map(l => l.id)); } } let error; let glyphMap; let iconMap; let patternMap; const taskMetadata = { type: 'maybePrepare', isSymbolTile: this.isSymbolTile, zoom: this.zoom }; const stacks = mapObject(options.glyphDependencies, glyphs => Object.keys(glyphs).map(Number)); if (Object.keys(stacks).length) { actor.send('getGlyphs', { uid: this.uid, stacks }, (err, result) => { if (!error) { error = err; glyphMap = result; maybePrepare.call(this); } }, undefined, undefined, taskMetadata); } else { glyphMap = {}; } const icons = Object.keys(options.iconDependencies); if (icons.length) { actor.send('getImages', { icons, source: this.source, tileID: this.tileID, type: 'icons' }, (err, result) => { if (!error) { error = err; iconMap = result; maybePrepare.call(this); } }, undefined, undefined, taskMetadata); } else { iconMap = {}; } const patterns = Object.keys(options.patternDependencies); if (patterns.length) { actor.send('getImages', { icons: patterns, source: this.source, tileID: this.tileID, type: 'patterns' }, (err, result) => { if (!error) { error = err; patternMap = result; maybePrepare.call(this); } }, undefined, undefined, taskMetadata); } else { patternMap = {}; } maybePrepare.call(this); function maybePrepare() { if (error) { return callback(error); } else if (glyphMap && iconMap && patternMap) { const glyphAtlas = new GlyphAtlas(glyphMap); const imageAtlas = new ImageAtlas(iconMap, patternMap); for (const key in buckets) { const bucket = buckets[key]; if (bucket instanceof SymbolBucket) { recalculateLayers(bucket.layers, this.zoom, availableImages); performSymbolLayout(bucket, glyphMap, glyphAtlas.positions, iconMap, imageAtlas.iconPositions, this.showCollisionBoxes, this.tileID.canonical, this.tileZoom); } else if (bucket.hasPattern && (bucket instanceof LineBucket || bucket instanceof FillBucket || bucket instanceof FillExtrusionBucket)) { recalculateLayers(bucket.layers, this.zoom, availableImages); bucket.addFeatures(options, this.tileID.canonical, imageAtlas.patternPositions); } } this.status = 'done'; callback(null, { buckets: values(buckets).filter(b => !b.isEmpty()), featureIndex, collisionBoxArray: this.collisionBoxArray, glyphAtlasImage: glyphAtlas.image, imageAtlas, glyphMap: this.returnDependencies ? glyphMap : null, iconMap: this.returnDependencies ? iconMap : null, glyphPositions: this.returnDependencies ? glyphAtlas.positions : null }); } } } } function recalculateLayers(layers, zoom, availableImages) { const parameters = new EvaluationParameters(zoom); for (const layer of layers) { layer.recalculate(parameters, availableImages); } } class DedupedRequest { constructor(scheduler) { this.entries = {}; this.scheduler = scheduler; } request(key, metadata, request, callback) { const entry = this.entries[key] = this.entries[key] || { callbacks: [] }; if (entry.result) { const [err, result] = entry.result; if (this.scheduler) { this.scheduler.add(() => { callback(err, result); }, metadata); } else { callback(err, result); } return () => { }; } entry.callbacks.push(callback); if (!entry.cancel) { entry.cancel = request((err, result) => { entry.result = [ err, result ]; for (const cb of entry.callbacks) { if (this.scheduler) { this.scheduler.add(() => { cb(err, result); }, metadata); } else { cb(err, result); } } setTimeout(() => delete this.entries[key], 1000 * 3); }); } return () => { if (entry.result) return; entry.callbacks = entry.callbacks.filter(cb => cb !== callback); if (!entry.callbacks.length) { entry.cancel(); delete this.entries[key]; } }; } } function loadVectorTile(params, callback, skipParse) { const key = JSON.stringify(params.request); const makeRequest = callback => { const request = getArrayBuffer(params.request, (err, data, cacheControl, expires) => { if (err) { callback(err); } else if (data) { callback(null, { vectorTile: skipParse ? undefined : new vectorTile.VectorTile(new pbf(data)), rawData: data, cacheControl, expires }); } }); return () => { request.cancel(); callback(); }; }; if (params.data) { this.deduped.entries[key] = { result: [ null, params.data ] }; } const callbackMetadata = { type: 'parseTile', isSymbolTile: params.isSymbolTile, zoom: params.tileZoom }; return this.deduped.request(key, callbackMetadata, makeRequest, callback); } class VectorTileWorkerSource extends Evented { constructor(actor, layerIndex, availableImages, isSpriteLoaded, loadVectorData) { super(); this.actor = actor; this.layerIndex = layerIndex; this.availableImages = availableImages; this.loadVectorData = loadVectorData || loadVectorTile; this.loading = {}; this.loaded = {}; this.deduped = new DedupedRequest(actor.scheduler); this.isSpriteLoaded = isSpriteLoaded; this.scheduler = actor.scheduler; } loadTile(params, callback) { const uid = params.uid; const perf = params && params.request && params.request.collectResourceTiming ? new RequestPerformance(params.request) : false; const workerTile = this.loading[uid] = new WorkerTile(params); workerTile.abort = this.loadVectorData(params, (err, response) => { const aborted = !this.loading[uid]; delete this.loading[uid]; if (aborted || err || !response) { workerTile.status = 'done'; if (!aborted) this.loaded[uid] = workerTile; return callback(err); } const rawTileData = response.rawData; const cacheControl = {}; if (response.expires) cacheControl.expires = response.expires; if (response.cacheControl) cacheControl.cacheControl = response.cacheControl; const resourceTiming = {}; if (perf) { const resourceTimingData = perf.finish(); if (resourceTimingData) resourceTiming.resourceTiming = JSON.parse(JSON.stringify(resourceTimingData)); } workerTile.vectorTile = response.vectorTile || new vectorTile.VectorTile(new pbf(rawTileData)); const parseTile = () => { workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, (err, result) => { if (err || !result) return callback(err); callback(null, extend({ rawTileData: rawTileData.slice(0) }, result, cacheControl, resourceTiming)); }); }; if (this.isSpriteLoaded) { parseTile(); } else { this.once('isSpriteLoaded', () => { if (this.scheduler) { const metadata = { type: 'parseTile', isSymbolTile: params.isSymbolTile, zoom: params.tileZoom }; this.scheduler.add(parseTile, metadata); } else { parseTile(); } }); } this.loaded = this.loaded || {}; this.loaded[uid] = workerTile; }); } reloadTile(params, callback) { const loaded = this.loaded, uid = params.uid, vtSource = this; if (loaded && loaded[uid]) { const workerTile = loaded[uid]; workerTile.showCollisionBoxes = params.showCollisionBoxes; workerTile.enableTerrain = !!params.enableTerrain; const done = (err, data) => { const reloadCallback = workerTile.reloadCallback; if (reloadCallback) { delete workerTile.reloadCallback; workerTile.parse(workerTile.vectorTile, vtSource.layerIndex, this.availableImages, vtSource.actor, reloadCallback); } callback(err, data); }; if (workerTile.status === 'parsing') { workerTile.reloadCallback = done; } else if (workerTile.status === 'done') { if (workerTile.vectorTile) { workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, done); } else { done(); } } } } abortTile(params, callback) { const uid = params.uid; const tile = this.loading[uid]; if (tile) { if (tile.abort) tile.abort(); delete this.loading[uid]; } callback(); } removeTile(params, callback) { const loaded = this.loaded, uid = params.uid; if (loaded && loaded[uid]) { delete loaded[uid]; } callback(); } } var refProperties = [ 'type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout' ]; exports.AUTH_ERR_MSG = AUTH_ERR_MSG; exports.Aabb = Aabb; exports.Actor = Actor; exports.CanonicalTileID = CanonicalTileID; exports.Color = Color; exports.ColorMode = ColorMode; exports.Context = Context; exports.CullFaceMode = CullFaceMode; exports.DEMData = DEMData; exports.DataConstantProperty = DataConstantProperty; exports.DedupedRequest = DedupedRequest; exports.DepthMode = DepthMode; exports.DepthStencilAttachment = DepthStencilAttachment; exports.EXTENT = EXTENT$1; exports.Elevation = Elevation; exports.ErrorEvent = ErrorEvent; exports.EvaluationParameters = EvaluationParameters; exports.Event = Event; exports.Evented = Evented; exports.Frustum = Frustum; exports.GlyphManager = GlyphManager; exports.ImagePosition = ImagePosition; exports.LngLat = LngLat; exports.LngLatBounds = LngLatBounds; exports.LocalGlyphMode = LocalGlyphMode; exports.MAX_SAFE_INTEGER = MAX_SAFE_INTEGER; exports.MercatorCoordinate = MercatorCoordinate; exports.ONE_EM = ONE_EM; exports.OverscaledTileID = OverscaledTileID; exports.Point = pointGeometry; exports.Point$1 = pointGeometry; exports.Properties = Properties; exports.RGBAImage = RGBAImage; exports.Ray = Ray; exports.RequestManager = RequestManager; exports.RequestPerformance = RequestPerformance; exports.ResourceType = ResourceType; exports.SegmentVector = SegmentVector; exports.SourceCache = SourceCache; exports.StencilMode = StencilMode; exports.StructArrayLayout1ui2 = StructArrayLayout1ui2; exports.StructArrayLayout2f1f2i16 = StructArrayLayout2f1f2i16; exports.StructArrayLayout2i4 = StructArrayLayout2i4; exports.StructArrayLayout3f12 = StructArrayLayout3f12; exports.StructArrayLayout3ui6 = StructArrayLayout3ui6; exports.StructArrayLayout4i8 = StructArrayLayout4i8; exports.Texture = Texture; exports.Tile = Tile; exports.Transitionable = Transitionable; exports.Uniform1f = Uniform1f; exports.Uniform1i = Uniform1i; exports.Uniform2f = Uniform2f; exports.Uniform3f = Uniform3f; exports.Uniform4f = Uniform4f; exports.UniformColor = UniformColor; exports.UniformMatrix3f = UniformMatrix3f; exports.UniformMatrix4f = UniformMatrix4f; exports.UnwrappedTileID = UnwrappedTileID; exports.ValidationError = ValidationError; exports.VectorTileWorkerSource = VectorTileWorkerSource; exports.WritingMode = WritingMode; exports.ZoomHistory = ZoomHistory; exports.add = add; exports.addDynamicAttributes = addDynamicAttributes; exports.altitudeFromMercatorZ = altitudeFromMercatorZ; exports.asyncAll = asyncAll; exports.bezier = bezier; exports.bindAll = bindAll; exports.browser = exported; exports.bufferConvexPolygon = bufferConvexPolygon; exports.cacheEntryPossiblyAdded = cacheEntryPossiblyAdded; exports.clamp = clamp; exports.clearTileCache = clearTileCache; exports.clipLine = clipLine; exports.clone = clone$1; exports.clone$1 = clone; exports.collisionCircleLayout = collisionCircleLayout; exports.config = config; exports.conjugate = conjugate; exports.create = create$2; exports.create$1 = create$1; exports.create$2 = create; exports.createExpression = createExpression; exports.createLayout = createLayout; exports.createStyleLayer = createStyleLayer; exports.cross = cross; exports.deepEqual = deepEqual; exports.degToRad = degToRad; exports.div = div; exports.dot = dot; exports.ease = ease; exports.easeCubicInOut = easeCubicInOut; exports.emitValidationErrors = emitValidationErrors; exports.endsWith = endsWith; exports.enforceCacheSizeLimit = enforceCacheSizeLimit; exports.evaluateSizeForFeature = evaluateSizeForFeature; exports.evaluateSizeForZoom = evaluateSizeForZoom; exports.evaluateVariableOffset = evaluateVariableOffset; exports.evented = evented; exports.exactEquals = exactEquals$2; exports.exactEquals$1 = exactEquals; exports.extend = extend; exports.featureFilter = createFilter; exports.filterObject = filterObject; exports.fromMat4 = fromMat4; exports.fromQuat = fromQuat; exports.fromRotation = fromRotation; exports.getAnchorAlignment = getAnchorAlignment; exports.getAnchorJustification = getAnchorJustification; exports.getBounds = getBounds; exports.getImage = getImage; exports.getJSON = getJSON; exports.getMapSessionAPI = getMapSessionAPI; exports.getRTLTextPluginStatus = getRTLTextPluginStatus; exports.getReferrer = getReferrer; exports.getVideo = getVideo; exports.identity = identity; exports.identity$1 = identity$1; exports.invert = invert; exports.isMapboxURL = isMapboxURL; exports.latFromMercatorY = latFromMercatorY; exports.len = len; exports.length = length; exports.length$1 = length$2; exports.loadVectorTile = loadVectorTile; exports.makeRequest = makeRequest; exports.mercatorXfromLng = mercatorXfromLng$1; exports.mercatorYfromLat = mercatorYfromLat$1; exports.mercatorZfromAltitude = mercatorZfromAltitude; exports.mul = mul; exports.mul$1 = mul$1; exports.multiply = multiply; exports.mvt = vectorTile; exports.nextPowerOfTwo = nextPowerOfTwo; exports.normalize = normalize; exports.normalize$1 = normalize$2; exports.number = number; exports.offscreenCanvasSupported = offscreenCanvasSupported; exports.ortho = ortho; exports.pbf = pbf; exports.perspective = perspective; exports.pick = pick; exports.plugin = plugin; exports.polygonIntersectsBox = polygonIntersectsBox; exports.polygonIntersectsPolygon = polygonIntersectsPolygon; exports.polygonizeBounds = polygonizeBounds; exports.posAttributes = posAttributes; exports.postMapLoadEvent = postMapLoadEvent; exports.postTurnstileEvent = postTurnstileEvent; exports.potpack = potpack; exports.prevPowerOfTwo = prevPowerOfTwo; exports.radToDeg = radToDeg; exports.refProperties = refProperties; exports.registerForPluginStateChange = registerForPluginStateChange; exports.renderColorRamp = renderColorRamp; exports.rotate = rotate; exports.rotateX = rotateX; exports.rotateX$1 = rotateX$1; exports.rotateY = rotateY; exports.rotateZ = rotateZ; exports.rotateZ$1 = rotateZ$1; exports.scale = scale; exports.scale$1 = scale$2; exports.scale$2 = scale$1; exports.scaleAndAdd = scaleAndAdd; exports.setCacheLimits = setCacheLimits; exports.setRTLTextPlugin = setRTLTextPlugin; exports.styleSpec = spec; exports.sub = sub; exports.subtract = subtract; exports.symbolSize = symbolSize; exports.transformMat3 = transformMat3; exports.transformMat4 = transformMat4$1; exports.transformQuat = transformQuat; exports.translate = translate$1; exports.triggerPluginCompletionEvent = triggerPluginCompletionEvent; exports.uniqueId = uniqueId; exports.validateCustomStyleLayer = validateCustomStyleLayer; exports.validateLight = validateLight$1; exports.validateStyle = validateStyle; exports.values = values; exports.vectorTile = vectorTile; exports.version = version; exports.warnOnce = warnOnce; exports.webpSupported = exported$1; exports.window = window$1; exports.wrap = wrap; }); define(['./shared'], function (ref_properties) { 'use strict'; function stringify(obj) { const type = typeof obj; if (type === 'number' || type === 'boolean' || type === 'string' || obj === undefined || obj === null) return JSON.stringify(obj); if (Array.isArray(obj)) { let str = '['; for (const val of obj) { str += `${ stringify(val) },`; } return `${ str }]`; } const keys = Object.keys(obj).sort(); let str = '{'; for (let i = 0; i < keys.length; i++) { str += `${ JSON.stringify(keys[i]) }:${ stringify(obj[keys[i]]) },`; } return `${ str }}`; } function getKey(layer) { let key = ''; for (const k of ref_properties.refProperties) { key += `/${ stringify(layer[k]) }`; } return key; } function groupByLayout(layers, cachedKeys) { const groups = {}; for (let i = 0; i < layers.length; i++) { const k = cachedKeys && cachedKeys[layers[i].id] || getKey(layers[i]); if (cachedKeys) cachedKeys[layers[i].id] = k; let group = groups[k]; if (!group) { group = groups[k] = []; } group.push(layers[i]); } const result = []; for (const k in groups) { result.push(groups[k]); } return result; } class StyleLayerIndex { constructor(layerConfigs) { this.keyCache = {}; if (layerConfigs) { this.replace(layerConfigs); } } replace(layerConfigs) { this._layerConfigs = {}; this._layers = {}; this.update(layerConfigs, []); } update(layerConfigs, removedIds) { for (const layerConfig of layerConfigs) { this._layerConfigs[layerConfig.id] = layerConfig; const layer = this._layers[layerConfig.id] = ref_properties.createStyleLayer(layerConfig); layer._featureFilter = ref_properties.featureFilter(layer.filter); if (this.keyCache[layerConfig.id]) delete this.keyCache[layerConfig.id]; } for (const id of removedIds) { delete this.keyCache[id]; delete this._layerConfigs[id]; delete this._layers[id]; } this.familiesBySource = {}; const groups = groupByLayout(ref_properties.values(this._layerConfigs), this.keyCache); for (const layerConfigs of groups) { const layers = layerConfigs.map(layerConfig => this._layers[layerConfig.id]); const layer = layers[0]; if (layer.visibility === 'none') { continue; } const sourceId = layer.source || ''; let sourceGroup = this.familiesBySource[sourceId]; if (!sourceGroup) { sourceGroup = this.familiesBySource[sourceId] = {}; } const sourceLayerId = layer.sourceLayer || '_geojsonTileLayer'; let sourceLayerFamilies = sourceGroup[sourceLayerId]; if (!sourceLayerFamilies) { sourceLayerFamilies = sourceGroup[sourceLayerId] = []; } sourceLayerFamilies.push(layers); } } } const {ImageBitmap} = ref_properties.window; class RasterDEMTileWorkerSource { loadTile(params, callback) { const {uid, encoding, rawImageData, padding, buildQuadTree} = params; const imagePixels = ImageBitmap && rawImageData instanceof ImageBitmap ? this.getImageData(rawImageData, padding) : rawImageData; const dem = new ref_properties.DEMData(uid, imagePixels, encoding, padding < 1, buildQuadTree); callback(null, dem); } getImageData(imgBitmap, padding) { if (!this.offscreenCanvas || !this.offscreenCanvasContext) { this.offscreenCanvas = new OffscreenCanvas(imgBitmap.width, imgBitmap.height); this.offscreenCanvasContext = this.offscreenCanvas.getContext('2d'); } this.offscreenCanvas.width = imgBitmap.width; this.offscreenCanvas.height = imgBitmap.height; this.offscreenCanvasContext.drawImage(imgBitmap, 0, 0, imgBitmap.width, imgBitmap.height); const imgData = this.offscreenCanvasContext.getImageData(-padding, -padding, imgBitmap.width + 2 * padding, imgBitmap.height + 2 * padding); this.offscreenCanvasContext.clearRect(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height); return new ref_properties.RGBAImage({ width: imgData.width, height: imgData.height }, imgData.data); } } var geojsonRewind = rewind; function rewind(gj, outer) { var type = gj && gj.type, i; if (type === 'FeatureCollection') { for (i = 0; i < gj.features.length; i++) rewind(gj.features[i], outer); } else if (type === 'GeometryCollection') { for (i = 0; i < gj.geometries.length; i++) rewind(gj.geometries[i], outer); } else if (type === 'Feature') { rewind(gj.geometry, outer); } else if (type === 'Polygon') { rewindRings(gj.coordinates, outer); } else if (type === 'MultiPolygon') { for (i = 0; i < gj.coordinates.length; i++) rewindRings(gj.coordinates[i], outer); } return gj; } function rewindRings(rings, outer) { if (rings.length === 0) return; rewindRing(rings[0], outer); for (var i = 1; i < rings.length; i++) { rewindRing(rings[i], !outer); } } function rewindRing(ring, dir) { var area = 0; for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) { area += (ring[i][0] - ring[j][0]) * (ring[j][1] + ring[i][1]); } if (area >= 0 !== !!dir) ring.reverse(); } const toGeoJSON = ref_properties.vectorTile.VectorTileFeature.prototype.toGeoJSON; class FeatureWrapper { constructor(feature) { this._feature = feature; this.extent = ref_properties.EXTENT; this.type = feature.type; this.properties = feature.tags; if ('id' in feature && !isNaN(feature.id)) { this.id = parseInt(feature.id, 10); } } loadGeometry() { if (this._feature.type === 1) { const geometry = []; for (const point of this._feature.geometry) { geometry.push([new ref_properties.Point$1(point[0], point[1])]); } return geometry; } else { const geometry = []; for (const ring of this._feature.geometry) { const newRing = []; for (const point of ring) { newRing.push(new ref_properties.Point$1(point[0], point[1])); } geometry.push(newRing); } return geometry; } } toGeoJSON(x, y, z) { return toGeoJSON.call(this, x, y, z); } } class GeoJSONWrapper { constructor(features) { this.layers = { '_geojsonTileLayer': this }; this.name = '_geojsonTileLayer'; this.extent = ref_properties.EXTENT; this.length = features.length; this._features = features; } feature(i) { return new FeatureWrapper(this._features[i]); } } var VectorTileFeature = ref_properties.vectorTile.VectorTileFeature; var geojson_wrapper = GeoJSONWrapper$1; function GeoJSONWrapper$1(features, options) { this.options = options || {}; this.features = features; this.length = features.length; } GeoJSONWrapper$1.prototype.feature = function (i) { return new FeatureWrapper$1(this.features[i], this.options.extent); }; function FeatureWrapper$1(feature, extent) { this.id = typeof feature.id === 'number' ? feature.id : undefined; this.type = feature.type; this.rawGeometry = feature.type === 1 ? [feature.geometry] : feature.geometry; this.properties = feature.tags; this.extent = extent || 4096; } FeatureWrapper$1.prototype.loadGeometry = function () { var rings = this.rawGeometry; this.geometry = []; for (var i = 0; i < rings.length; i++) { var ring = rings[i]; var newRing = []; for (var j = 0; j < ring.length; j++) { newRing.push(new ref_properties.Point$1(ring[j][0], ring[j][1])); } this.geometry.push(newRing); } return this.geometry; }; FeatureWrapper$1.prototype.bbox = function () { if (!this.geometry) this.loadGeometry(); var rings = this.geometry; var x1 = Infinity; var x2 = -Infinity; var y1 = Infinity; var y2 = -Infinity; for (var i = 0; i < rings.length; i++) { var ring = rings[i]; for (var j = 0; j < ring.length; j++) { var coord = ring[j]; x1 = Math.min(x1, coord.x); x2 = Math.max(x2, coord.x); y1 = Math.min(y1, coord.y); y2 = Math.max(y2, coord.y); } } return [ x1, y1, x2, y2 ]; }; FeatureWrapper$1.prototype.toGeoJSON = VectorTileFeature.prototype.toGeoJSON; var vtPbf = fromVectorTileJs; var fromVectorTileJs_1 = fromVectorTileJs; var fromGeojsonVt_1 = fromGeojsonVt; var GeoJSONWrapper_1 = geojson_wrapper; function fromVectorTileJs(tile) { var out = new ref_properties.pbf(); writeTile(tile, out); return out.finish(); } function fromGeojsonVt(layers, options) { options = options || {}; var l = {}; for (var k in layers) { l[k] = new geojson_wrapper(layers[k].features, options); l[k].name = k; l[k].version = options.version; l[k].extent = options.extent; } return fromVectorTileJs({ layers: l }); } function writeTile(tile, pbf) { for (var key in tile.layers) { pbf.writeMessage(3, writeLayer, tile.layers[key]); } } function writeLayer(layer, pbf) { pbf.writeVarintField(15, layer.version || 1); pbf.writeStringField(1, layer.name || ''); pbf.writeVarintField(5, layer.extent || 4096); var i; var context = { keys: [], values: [], keycache: {}, valuecache: {} }; for (i = 0; i < layer.length; i++) { context.feature = layer.feature(i); pbf.writeMessage(2, writeFeature, context); } var keys = context.keys; for (i = 0; i < keys.length; i++) { pbf.writeStringField(3, keys[i]); } var values = context.values; for (i = 0; i < values.length; i++) { pbf.writeMessage(4, writeValue, values[i]); } } function writeFeature(context, pbf) { var feature = context.feature; if (feature.id !== undefined) { pbf.writeVarintField(1, feature.id); } pbf.writeMessage(2, writeProperties, context); pbf.writeVarintField(3, feature.type); pbf.writeMessage(4, writeGeometry, feature); } function writeProperties(context, pbf) { var feature = context.feature; var keys = context.keys; var values = context.values; var keycache = context.keycache; var valuecache = context.valuecache; for (var key in feature.properties) { var keyIndex = keycache[key]; if (typeof keyIndex === 'undefined') { keys.push(key); keyIndex = keys.length - 1; keycache[key] = keyIndex; } pbf.writeVarint(keyIndex); var value = feature.properties[key]; var type = typeof value; if (type !== 'string' && type !== 'boolean' && type !== 'number') { value = JSON.stringify(value); } var valueKey = type + ':' + value; var valueIndex = valuecache[valueKey]; if (typeof valueIndex === 'undefined') { values.push(value); valueIndex = values.length - 1; valuecache[valueKey] = valueIndex; } pbf.writeVarint(valueIndex); } } function command(cmd, length) { return (length << 3) + (cmd & 7); } function zigzag(num) { return num << 1 ^ num >> 31; } function writeGeometry(feature, pbf) { var geometry = feature.loadGeometry(); var type = feature.type; var x = 0; var y = 0; var rings = geometry.length; for (var r = 0; r < rings; r++) { var ring = geometry[r]; var count = 1; if (type === 1) { count = ring.length; } pbf.writeVarint(command(1, count)); var lineCount = type === 3 ? ring.length - 1 : ring.length; for (var i = 0; i < lineCount; i++) { if (i === 1 && type !== 1) { pbf.writeVarint(command(2, lineCount - 1)); } var dx = ring[i].x - x; var dy = ring[i].y - y; pbf.writeVarint(zigzag(dx)); pbf.writeVarint(zigzag(dy)); x += dx; y += dy; } if (type === 3) { pbf.writeVarint(command(7, 1)); } } } function writeValue(value, pbf) { var type = typeof value; if (type === 'string') { pbf.writeStringField(1, value); } else if (type === 'boolean') { pbf.writeBooleanField(7, value); } else if (type === 'number') { if (value % 1 !== 0) { pbf.writeDoubleField(3, value); } else if (value < 0) { pbf.writeSVarintField(6, value); } else { pbf.writeVarintField(5, value); } } } vtPbf.fromVectorTileJs = fromVectorTileJs_1; vtPbf.fromGeojsonVt = fromGeojsonVt_1; vtPbf.GeoJSONWrapper = GeoJSONWrapper_1; function sortKD(ids, coords, nodeSize, left, right, depth) { if (right - left <= nodeSize) return; const m = left + right >> 1; select(ids, coords, m, left, right, depth % 2); sortKD(ids, coords, nodeSize, left, m - 1, depth + 1); sortKD(ids, coords, nodeSize, m + 1, right, depth + 1); } function select(ids, coords, k, left, right, inc) { while (right > left) { if (right - left > 600) { const n = right - left + 1; const m = k - left + 1; const z = Math.log(n); const s = 0.5 * Math.exp(2 * z / 3); const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); const newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); select(ids, coords, k, newLeft, newRight, inc); } const t = coords[2 * k + inc]; let i = left; let j = right; swapItem(ids, coords, left, k); if (coords[2 * right + inc] > t) swapItem(ids, coords, left, right); while (i < j) { swapItem(ids, coords, i, j); i++; j--; while (coords[2 * i + inc] < t) i++; while (coords[2 * j + inc] > t) j--; } if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j); else { j++; swapItem(ids, coords, j, right); } if (j <= k) left = j + 1; if (k <= j) right = j - 1; } } function swapItem(ids, coords, i, j) { swap(ids, i, j); swap(coords, 2 * i, 2 * j); swap(coords, 2 * i + 1, 2 * j + 1); } function swap(arr, i, j) { const tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } function range(ids, coords, minX, minY, maxX, maxY, nodeSize) { const stack = [ 0, ids.length - 1, 0 ]; const result = []; let x, y; while (stack.length) { const axis = stack.pop(); const right = stack.pop(); const left = stack.pop(); if (right - left <= nodeSize) { for (let i = left; i <= right; i++) { x = coords[2 * i]; y = coords[2 * i + 1]; if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]); } continue; } const m = Math.floor((left + right) / 2); x = coords[2 * m]; y = coords[2 * m + 1]; if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]); const nextAxis = (axis + 1) % 2; if (axis === 0 ? minX <= x : minY <= y) { stack.push(left); stack.push(m - 1); stack.push(nextAxis); } if (axis === 0 ? maxX >= x : maxY >= y) { stack.push(m + 1); stack.push(right); stack.push(nextAxis); } } return result; } function within(ids, coords, qx, qy, r, nodeSize) { const stack = [ 0, ids.length - 1, 0 ]; const result = []; const r2 = r * r; while (stack.length) { const axis = stack.pop(); const right = stack.pop(); const left = stack.pop(); if (right - left <= nodeSize) { for (let i = left; i <= right; i++) { if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]); } continue; } const m = Math.floor((left + right) / 2); const x = coords[2 * m]; const y = coords[2 * m + 1]; if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]); const nextAxis = (axis + 1) % 2; if (axis === 0 ? qx - r <= x : qy - r <= y) { stack.push(left); stack.push(m - 1); stack.push(nextAxis); } if (axis === 0 ? qx + r >= x : qy + r >= y) { stack.push(m + 1); stack.push(right); stack.push(nextAxis); } } return result; } function sqDist(ax, ay, bx, by) { const dx = ax - bx; const dy = ay - by; return dx * dx + dy * dy; } const defaultGetX = p => p[0]; const defaultGetY = p => p[1]; class KDBush { constructor(points, getX = defaultGetX, getY = defaultGetY, nodeSize = 64, ArrayType = Float64Array) { this.nodeSize = nodeSize; this.points = points; const IndexArrayType = points.length < 65536 ? Uint16Array : Uint32Array; const ids = this.ids = new IndexArrayType(points.length); const coords = this.coords = new ArrayType(points.length * 2); for (let i = 0; i < points.length; i++) { ids[i] = i; coords[2 * i] = getX(points[i]); coords[2 * i + 1] = getY(points[i]); } sortKD(ids, coords, nodeSize, 0, ids.length - 1, 0); } range(minX, minY, maxX, maxY) { return range(this.ids, this.coords, minX, minY, maxX, maxY, this.nodeSize); } within(x, y, r) { return within(this.ids, this.coords, x, y, r, this.nodeSize); } } const defaultOptions = { minZoom: 0, maxZoom: 16, minPoints: 2, radius: 40, extent: 512, nodeSize: 64, log: false, generateId: false, reduce: null, map: props => props }; const fround = Math.fround || (tmp => x => { tmp[0] = +x; return tmp[0]; })(new Float32Array(1)); class Supercluster { constructor(options) { this.options = extend(Object.create(defaultOptions), options); this.trees = new Array(this.options.maxZoom + 1); } load(points) { const {log, minZoom, maxZoom, nodeSize} = this.options; if (log) console.time('total time'); const timerId = `prepare ${ points.length } points`; if (log) console.time(timerId); this.points = points; let clusters = []; for (let i = 0; i < points.length; i++) { if (!points[i].geometry) continue; clusters.push(createPointCluster(points[i], i)); } this.trees[maxZoom + 1] = new KDBush(clusters, getX, getY, nodeSize, Float32Array); if (log) console.timeEnd(timerId); for (let z = maxZoom; z >= minZoom; z--) { const now = +Date.now(); clusters = this._cluster(clusters, z); this.trees[z] = new KDBush(clusters, getX, getY, nodeSize, Float32Array); if (log) console.log('z%d: %d clusters in %dms', z, clusters.length, +Date.now() - now); } if (log) console.timeEnd('total time'); return this; } getClusters(bbox, zoom) { let minLng = ((bbox[0] + 180) % 360 + 360) % 360 - 180; const minLat = Math.max(-90, Math.min(90, bbox[1])); let maxLng = bbox[2] === 180 ? 180 : ((bbox[2] + 180) % 360 + 360) % 360 - 180; const maxLat = Math.max(-90, Math.min(90, bbox[3])); if (bbox[2] - bbox[0] >= 360) { minLng = -180; maxLng = 180; } else if (minLng > maxLng) { const easternHem = this.getClusters([ minLng, minLat, 180, maxLat ], zoom); const westernHem = this.getClusters([ -180, minLat, maxLng, maxLat ], zoom); return easternHem.concat(westernHem); } const tree = this.trees[this._limitZoom(zoom)]; const ids = tree.range(lngX(minLng), latY(maxLat), lngX(maxLng), latY(minLat)); const clusters = []; for (const id of ids) { const c = tree.points[id]; clusters.push(c.numPoints ? getClusterJSON(c) : this.points[c.index]); } return clusters; } getChildren(clusterId) { const originId = this._getOriginId(clusterId); const originZoom = this._getOriginZoom(clusterId); const errorMsg = 'No cluster with the specified id.'; const index = this.trees[originZoom]; if (!index) throw new Error(errorMsg); const origin = index.points[originId]; if (!origin) throw new Error(errorMsg); const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1)); const ids = index.within(origin.x, origin.y, r); const children = []; for (const id of ids) { const c = index.points[id]; if (c.parentId === clusterId) { children.push(c.numPoints ? getClusterJSON(c) : this.points[c.index]); } } if (children.length === 0) throw new Error(errorMsg); return children; } getLeaves(clusterId, limit, offset) { limit = limit || 10; offset = offset || 0; const leaves = []; this._appendLeaves(leaves, clusterId, limit, offset, 0); return leaves; } getTile(z, x, y) { const tree = this.trees[this._limitZoom(z)]; const z2 = Math.pow(2, z); const {extent, radius} = this.options; const p = radius / extent; const top = (y - p) / z2; const bottom = (y + 1 + p) / z2; const tile = { features: [] }; this._addTileFeatures(tree.range((x - p) / z2, top, (x + 1 + p) / z2, bottom), tree.points, x, y, z2, tile); if (x === 0) { this._addTileFeatures(tree.range(1 - p / z2, top, 1, bottom), tree.points, z2, y, z2, tile); } if (x === z2 - 1) { this._addTileFeatures(tree.range(0, top, p / z2, bottom), tree.points, -1, y, z2, tile); } return tile.features.length ? tile : null; } getClusterExpansionZoom(clusterId) { let expansionZoom = this._getOriginZoom(clusterId) - 1; while (expansionZoom <= this.options.maxZoom) { const children = this.getChildren(clusterId); expansionZoom++; if (children.length !== 1) break; clusterId = children[0].properties.cluster_id; } return expansionZoom; } _appendLeaves(result, clusterId, limit, offset, skipped) { const children = this.getChildren(clusterId); for (const child of children) { const props = child.properties; if (props && props.cluster) { if (skipped + props.point_count <= offset) { skipped += props.point_count; } else { skipped = this._appendLeaves(result, props.cluster_id, limit, offset, skipped); } } else if (skipped < offset) { skipped++; } else { result.push(child); } if (result.length === limit) break; } return skipped; } _addTileFeatures(ids, points, x, y, z2, tile) { for (const i of ids) { const c = points[i]; const isCluster = c.numPoints; const f = { type: 1, geometry: [[ Math.round(this.options.extent * (c.x * z2 - x)), Math.round(this.options.extent * (c.y * z2 - y)) ]], tags: isCluster ? getClusterProperties(c) : this.points[c.index].properties }; let id; if (isCluster) { id = c.id; } else if (this.options.generateId) { id = c.index; } else if (this.points[c.index].id) { id = this.points[c.index].id; } if (id !== undefined) f.id = id; tile.features.push(f); } } _limitZoom(z) { return Math.max(this.options.minZoom, Math.min(+z, this.options.maxZoom + 1)); } _cluster(points, zoom) { const clusters = []; const {radius, extent, reduce, minPoints} = this.options; const r = radius / (extent * Math.pow(2, zoom)); for (let i = 0; i < points.length; i++) { const p = points[i]; if (p.zoom <= zoom) continue; p.zoom = zoom; const tree = this.trees[zoom + 1]; const neighborIds = tree.within(p.x, p.y, r); const numPointsOrigin = p.numPoints || 1; let numPoints = numPointsOrigin; for (const neighborId of neighborIds) { const b = tree.points[neighborId]; if (b.zoom > zoom) numPoints += b.numPoints || 1; } if (numPoints >= minPoints) { let wx = p.x * numPointsOrigin; let wy = p.y * numPointsOrigin; let clusterProperties = reduce && numPointsOrigin > 1 ? this._map(p, true) : null; const id = (i << 5) + (zoom + 1) + this.points.length; for (const neighborId of neighborIds) { const b = tree.points[neighborId]; if (b.zoom <= zoom) continue; b.zoom = zoom; const numPoints2 = b.numPoints || 1; wx += b.x * numPoints2; wy += b.y * numPoints2; b.parentId = id; if (reduce) { if (!clusterProperties) clusterProperties = this._map(p, true); reduce(clusterProperties, this._map(b)); } } p.parentId = id; clusters.push(createCluster(wx / numPoints, wy / numPoints, id, numPoints, clusterProperties)); } else { clusters.push(p); if (numPoints > 1) { for (const neighborId of neighborIds) { const b = tree.points[neighborId]; if (b.zoom <= zoom) continue; b.zoom = zoom; clusters.push(b); } } } } return clusters; } _getOriginId(clusterId) { return clusterId - this.points.length >> 5; } _getOriginZoom(clusterId) { return (clusterId - this.points.length) % 32; } _map(point, clone) { if (point.numPoints) { return clone ? extend({}, point.properties) : point.properties; } const original = this.points[point.index].properties; const result = this.options.map(original); return clone && result === original ? extend({}, result) : result; } } function createCluster(x, y, id, numPoints, properties) { return { x: fround(x), y: fround(y), zoom: Infinity, id, parentId: -1, numPoints, properties }; } function createPointCluster(p, id) { const [x, y] = p.geometry.coordinates; return { x: fround(lngX(x)), y: fround(latY(y)), zoom: Infinity, index: id, parentId: -1 }; } function getClusterJSON(cluster) { return { type: 'Feature', id: cluster.id, properties: getClusterProperties(cluster), geometry: { type: 'Point', coordinates: [ xLng(cluster.x), yLat(cluster.y) ] } }; } function getClusterProperties(cluster) { const count = cluster.numPoints; const abbrev = count >= 10000 ? `${ Math.round(count / 1000) }k` : count >= 1000 ? `${ Math.round(count / 100) / 10 }k` : count; return extend(extend({}, cluster.properties), { cluster: true, cluster_id: cluster.id, point_count: count, point_count_abbreviated: abbrev }); } function lngX(lng) { return lng / 360 + 0.5; } function latY(lat) { const sin = Math.sin(lat * Math.PI / 180); const y = 0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI; return y < 0 ? 0 : y > 1 ? 1 : y; } function xLng(x) { return (x - 0.5) * 360; } function yLat(y) { const y2 = (180 - y * 360) * Math.PI / 180; return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90; } function extend(dest, src) { for (const id in src) dest[id] = src[id]; return dest; } function getX(p) { return p.x; } function getY(p) { return p.y; } function simplify(coords, first, last, sqTolerance) { var maxSqDist = sqTolerance; var mid = last - first >> 1; var minPosToMid = last - first; var index; var ax = coords[first]; var ay = coords[first + 1]; var bx = coords[last]; var by = coords[last + 1]; for (var i = first + 3; i < last; i += 3) { var d = getSqSegDist(coords[i], coords[i + 1], ax, ay, bx, by); if (d > maxSqDist) { index = i; maxSqDist = d; } else if (d === maxSqDist) { var posToMid = Math.abs(i - mid); if (posToMid < minPosToMid) { index = i; minPosToMid = posToMid; } } } if (maxSqDist > sqTolerance) { if (index - first > 3) simplify(coords, first, index, sqTolerance); coords[index + 2] = maxSqDist; if (last - index > 3) simplify(coords, index, last, sqTolerance); } } function getSqSegDist(px, py, x, y, bx, by) { var dx = bx - x; var dy = by - y; if (dx !== 0 || dy !== 0) { var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy); if (t > 1) { x = bx; y = by; } else if (t > 0) { x += dx * t; y += dy * t; } } dx = px - x; dy = py - y; return dx * dx + dy * dy; } function createFeature(id, type, geom, tags) { var feature = { id: typeof id === 'undefined' ? null : id, type: type, geometry: geom, tags: tags, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }; calcBBox(feature); return feature; } function calcBBox(feature) { var geom = feature.geometry; var type = feature.type; if (type === 'Point' || type === 'MultiPoint' || type === 'LineString') { calcLineBBox(feature, geom); } else if (type === 'Polygon' || type === 'MultiLineString') { for (var i = 0; i < geom.length; i++) { calcLineBBox(feature, geom[i]); } } else if (type === 'MultiPolygon') { for (i = 0; i < geom.length; i++) { for (var j = 0; j < geom[i].length; j++) { calcLineBBox(feature, geom[i][j]); } } } } function calcLineBBox(feature, geom) { for (var i = 0; i < geom.length; i += 3) { feature.minX = Math.min(feature.minX, geom[i]); feature.minY = Math.min(feature.minY, geom[i + 1]); feature.maxX = Math.max(feature.maxX, geom[i]); feature.maxY = Math.max(feature.maxY, geom[i + 1]); } } function convert(data, options) { var features = []; if (data.type === 'FeatureCollection') { for (var i = 0; i < data.features.length; i++) { convertFeature(features, data.features[i], options, i); } } else if (data.type === 'Feature') { convertFeature(features, data, options); } else { convertFeature(features, { geometry: data }, options); } return features; } function convertFeature(features, geojson, options, index) { if (!geojson.geometry) return; var coords = geojson.geometry.coordinates; var type = geojson.geometry.type; var tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2); var geometry = []; var id = geojson.id; if (options.promoteId) { id = geojson.properties[options.promoteId]; } else if (options.generateId) { id = index || 0; } if (type === 'Point') { convertPoint(coords, geometry); } else if (type === 'MultiPoint') { for (var i = 0; i < coords.length; i++) { convertPoint(coords[i], geometry); } } else if (type === 'LineString') { convertLine(coords, geometry, tolerance, false); } else if (type === 'MultiLineString') { if (options.lineMetrics) { for (i = 0; i < coords.length; i++) { geometry = []; convertLine(coords[i], geometry, tolerance, false); features.push(createFeature(id, 'LineString', geometry, geojson.properties)); } return; } else { convertLines(coords, geometry, tolerance, false); } } else if (type === 'Polygon') { convertLines(coords, geometry, tolerance, true); } else if (type === 'MultiPolygon') { for (i = 0; i < coords.length; i++) { var polygon = []; convertLines(coords[i], polygon, tolerance, true); geometry.push(polygon); } } else if (type === 'GeometryCollection') { for (i = 0; i < geojson.geometry.geometries.length; i++) { convertFeature(features, { id: id, geometry: geojson.geometry.geometries[i], properties: geojson.properties }, options, index); } return; } else { throw new Error('Input data is not a valid GeoJSON object.'); } features.push(createFeature(id, type, geometry, geojson.properties)); } function convertPoint(coords, out) { out.push(projectX(coords[0])); out.push(projectY(coords[1])); out.push(0); } function convertLine(ring, out, tolerance, isPolygon) { var x0, y0; var size = 0; for (var j = 0; j < ring.length; j++) { var x = projectX(ring[j][0]); var y = projectY(ring[j][1]); out.push(x); out.push(y); out.push(0); if (j > 0) { if (isPolygon) { size += (x0 * y - x * y0) / 2; } else { size += Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)); } } x0 = x; y0 = y; } var last = out.length - 3; out[2] = 1; simplify(out, 0, last, tolerance); out[last + 2] = 1; out.size = Math.abs(size); out.start = 0; out.end = out.size; } function convertLines(rings, out, tolerance, isPolygon) { for (var i = 0; i < rings.length; i++) { var geom = []; convertLine(rings[i], geom, tolerance, isPolygon); out.push(geom); } } function projectX(x) { return x / 360 + 0.5; } function projectY(y) { var sin = Math.sin(y * Math.PI / 180); var y2 = 0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI; return y2 < 0 ? 0 : y2 > 1 ? 1 : y2; } function clip(features, scale, k1, k2, axis, minAll, maxAll, options) { k1 /= scale; k2 /= scale; if (minAll >= k1 && maxAll < k2) return features; else if (maxAll < k1 || minAll >= k2) return null; var clipped = []; for (var i = 0; i < features.length; i++) { var feature = features[i]; var geometry = feature.geometry; var type = feature.type; var min = axis === 0 ? feature.minX : feature.minY; var max = axis === 0 ? feature.maxX : feature.maxY; if (min >= k1 && max < k2) { clipped.push(feature); continue; } else if (max < k1 || min >= k2) { continue; } var newGeometry = []; if (type === 'Point' || type === 'MultiPoint') { clipPoints(geometry, newGeometry, k1, k2, axis); } else if (type === 'LineString') { clipLine(geometry, newGeometry, k1, k2, axis, false, options.lineMetrics); } else if (type === 'MultiLineString') { clipLines(geometry, newGeometry, k1, k2, axis, false); } else if (type === 'Polygon') { clipLines(geometry, newGeometry, k1, k2, axis, true); } else if (type === 'MultiPolygon') { for (var j = 0; j < geometry.length; j++) { var polygon = []; clipLines(geometry[j], polygon, k1, k2, axis, true); if (polygon.length) { newGeometry.push(polygon); } } } if (newGeometry.length) { if (options.lineMetrics && type === 'LineString') { for (j = 0; j < newGeometry.length; j++) { clipped.push(createFeature(feature.id, type, newGeometry[j], feature.tags)); } continue; } if (type === 'LineString' || type === 'MultiLineString') { if (newGeometry.length === 1) { type = 'LineString'; newGeometry = newGeometry[0]; } else { type = 'MultiLineString'; } } if (type === 'Point' || type === 'MultiPoint') { type = newGeometry.length === 3 ? 'Point' : 'MultiPoint'; } clipped.push(createFeature(feature.id, type, newGeometry, feature.tags)); } } return clipped.length ? clipped : null; } function clipPoints(geom, newGeom, k1, k2, axis) { for (var i = 0; i < geom.length; i += 3) { var a = geom[i + axis]; if (a >= k1 && a <= k2) { newGeom.push(geom[i]); newGeom.push(geom[i + 1]); newGeom.push(geom[i + 2]); } } } function clipLine(geom, newGeom, k1, k2, axis, isPolygon, trackMetrics) { var slice = newSlice(geom); var intersect = axis === 0 ? intersectX : intersectY; var len = geom.start; var segLen, t; for (var i = 0; i < geom.length - 3; i += 3) { var ax = geom[i]; var ay = geom[i + 1]; var az = geom[i + 2]; var bx = geom[i + 3]; var by = geom[i + 4]; var a = axis === 0 ? ax : ay; var b = axis === 0 ? bx : by; var exited = false; if (trackMetrics) segLen = Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2)); if (a < k1) { if (b > k1) { t = intersect(slice, ax, ay, bx, by, k1); if (trackMetrics) slice.start = len + segLen * t; } } else if (a > k2) { if (b < k2) { t = intersect(slice, ax, ay, bx, by, k2); if (trackMetrics) slice.start = len + segLen * t; } } else { addPoint(slice, ax, ay, az); } if (b < k1 && a >= k1) { t = intersect(slice, ax, ay, bx, by, k1); exited = true; } if (b > k2 && a <= k2) { t = intersect(slice, ax, ay, bx, by, k2); exited = true; } if (!isPolygon && exited) { if (trackMetrics) slice.end = len + segLen * t; newGeom.push(slice); slice = newSlice(geom); } if (trackMetrics) len += segLen; } var last = geom.length - 3; ax = geom[last]; ay = geom[last + 1]; az = geom[last + 2]; a = axis === 0 ? ax : ay; if (a >= k1 && a <= k2) addPoint(slice, ax, ay, az); last = slice.length - 3; if (isPolygon && last >= 3 && (slice[last] !== slice[0] || slice[last + 1] !== slice[1])) { addPoint(slice, slice[0], slice[1], slice[2]); } if (slice.length) { newGeom.push(slice); } } function newSlice(line) { var slice = []; slice.size = line.size; slice.start = line.start; slice.end = line.end; return slice; } function clipLines(geom, newGeom, k1, k2, axis, isPolygon) { for (var i = 0; i < geom.length; i++) { clipLine(geom[i], newGeom, k1, k2, axis, isPolygon, false); } } function addPoint(out, x, y, z) { out.push(x); out.push(y); out.push(z); } function intersectX(out, ax, ay, bx, by, x) { var t = (x - ax) / (bx - ax); out.push(x); out.push(ay + (by - ay) * t); out.push(1); return t; } function intersectY(out, ax, ay, bx, by, y) { var t = (y - ay) / (by - ay); out.push(ax + (bx - ax) * t); out.push(y); out.push(1); return t; } function wrap(features, options) { var buffer = options.buffer / options.extent; var merged = features; var left = clip(features, 1, -1 - buffer, buffer, 0, -1, 2, options); var right = clip(features, 1, 1 - buffer, 2 + buffer, 0, -1, 2, options); if (left || right) { merged = clip(features, 1, -buffer, 1 + buffer, 0, -1, 2, options) || []; if (left) merged = shiftFeatureCoords(left, 1).concat(merged); if (right) merged = merged.concat(shiftFeatureCoords(right, -1)); } return merged; } function shiftFeatureCoords(features, offset) { var newFeatures = []; for (var i = 0; i < features.length; i++) { var feature = features[i], type = feature.type; var newGeometry; if (type === 'Point' || type === 'MultiPoint' || type === 'LineString') { newGeometry = shiftCoords(feature.geometry, offset); } else if (type === 'MultiLineString' || type === 'Polygon') { newGeometry = []; for (var j = 0; j < feature.geometry.length; j++) { newGeometry.push(shiftCoords(feature.geometry[j], offset)); } } else if (type === 'MultiPolygon') { newGeometry = []; for (j = 0; j < feature.geometry.length; j++) { var newPolygon = []; for (var k = 0; k < feature.geometry[j].length; k++) { newPolygon.push(shiftCoords(feature.geometry[j][k], offset)); } newGeometry.push(newPolygon); } } newFeatures.push(createFeature(feature.id, type, newGeometry, feature.tags)); } return newFeatures; } function shiftCoords(points, offset) { var newPoints = []; newPoints.size = points.size; if (points.start !== undefined) { newPoints.start = points.start; newPoints.end = points.end; } for (var i = 0; i < points.length; i += 3) { newPoints.push(points[i] + offset, points[i + 1], points[i + 2]); } return newPoints; } function transformTile(tile, extent) { if (tile.transformed) return tile; var z2 = 1 << tile.z, tx = tile.x, ty = tile.y, i, j, k; for (i = 0; i < tile.features.length; i++) { var feature = tile.features[i], geom = feature.geometry, type = feature.type; feature.geometry = []; if (type === 1) { for (j = 0; j < geom.length; j += 2) { feature.geometry.push(transformPoint(geom[j], geom[j + 1], extent, z2, tx, ty)); } } else { for (j = 0; j < geom.length; j++) { var ring = []; for (k = 0; k < geom[j].length; k += 2) { ring.push(transformPoint(geom[j][k], geom[j][k + 1], extent, z2, tx, ty)); } feature.geometry.push(ring); } } } tile.transformed = true; return tile; } function transformPoint(x, y, extent, z2, tx, ty) { return [ Math.round(extent * (x * z2 - tx)), Math.round(extent * (y * z2 - ty)) ]; } function createTile(features, z, tx, ty, options) { var tolerance = z === options.maxZoom ? 0 : options.tolerance / ((1 << z) * options.extent); var tile = { features: [], numPoints: 0, numSimplified: 0, numFeatures: 0, source: null, x: tx, y: ty, z: z, transformed: false, minX: 2, minY: 1, maxX: -1, maxY: 0 }; for (var i = 0; i < features.length; i++) { tile.numFeatures++; addFeature(tile, features[i], tolerance, options); var minX = features[i].minX; var minY = features[i].minY; var maxX = features[i].maxX; var maxY = features[i].maxY; if (minX < tile.minX) tile.minX = minX; if (minY < tile.minY) tile.minY = minY; if (maxX > tile.maxX) tile.maxX = maxX; if (maxY > tile.maxY) tile.maxY = maxY; } return tile; } function addFeature(tile, feature, tolerance, options) { var geom = feature.geometry, type = feature.type, simplified = []; if (type === 'Point' || type === 'MultiPoint') { for (var i = 0; i < geom.length; i += 3) { simplified.push(geom[i]); simplified.push(geom[i + 1]); tile.numPoints++; tile.numSimplified++; } } else if (type === 'LineString') { addLine(simplified, geom, tile, tolerance, false, false); } else if (type === 'MultiLineString' || type === 'Polygon') { for (i = 0; i < geom.length; i++) { addLine(simplified, geom[i], tile, tolerance, type === 'Polygon', i === 0); } } else if (type === 'MultiPolygon') { for (var k = 0; k < geom.length; k++) { var polygon = geom[k]; for (i = 0; i < polygon.length; i++) { addLine(simplified, polygon[i], tile, tolerance, true, i === 0); } } } if (simplified.length) { var tags = feature.tags || null; if (type === 'LineString' && options.lineMetrics) { tags = {}; for (var key in feature.tags) tags[key] = feature.tags[key]; tags['mapbox_clip_start'] = geom.start / geom.size; tags['mapbox_clip_end'] = geom.end / geom.size; } var tileFeature = { geometry: simplified, type: type === 'Polygon' || type === 'MultiPolygon' ? 3 : type === 'LineString' || type === 'MultiLineString' ? 2 : 1, tags: tags }; if (feature.id !== null) { tileFeature.id = feature.id; } tile.features.push(tileFeature); } } function addLine(result, geom, tile, tolerance, isPolygon, isOuter) { var sqTolerance = tolerance * tolerance; if (tolerance > 0 && geom.size < (isPolygon ? sqTolerance : tolerance)) { tile.numPoints += geom.length / 3; return; } var ring = []; for (var i = 0; i < geom.length; i += 3) { if (tolerance === 0 || geom[i + 2] > sqTolerance) { tile.numSimplified++; ring.push(geom[i]); ring.push(geom[i + 1]); } tile.numPoints++; } if (isPolygon) rewind$1(ring, isOuter); result.push(ring); } function rewind$1(ring, clockwise) { var area = 0; for (var i = 0, len = ring.length, j = len - 2; i < len; j = i, i += 2) { area += (ring[i] - ring[j]) * (ring[i + 1] + ring[j + 1]); } if (area > 0 === clockwise) { for (i = 0, len = ring.length; i < len / 2; i += 2) { var x = ring[i]; var y = ring[i + 1]; ring[i] = ring[len - 2 - i]; ring[i + 1] = ring[len - 1 - i]; ring[len - 2 - i] = x; ring[len - 1 - i] = y; } } } function geojsonvt(data, options) { return new GeoJSONVT(data, options); } function GeoJSONVT(data, options) { options = this.options = extend$1(Object.create(this.options), options); var debug = options.debug; if (debug) console.time('preprocess data'); if (options.maxZoom < 0 || options.maxZoom > 24) throw new Error('maxZoom should be in the 0-24 range'); if (options.promoteId && options.generateId) throw new Error('promoteId and generateId cannot be used together.'); var features = convert(data, options); this.tiles = {}; this.tileCoords = []; if (debug) { console.timeEnd('preprocess data'); console.log('index: maxZoom: %d, maxPoints: %d', options.indexMaxZoom, options.indexMaxPoints); console.time('generate tiles'); this.stats = {}; this.total = 0; } features = wrap(features, options); if (features.length) this.splitTile(features, 0, 0, 0); if (debug) { if (features.length) console.log('features: %d, points: %d', this.tiles[0].numFeatures, this.tiles[0].numPoints); console.timeEnd('generate tiles'); console.log('tiles generated:', this.total, JSON.stringify(this.stats)); } } GeoJSONVT.prototype.options = { maxZoom: 14, indexMaxZoom: 5, indexMaxPoints: 100000, tolerance: 3, extent: 4096, buffer: 64, lineMetrics: false, promoteId: null, generateId: false, debug: 0 }; GeoJSONVT.prototype.splitTile = function (features, z, x, y, cz, cx, cy) { var stack = [ features, z, x, y ], options = this.options, debug = options.debug; while (stack.length) { y = stack.pop(); x = stack.pop(); z = stack.pop(); features = stack.pop(); var z2 = 1 << z, id = toID(z, x, y), tile = this.tiles[id]; if (!tile) { if (debug > 1) console.time('creation'); tile = this.tiles[id] = createTile(features, z, x, y, options); this.tileCoords.push({ z: z, x: x, y: y }); if (debug) { if (debug > 1) { console.log('tile z%d-%d-%d (features: %d, points: %d, simplified: %d)', z, x, y, tile.numFeatures, tile.numPoints, tile.numSimplified); console.timeEnd('creation'); } var key = 'z' + z; this.stats[key] = (this.stats[key] || 0) + 1; this.total++; } } tile.source = features; if (!cz) { if (z === options.indexMaxZoom || tile.numPoints <= options.indexMaxPoints) continue; } else { if (z === options.maxZoom || z === cz) continue; var m = 1 << cz - z; if (x !== Math.floor(cx / m) || y !== Math.floor(cy / m)) continue; } tile.source = null; if (features.length === 0) continue; if (debug > 1) console.time('clipping'); var k1 = 0.5 * options.buffer / options.extent, k2 = 0.5 - k1, k3 = 0.5 + k1, k4 = 1 + k1, tl, bl, tr, br, left, right; tl = bl = tr = br = null; left = clip(features, z2, x - k1, x + k3, 0, tile.minX, tile.maxX, options); right = clip(features, z2, x + k2, x + k4, 0, tile.minX, tile.maxX, options); features = null; if (left) { tl = clip(left, z2, y - k1, y + k3, 1, tile.minY, tile.maxY, options); bl = clip(left, z2, y + k2, y + k4, 1, tile.minY, tile.maxY, options); left = null; } if (right) { tr = clip(right, z2, y - k1, y + k3, 1, tile.minY, tile.maxY, options); br = clip(right, z2, y + k2, y + k4, 1, tile.minY, tile.maxY, options); right = null; } if (debug > 1) console.timeEnd('clipping'); stack.push(tl || [], z + 1, x * 2, y * 2); stack.push(bl || [], z + 1, x * 2, y * 2 + 1); stack.push(tr || [], z + 1, x * 2 + 1, y * 2); stack.push(br || [], z + 1, x * 2 + 1, y * 2 + 1); } }; GeoJSONVT.prototype.getTile = function (z, x, y) { var options = this.options, extent = options.extent, debug = options.debug; if (z < 0 || z > 24) return null; var z2 = 1 << z; x = (x % z2 + z2) % z2; var id = toID(z, x, y); if (this.tiles[id]) return transformTile(this.tiles[id], extent); if (debug > 1) console.log('drilling down to z%d-%d-%d', z, x, y); var z0 = z, x0 = x, y0 = y, parent; while (!parent && z0 > 0) { z0--; x0 = Math.floor(x0 / 2); y0 = Math.floor(y0 / 2); parent = this.tiles[toID(z0, x0, y0)]; } if (!parent || !parent.source) return null; if (debug > 1) console.log('found parent tile z%d-%d-%d', z0, x0, y0); if (debug > 1) console.time('drilling down'); this.splitTile(parent.source, z0, x0, y0, z, x, y); if (debug > 1) console.timeEnd('drilling down'); return this.tiles[id] ? transformTile(this.tiles[id], extent) : null; }; function toID(z, x, y) { return ((1 << z) * y + x) * 32 + z; } function extend$1(dest, src) { for (var i in src) dest[i] = src[i]; return dest; } function loadGeoJSONTile(params, callback) { const canonical = params.tileID.canonical; if (!this._geoJSONIndex) { return callback(null, null); } const geoJSONTile = this._geoJSONIndex.getTile(canonical.z, canonical.x, canonical.y); if (!geoJSONTile) { return callback(null, null); } const geojsonWrapper = new GeoJSONWrapper(geoJSONTile.features); let pbf = vtPbf(geojsonWrapper); if (pbf.byteOffset !== 0 || pbf.byteLength !== pbf.buffer.byteLength) { pbf = new Uint8Array(pbf); } callback(null, { vectorTile: geojsonWrapper, rawData: pbf.buffer }); } class GeoJSONWorkerSource extends ref_properties.VectorTileWorkerSource { constructor(actor, layerIndex, availableImages, isSpriteLoaded, loadGeoJSON) { super(actor, layerIndex, availableImages, isSpriteLoaded, loadGeoJSONTile); if (loadGeoJSON) { this.loadGeoJSON = loadGeoJSON; } } loadData(params, callback) { if (this._pendingCallback) { this._pendingCallback(null, { abandoned: true }); } this._pendingCallback = callback; this._pendingLoadDataParams = params; if (this._state && this._state !== 'Idle') { this._state = 'NeedsLoadData'; } else { this._state = 'Coalescing'; this._loadData(); } } _loadData() { if (!this._pendingCallback || !this._pendingLoadDataParams) { return; } const callback = this._pendingCallback; const params = this._pendingLoadDataParams; delete this._pendingCallback; delete this._pendingLoadDataParams; const perf = params && params.request && params.request.collectResourceTiming ? new ref_properties.RequestPerformance(params.request) : false; this.loadGeoJSON(params, (err, data) => { if (err || !data) { return callback(err); } else if (typeof data !== 'object') { return callback(new Error(`Input data given to '${ params.source }' is not a valid GeoJSON object.`)); } else { geojsonRewind(data, true); try { if (params.filter) { const compiled = ref_properties.createExpression(params.filter, { type: 'boolean', 'property-type': 'data-driven', overridable: false, transition: false }); if (compiled.result === 'error') throw new Error(compiled.value.map(err => `${ err.key }: ${ err.message }`).join(', ')); const features = data.features.filter(feature => compiled.value.evaluate({ zoom: 0 }, feature)); data = { type: 'FeatureCollection', features }; } this._geoJSONIndex = params.cluster ? new Supercluster(getSuperclusterOptions(params)).load(data.features) : geojsonvt(data, params.geojsonVtOptions); } catch (err) { return callback(err); } this.loaded = {}; const result = {}; if (perf) { const resourceTimingData = perf.finish(); if (resourceTimingData) { result.resourceTiming = {}; result.resourceTiming[params.source] = JSON.parse(JSON.stringify(resourceTimingData)); } } callback(null, result); } }); } coalesce() { if (this._state === 'Coalescing') { this._state = 'Idle'; } else if (this._state === 'NeedsLoadData') { this._state = 'Coalescing'; this._loadData(); } } reloadTile(params, callback) { const loaded = this.loaded, uid = params.uid; if (loaded && loaded[uid]) { return super.reloadTile(params, callback); } else { return this.loadTile(params, callback); } } loadGeoJSON(params, callback) { if (params.request) { ref_properties.getJSON(params.request, callback); } else if (typeof params.data === 'string') { try { return callback(null, JSON.parse(params.data)); } catch (e) { return callback(new Error(`Input data given to '${ params.source }' is not a valid GeoJSON object.`)); } } else { return callback(new Error(`Input data given to '${ params.source }' is not a valid GeoJSON object.`)); } } removeSource(params, callback) { if (this._pendingCallback) { this._pendingCallback(null, { abandoned: true }); } callback(); } getClusterExpansionZoom(params, callback) { try { callback(null, this._geoJSONIndex.getClusterExpansionZoom(params.clusterId)); } catch (e) { callback(e); } } getClusterChildren(params, callback) { try { callback(null, this._geoJSONIndex.getChildren(params.clusterId)); } catch (e) { callback(e); } } getClusterLeaves(params, callback) { try { callback(null, this._geoJSONIndex.getLeaves(params.clusterId, params.limit, params.offset)); } catch (e) { callback(e); } } } function getSuperclusterOptions({superclusterOptions, clusterProperties}) { if (!clusterProperties || !superclusterOptions) return superclusterOptions; const mapExpressions = {}; const reduceExpressions = {}; const globals = { accumulated: null, zoom: 0 }; const feature = { properties: null }; const propertyNames = Object.keys(clusterProperties); for (const key of propertyNames) { const [operator, mapExpression] = clusterProperties[key]; const mapExpressionParsed = ref_properties.createExpression(mapExpression); const reduceExpressionParsed = ref_properties.createExpression(typeof operator === 'string' ? [ operator, ['accumulated'], [ 'get', key ] ] : operator); mapExpressions[key] = mapExpressionParsed.value; reduceExpressions[key] = reduceExpressionParsed.value; } superclusterOptions.map = pointProperties => { feature.properties = pointProperties; const properties = {}; for (const key of propertyNames) { properties[key] = mapExpressions[key].evaluate(globals, feature); } return properties; }; superclusterOptions.reduce = (accumulated, clusterProperties) => { feature.properties = clusterProperties; for (const key of propertyNames) { globals.accumulated = accumulated[key]; accumulated[key] = reduceExpressions[key].evaluate(globals, feature); } }; return superclusterOptions; } class Worker { constructor(self) { this.self = self; this.actor = new ref_properties.Actor(self, this); this.layerIndexes = {}; this.availableImages = {}; this.isSpriteLoaded = false; this.workerSourceTypes = { vector: ref_properties.VectorTileWorkerSource, geojson: GeoJSONWorkerSource }; this.workerSources = {}; this.demWorkerSources = {}; this.self.registerWorkerSource = (name, WorkerSource) => { if (this.workerSourceTypes[name]) { throw new Error(`Worker source with name "${ name }" already registered.`); } this.workerSourceTypes[name] = WorkerSource; }; this.self.registerRTLTextPlugin = rtlTextPlugin => { if (ref_properties.plugin.isParsed()) { throw new Error('RTL text plugin already registered.'); } ref_properties.plugin['applyArabicShaping'] = rtlTextPlugin.applyArabicShaping; ref_properties.plugin['processBidirectionalText'] = rtlTextPlugin.processBidirectionalText; ref_properties.plugin['processStyledBidirectionalText'] = rtlTextPlugin.processStyledBidirectionalText; }; } checkIfReady(mapID, unused, callback) { callback(); } setReferrer(mapID, referrer) { this.referrer = referrer; } spriteLoaded(mapId, bool) { this.isSpriteLoaded = bool; for (const workerSource in this.workerSources[mapId]) { const ws = this.workerSources[mapId][workerSource]; for (const source in ws) { if (ws[source] instanceof ref_properties.VectorTileWorkerSource) { ws[source].isSpriteLoaded = bool; ws[source].fire(new ref_properties.Event('isSpriteLoaded')); } } } } setImages(mapId, images, callback) { this.availableImages[mapId] = images; for (const workerSource in this.workerSources[mapId]) { const ws = this.workerSources[mapId][workerSource]; for (const source in ws) { ws[source].availableImages = images; } } callback(); } enableTerrain(mapId, enable, callback) { this.terrain = enable; callback(); } setLayers(mapId, layers, callback) { this.getLayerIndex(mapId).replace(layers); callback(); } updateLayers(mapId, params, callback) { this.getLayerIndex(mapId).update(params.layers, params.removedIds); callback(); } loadTile(mapId, params, callback) { const p = this.enableTerrain ? ref_properties.extend({ enableTerrain: this.terrain }, params) : params; this.getWorkerSource(mapId, params.type, params.source).loadTile(p, callback); } loadDEMTile(mapId, params, callback) { const p = this.enableTerrain ? ref_properties.extend({ buildQuadTree: this.terrain }, params) : params; this.getDEMWorkerSource(mapId, params.source).loadTile(p, callback); } reloadTile(mapId, params, callback) { const p = this.enableTerrain ? ref_properties.extend({ enableTerrain: this.terrain }, params) : params; this.getWorkerSource(mapId, params.type, params.source).reloadTile(p, callback); } abortTile(mapId, params, callback) { this.getWorkerSource(mapId, params.type, params.source).abortTile(params, callback); } removeTile(mapId, params, callback) { this.getWorkerSource(mapId, params.type, params.source).removeTile(params, callback); } removeSource(mapId, params, callback) { if (!this.workerSources[mapId] || !this.workerSources[mapId][params.type] || !this.workerSources[mapId][params.type][params.source]) { return; } const worker = this.workerSources[mapId][params.type][params.source]; delete this.workerSources[mapId][params.type][params.source]; if (worker.removeSource !== undefined) { worker.removeSource(params, callback); } else { callback(); } } loadWorkerSource(map, params, callback) { try { this.self.importScripts(params.url); callback(); } catch (e) { callback(e.toString()); } } syncRTLPluginState(map, state, callback) { try { ref_properties.plugin.setState(state); const pluginURL = ref_properties.plugin.getPluginURL(); if (ref_properties.plugin.isLoaded() && !ref_properties.plugin.isParsed() && pluginURL != null) { this.self.importScripts(pluginURL); const complete = ref_properties.plugin.isParsed(); const error = complete ? undefined : new Error(`RTL Text Plugin failed to import scripts from ${ pluginURL }`); callback(error, complete); } } catch (e) { callback(e.toString()); } } getAvailableImages(mapId) { let availableImages = this.availableImages[mapId]; if (!availableImages) { availableImages = []; } return availableImages; } getLayerIndex(mapId) { let layerIndexes = this.layerIndexes[mapId]; if (!layerIndexes) { layerIndexes = this.layerIndexes[mapId] = new StyleLayerIndex(); } return layerIndexes; } getWorkerSource(mapId, type, source) { if (!this.workerSources[mapId]) this.workerSources[mapId] = {}; if (!this.workerSources[mapId][type]) this.workerSources[mapId][type] = {}; if (!this.workerSources[mapId][type][source]) { const actor = { send: (type, data, callback, mustQueue, _, metadata) => { this.actor.send(type, data, callback, mapId, mustQueue, metadata); }, scheduler: this.actor.scheduler }; this.workerSources[mapId][type][source] = new this.workerSourceTypes[type](actor, this.getLayerIndex(mapId), this.getAvailableImages(mapId), this.isSpriteLoaded); } return this.workerSources[mapId][type][source]; } getDEMWorkerSource(mapId, source) { if (!this.demWorkerSources[mapId]) this.demWorkerSources[mapId] = {}; if (!this.demWorkerSources[mapId][source]) { this.demWorkerSources[mapId][source] = new RasterDEMTileWorkerSource(); } return this.demWorkerSources[mapId][source]; } enforceCacheSizeLimit(mapId, limit) { ref_properties.enforceCacheSizeLimit(limit); } getWorkerPerformanceMetrics(mapId, params, callback) { callback(undefined, void 0); } } if (typeof WorkerGlobalScope !== 'undefined' && typeof self !== 'undefined' && self instanceof WorkerGlobalScope) { self.worker = new Worker(self); } return Worker; }); define(['./shared'], function (ref_properties) { 'use strict'; var supported = isSupported; function isSupported(options) { return !notSupportedReason(options); } function notSupportedReason(options) { if (!isBrowser()) return 'not a browser'; if (!isArraySupported()) return 'insufficent Array support'; if (!isFunctionSupported()) return 'insufficient Function support'; if (!isObjectSupported()) return 'insufficient Object support'; if (!isJSONSupported()) return 'insufficient JSON support'; if (!isWorkerSupported()) return 'insufficient worker support'; if (!isUint8ClampedArraySupported()) return 'insufficient Uint8ClampedArray support'; if (!isArrayBufferSupported()) return 'insufficient ArrayBuffer support'; if (!isCanvasGetImageDataSupported()) return 'insufficient Canvas/getImageData support'; if (!isWebGLSupportedCached(options && options.failIfMajorPerformanceCaveat)) return 'insufficient WebGL support'; if (!isNotIE()) return 'insufficient ECMAScript 6 support'; } function isBrowser() { return typeof window !== 'undefined' && typeof document !== 'undefined'; } function isArraySupported() { return Array.prototype && Array.prototype.every && Array.prototype.filter && Array.prototype.forEach && Array.prototype.indexOf && Array.prototype.lastIndexOf && Array.prototype.map && Array.prototype.some && Array.prototype.reduce && Array.prototype.reduceRight && Array.isArray; } function isFunctionSupported() { return Function.prototype && Function.prototype.bind; } function isObjectSupported() { return Object.keys && Object.create && Object.getPrototypeOf && Object.getOwnPropertyNames && Object.isSealed && Object.isFrozen && Object.isExtensible && Object.getOwnPropertyDescriptor && Object.defineProperty && Object.defineProperties && Object.seal && Object.freeze && Object.preventExtensions; } function isJSONSupported() { return 'JSON' in window && 'parse' in JSON && 'stringify' in JSON; } function isWorkerSupported() { if (!('Worker' in window && 'Blob' in window && 'URL' in window)) { return false; } var blob = new Blob([''], { type: 'text/javascript' }); var workerURL = URL.createObjectURL(blob); var supported; var worker; try { worker = new Worker(workerURL); supported = true; } catch (e) { supported = false; } if (worker) { worker.terminate(); } URL.revokeObjectURL(workerURL); return supported; } function isUint8ClampedArraySupported() { return 'Uint8ClampedArray' in window; } function isArrayBufferSupported() { return ArrayBuffer.isView; } function isCanvasGetImageDataSupported() { var canvas = document.createElement('canvas'); canvas.width = canvas.height = 1; var context = canvas.getContext('2d'); if (!context) { return false; } var imageData = context.getImageData(0, 0, 1, 1); return imageData && imageData.width === canvas.width; } var isWebGLSupportedCache = {}; function isWebGLSupportedCached(failIfMajorPerformanceCaveat) { if (isWebGLSupportedCache[failIfMajorPerformanceCaveat] === undefined) { isWebGLSupportedCache[failIfMajorPerformanceCaveat] = isWebGLSupported(failIfMajorPerformanceCaveat); } return isWebGLSupportedCache[failIfMajorPerformanceCaveat]; } isSupported.webGLContextAttributes = { antialias: false, alpha: true, stencil: true, depth: true }; function getWebGLContext(failIfMajorPerformanceCaveat) { var canvas = document.createElement('canvas'); var attributes = Object.create(isSupported.webGLContextAttributes); attributes.failIfMajorPerformanceCaveat = failIfMajorPerformanceCaveat; return canvas.getContext('webgl', attributes) || canvas.getContext('experimental-webgl', attributes); } function isWebGLSupported(failIfMajorPerformanceCaveat) { var gl = getWebGLContext(failIfMajorPerformanceCaveat); if (!gl) { return false; } var shader; try { shader = gl.createShader(gl.VERTEX_SHADER); } catch (e) { return false; } if (!shader || gl.isContextLost()) { return false; } gl.shaderSource(shader, 'void main() {}'); gl.compileShader(shader); return gl.getShaderParameter(shader, gl.COMPILE_STATUS) === true; } function isNotIE() { return !document.documentMode; } const DOM = {}; DOM.create = function (tagName, className, container) { const el = ref_properties.window.document.createElement(tagName); if (className !== undefined) el.className = className; if (container) container.appendChild(el); return el; }; DOM.createNS = function (namespaceURI, tagName) { const el = ref_properties.window.document.createElementNS(namespaceURI, tagName); return el; }; const docStyle = ref_properties.window.document && ref_properties.window.document.documentElement.style; const selectProp = docStyle && docStyle.userSelect !== undefined ? 'userSelect' : 'WebkitUserSelect'; let userSelect; DOM.disableDrag = function () { if (docStyle && selectProp) { userSelect = docStyle[selectProp]; docStyle[selectProp] = 'none'; } }; DOM.enableDrag = function () { if (docStyle && selectProp) { docStyle[selectProp] = userSelect; } }; DOM.setTransform = function (el, value) { el.style.transform = value; }; let passiveSupported = false; try { const options = Object.defineProperty({}, 'passive', { get() { passiveSupported = true; } }); ref_properties.window.addEventListener('test', options, options); ref_properties.window.removeEventListener('test', options, options); } catch (err) { passiveSupported = false; } DOM.addEventListener = function (target, type, callback, options = {}) { if ('passive' in options && passiveSupported) { target.addEventListener(type, callback, options); } else { target.addEventListener(type, callback, options.capture); } }; DOM.removeEventListener = function (target, type, callback, options = {}) { if ('passive' in options && passiveSupported) { target.removeEventListener(type, callback, options); } else { target.removeEventListener(type, callback, options.capture); } }; const suppressClick = function (e) { e.preventDefault(); e.stopPropagation(); ref_properties.window.removeEventListener('click', suppressClick, true); }; DOM.suppressClick = function () { ref_properties.window.addEventListener('click', suppressClick, true); ref_properties.window.setTimeout(() => { ref_properties.window.removeEventListener('click', suppressClick, true); }, 0); }; DOM.mousePos = function (el, e) { const rect = el.getBoundingClientRect(); return new ref_properties.Point(e.clientX - rect.left - el.clientLeft, e.clientY - rect.top - el.clientTop); }; DOM.touchPos = function (el, touches) { const rect = el.getBoundingClientRect(), points = []; for (let i = 0; i < touches.length; i++) { points.push(new ref_properties.Point(touches[i].clientX - rect.left - el.clientLeft, touches[i].clientY - rect.top - el.clientTop)); } return points; }; DOM.mouseButton = function (e) { if (typeof ref_properties.window.InstallTrigger !== 'undefined' && e.button === 2 && e.ctrlKey && ref_properties.window.navigator.platform.toUpperCase().indexOf('MAC') >= 0) { return 0; } return e.button; }; DOM.remove = function (node) { if (node.parentNode) { node.parentNode.removeChild(node); } }; function loadSprite (baseURL, requestManager, callback) { let json, image, error; const format = ref_properties.browser.devicePixelRatio > 1 ? '@2x' : ''; let jsonRequest = ref_properties.getJSON(requestManager.transformRequest(requestManager.normalizeSpriteURL(baseURL, format, '.json'), ref_properties.ResourceType.SpriteJSON), (err, data) => { jsonRequest = null; if (!error) { error = err; json = data; maybeComplete(); } }); let imageRequest = ref_properties.getImage(requestManager.transformRequest(requestManager.normalizeSpriteURL(baseURL, format, '.png'), ref_properties.ResourceType.SpriteImage), (err, img) => { imageRequest = null; if (!error) { error = err; image = img; maybeComplete(); } }); function maybeComplete() { if (error) { callback(error); } else if (json && image) { const imageData = ref_properties.browser.getImageData(image); const result = {}; for (const id in json) { const {width, height, x, y, sdf, pixelRatio, stretchX, stretchY, content} = json[id]; const data = new ref_properties.RGBAImage({ width, height }); ref_properties.RGBAImage.copy(imageData, data, { x, y }, { x: 0, y: 0 }, { width, height }); result[id] = { data, pixelRatio, sdf, stretchX, stretchY, content }; } callback(null, result); } } return { cancel() { if (jsonRequest) { jsonRequest.cancel(); jsonRequest = null; } if (imageRequest) { imageRequest.cancel(); imageRequest = null; } } }; } function renderStyleImage(image) { const {userImage} = image; if (userImage && userImage.render) { const updated = userImage.render(); if (updated) { image.data.replace(new Uint8Array(userImage.data.buffer)); return true; } } return false; } const padding = 1; class ImageManager extends ref_properties.Evented { constructor() { super(); this.images = {}; this.updatedImages = {}; this.callbackDispatchedThisFrame = {}; this.loaded = false; this.requestors = []; this.patterns = {}; this.atlasImage = new ref_properties.RGBAImage({ width: 1, height: 1 }); this.dirty = true; } isLoaded() { return this.loaded; } setLoaded(loaded) { if (this.loaded === loaded) { return; } this.loaded = loaded; if (loaded) { for (const {ids, callback} of this.requestors) { this._notify(ids, callback); } this.requestors = []; } } getImage(id) { return this.images[id]; } addImage(id, image) { if (this._validate(id, image)) { this.images[id] = image; } } _validate(id, image) { let valid = true; if (!this._validateStretch(image.stretchX, image.data && image.data.width)) { this.fire(new ref_properties.ErrorEvent(new Error(`Image "${ id }" has invalid "stretchX" value`))); valid = false; } if (!this._validateStretch(image.stretchY, image.data && image.data.height)) { this.fire(new ref_properties.ErrorEvent(new Error(`Image "${ id }" has invalid "stretchY" value`))); valid = false; } if (!this._validateContent(image.content, image)) { this.fire(new ref_properties.ErrorEvent(new Error(`Image "${ id }" has invalid "content" value`))); valid = false; } return valid; } _validateStretch(stretch, size) { if (!stretch) return true; let last = 0; for (const part of stretch) { if (part[0] < last || part[1] < part[0] || size < part[1]) return false; last = part[1]; } return true; } _validateContent(content, image) { if (!content) return true; if (content.length !== 4) return false; if (content[0] < 0 || image.data.width < content[0]) return false; if (content[1] < 0 || image.data.height < content[1]) return false; if (content[2] < 0 || image.data.width < content[2]) return false; if (content[3] < 0 || image.data.height < content[3]) return false; if (content[2] < content[0]) return false; if (content[3] < content[1]) return false; return true; } updateImage(id, image) { const oldImage = this.images[id]; image.version = oldImage.version + 1; this.images[id] = image; this.updatedImages[id] = true; } removeImage(id) { const image = this.images[id]; delete this.images[id]; delete this.patterns[id]; if (image.userImage && image.userImage.onRemove) { image.userImage.onRemove(); } } listImages() { return Object.keys(this.images); } getImages(ids, callback) { let hasAllDependencies = true; if (!this.isLoaded()) { for (const id of ids) { if (!this.images[id]) { hasAllDependencies = false; } } } if (this.isLoaded() || hasAllDependencies) { this._notify(ids, callback); } else { this.requestors.push({ ids, callback }); } } _notify(ids, callback) { const response = {}; for (const id of ids) { if (!this.images[id]) { this.fire(new ref_properties.Event('styleimagemissing', { id })); } const image = this.images[id]; if (image) { response[id] = { data: image.data.clone(), pixelRatio: image.pixelRatio, sdf: image.sdf, version: image.version, stretchX: image.stretchX, stretchY: image.stretchY, content: image.content, hasRenderCallback: Boolean(image.userImage && image.userImage.render) }; } else { ref_properties.warnOnce(`Image "${ id }" could not be loaded. Please make sure you have added the image with map.addImage() or a "sprite" property in your style. You can provide missing images by listening for the "styleimagemissing" map event.`); } } callback(null, response); } getPixelSize() { const {width, height} = this.atlasImage; return { width, height }; } getPattern(id) { const pattern = this.patterns[id]; const image = this.getImage(id); if (!image) { return null; } if (pattern && pattern.position.version === image.version) { return pattern.position; } if (!pattern) { const w = image.data.width + padding * 2; const h = image.data.height + padding * 2; const bin = { w, h, x: 0, y: 0 }; const position = new ref_properties.ImagePosition(bin, image); this.patterns[id] = { bin, position }; } else { pattern.position.version = image.version; } this._updatePatternAtlas(); return this.patterns[id].position; } bind(context) { const gl = context.gl; if (!this.atlasTexture) { this.atlasTexture = new ref_properties.Texture(context, this.atlasImage, gl.RGBA); } else if (this.dirty) { this.atlasTexture.update(this.atlasImage); this.dirty = false; } this.atlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); } _updatePatternAtlas() { const bins = []; for (const id in this.patterns) { bins.push(this.patterns[id].bin); } const {w, h} = ref_properties.potpack(bins); const dst = this.atlasImage; dst.resize({ width: w || 1, height: h || 1 }); for (const id in this.patterns) { const {bin} = this.patterns[id]; const x = bin.x + padding; const y = bin.y + padding; const src = this.images[id].data; const w = src.width; const h = src.height; ref_properties.RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y }, { width: w, height: h }); ref_properties.RGBAImage.copy(src, dst, { x: 0, y: h - 1 }, { x, y: y - 1 }, { width: w, height: 1 }); ref_properties.RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y: y + h }, { width: w, height: 1 }); ref_properties.RGBAImage.copy(src, dst, { x: w - 1, y: 0 }, { x: x - 1, y }, { width: 1, height: h }); ref_properties.RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x + w, y }, { width: 1, height: h }); } this.dirty = true; } beginFrame() { this.callbackDispatchedThisFrame = {}; } dispatchRenderCallbacks(ids) { for (const id of ids) { if (this.callbackDispatchedThisFrame[id]) continue; this.callbackDispatchedThisFrame[id] = true; const image = this.images[id]; const updated = renderStyleImage(image); if (updated) { this.updateImage(id, image); } } } } function sphericalToCartesian([r, azimuthal, polar]) { const a = ref_properties.degToRad(azimuthal + 90), p = ref_properties.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 }; } class LightPositionProperty { constructor() { this.specification = ref_properties.styleSpec.light.position; } possiblyEvaluate(value, parameters) { return sphericalToCartesian(value.expression.evaluate(parameters)); } interpolate(a, b, t) { return { x: ref_properties.number(a.x, b.x, t), y: ref_properties.number(a.y, b.y, t), z: ref_properties.number(a.z, b.z, t), azimuthal: ref_properties.number(a.azimuthal, b.azimuthal, t), polar: ref_properties.number(a.polar, b.polar, t) }; } } const properties = new ref_properties.Properties({ 'anchor': new ref_properties.DataConstantProperty(ref_properties.styleSpec.light.anchor), 'position': new LightPositionProperty(), 'color': new ref_properties.DataConstantProperty(ref_properties.styleSpec.light.color), 'intensity': new ref_properties.DataConstantProperty(ref_properties.styleSpec.light.intensity) }); const TRANSITION_SUFFIX = '-transition'; class Light extends ref_properties.Evented { constructor(lightOptions) { super(); this._transitionable = new ref_properties.Transitionable(properties); this.setLight(lightOptions); this._transitioning = this._transitionable.untransitioned(); } getLight() { return this._transitionable.serialize(); } setLight(light, options = {}) { if (this._validate(ref_properties.validateLight, light, options)) { return; } for (const name in light) { const value = light[name]; if (ref_properties.endsWith(name, TRANSITION_SUFFIX)) { this._transitionable.setTransition(name.slice(0, -TRANSITION_SUFFIX.length), value); } else { this._transitionable.setValue(name, value); } } } updateTransitions(parameters) { this._transitioning = this._transitionable.transitioned(parameters, this._transitioning); } hasTransition() { return this._transitioning.hasTransition(); } recalculate(parameters) { this.properties = this._transitioning.possiblyEvaluate(parameters); } _validate(validate, value, options) { if (options && options.validate === false) { return false; } return ref_properties.emitValidationErrors(this, validate.call(ref_properties.validateStyle, ref_properties.extend({ value, style: { glyphs: true, sprite: true }, styleSpec: ref_properties.styleSpec }))); } } const properties$1 = new ref_properties.Properties({ 'source': new ref_properties.DataConstantProperty(ref_properties.styleSpec.terrain.source), 'exaggeration': new ref_properties.DataConstantProperty(ref_properties.styleSpec.terrain.exaggeration) }); const TRANSITION_SUFFIX$1 = '-transition'; class Terrain extends ref_properties.Evented { constructor(terrainOptions) { super(); this._transitionable = new ref_properties.Transitionable(properties$1); this.set(terrainOptions); this._transitioning = this._transitionable.untransitioned(); } get() { return this._transitionable.serialize(); } set(terrain) { for (const name in terrain) { const value = terrain[name]; if (ref_properties.endsWith(name, TRANSITION_SUFFIX$1)) { this._transitionable.setTransition(name.slice(0, -TRANSITION_SUFFIX$1.length), value); } else { this._transitionable.setValue(name, value); } } } updateTransitions(parameters) { this._transitioning = this._transitionable.transitioned(parameters, this._transitioning); } hasTransition() { return this._transitioning.hasTransition(); } recalculate(parameters) { this.properties = this._transitioning.possiblyEvaluate(parameters); } } class LineAtlas { constructor(width, height) { this.width = width; this.height = height; this.nextRow = 0; this.data = new Uint8Array(this.width * this.height); this.dashEntry = {}; } getDash(dasharray, round) { const key = dasharray.join(',') + String(round); if (!this.dashEntry[key]) { this.dashEntry[key] = this.addDash(dasharray, round); } return this.dashEntry[key]; } getDashRanges(dasharray, lineAtlasWidth, stretch) { const oddDashArray = dasharray.length % 2 === 1; const ranges = []; let left = oddDashArray ? -dasharray[dasharray.length - 1] * stretch : 0; let right = dasharray[0] * stretch; let isDash = true; ranges.push({ left, right, isDash, zeroLength: dasharray[0] === 0 }); let currentDashLength = dasharray[0]; for (let i = 1; i < dasharray.length; i++) { isDash = !isDash; const dashLength = dasharray[i]; left = currentDashLength * stretch; currentDashLength += dashLength; right = currentDashLength * stretch; ranges.push({ left, right, isDash, zeroLength: dashLength === 0 }); } return ranges; } addRoundDash(ranges, stretch, n) { const halfStretch = stretch / 2; for (let y = -n; y <= n; y++) { const row = this.nextRow + n + y; const index = this.width * row; let currIndex = 0; let range = ranges[currIndex]; for (let x = 0; x < this.width; x++) { if (x / range.right > 1) { range = ranges[++currIndex]; } const distLeft = Math.abs(x - range.left); const distRight = Math.abs(x - range.right); const minDist = Math.min(distLeft, distRight); let signedDistance; const distMiddle = y / n * (halfStretch + 1); if (range.isDash) { const distEdge = halfStretch - Math.abs(distMiddle); signedDistance = Math.sqrt(minDist * minDist + distEdge * distEdge); } else { signedDistance = halfStretch - Math.sqrt(minDist * minDist + distMiddle * distMiddle); } this.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128)); } } } addRegularDash(ranges) { for (let i = ranges.length - 1; i >= 0; --i) { const part = ranges[i]; const next = ranges[i + 1]; if (part.zeroLength) { ranges.splice(i, 1); } else if (next && next.isDash === part.isDash) { next.left = part.left; ranges.splice(i, 1); } } const first = ranges[0]; const last = ranges[ranges.length - 1]; if (first.isDash === last.isDash) { first.left = last.left - this.width; last.right = first.right + this.width; } const index = this.width * this.nextRow; let currIndex = 0; let range = ranges[currIndex]; for (let x = 0; x < this.width; x++) { if (x / range.right > 1) { range = ranges[++currIndex]; } const distLeft = Math.abs(x - range.left); const distRight = Math.abs(x - range.right); const minDist = Math.min(distLeft, distRight); const signedDistance = range.isDash ? minDist : -minDist; this.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128)); } } addDash(dasharray, round) { const n = round ? 7 : 0; const height = 2 * n + 1; if (this.nextRow + height > this.height) { ref_properties.warnOnce('LineAtlas out of space'); return null; } if (dasharray.length === 0) { dasharray.push(1); } let length = 0; for (let i = 0; i < dasharray.length; i++) { if (dasharray[i] < 0) { ref_properties.warnOnce('Negative value is found in line dasharray, replacing values with 0'); dasharray[i] = 0; } length += dasharray[i]; } if (length !== 0) { const stretch = this.width / length; const ranges = this.getDashRanges(dasharray, this.width, stretch); if (round) { this.addRoundDash(ranges, stretch, n); } else { this.addRegularDash(ranges); } } const dashEntry = { y: (this.nextRow + n + 0.5) / this.height, height: 2 * n / this.height, width: length }; this.nextRow += height; this.dirty = true; return dashEntry; } bind(context) { const gl = context.gl; if (!this.texture) { this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, this.width, this.height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, this.data); } else { gl.bindTexture(gl.TEXTURE_2D, this.texture); if (this.dirty) { this.dirty = false; gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, this.height, gl.ALPHA, gl.UNSIGNED_BYTE, this.data); } } } } class Dispatcher { constructor(workerPool, parent) { this.workerPool = workerPool; this.actors = []; this.currentActor = 0; this.id = ref_properties.uniqueId(); const workers = this.workerPool.acquire(this.id); for (let i = 0; i < workers.length; i++) { const worker = workers[i]; const actor = new Dispatcher.Actor(worker, parent, this.id); actor.name = `Worker ${ i }`; this.actors.push(actor); } this.ready = false; this.broadcast('checkIfReady', null, () => { this.ready = true; }); } broadcast(type, data, cb) { cb = cb || function () { }; ref_properties.asyncAll(this.actors, (actor, done) => { actor.send(type, data, done); }, cb); } getActor() { this.currentActor = (this.currentActor + 1) % this.actors.length; return this.actors[this.currentActor]; } remove() { this.actors.forEach(actor => { actor.remove(); }); this.actors = []; this.workerPool.release(this.id); } } Dispatcher.Actor = ref_properties.Actor; function pixelsToTileUnits (tile, pixelValue, z) { return pixelValue * (ref_properties.EXTENT / (tile.tileSize * Math.pow(2, z - tile.tileID.overscaledZ))); } class QueryGeometry { constructor(screenBounds, cameraPoint, aboveHorizon, transform) { this.screenBounds = screenBounds; this.cameraPoint = cameraPoint; this._screenRaycastCache = {}; this._cameraRaycastCache = {}; this.isAboveHorizon = aboveHorizon; this.screenGeometry = this.bufferedScreenGeometry(0); this.screenGeometryMercator = this.screenGeometry.map(p => transform.pointCoordinate3D(p)); this.cameraGeometry = this.bufferedCameraGeometry(0); } static createFromScreenPoints(geometry, transform) { let screenGeometry; let aboveHorizon; if (geometry instanceof ref_properties.Point || typeof geometry[0] === 'number') { const pt = ref_properties.Point.convert(geometry); screenGeometry = [ref_properties.Point.convert(geometry)]; aboveHorizon = transform.isPointAboveHorizon(pt); } else { const tl = ref_properties.Point.convert(geometry[0]); const br = ref_properties.Point.convert(geometry[1]); screenGeometry = [ tl, br ]; aboveHorizon = ref_properties.polygonizeBounds(tl, br).every(p => transform.isPointAboveHorizon(p)); } return new QueryGeometry(screenGeometry, transform.getCameraPoint(), aboveHorizon, transform); } isPointQuery() { return this.screenBounds.length === 1; } bufferedScreenGeometry(buffer) { return ref_properties.polygonizeBounds(this.screenBounds[0], this.screenBounds.length === 1 ? this.screenBounds[0] : this.screenBounds[1], buffer); } bufferedCameraGeometry(buffer) { const min = this.screenBounds[0]; const max = this.screenBounds.length === 1 ? this.screenBounds[0].add(new ref_properties.Point(1, 1)) : this.screenBounds[1]; const cameraPolygon = ref_properties.polygonizeBounds(min, max, 0, false); if (this.cameraPoint.y > max.y) { if (this.cameraPoint.x > min.x && this.cameraPoint.x < max.x) { cameraPolygon.splice(3, 0, this.cameraPoint); } else if (this.cameraPoint.x >= max.x) { cameraPolygon[2] = this.cameraPoint; } else if (this.cameraPoint.x <= min.x) { cameraPolygon[3] = this.cameraPoint; } } return ref_properties.bufferConvexPolygon(cameraPolygon, buffer); } containsTile(tile, transform, use3D) { const bias = 1; const padding = tile.queryPadding + bias; const geometryForTileCheck = use3D ? this._bufferedCameraMercator(padding, transform).map(p => tile.tileID.getTilePoint(p)) : this._bufferedScreenMercator(padding, transform).map(p => tile.tileID.getTilePoint(p)); const tilespaceVec3s = this.screenGeometryMercator.map(p => tile.tileID.getTileVec3(p)); const tilespaceGeometry = tilespaceVec3s.map(v => new ref_properties.Point(v[0], v[1])); const cameraMercator = transform.getFreeCameraOptions().position || new ref_properties.MercatorCoordinate(0, 0, 0); const tilespaceCameraPosition = tile.tileID.getTileVec3(cameraMercator); const tilespaceRays = tilespaceVec3s.map(tileVec => { const dir = ref_properties.sub(tileVec, tileVec, tilespaceCameraPosition); ref_properties.normalize(dir, dir); return new ref_properties.Ray(tilespaceCameraPosition, dir); }); const pixelToTileUnitsFactor = pixelsToTileUnits(tile, 1, transform.zoom); if (ref_properties.polygonIntersectsBox(geometryForTileCheck, 0, 0, ref_properties.EXTENT, ref_properties.EXTENT)) { return { queryGeometry: this, tilespaceGeometry, tilespaceRays, bufferedTilespaceGeometry: geometryForTileCheck, bufferedTilespaceBounds: clampBoundsToTileExtents(ref_properties.getBounds(geometryForTileCheck)), tile, tileID: tile.tileID, pixelToTileUnitsFactor }; } } _bufferedScreenMercator(padding, transform) { const key = cacheKey(padding); if (this._screenRaycastCache[key]) { return this._screenRaycastCache[key]; } else { const poly = this.bufferedScreenGeometry(padding).map(p => transform.pointCoordinate3D(p)); this._screenRaycastCache[key] = poly; return poly; } } _bufferedCameraMercator(padding, transform) { const key = cacheKey(padding); if (this._cameraRaycastCache[key]) { return this._cameraRaycastCache[key]; } else { const poly = this.bufferedCameraGeometry(padding).map(p => transform.pointCoordinate3D(p)); this._cameraRaycastCache[key] = poly; return poly; } } } function cacheKey(padding) { return padding * 100 | 0; } function clampBoundsToTileExtents(bounds) { bounds.min.x = ref_properties.clamp(bounds.min.x, 0, ref_properties.EXTENT); bounds.min.y = ref_properties.clamp(bounds.min.y, 0, ref_properties.EXTENT); bounds.max.x = ref_properties.clamp(bounds.max.x, 0, ref_properties.EXTENT); bounds.max.y = ref_properties.clamp(bounds.max.y, 0, ref_properties.EXTENT); return bounds; } function loadTileJSON (options, requestManager, callback) { const loaded = function (err, tileJSON) { if (err) { return callback(err); } else if (tileJSON) { const result = ref_properties.pick(ref_properties.extend(tileJSON, options), [ 'tiles', 'minzoom', 'maxzoom', 'attribution', 'mapbox_logo', 'bounds', 'scheme', 'tileSize', 'encoding' ]); if (tileJSON.vector_layers) { result.vectorLayers = tileJSON.vector_layers; result.vectorLayerIds = result.vectorLayers.map(layer => { return layer.id; }); } result.tiles = requestManager.canonicalizeTileset(result, options.url); callback(null, result); } }; if (options.url) { return ref_properties.getJSON(requestManager.transformRequest(requestManager.normalizeSourceURL(options.url), ref_properties.ResourceType.Source), loaded); } else { return ref_properties.browser.frame(() => loaded(null, options)); } } class TileBounds { constructor(bounds, minzoom, maxzoom) { this.bounds = ref_properties.LngLatBounds.convert(this.validateBounds(bounds)); this.minzoom = minzoom || 0; this.maxzoom = maxzoom || 24; } validateBounds(bounds) { if (!Array.isArray(bounds) || bounds.length !== 4) return [ -180, -90, 180, 90 ]; return [ Math.max(-180, bounds[0]), Math.max(-90, bounds[1]), Math.min(180, bounds[2]), Math.min(90, bounds[3]) ]; } contains(tileID) { const worldSize = Math.pow(2, tileID.z); const level = { minX: Math.floor(ref_properties.mercatorXfromLng(this.bounds.getWest()) * worldSize), minY: Math.floor(ref_properties.mercatorYfromLat(this.bounds.getNorth()) * worldSize), maxX: Math.ceil(ref_properties.mercatorXfromLng(this.bounds.getEast()) * worldSize), maxY: Math.ceil(ref_properties.mercatorYfromLat(this.bounds.getSouth()) * worldSize) }; const hit = tileID.x >= level.minX && tileID.x < level.maxX && tileID.y >= level.minY && tileID.y < level.maxY; return hit; } } class VectorTileSource extends ref_properties.Evented { constructor(id, options, dispatcher, eventedParent) { super(); this.id = id; this.dispatcher = dispatcher; this.type = 'vector'; this.minzoom = 0; this.maxzoom = 22; this.scheme = 'xyz'; this.tileSize = 512; this.reparseOverscaled = true; this.isTileClipped = true; this._loaded = false; ref_properties.extend(this, ref_properties.pick(options, [ 'url', 'scheme', 'tileSize', 'promoteId' ])); this._options = ref_properties.extend({ type: 'vector' }, options); this._collectResourceTiming = options.collectResourceTiming; if (this.tileSize !== 512) { throw new Error('vector tile sources must have a tileSize of 512'); } this.setEventedParent(eventedParent); this._tileWorkers = {}; this._deduped = new ref_properties.DedupedRequest(); } load() { this._loaded = false; this.fire(new ref_properties.Event('dataloading', { dataType: 'source' })); this._tileJSONRequest = loadTileJSON(this._options, this.map._requestManager, (err, tileJSON) => { this._tileJSONRequest = null; this._loaded = true; if (err) { this.fire(new ref_properties.ErrorEvent(err)); } else if (tileJSON) { ref_properties.extend(this, tileJSON); if (tileJSON.bounds) this.tileBounds = new TileBounds(tileJSON.bounds, this.minzoom, this.maxzoom); ref_properties.postTurnstileEvent(tileJSON.tiles, this.map._requestManager._customAccessToken); this.fire(new ref_properties.Event('data', { dataType: 'source', sourceDataType: 'metadata' })); this.fire(new ref_properties.Event('data', { dataType: 'source', sourceDataType: 'content' })); } }); } loaded() { return this._loaded; } hasTile(tileID) { return !this.tileBounds || this.tileBounds.contains(tileID.canonical); } onAdd(map) { this.map = map; this.load(); } setSourceProperty(callback) { if (this._tileJSONRequest) { this._tileJSONRequest.cancel(); } callback(); const sourceCaches = this.map.style._getSourceCaches(this.id); for (const sourceCache of sourceCaches) { sourceCache.clearTiles(); } this.load(); } setTiles(tiles) { this.setSourceProperty(() => { this._options.tiles = tiles; }); return this; } setUrl(url) { this.setSourceProperty(() => { this.url = url; this._options.url = url; }); return this; } onRemove() { if (this._tileJSONRequest) { this._tileJSONRequest.cancel(); this._tileJSONRequest = null; } } serialize() { return ref_properties.extend({}, this._options); } loadTile(tile, callback) { const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme)); const request = this.map._requestManager.transformRequest(url, ref_properties.ResourceType.Tile); const params = { request, data: undefined, uid: tile.uid, tileID: tile.tileID, tileZoom: tile.tileZoom, zoom: tile.tileID.overscaledZ, tileSize: this.tileSize * tile.tileID.overscaleFactor(), type: this.type, source: this.id, pixelRatio: ref_properties.browser.devicePixelRatio, showCollisionBoxes: this.map.showCollisionBoxes, promoteId: this.promoteId, isSymbolTile: tile.isSymbolTile }; params.request.collectResourceTiming = this._collectResourceTiming; if (!tile.actor || tile.state === 'expired') { tile.actor = this._tileWorkers[url] = this._tileWorkers[url] || this.dispatcher.getActor(); if (!this.dispatcher.ready) { const cancel = ref_properties.loadVectorTile.call({ deduped: this._deduped }, params, (err, data) => { if (err || !data) { done.call(this, err); } else { params.data = { cacheControl: data.cacheControl, expires: data.expires, rawData: data.rawData.slice(0) }; if (tile.actor) tile.actor.send('loadTile', params, done.bind(this)); } }, true); tile.request = { cancel }; } else { tile.request = tile.actor.send('loadTile', params, done.bind(this)); } } else if (tile.state === 'loading') { tile.reloadCallback = callback; } else { tile.request = tile.actor.send('reloadTile', params, done.bind(this)); } function done(err, data) { delete tile.request; if (tile.aborted) return callback(null); if (err && err.status !== 404) { return callback(err); } if (data && data.resourceTiming) tile.resourceTiming = data.resourceTiming; if (this.map._refreshExpiredTiles && data) tile.setExpiryData(data); tile.loadVectorData(data, this.map.painter); ref_properties.cacheEntryPossiblyAdded(this.dispatcher); callback(null); if (tile.reloadCallback) { this.loadTile(tile, tile.reloadCallback); tile.reloadCallback = null; } } } abortTile(tile) { if (tile.request) { tile.request.cancel(); delete tile.request; } if (tile.actor) { tile.actor.send('abortTile', { uid: tile.uid, type: this.type, source: this.id }, undefined); } } unloadTile(tile) { tile.unloadVectorData(); if (tile.actor) { tile.actor.send('removeTile', { uid: tile.uid, type: this.type, source: this.id }, undefined); } } hasTransition() { return false; } afterUpdate() { this._tileWorkers = {}; } } class RasterTileSource extends ref_properties.Evented { constructor(id, options, dispatcher, eventedParent) { super(); this.id = id; this.dispatcher = dispatcher; this.setEventedParent(eventedParent); this.type = 'raster'; this.minzoom = 0; this.maxzoom = 22; this.roundZoom = true; this.scheme = 'xyz'; this.tileSize = 512; this._loaded = false; this._options = ref_properties.extend({ type: 'raster' }, options); ref_properties.extend(this, ref_properties.pick(options, [ 'url', 'scheme', 'tileSize' ])); } load() { this._loaded = false; this.fire(new ref_properties.Event('dataloading', { dataType: 'source' })); this._tileJSONRequest = loadTileJSON(this._options, this.map._requestManager, (err, tileJSON) => { this._tileJSONRequest = null; this._loaded = true; if (err) { this.fire(new ref_properties.ErrorEvent(err)); } else if (tileJSON) { ref_properties.extend(this, tileJSON); if (tileJSON.bounds) this.tileBounds = new TileBounds(tileJSON.bounds, this.minzoom, this.maxzoom); ref_properties.postTurnstileEvent(tileJSON.tiles); this.fire(new ref_properties.Event('data', { dataType: 'source', sourceDataType: 'metadata' })); this.fire(new ref_properties.Event('data', { dataType: 'source', sourceDataType: 'content' })); } }); } loaded() { return this._loaded; } onAdd(map) { this.map = map; this.load(); } onRemove() { if (this._tileJSONRequest) { this._tileJSONRequest.cancel(); this._tileJSONRequest = null; } } serialize() { return ref_properties.extend({}, this._options); } hasTile(tileID) { return !this.tileBounds || this.tileBounds.contains(tileID.canonical); } loadTile(tile, callback) { const use2x = ref_properties.browser.devicePixelRatio >= 2; const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme), use2x, this.tileSize); tile.request = ref_properties.getImage(this.map._requestManager.transformRequest(url, ref_properties.ResourceType.Tile), (err, img) => { delete tile.request; if (tile.aborted) { tile.state = 'unloaded'; callback(null); } else if (err) { tile.state = 'errored'; callback(err); } else if (img) { if (this.map._refreshExpiredTiles) tile.setExpiryData(img); delete img.cacheControl; delete img.expires; const context = this.map.painter.context; const gl = context.gl; tile.texture = this.map.painter.getTileTexture(img.width); if (tile.texture) { tile.texture.update(img, { useMipmap: true }); } else { tile.texture = new ref_properties.Texture(context, img, gl.RGBA, { useMipmap: true }); tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); if (context.extTextureFilterAnisotropic) { gl.texParameterf(gl.TEXTURE_2D, context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, context.extTextureFilterAnisotropicMax); } } tile.state = 'loaded'; ref_properties.cacheEntryPossiblyAdded(this.dispatcher); callback(null); } }); } abortTile(tile, callback) { if (tile.request) { tile.request.cancel(); delete tile.request; } callback(); } unloadTile(tile, callback) { if (tile.texture) this.map.painter.saveTileTexture(tile.texture); callback(); } hasTransition() { return false; } } class RasterDEMTileSource extends RasterTileSource { constructor(id, options, dispatcher, eventedParent) { super(id, options, dispatcher, eventedParent); this.type = 'raster-dem'; this.maxzoom = 22; this._options = ref_properties.extend({ type: 'raster-dem' }, options); this.encoding = options.encoding || 'mapbox'; } serialize() { return { type: 'raster-dem', url: this.url, tileSize: this.tileSize, tiles: this.tiles, bounds: this.bounds, encoding: this.encoding }; } loadTile(tile, callback) { const url = this.map._requestManager.normalizeTileURL(tile.tileID.canonical.url(this.tiles, this.scheme), false, this.tileSize); tile.request = ref_properties.getImage(this.map._requestManager.transformRequest(url, ref_properties.ResourceType.Tile), imageLoaded.bind(this)); function imageLoaded(err, img) { delete tile.request; if (tile.aborted) { tile.state = 'unloaded'; callback(null); } else if (err) { tile.state = 'errored'; callback(err); } else if (img) { if (this.map._refreshExpiredTiles) tile.setExpiryData(img); delete img.cacheControl; delete img.expires; const transfer = ref_properties.window.ImageBitmap && img instanceof ref_properties.window.ImageBitmap && ref_properties.offscreenCanvasSupported(); const buffer = (img.width - ref_properties.prevPowerOfTwo(img.width)) / 2; const padding = 1 - buffer; const borderReady = padding < 1; if (!borderReady && !tile.neighboringTiles) { tile.neighboringTiles = this._getNeighboringTiles(tile.tileID); } const rawImageData = transfer ? img : ref_properties.browser.getImageData(img, padding); const params = { uid: tile.uid, coord: tile.tileID, source: this.id, rawImageData, encoding: this.encoding, padding }; if (!tile.actor || tile.state === 'expired') { tile.actor = this.dispatcher.getActor(); tile.actor.send('loadDEMTile', params, done.bind(this)); } } } function done(err, dem) { if (err) { tile.state = 'errored'; callback(err); } if (dem) { tile.dem = dem; tile.dem.onDeserialize(); tile.needsHillshadePrepare = true; tile.needsDEMTextureUpload = true; tile.state = 'loaded'; callback(null); } } } _getNeighboringTiles(tileID) { const canonical = tileID.canonical; const dim = Math.pow(2, canonical.z); const px = (canonical.x - 1 + dim) % dim; const pxw = canonical.x === 0 ? tileID.wrap - 1 : tileID.wrap; const nx = (canonical.x + 1 + dim) % dim; const nxw = canonical.x + 1 === dim ? tileID.wrap + 1 : tileID.wrap; const neighboringTiles = {}; neighboringTiles[new ref_properties.OverscaledTileID(tileID.overscaledZ, pxw, canonical.z, px, canonical.y).key] = { backfilled: false }; neighboringTiles[new ref_properties.OverscaledTileID(tileID.overscaledZ, nxw, canonical.z, nx, canonical.y).key] = { backfilled: false }; if (canonical.y > 0) { neighboringTiles[new ref_properties.OverscaledTileID(tileID.overscaledZ, pxw, canonical.z, px, canonical.y - 1).key] = { backfilled: false }; neighboringTiles[new ref_properties.OverscaledTileID(tileID.overscaledZ, tileID.wrap, canonical.z, canonical.x, canonical.y - 1).key] = { backfilled: false }; neighboringTiles[new ref_properties.OverscaledTileID(tileID.overscaledZ, nxw, canonical.z, nx, canonical.y - 1).key] = { backfilled: false }; } if (canonical.y + 1 < dim) { neighboringTiles[new ref_properties.OverscaledTileID(tileID.overscaledZ, pxw, canonical.z, px, canonical.y + 1).key] = { backfilled: false }; neighboringTiles[new ref_properties.OverscaledTileID(tileID.overscaledZ, tileID.wrap, canonical.z, canonical.x, canonical.y + 1).key] = { backfilled: false }; neighboringTiles[new ref_properties.OverscaledTileID(tileID.overscaledZ, nxw, canonical.z, nx, canonical.y + 1).key] = { backfilled: false }; } return neighboringTiles; } unloadTile(tile) { if (tile.demTexture) this.map.painter.saveTileTexture(tile.demTexture); if (tile.fbo) { tile.fbo.destroy(); delete tile.fbo; } if (tile.dem) delete tile.dem; delete tile.neighboringTiles; tile.state = 'unloaded'; } } class GeoJSONSource extends ref_properties.Evented { constructor(id, options, dispatcher, eventedParent) { super(); this.id = id; this.type = 'geojson'; this.minzoom = 0; this.maxzoom = 18; this.tileSize = 512; this.isTileClipped = true; this.reparseOverscaled = true; this._removed = false; this._loaded = false; this.actor = dispatcher.getActor(); this.setEventedParent(eventedParent); this._data = options.data; this._options = ref_properties.extend({}, options); this._collectResourceTiming = options.collectResourceTiming; this._resourceTiming = []; if (options.maxzoom !== undefined) this.maxzoom = options.maxzoom; if (options.type) this.type = options.type; if (options.attribution) this.attribution = options.attribution; this.promoteId = options.promoteId; const scale = ref_properties.EXTENT / this.tileSize; this.workerOptions = ref_properties.extend({ source: this.id, cluster: options.cluster || false, geojsonVtOptions: { buffer: (options.buffer !== undefined ? options.buffer : 128) * scale, tolerance: (options.tolerance !== undefined ? options.tolerance : 0.375) * scale, extent: ref_properties.EXTENT, maxZoom: this.maxzoom, lineMetrics: options.lineMetrics || false, generateId: options.generateId || false }, superclusterOptions: { maxZoom: options.clusterMaxZoom !== undefined ? options.clusterMaxZoom : this.maxzoom - 1, minPoints: Math.max(2, options.clusterMinPoints || 2), extent: ref_properties.EXTENT, radius: (options.clusterRadius || 50) * scale, log: false, generateId: options.generateId || false }, clusterProperties: options.clusterProperties, filter: options.filter }, options.workerOptions); } load() { this.fire(new ref_properties.Event('dataloading', { dataType: 'source' })); this._updateWorkerData(err => { if (err) { this.fire(new ref_properties.ErrorEvent(err)); return; } const data = { dataType: 'source', sourceDataType: 'metadata' }; if (this._collectResourceTiming && this._resourceTiming && this._resourceTiming.length > 0) { data.resourceTiming = this._resourceTiming; this._resourceTiming = []; } this.fire(new ref_properties.Event('data', data)); }); } onAdd(map) { this.map = map; this.load(); } setData(data) { this._data = data; this.fire(new ref_properties.Event('dataloading', { dataType: 'source' })); this._updateWorkerData(err => { if (err) { this.fire(new ref_properties.ErrorEvent(err)); return; } const data = { dataType: 'source', sourceDataType: 'content' }; if (this._collectResourceTiming && this._resourceTiming && this._resourceTiming.length > 0) { data.resourceTiming = this._resourceTiming; this._resourceTiming = []; } this.fire(new ref_properties.Event('data', data)); }); return this; } getClusterExpansionZoom(clusterId, callback) { this.actor.send('geojson.getClusterExpansionZoom', { clusterId, source: this.id }, callback); return this; } getClusterChildren(clusterId, callback) { this.actor.send('geojson.getClusterChildren', { clusterId, source: this.id }, callback); return this; } getClusterLeaves(clusterId, limit, offset, callback) { this.actor.send('geojson.getClusterLeaves', { source: this.id, clusterId, limit, offset }, callback); return this; } _updateWorkerData(callback) { this._loaded = false; const options = ref_properties.extend({}, this.workerOptions); const data = this._data; if (typeof data === 'string') { options.request = this.map._requestManager.transformRequest(ref_properties.browser.resolveURL(data), ref_properties.ResourceType.Source); options.request.collectResourceTiming = this._collectResourceTiming; } else { options.data = JSON.stringify(data); } this.actor.send(`${ this.type }.loadData`, options, (err, result) => { if (this._removed || result && result.abandoned) { return; } this._loaded = true; if (result && result.resourceTiming && result.resourceTiming[this.id]) this._resourceTiming = result.resourceTiming[this.id].slice(0); this.actor.send(`${ this.type }.coalesce`, { source: options.source }, null); callback(err); }); } loaded() { return this._loaded; } loadTile(tile, callback) { const message = !tile.actor ? 'loadTile' : 'reloadTile'; tile.actor = this.actor; const params = { type: this.type, uid: tile.uid, tileID: tile.tileID, tileZoom: tile.tileZoom, zoom: tile.tileID.overscaledZ, maxZoom: this.maxzoom, tileSize: this.tileSize, source: this.id, pixelRatio: ref_properties.browser.devicePixelRatio, showCollisionBoxes: this.map.showCollisionBoxes, promoteId: this.promoteId }; tile.request = this.actor.send(message, params, (err, data) => { delete tile.request; tile.unloadVectorData(); if (tile.aborted) { return callback(null); } if (err) { return callback(err); } tile.loadVectorData(data, this.map.painter, message === 'reloadTile'); return callback(null); }); } abortTile(tile) { if (tile.request) { tile.request.cancel(); delete tile.request; } tile.aborted = true; } unloadTile(tile) { tile.unloadVectorData(); this.actor.send('removeTile', { uid: tile.uid, type: this.type, source: this.id }); } onRemove() { this._removed = true; this.actor.send('removeSource', { type: this.type, source: this.id }); } serialize() { return ref_properties.extend({}, this._options, { type: this.type, data: this._data }); } hasTransition() { return false; } } var rasterBoundsAttributes = ref_properties.createLayout([ { name: 'a_pos', type: 'Int16', components: 2 }, { name: 'a_texture_pos', type: 'Int16', components: 2 } ]); class ImageSource extends ref_properties.Evented { constructor(id, options, dispatcher, eventedParent) { super(); this.id = id; this.dispatcher = dispatcher; this.coordinates = options.coordinates; this.type = 'image'; this.minzoom = 0; this.maxzoom = 22; this.tileSize = 512; this.tiles = {}; this._loaded = false; this.setEventedParent(eventedParent); this.options = options; } load(newCoordinates, successCallback) { this._loaded = false; this.fire(new ref_properties.Event('dataloading', { dataType: 'source' })); this.url = this.options.url; ref_properties.getImage(this.map._requestManager.transformRequest(this.url, ref_properties.ResourceType.Image), (err, image) => { this._loaded = true; if (err) { this.fire(new ref_properties.ErrorEvent(err)); } else if (image) { this.image = image; if (newCoordinates) { this.coordinates = newCoordinates; } if (successCallback) { successCallback(); } this._finishLoading(); } }); } loaded() { return this._loaded; } updateImage(options) { if (!this.image || !options.url) { return this; } this.options.url = options.url; this.load(options.coordinates, () => { this.texture = null; }); return this; } _finishLoading() { if (this.map) { this.setCoordinates(this.coordinates); this.fire(new ref_properties.Event('data', { dataType: 'source', sourceDataType: 'metadata' })); } } onAdd(map) { this.map = map; this.load(); } setCoordinates(coordinates) { this.coordinates = coordinates; const cornerCoords = coordinates.map(ref_properties.MercatorCoordinate.fromLngLat); this.tileID = getCoordinatesCenterTileID(cornerCoords); this.minzoom = this.maxzoom = this.tileID.z; const tileCoords = cornerCoords.map(coord => this.tileID.getTilePoint(coord)._round()); this._boundsArray = new ref_properties.StructArrayLayout4i8(); this._boundsArray.emplaceBack(tileCoords[0].x, tileCoords[0].y, 0, 0); this._boundsArray.emplaceBack(tileCoords[1].x, tileCoords[1].y, ref_properties.EXTENT, 0); this._boundsArray.emplaceBack(tileCoords[3].x, tileCoords[3].y, 0, ref_properties.EXTENT); this._boundsArray.emplaceBack(tileCoords[2].x, tileCoords[2].y, ref_properties.EXTENT, ref_properties.EXTENT); if (this.boundsBuffer) { this.boundsBuffer.destroy(); delete this.boundsBuffer; } this.fire(new ref_properties.Event('data', { dataType: 'source', sourceDataType: 'content' })); return this; } prepare() { if (Object.keys(this.tiles).length === 0 || !this.image) { return; } const context = this.map.painter.context; const gl = context.gl; if (!this.boundsBuffer) { this.boundsBuffer = context.createVertexBuffer(this._boundsArray, rasterBoundsAttributes.members); } if (!this.boundsSegments) { this.boundsSegments = ref_properties.SegmentVector.simpleSegment(0, 0, 4, 2); } if (!this.texture) { this.texture = new ref_properties.Texture(context, this.image, gl.RGBA); this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); } for (const w in this.tiles) { const tile = this.tiles[w]; if (tile.state !== 'loaded') { tile.state = 'loaded'; tile.texture = this.texture; } } } loadTile(tile, callback) { if (this.tileID && this.tileID.equals(tile.tileID.canonical)) { this.tiles[String(tile.tileID.wrap)] = tile; tile.buckets = {}; callback(null); } else { tile.state = 'errored'; callback(null); } } serialize() { return { type: 'image', url: this.options.url, coordinates: this.coordinates }; } hasTransition() { return false; } } function getCoordinatesCenterTileID(coords) { let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; for (const coord of coords) { minX = Math.min(minX, coord.x); minY = Math.min(minY, coord.y); maxX = Math.max(maxX, coord.x); maxY = Math.max(maxY, coord.y); } const dx = maxX - minX; const dy = maxY - minY; const dMax = Math.max(dx, dy); const zoom = Math.max(0, Math.floor(-Math.log(dMax) / Math.LN2)); const tilesAtZoom = Math.pow(2, zoom); return new ref_properties.CanonicalTileID(zoom, Math.floor((minX + maxX) / 2 * tilesAtZoom), Math.floor((minY + maxY) / 2 * tilesAtZoom)); } class VideoSource extends ImageSource { constructor(id, options, dispatcher, eventedParent) { super(id, options, dispatcher, eventedParent); this.roundZoom = true; this.type = 'video'; this.options = options; } load() { this._loaded = false; const options = this.options; this.urls = []; for (const url of options.urls) { this.urls.push(this.map._requestManager.transformRequest(url, ref_properties.ResourceType.Source).url); } ref_properties.getVideo(this.urls, (err, video) => { this._loaded = true; if (err) { this.fire(new ref_properties.ErrorEvent(err)); } else if (video) { this.video = video; this.video.loop = true; this.video.addEventListener('playing', () => { this.map.triggerRepaint(); }); if (this.map) { this.video.play(); } this._finishLoading(); } }); } pause() { if (this.video) { this.video.pause(); } } play() { if (this.video) { this.video.play(); } } seek(seconds) { if (this.video) { const seekableRange = this.video.seekable; if (seconds < seekableRange.start(0) || seconds > seekableRange.end(0)) { this.fire(new ref_properties.ErrorEvent(new ref_properties.ValidationError(`sources.${ this.id }`, null, `Playback for this video can be set only between the ${ seekableRange.start(0) } and ${ seekableRange.end(0) }-second mark.`))); } else this.video.currentTime = seconds; } } getVideo() { return this.video; } onAdd(map) { if (this.map) return; this.map = map; this.load(); if (this.video) { this.video.play(); this.setCoordinates(this.coordinates); } } prepare() { if (Object.keys(this.tiles).length === 0 || this.video.readyState < 2) { return; } const context = this.map.painter.context; const gl = context.gl; if (!this.boundsBuffer) { this.boundsBuffer = context.createVertexBuffer(this._boundsArray, rasterBoundsAttributes.members); } if (!this.boundsSegments) { this.boundsSegments = ref_properties.SegmentVector.simpleSegment(0, 0, 4, 2); } if (!this.texture) { this.texture = new ref_properties.Texture(context, this.video, gl.RGBA); this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); } else if (!this.video.paused) { this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.video); } for (const w in this.tiles) { const tile = this.tiles[w]; if (tile.state !== 'loaded') { tile.state = 'loaded'; tile.texture = this.texture; } } } serialize() { return { type: 'video', urls: this.urls, coordinates: this.coordinates }; } hasTransition() { return this.video && !this.video.paused; } } class CanvasSource extends ImageSource { constructor(id, options, dispatcher, eventedParent) { super(id, options, dispatcher, eventedParent); if (!options.coordinates) { this.fire(new ref_properties.ErrorEvent(new ref_properties.ValidationError(`sources.${ id }`, null, 'missing required property "coordinates"'))); } else if (!Array.isArray(options.coordinates) || options.coordinates.length !== 4 || options.coordinates.some(c => !Array.isArray(c) || c.length !== 2 || c.some(l => typeof l !== 'number'))) { this.fire(new ref_properties.ErrorEvent(new ref_properties.ValidationError(`sources.${ id }`, null, '"coordinates" property must be an array of 4 longitude/latitude array pairs'))); } if (options.animate && typeof options.animate !== 'boolean') { this.fire(new ref_properties.ErrorEvent(new ref_properties.ValidationError(`sources.${ id }`, null, 'optional "animate" property must be a boolean value'))); } if (!options.canvas) { this.fire(new ref_properties.ErrorEvent(new ref_properties.ValidationError(`sources.${ id }`, null, 'missing required property "canvas"'))); } else if (typeof options.canvas !== 'string' && !(options.canvas instanceof ref_properties.window.HTMLCanvasElement)) { this.fire(new ref_properties.ErrorEvent(new ref_properties.ValidationError(`sources.${ id }`, null, '"canvas" must be either a string representing the ID of the canvas element from which to read, or an HTMLCanvasElement instance'))); } this.options = options; this.animate = options.animate !== undefined ? options.animate : true; } load() { this._loaded = true; if (!this.canvas) { this.canvas = this.options.canvas instanceof ref_properties.window.HTMLCanvasElement ? this.options.canvas : ref_properties.window.document.getElementById(this.options.canvas); } this.width = this.canvas.width; this.height = this.canvas.height; if (this._hasInvalidDimensions()) { this.fire(new ref_properties.ErrorEvent(new Error('Canvas dimensions cannot be less than or equal to zero.'))); return; } this.play = function () { this._playing = true; this.map.triggerRepaint(); }; this.pause = function () { if (this._playing) { this.prepare(); this._playing = false; } }; this._finishLoading(); } getCanvas() { return this.canvas; } onAdd(map) { this.map = map; this.load(); if (this.canvas) { if (this.animate) this.play(); } } onRemove() { this.pause(); } prepare() { let resize = false; if (this.canvas.width !== this.width) { this.width = this.canvas.width; resize = true; } if (this.canvas.height !== this.height) { this.height = this.canvas.height; resize = true; } if (this._hasInvalidDimensions()) return; if (Object.keys(this.tiles).length === 0) return; const context = this.map.painter.context; const gl = context.gl; if (!this.boundsBuffer) { this.boundsBuffer = context.createVertexBuffer(this._boundsArray, rasterBoundsAttributes.members); } if (!this.boundsSegments) { this.boundsSegments = ref_properties.SegmentVector.simpleSegment(0, 0, 4, 2); } if (!this.texture) { this.texture = new ref_properties.Texture(context, this.canvas, gl.RGBA, { premultiply: true }); } else if (resize || this._playing) { this.texture.update(this.canvas, { premultiply: true }); } for (const w in this.tiles) { const tile = this.tiles[w]; if (tile.state !== 'loaded') { tile.state = 'loaded'; tile.texture = this.texture; } } } serialize() { return { type: 'canvas', coordinates: this.coordinates }; } hasTransition() { return this._playing; } _hasInvalidDimensions() { for (const x of [ this.canvas.width, this.canvas.height ]) { if (isNaN(x) || x <= 0) return true; } return false; } } const sourceTypes = { vector: VectorTileSource, raster: RasterTileSource, 'raster-dem': RasterDEMTileSource, geojson: GeoJSONSource, video: VideoSource, image: ImageSource, canvas: CanvasSource }; const create = function (id, specification, dispatcher, eventedParent) { const source = new sourceTypes[specification.type](id, specification, dispatcher, eventedParent); if (source.id !== id) { throw new Error(`Expected Source id to be ${ id } instead of ${ source.id }`); } ref_properties.bindAll([ 'load', 'abort', 'unload', 'serialize', 'prepare' ], source); return source; }; const getType = function (name) { return sourceTypes[name]; }; const setType = function (name, type) { sourceTypes[name] = type; }; function getPixelPosMatrix(transform, tileID) { const t = ref_properties.identity([]); ref_properties.scale(t, t, [ transform.width * 0.5, -transform.height * 0.5, 1 ]); ref_properties.translate(t, t, [ 1, -1, 0 ]); return ref_properties.multiply(t, t, transform.calculatePosMatrix(tileID.toUnwrapped())); } function queryRenderedFeatures(sourceCache, styleLayers, serializedLayers, queryGeometry, params, transform, use3DQuery, visualizeQueryGeometry = false) { const tileResults = sourceCache.tilesIn(queryGeometry, use3DQuery, visualizeQueryGeometry); tileResults.sort(sortTilesIn); const renderedFeatureLayers = []; for (const tileResult of tileResults) { renderedFeatureLayers.push({ wrappedTileID: tileResult.tile.tileID.wrapped().key, queryResults: tileResult.tile.queryRenderedFeatures(styleLayers, serializedLayers, sourceCache._state, tileResult, params, transform, getPixelPosMatrix(sourceCache.transform, tileResult.tile.tileID), visualizeQueryGeometry) }); } const result = mergeRenderedFeatureLayers(renderedFeatureLayers); for (const layerID in result) { result[layerID].forEach(featureWrapper => { const feature = featureWrapper.feature; const state = sourceCache.getFeatureState(feature.layer['source-layer'], feature.id); feature.source = feature.layer.source; if (feature.layer['source-layer']) { feature.sourceLayer = feature.layer['source-layer']; } feature.state = state; }); } return result; } function queryRenderedSymbols(styleLayers, serializedLayers, getLayerSourceCache, queryGeometry, params, collisionIndex, retainedQueryData) { const result = {}; const renderedSymbols = collisionIndex.queryRenderedSymbols(queryGeometry); const bucketQueryData = []; for (const bucketInstanceId of Object.keys(renderedSymbols).map(Number)) { bucketQueryData.push(retainedQueryData[bucketInstanceId]); } bucketQueryData.sort(sortTilesIn); for (const queryData of bucketQueryData) { const bucketSymbols = queryData.featureIndex.lookupSymbolFeatures(renderedSymbols[queryData.bucketInstanceId], serializedLayers, queryData.bucketIndex, queryData.sourceLayerIndex, params.filter, params.layers, params.availableImages, styleLayers); for (const layerID in bucketSymbols) { const resultFeatures = result[layerID] = result[layerID] || []; const layerSymbols = bucketSymbols[layerID]; layerSymbols.sort((a, b) => { const featureSortOrder = queryData.featureSortOrder; if (featureSortOrder) { const sortedA = featureSortOrder.indexOf(a.featureIndex); const sortedB = featureSortOrder.indexOf(b.featureIndex); return sortedB - sortedA; } else { return b.featureIndex - a.featureIndex; } }); for (const symbolFeature of layerSymbols) { resultFeatures.push(symbolFeature); } } } for (const layerName in result) { result[layerName].forEach(featureWrapper => { const feature = featureWrapper.feature; const layer = styleLayers[layerName]; const sourceCache = getLayerSourceCache(layer); const state = sourceCache.getFeatureState(feature.layer['source-layer'], feature.id); feature.source = feature.layer.source; if (feature.layer['source-layer']) { feature.sourceLayer = feature.layer['source-layer']; } feature.state = state; }); } return result; } function querySourceFeatures(sourceCache, params) { const tiles = sourceCache.getRenderableIds().map(id => { return sourceCache.getTileByID(id); }); const result = []; const dataTiles = {}; for (let i = 0; i < tiles.length; i++) { const tile = tiles[i]; const dataID = tile.tileID.canonical.key; if (!dataTiles[dataID]) { dataTiles[dataID] = true; tile.querySourceFeatures(result, params); } } return result; } function sortTilesIn(a, b) { const idA = a.tileID; const idB = b.tileID; return idA.overscaledZ - idB.overscaledZ || idA.canonical.y - idB.canonical.y || idA.wrap - idB.wrap || idA.canonical.x - idB.canonical.x; } function mergeRenderedFeatureLayers(tiles) { const result = {}; const wrappedIDLayerMap = {}; for (const tile of tiles) { const queryResults = tile.queryResults; const wrappedID = tile.wrappedTileID; const wrappedIDLayers = wrappedIDLayerMap[wrappedID] = wrappedIDLayerMap[wrappedID] || {}; for (const layerID in queryResults) { const tileFeatures = queryResults[layerID]; const wrappedIDFeatures = wrappedIDLayers[layerID] = wrappedIDLayers[layerID] || {}; const resultFeatures = result[layerID] = result[layerID] || []; for (const tileFeature of tileFeatures) { if (!wrappedIDFeatures[tileFeature.featureIndex]) { wrappedIDFeatures[tileFeature.featureIndex] = true; resultFeatures.push(tileFeature); } } } } return result; } function WebWorker () { return exported.workerClass != null ? new exported.workerClass() : new ref_properties.window.Worker(exported.workerUrl); } const PRELOAD_POOL_ID = 'mapboxgl_preloaded_worker_pool'; class WorkerPool { constructor() { this.active = {}; } acquire(mapId) { if (!this.workers) { this.workers = []; while (this.workers.length < WorkerPool.workerCount) { this.workers.push(new WebWorker()); } } this.active[mapId] = true; return this.workers.slice(); } release(mapId) { delete this.active[mapId]; if (this.numActive() === 0) { this.workers.forEach(w => { w.terminate(); }); this.workers = null; } } isPreloaded() { return !!this.active[PRELOAD_POOL_ID]; } numActive() { return Object.keys(this.active).length; } } WorkerPool.workerCount = 2; let globalWorkerPool; function getGlobalWorkerPool() { if (!globalWorkerPool) { globalWorkerPool = new WorkerPool(); } return globalWorkerPool; } function prewarm() { const workerPool = getGlobalWorkerPool(); workerPool.acquire(PRELOAD_POOL_ID); } function clearPrewarmedResources() { const pool = globalWorkerPool; if (pool) { if (pool.isPreloaded() && pool.numActive() === 1) { pool.release(PRELOAD_POOL_ID); globalWorkerPool = null; } else { console.warn('Could not clear WebWorkers since there are active Map instances that still reference it. The pre-warmed WebWorker pool can only be cleared when all map instances have been removed with map.remove()'); } } } function deref(layer, parent) { const result = {}; for (const k in layer) { if (k !== 'ref') { result[k] = layer[k]; } } ref_properties.refProperties.forEach(k => { if (k in parent) { result[k] = parent[k]; } }); return result; } function derefLayers(layers) { layers = layers.slice(); const map = Object.create(null); for (let i = 0; i < layers.length; i++) { map[layers[i].id] = layers[i]; } for (let i = 0; i < layers.length; i++) { if ('ref' in layers[i]) { layers[i] = deref(layers[i], map[layers[i].ref]); } } return layers; } function emptyStyle() { const style = {}; const version = ref_properties.styleSpec['$version']; for (const styleKey in ref_properties.styleSpec['$root']) { const spec = ref_properties.styleSpec['$root'][styleKey]; if (spec.required) { let value = null; if (styleKey === 'version') { value = version; } else { if (spec.type === 'array') { value = []; } else { value = {}; } } if (value != null) { style[styleKey] = value; } } } return style; } const operations = { setStyle: 'setStyle', addLayer: 'addLayer', removeLayer: 'removeLayer', setPaintProperty: 'setPaintProperty', setLayoutProperty: 'setLayoutProperty', setFilter: 'setFilter', addSource: 'addSource', removeSource: 'removeSource', setGeoJSONSourceData: 'setGeoJSONSourceData', setLayerZoomRange: 'setLayerZoomRange', setLayerProperty: 'setLayerProperty', setCenter: 'setCenter', setZoom: 'setZoom', setBearing: 'setBearing', setPitch: 'setPitch', setSprite: 'setSprite', setGlyphs: 'setGlyphs', setTransition: 'setTransition', setLight: 'setLight', setTerrain: 'setTerrain' }; function addSource(sourceId, after, commands) { commands.push({ command: operations.addSource, args: [ sourceId, after[sourceId] ] }); } function removeSource(sourceId, commands, sourcesRemoved) { commands.push({ command: operations.removeSource, args: [sourceId] }); sourcesRemoved[sourceId] = true; } function updateSource(sourceId, after, commands, sourcesRemoved) { removeSource(sourceId, commands, sourcesRemoved); addSource(sourceId, after, commands); } function canUpdateGeoJSON(before, after, sourceId) { let prop; for (prop in before[sourceId]) { if (!before[sourceId].hasOwnProperty(prop)) continue; if (prop !== 'data' && !ref_properties.deepEqual(before[sourceId][prop], after[sourceId][prop])) { return false; } } for (prop in after[sourceId]) { if (!after[sourceId].hasOwnProperty(prop)) continue; if (prop !== 'data' && !ref_properties.deepEqual(before[sourceId][prop], after[sourceId][prop])) { return false; } } return true; } function diffSources(before, after, commands, sourcesRemoved) { before = before || {}; after = after || {}; let sourceId; for (sourceId in before) { if (!before.hasOwnProperty(sourceId)) continue; if (!after.hasOwnProperty(sourceId)) { removeSource(sourceId, commands, sourcesRemoved); } } for (sourceId in after) { if (!after.hasOwnProperty(sourceId)) continue; if (!before.hasOwnProperty(sourceId)) { addSource(sourceId, after, commands); } else if (!ref_properties.deepEqual(before[sourceId], after[sourceId])) { if (before[sourceId].type === 'geojson' && after[sourceId].type === 'geojson' && canUpdateGeoJSON(before, after, sourceId)) { commands.push({ command: operations.setGeoJSONSourceData, args: [ sourceId, after[sourceId].data ] }); } else { updateSource(sourceId, after, commands, sourcesRemoved); } } } } function diffLayerPropertyChanges(before, after, commands, layerId, klass, command) { before = before || {}; after = after || {}; let prop; for (prop in before) { if (!before.hasOwnProperty(prop)) continue; if (!ref_properties.deepEqual(before[prop], after[prop])) { commands.push({ command, args: [ layerId, prop, after[prop], klass ] }); } } for (prop in after) { if (!after.hasOwnProperty(prop) || before.hasOwnProperty(prop)) continue; if (!ref_properties.deepEqual(before[prop], after[prop])) { commands.push({ command, args: [ layerId, prop, after[prop], klass ] }); } } } function pluckId(layer) { return layer.id; } function indexById(group, layer) { group[layer.id] = layer; return group; } function diffLayers(before, after, commands) { before = before || []; after = after || []; const beforeOrder = before.map(pluckId); const afterOrder = after.map(pluckId); const beforeIndex = before.reduce(indexById, {}); const afterIndex = after.reduce(indexById, {}); const tracker = beforeOrder.slice(); const clean = Object.create(null); let i, d, layerId, beforeLayer, afterLayer, insertBeforeLayerId, prop; for (i = 0, d = 0; i < beforeOrder.length; i++) { layerId = beforeOrder[i]; if (!afterIndex.hasOwnProperty(layerId)) { commands.push({ command: operations.removeLayer, args: [layerId] }); tracker.splice(tracker.indexOf(layerId, d), 1); } else { d++; } } for (i = 0, d = 0; i < afterOrder.length; i++) { layerId = afterOrder[afterOrder.length - 1 - i]; if (tracker[tracker.length - 1 - i] === layerId) continue; if (beforeIndex.hasOwnProperty(layerId)) { commands.push({ command: operations.removeLayer, args: [layerId] }); tracker.splice(tracker.lastIndexOf(layerId, tracker.length - d), 1); } else { d++; } insertBeforeLayerId = tracker[tracker.length - i]; commands.push({ command: operations.addLayer, args: [ afterIndex[layerId], insertBeforeLayerId ] }); tracker.splice(tracker.length - i, 0, layerId); clean[layerId] = true; } for (i = 0; i < afterOrder.length; i++) { layerId = afterOrder[i]; beforeLayer = beforeIndex[layerId]; afterLayer = afterIndex[layerId]; if (clean[layerId] || ref_properties.deepEqual(beforeLayer, afterLayer)) continue; if (!ref_properties.deepEqual(beforeLayer.source, afterLayer.source) || !ref_properties.deepEqual(beforeLayer['source-layer'], afterLayer['source-layer']) || !ref_properties.deepEqual(beforeLayer.type, afterLayer.type)) { commands.push({ command: operations.removeLayer, args: [layerId] }); insertBeforeLayerId = tracker[tracker.lastIndexOf(layerId) + 1]; commands.push({ command: operations.addLayer, args: [ afterLayer, insertBeforeLayerId ] }); continue; } diffLayerPropertyChanges(beforeLayer.layout, afterLayer.layout, commands, layerId, null, operations.setLayoutProperty); diffLayerPropertyChanges(beforeLayer.paint, afterLayer.paint, commands, layerId, null, operations.setPaintProperty); if (!ref_properties.deepEqual(beforeLayer.filter, afterLayer.filter)) { commands.push({ command: operations.setFilter, args: [ layerId, afterLayer.filter ] }); } if (!ref_properties.deepEqual(beforeLayer.minzoom, afterLayer.minzoom) || !ref_properties.deepEqual(beforeLayer.maxzoom, afterLayer.maxzoom)) { commands.push({ command: operations.setLayerZoomRange, args: [ layerId, afterLayer.minzoom, afterLayer.maxzoom ] }); } for (prop in beforeLayer) { if (!beforeLayer.hasOwnProperty(prop)) continue; if (prop === 'layout' || prop === 'paint' || prop === 'filter' || prop === 'metadata' || prop === 'minzoom' || prop === 'maxzoom') continue; if (prop.indexOf('paint.') === 0) { diffLayerPropertyChanges(beforeLayer[prop], afterLayer[prop], commands, layerId, prop.slice(6), operations.setPaintProperty); } else if (!ref_properties.deepEqual(beforeLayer[prop], afterLayer[prop])) { commands.push({ command: operations.setLayerProperty, args: [ layerId, prop, afterLayer[prop] ] }); } } for (prop in afterLayer) { if (!afterLayer.hasOwnProperty(prop) || beforeLayer.hasOwnProperty(prop)) continue; if (prop === 'layout' || prop === 'paint' || prop === 'filter' || prop === 'metadata' || prop === 'minzoom' || prop === 'maxzoom') continue; if (prop.indexOf('paint.') === 0) { diffLayerPropertyChanges(beforeLayer[prop], afterLayer[prop], commands, layerId, prop.slice(6), operations.setPaintProperty); } else if (!ref_properties.deepEqual(beforeLayer[prop], afterLayer[prop])) { commands.push({ command: operations.setLayerProperty, args: [ layerId, prop, afterLayer[prop] ] }); } } } } function diffStyles(before, after) { if (!before) return [{ command: operations.setStyle, args: [after] }]; let commands = []; try { if (!ref_properties.deepEqual(before.version, after.version)) { return [{ command: operations.setStyle, args: [after] }]; } if (!ref_properties.deepEqual(before.center, after.center)) { commands.push({ command: operations.setCenter, args: [after.center] }); } if (!ref_properties.deepEqual(before.zoom, after.zoom)) { commands.push({ command: operations.setZoom, args: [after.zoom] }); } if (!ref_properties.deepEqual(before.bearing, after.bearing)) { commands.push({ command: operations.setBearing, args: [after.bearing] }); } if (!ref_properties.deepEqual(before.pitch, after.pitch)) { commands.push({ command: operations.setPitch, args: [after.pitch] }); } if (!ref_properties.deepEqual(before.sprite, after.sprite)) { commands.push({ command: operations.setSprite, args: [after.sprite] }); } if (!ref_properties.deepEqual(before.glyphs, after.glyphs)) { commands.push({ command: operations.setGlyphs, args: [after.glyphs] }); } if (!ref_properties.deepEqual(before.transition, after.transition)) { commands.push({ command: operations.setTransition, args: [after.transition] }); } if (!ref_properties.deepEqual(before.light, after.light)) { commands.push({ command: operations.setLight, args: [after.light] }); } const sourcesRemoved = {}; const removeOrAddSourceCommands = []; diffSources(before.sources, after.sources, removeOrAddSourceCommands, sourcesRemoved); const beforeLayers = []; if (before.layers) { before.layers.forEach(layer => { if (sourcesRemoved[layer.source]) { commands.push({ command: operations.removeLayer, args: [layer.id] }); } else { beforeLayers.push(layer); } }); } let beforeTerrain = before.terrain; if (beforeTerrain) { if (sourcesRemoved[beforeTerrain.source]) { commands.push({ command: operations.setTerrain, args: [undefined] }); beforeTerrain = undefined; } } commands = commands.concat(removeOrAddSourceCommands); if (!ref_properties.deepEqual(beforeTerrain, after.terrain)) { commands.push({ command: operations.setTerrain, args: [after.terrain] }); } diffLayers(beforeLayers, after.layers, commands); } catch (e) { console.warn('Unable to compute style diff:', e); commands = [{ command: operations.setStyle, args: [after] }]; } return commands; } class PathInterpolator { constructor(points_, padding_) { this.reset(points_, padding_); } reset(points_, padding_) { this.points = points_ || []; this._distances = [0]; for (let i = 1; i < this.points.length; i++) { this._distances[i] = this._distances[i - 1] + this.points[i].dist(this.points[i - 1]); } this.length = this._distances[this._distances.length - 1]; this.padding = Math.min(padding_ || 0, this.length * 0.5); this.paddedLength = this.length - this.padding * 2; } lerp(t) { if (this.points.length === 1) { return this.points[0]; } t = ref_properties.clamp(t, 0, 1); let currentIndex = 1; let distOfCurrentIdx = this._distances[currentIndex]; const distToTarget = t * this.paddedLength + this.padding; while (distOfCurrentIdx < distToTarget && currentIndex < this._distances.length) { distOfCurrentIdx = this._distances[++currentIndex]; } const idxOfPrevPoint = currentIndex - 1; const distOfPrevIdx = this._distances[idxOfPrevPoint]; const segmentLength = distOfCurrentIdx - distOfPrevIdx; const segmentT = segmentLength > 0 ? (distToTarget - distOfPrevIdx) / segmentLength : 0; return this.points[idxOfPrevPoint].mult(1 - segmentT).add(this.points[currentIndex].mult(segmentT)); } } class GridIndex { constructor(width, height, cellSize) { const boxCells = this.boxCells = []; const circleCells = this.circleCells = []; this.xCellCount = Math.ceil(width / cellSize); this.yCellCount = Math.ceil(height / cellSize); for (let i = 0; i < this.xCellCount * this.yCellCount; i++) { boxCells.push([]); circleCells.push([]); } this.circleKeys = []; this.boxKeys = []; this.bboxes = []; this.circles = []; this.width = width; this.height = height; this.xScale = this.xCellCount / width; this.yScale = this.yCellCount / height; this.boxUid = 0; this.circleUid = 0; } keysLength() { return this.boxKeys.length + this.circleKeys.length; } insert(key, x1, y1, x2, y2) { this._forEachCell(x1, y1, x2, y2, this._insertBoxCell, this.boxUid++); this.boxKeys.push(key); this.bboxes.push(x1); this.bboxes.push(y1); this.bboxes.push(x2); this.bboxes.push(y2); } insertCircle(key, x, y, radius) { this._forEachCell(x - radius, y - radius, x + radius, y + radius, this._insertCircleCell, this.circleUid++); this.circleKeys.push(key); this.circles.push(x); this.circles.push(y); this.circles.push(radius); } _insertBoxCell(x1, y1, x2, y2, cellIndex, uid) { this.boxCells[cellIndex].push(uid); } _insertCircleCell(x1, y1, x2, y2, cellIndex, uid) { this.circleCells[cellIndex].push(uid); } _query(x1, y1, x2, y2, hitTest, predicate) { if (x2 < 0 || x1 > this.width || y2 < 0 || y1 > this.height) { return hitTest ? false : []; } const result = []; if (x1 <= 0 && y1 <= 0 && this.width <= x2 && this.height <= y2) { if (hitTest) { return true; } for (let boxUid = 0; boxUid < this.boxKeys.length; boxUid++) { result.push({ key: this.boxKeys[boxUid], x1: this.bboxes[boxUid * 4], y1: this.bboxes[boxUid * 4 + 1], x2: this.bboxes[boxUid * 4 + 2], y2: this.bboxes[boxUid * 4 + 3] }); } for (let circleUid = 0; circleUid < this.circleKeys.length; circleUid++) { const x = this.circles[circleUid * 3]; const y = this.circles[circleUid * 3 + 1]; const radius = this.circles[circleUid * 3 + 2]; result.push({ key: this.circleKeys[circleUid], x1: x - radius, y1: y - radius, x2: x + radius, y2: y + radius }); } return predicate ? result.filter(predicate) : result; } else { const queryArgs = { hitTest, seenUids: { box: {}, circle: {} } }; this._forEachCell(x1, y1, x2, y2, this._queryCell, result, queryArgs, predicate); return hitTest ? result.length > 0 : result; } } _queryCircle(x, y, radius, hitTest, predicate) { const x1 = x - radius; const x2 = x + radius; const y1 = y - radius; const y2 = y + radius; if (x2 < 0 || x1 > this.width || y2 < 0 || y1 > this.height) { return hitTest ? false : []; } const result = []; const queryArgs = { hitTest, circle: { x, y, radius }, seenUids: { box: {}, circle: {} } }; this._forEachCell(x1, y1, x2, y2, this._queryCellCircle, result, queryArgs, predicate); return hitTest ? result.length > 0 : result; } query(x1, y1, x2, y2, predicate) { return this._query(x1, y1, x2, y2, false, predicate); } hitTest(x1, y1, x2, y2, predicate) { return this._query(x1, y1, x2, y2, true, predicate); } hitTestCircle(x, y, radius, predicate) { return this._queryCircle(x, y, radius, true, predicate); } _queryCell(x1, y1, x2, y2, cellIndex, result, queryArgs, predicate) { const seenUids = queryArgs.seenUids; const boxCell = this.boxCells[cellIndex]; if (boxCell !== null) { const bboxes = this.bboxes; for (const boxUid of boxCell) { if (!seenUids.box[boxUid]) { seenUids.box[boxUid] = true; const offset = boxUid * 4; if (x1 <= bboxes[offset + 2] && y1 <= bboxes[offset + 3] && x2 >= bboxes[offset + 0] && y2 >= bboxes[offset + 1] && (!predicate || predicate(this.boxKeys[boxUid]))) { if (queryArgs.hitTest) { result.push(true); return true; } else { result.push({ key: this.boxKeys[boxUid], x1: bboxes[offset], y1: bboxes[offset + 1], x2: bboxes[offset + 2], y2: bboxes[offset + 3] }); } } } } } const circleCell = this.circleCells[cellIndex]; if (circleCell !== null) { const circles = this.circles; for (const circleUid of circleCell) { if (!seenUids.circle[circleUid]) { seenUids.circle[circleUid] = true; const offset = circleUid * 3; if (this._circleAndRectCollide(circles[offset], circles[offset + 1], circles[offset + 2], x1, y1, x2, y2) && (!predicate || predicate(this.circleKeys[circleUid]))) { if (queryArgs.hitTest) { result.push(true); return true; } else { const x = circles[offset]; const y = circles[offset + 1]; const radius = circles[offset + 2]; result.push({ key: this.circleKeys[circleUid], x1: x - radius, y1: y - radius, x2: x + radius, y2: y + radius }); } } } } } } _queryCellCircle(x1, y1, x2, y2, cellIndex, result, queryArgs, predicate) { const circle = queryArgs.circle; const seenUids = queryArgs.seenUids; const boxCell = this.boxCells[cellIndex]; if (boxCell !== null) { const bboxes = this.bboxes; for (const boxUid of boxCell) { if (!seenUids.box[boxUid]) { seenUids.box[boxUid] = true; const offset = boxUid * 4; if (this._circleAndRectCollide(circle.x, circle.y, circle.radius, bboxes[offset + 0], bboxes[offset + 1], bboxes[offset + 2], bboxes[offset + 3]) && (!predicate || predicate(this.boxKeys[boxUid]))) { result.push(true); return true; } } } } const circleCell = this.circleCells[cellIndex]; if (circleCell !== null) { const circles = this.circles; for (const circleUid of circleCell) { if (!seenUids.circle[circleUid]) { seenUids.circle[circleUid] = true; const offset = circleUid * 3; if (this._circlesCollide(circles[offset], circles[offset + 1], circles[offset + 2], circle.x, circle.y, circle.radius) && (!predicate || predicate(this.circleKeys[circleUid]))) { result.push(true); return true; } } } } } _forEachCell(x1, y1, x2, y2, fn, arg1, arg2, predicate) { const cx1 = this._convertToXCellCoord(x1); const cy1 = this._convertToYCellCoord(y1); const cx2 = this._convertToXCellCoord(x2); const cy2 = this._convertToYCellCoord(y2); for (let x = cx1; x <= cx2; x++) { for (let y = cy1; y <= cy2; y++) { const cellIndex = this.xCellCount * y + x; if (fn.call(this, x1, y1, x2, y2, cellIndex, arg1, arg2, predicate)) return; } } } _convertToXCellCoord(x) { return Math.max(0, Math.min(this.xCellCount - 1, Math.floor(x * this.xScale))); } _convertToYCellCoord(y) { return Math.max(0, Math.min(this.yCellCount - 1, Math.floor(y * this.yScale))); } _circlesCollide(x1, y1, r1, x2, y2, r2) { const dx = x2 - x1; const dy = y2 - y1; const bothRadii = r1 + r2; return bothRadii * bothRadii > dx * dx + dy * dy; } _circleAndRectCollide(circleX, circleY, radius, x1, y1, x2, y2) { const halfRectWidth = (x2 - x1) / 2; const distX = Math.abs(circleX - (x1 + halfRectWidth)); if (distX > halfRectWidth + radius) { return false; } const halfRectHeight = (y2 - y1) / 2; const distY = Math.abs(circleY - (y1 + halfRectHeight)); if (distY > halfRectHeight + radius) { return false; } if (distX <= halfRectWidth || distY <= halfRectHeight) { return true; } const dx = distX - halfRectWidth; const dy = distY - halfRectHeight; return dx * dx + dy * dy <= radius * radius; } } function getLabelPlaneMatrix(posMatrix, pitchWithMap, rotateWithMap, transform, pixelsToTileUnits) { const m = ref_properties.create(); if (pitchWithMap) { ref_properties.scale(m, m, [ 1 / pixelsToTileUnits, 1 / pixelsToTileUnits, 1 ]); if (!rotateWithMap) { ref_properties.rotateZ(m, m, transform.angle); } } else { ref_properties.multiply(m, transform.labelPlaneMatrix, posMatrix); } return m; } function getGlCoordMatrix(posMatrix, pitchWithMap, rotateWithMap, transform, pixelsToTileUnits) { if (pitchWithMap) { const m = ref_properties.clone(posMatrix); ref_properties.scale(m, m, [ pixelsToTileUnits, pixelsToTileUnits, 1 ]); if (!rotateWithMap) { ref_properties.rotateZ(m, m, -transform.angle); } return m; } else { return transform.glCoordMatrix; } } function project(point, matrix, elevation = 0) { const pos = [ point.x, point.y, elevation, 1 ]; if (elevation) { ref_properties.transformMat4(pos, pos, matrix); } else { xyTransformMat4(pos, pos, matrix); } const w = pos[3]; return { point: new ref_properties.Point(pos[0] / w, pos[1] / w), signedDistanceFromCamera: w }; } function getPerspectiveRatio(cameraToCenterDistance, signedDistanceFromCamera) { return Math.min(0.5 + 0.5 * (cameraToCenterDistance / signedDistanceFromCamera), 1.5); } function isVisible(anchorPos, clippingBuffer) { const x = anchorPos[0] / anchorPos[3]; const y = anchorPos[1] / anchorPos[3]; const inPaddedViewport = x >= -clippingBuffer[0] && x <= clippingBuffer[0] && y >= -clippingBuffer[1] && y <= clippingBuffer[1]; return inPaddedViewport; } function updateLineLabels(bucket, posMatrix, painter, isText, labelPlaneMatrix, glCoordMatrix, pitchWithMap, keepUpright, getElevation) { const sizeData = isText ? bucket.textSizeData : bucket.iconSizeData; const partiallyEvaluatedSize = ref_properties.evaluateSizeForZoom(sizeData, painter.transform.zoom); const clippingBuffer = [ 256 / painter.width * 2 + 1, 256 / painter.height * 2 + 1 ]; const dynamicLayoutVertexArray = isText ? bucket.text.dynamicLayoutVertexArray : bucket.icon.dynamicLayoutVertexArray; dynamicLayoutVertexArray.clear(); const lineVertexArray = bucket.lineVertexArray; const placedSymbols = isText ? bucket.text.placedSymbolArray : bucket.icon.placedSymbolArray; const aspectRatio = painter.transform.width / painter.transform.height; let useVertical = false; for (let s = 0; s < placedSymbols.length; s++) { const symbol = placedSymbols.get(s); if (symbol.hidden || symbol.writingMode === ref_properties.WritingMode.vertical && !useVertical) { hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray); continue; } useVertical = false; const elevation = getElevation ? getElevation({ x: symbol.anchorX, y: symbol.anchorY }) : 0; const anchorPos = [ symbol.anchorX, symbol.anchorY, elevation, 1 ]; ref_properties.transformMat4(anchorPos, anchorPos, posMatrix); if (!isVisible(anchorPos, clippingBuffer)) { hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray); continue; } const cameraToAnchorDistance = anchorPos[3]; const perspectiveRatio = getPerspectiveRatio(painter.transform.cameraToCenterDistance, cameraToAnchorDistance); const fontSize = ref_properties.evaluateSizeForFeature(sizeData, partiallyEvaluatedSize, symbol); const pitchScaledFontSize = pitchWithMap ? fontSize / perspectiveRatio : fontSize * perspectiveRatio; const tileAnchorPoint = new ref_properties.Point(symbol.anchorX, symbol.anchorY); const transformedTileAnchor = project(tileAnchorPoint, labelPlaneMatrix, elevation); if (transformedTileAnchor.signedDistanceFromCamera <= 0) { hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray); continue; } const anchorPoint = transformedTileAnchor.point; let projectionCache = {}; const getElevationForPlacement = pitchWithMap ? null : getElevation; const placeUnflipped = placeGlyphsAlongLine(symbol, pitchScaledFontSize, false, keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, bucket.glyphOffsetArray, lineVertexArray, dynamicLayoutVertexArray, anchorPoint, tileAnchorPoint, projectionCache, aspectRatio, getElevationForPlacement); useVertical = placeUnflipped.useVertical; if (getElevationForPlacement && placeUnflipped.needsFlipping) projectionCache = {}; if (placeUnflipped.notEnoughRoom || useVertical || placeUnflipped.needsFlipping && placeGlyphsAlongLine(symbol, pitchScaledFontSize, true, keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, bucket.glyphOffsetArray, lineVertexArray, dynamicLayoutVertexArray, anchorPoint, tileAnchorPoint, projectionCache, aspectRatio, getElevationForPlacement).notEnoughRoom) { hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray); } } if (isText) { bucket.text.dynamicLayoutVertexBuffer.updateData(dynamicLayoutVertexArray); } else { bucket.icon.dynamicLayoutVertexBuffer.updateData(dynamicLayoutVertexArray); } } function placeFirstAndLastGlyph(fontScale, glyphOffsetArray, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol, lineVertexArray, labelPlaneMatrix, projectionCache, getElevation, returnPathInTileCoords) { const glyphEndIndex = symbol.glyphStartIndex + symbol.numGlyphs; const lineStartIndex = symbol.lineStartIndex; const lineEndIndex = symbol.lineStartIndex + symbol.lineLength; const firstGlyphOffset = glyphOffsetArray.getoffsetX(symbol.glyphStartIndex); const lastGlyphOffset = glyphOffsetArray.getoffsetX(glyphEndIndex - 1); const firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, lineStartIndex, lineEndIndex, lineVertexArray, labelPlaneMatrix, projectionCache, getElevation, returnPathInTileCoords, true); if (!firstPlacedGlyph) return null; const lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, lineStartIndex, lineEndIndex, lineVertexArray, labelPlaneMatrix, projectionCache, getElevation, returnPathInTileCoords, true); if (!lastPlacedGlyph) return null; return { first: firstPlacedGlyph, last: lastPlacedGlyph }; } function requiresOrientationChange(writingMode, firstPoint, lastPoint, aspectRatio) { if (writingMode === ref_properties.WritingMode.horizontal) { const rise = Math.abs(lastPoint.y - firstPoint.y); const run = Math.abs(lastPoint.x - firstPoint.x) * aspectRatio; if (rise > run) { return { useVertical: true }; } } if (writingMode === ref_properties.WritingMode.vertical ? firstPoint.y < lastPoint.y : firstPoint.x > lastPoint.x) { return { needsFlipping: true }; } return null; } function placeGlyphsAlongLine(symbol, fontSize, flip, keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, glyphOffsetArray, lineVertexArray, dynamicLayoutVertexArray, anchorPoint, tileAnchorPoint, projectionCache, aspectRatio, getElevation) { const fontScale = fontSize / 24; const lineOffsetX = symbol.lineOffsetX * fontScale; const lineOffsetY = symbol.lineOffsetY * fontScale; let placedGlyphs; if (symbol.numGlyphs > 1) { const glyphEndIndex = symbol.glyphStartIndex + symbol.numGlyphs; const lineStartIndex = symbol.lineStartIndex; const lineEndIndex = symbol.lineStartIndex + symbol.lineLength; const firstAndLastGlyph = placeFirstAndLastGlyph(fontScale, glyphOffsetArray, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol, lineVertexArray, labelPlaneMatrix, projectionCache, getElevation); if (!firstAndLastGlyph) { return { notEnoughRoom: true }; } const firstPoint = project(firstAndLastGlyph.first.point, glCoordMatrix).point; const lastPoint = project(firstAndLastGlyph.last.point, glCoordMatrix).point; if (keepUpright && !flip) { const orientationChange = requiresOrientationChange(symbol.writingMode, firstPoint, lastPoint, aspectRatio); if (orientationChange) { return orientationChange; } } placedGlyphs = [firstAndLastGlyph.first]; for (let glyphIndex = symbol.glyphStartIndex + 1; glyphIndex < glyphEndIndex - 1; glyphIndex++) { placedGlyphs.push(placeGlyphAlongLine(fontScale * glyphOffsetArray.getoffsetX(glyphIndex), lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, lineStartIndex, lineEndIndex, lineVertexArray, labelPlaneMatrix, projectionCache, getElevation)); } placedGlyphs.push(firstAndLastGlyph.last); } else { if (keepUpright && !flip) { const a = project(tileAnchorPoint, posMatrix).point; const tileVertexIndex = symbol.lineStartIndex + symbol.segment + 1; const tileSegmentEnd = new ref_properties.Point(lineVertexArray.getx(tileVertexIndex), lineVertexArray.gety(tileVertexIndex)); const projectedVertex = project(tileSegmentEnd, posMatrix); const b = projectedVertex.signedDistanceFromCamera > 0 ? projectedVertex.point : projectTruncatedLineSegment(tileAnchorPoint, tileSegmentEnd, a, 1, posMatrix); const orientationChange = requiresOrientationChange(symbol.writingMode, a, b, aspectRatio); if (orientationChange) { return orientationChange; } } const singleGlyph = placeGlyphAlongLine(fontScale * glyphOffsetArray.getoffsetX(symbol.glyphStartIndex), lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.lineStartIndex, symbol.lineStartIndex + symbol.lineLength, lineVertexArray, labelPlaneMatrix, projectionCache, getElevation); if (!singleGlyph) return { notEnoughRoom: true }; placedGlyphs = [singleGlyph]; } for (const glyph of placedGlyphs) { ref_properties.addDynamicAttributes(dynamicLayoutVertexArray, glyph.point, glyph.angle); } return {}; } function projectTruncatedLineSegment(previousTilePoint, currentTilePoint, previousProjectedPoint, minimumLength, projectionMatrix, getElevation) { const unitVertex = previousTilePoint.add(previousTilePoint.sub(currentTilePoint)._unit()); const projectedUnitVertex = project(unitVertex, projectionMatrix, getElevation ? getElevation(unitVertex) : 0).point; const projectedUnitSegment = previousProjectedPoint.sub(projectedUnitVertex); return previousProjectedPoint.add(projectedUnitSegment._mult(minimumLength / projectedUnitSegment.mag())); } function interpolate(p1, p2, a) { const b = 1 - a; return new ref_properties.Point(p1.x * b + p2.x * a, p1.y * b + p2.y * a); } function placeGlyphAlongLine(offsetX, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, anchorSegment, lineStartIndex, lineEndIndex, lineVertexArray, labelPlaneMatrix, projectionCache, getElevation, returnPathInTileCoords, endGlyph) { const combinedOffsetX = flip ? offsetX - lineOffsetX : offsetX + lineOffsetX; let dir = combinedOffsetX > 0 ? 1 : -1; let angle = 0; if (flip) { dir *= -1; angle = Math.PI; } if (dir < 0) angle += Math.PI; let currentIndex = dir > 0 ? lineStartIndex + anchorSegment : lineStartIndex + anchorSegment + 1; let current = anchorPoint; let prev = anchorPoint; let distanceToPrev = 0; let currentSegmentDistance = 0; const absOffsetX = Math.abs(combinedOffsetX); const pathVertices = []; const tilePath = []; let currentVertex = tileAnchorPoint; const previousTilePoint = () => { const previousLineVertexIndex = currentIndex - dir; return distanceToPrev === 0 ? tileAnchorPoint : new ref_properties.Point(lineVertexArray.getx(previousLineVertexIndex), lineVertexArray.gety(previousLineVertexIndex)); }; const getTruncatedLineSegment = () => { return projectTruncatedLineSegment(previousTilePoint(), currentVertex, prev, absOffsetX - distanceToPrev + 1, labelPlaneMatrix, getElevation); }; while (distanceToPrev + currentSegmentDistance <= absOffsetX) { currentIndex += dir; if (currentIndex < lineStartIndex || currentIndex >= lineEndIndex) return null; prev = current; pathVertices.push(current); if (returnPathInTileCoords) tilePath.push(currentVertex || previousTilePoint()); current = projectionCache[currentIndex]; if (current === undefined) { currentVertex = new ref_properties.Point(lineVertexArray.getx(currentIndex), lineVertexArray.gety(currentIndex)); const projection = project(currentVertex, labelPlaneMatrix, getElevation ? getElevation(currentVertex) : 0); if (projection.signedDistanceFromCamera > 0) { current = projectionCache[currentIndex] = projection.point; } else { current = getTruncatedLineSegment(); } } else { currentVertex = null; } distanceToPrev += currentSegmentDistance; currentSegmentDistance = prev.dist(current); } if (endGlyph && getElevation) { currentVertex = currentVertex || new ref_properties.Point(lineVertexArray.getx(currentIndex), lineVertexArray.gety(currentIndex)); projectionCache[currentIndex] = current = projectionCache[currentIndex] === undefined ? current : getTruncatedLineSegment(); currentSegmentDistance = prev.dist(current); } const segmentInterpolationT = (absOffsetX - distanceToPrev) / currentSegmentDistance; const prevToCurrent = current.sub(prev); const p = prevToCurrent.mult(segmentInterpolationT)._add(prev); if (lineOffsetY) p._add(prevToCurrent._unit()._perp()._mult(lineOffsetY * dir)); const segmentAngle = angle + Math.atan2(current.y - prev.y, current.x - prev.x); pathVertices.push(p); if (returnPathInTileCoords) { currentVertex = currentVertex || new ref_properties.Point(lineVertexArray.getx(currentIndex), lineVertexArray.gety(currentIndex)); const prevVertex = tilePath.length > 0 ? tilePath[tilePath.length - 1] : currentVertex; tilePath.push(interpolate(prevVertex, currentVertex, segmentInterpolationT)); } return { point: p, angle: segmentAngle, path: pathVertices, tilePath }; } const hiddenGlyphAttributes = new Float32Array([ -Infinity, -Infinity, 0, -Infinity, -Infinity, 0, -Infinity, -Infinity, 0, -Infinity, -Infinity, 0 ]); function hideGlyphs(num, dynamicLayoutVertexArray) { for (let i = 0; i < num; i++) { const offset = dynamicLayoutVertexArray.length; dynamicLayoutVertexArray.resize(offset + 4); dynamicLayoutVertexArray.float32.set(hiddenGlyphAttributes, offset * 3); } } function xyTransformMat4(out, a, m) { const x = a[0], y = a[1]; out[0] = m[0] * x + m[4] * y + m[12]; out[1] = m[1] * x + m[5] * y + m[13]; out[3] = m[3] * x + m[7] * y + m[15]; return out; } const viewportPadding = 100; class CollisionIndex { constructor(transform, grid = new GridIndex(transform.width + 2 * viewportPadding, transform.height + 2 * viewportPadding, 25), ignoredGrid = new GridIndex(transform.width + 2 * viewportPadding, transform.height + 2 * viewportPadding, 25)) { this.transform = transform; this.grid = grid; this.ignoredGrid = ignoredGrid; this.pitchfactor = Math.cos(transform._pitch) * transform.cameraToCenterDistance; this.screenRightBoundary = transform.width + viewportPadding; this.screenBottomBoundary = transform.height + viewportPadding; this.gridRightBoundary = transform.width + 2 * viewportPadding; this.gridBottomBoundary = transform.height + 2 * viewportPadding; } placeCollisionBox(scale, collisionBox, shift, allowOverlap, textPixelRatio, posMatrix, collisionGroupPredicate) { const projectedPoint = this.projectAndGetPerspectiveRatio(posMatrix, collisionBox.anchorPointX, collisionBox.anchorPointY, collisionBox.elevation); const tileToViewport = textPixelRatio * projectedPoint.perspectiveRatio; const tlX = (collisionBox.x1 * scale + shift.x - collisionBox.padding) * tileToViewport + projectedPoint.point.x; const tlY = (collisionBox.y1 * scale + shift.y - collisionBox.padding) * tileToViewport + projectedPoint.point.y; const brX = (collisionBox.x2 * scale + shift.x + collisionBox.padding) * tileToViewport + projectedPoint.point.x; const brY = (collisionBox.y2 * scale + shift.y + collisionBox.padding) * tileToViewport + projectedPoint.point.y; const minPerspectiveRatio = 0.55; const isClipped = projectedPoint.perspectiveRatio <= minPerspectiveRatio || projectedPoint.aboveHorizon; if (!this.isInsideGrid(tlX, tlY, brX, brY) || !allowOverlap && this.grid.hitTest(tlX, tlY, brX, brY, collisionGroupPredicate) || isClipped) { return { box: [], offscreen: false }; } return { box: [ tlX, tlY, brX, brY ], offscreen: this.isOffscreen(tlX, tlY, brX, brY) }; } placeCollisionCircles(allowOverlap, symbol, lineVertexArray, glyphOffsetArray, fontSize, posMatrix, labelPlaneMatrix, labelToScreenMatrix, showCollisionCircles, pitchWithMap, collisionGroupPredicate, circlePixelDiameter, textPixelPadding, tileID) { const placedCollisionCircles = []; const elevation = this.transform.elevation; const getElevation = elevation ? p => elevation.getAtTileOffset(tileID, p.x, p.y) : _ => 0; const tileUnitAnchorPoint = new ref_properties.Point(symbol.anchorX, symbol.anchorY); const anchorElevation = getElevation(tileUnitAnchorPoint); const screenAnchorPoint = this.projectAndGetPerspectiveRatio(posMatrix, tileUnitAnchorPoint.x, tileUnitAnchorPoint.y, anchorElevation); const {perspectiveRatio} = screenAnchorPoint; const labelPlaneFontSize = pitchWithMap ? fontSize / perspectiveRatio : fontSize * perspectiveRatio; const labelPlaneFontScale = labelPlaneFontSize / ref_properties.ONE_EM; const labelPlaneAnchorPoint = project(tileUnitAnchorPoint, labelPlaneMatrix, anchorElevation).point; const projectionCache = {}; const lineOffsetX = symbol.lineOffsetX * labelPlaneFontScale; const lineOffsetY = symbol.lineOffsetY * labelPlaneFontScale; const firstAndLastGlyph = screenAnchorPoint.signedDistanceFromCamera > 0 ? placeFirstAndLastGlyph(labelPlaneFontScale, glyphOffsetArray, lineOffsetX, lineOffsetY, false, labelPlaneAnchorPoint, tileUnitAnchorPoint, symbol, lineVertexArray, labelPlaneMatrix, projectionCache, elevation && !pitchWithMap ? getElevation : null, pitchWithMap && !!elevation) : null; let collisionDetected = false; let inGrid = false; let entirelyOffscreen = true; if (firstAndLastGlyph && !screenAnchorPoint.aboveHorizon) { const radius = circlePixelDiameter * 0.5 * perspectiveRatio + textPixelPadding; const screenPlaneMin = new ref_properties.Point(-viewportPadding, -viewportPadding); const screenPlaneMax = new ref_properties.Point(this.screenRightBoundary, this.screenBottomBoundary); const interpolator = new PathInterpolator(); const first = firstAndLastGlyph.first; const last = firstAndLastGlyph.last; let projectedPath = []; for (let i = first.path.length - 1; i >= 1; i--) { projectedPath.push(first.path[i]); } for (let i = 1; i < last.path.length; i++) { projectedPath.push(last.path[i]); } const circleDist = radius * 2.5; if (labelToScreenMatrix) { const screenSpacePath = elevation ? projectedPath.map((p, index) => { const z = getElevation(index < first.path.length - 1 ? first.tilePath[first.path.length - 1 - index] : last.tilePath[index - first.path.length + 2]); return project(p, labelToScreenMatrix, z); }) : projectedPath.map(p => project(p, labelToScreenMatrix)); if (screenSpacePath.some(point => point.signedDistanceFromCamera <= 0)) { projectedPath = []; } else { projectedPath = screenSpacePath.map(p => p.point); } } let segments = []; if (projectedPath.length > 0) { const minPoint = projectedPath[0].clone(); const maxPoint = projectedPath[0].clone(); for (let i = 1; i < projectedPath.length; i++) { minPoint.x = Math.min(minPoint.x, projectedPath[i].x); minPoint.y = Math.min(minPoint.y, projectedPath[i].y); maxPoint.x = Math.max(maxPoint.x, projectedPath[i].x); maxPoint.y = Math.max(maxPoint.y, projectedPath[i].y); } if (minPoint.x >= screenPlaneMin.x && maxPoint.x <= screenPlaneMax.x && minPoint.y >= screenPlaneMin.y && maxPoint.y <= screenPlaneMax.y) { segments = [projectedPath]; } else if (maxPoint.x < screenPlaneMin.x || minPoint.x > screenPlaneMax.x || maxPoint.y < screenPlaneMin.y || minPoint.y > screenPlaneMax.y) { segments = []; } else { segments = ref_properties.clipLine([projectedPath], screenPlaneMin.x, screenPlaneMin.y, screenPlaneMax.x, screenPlaneMax.y); } } for (const seg of segments) { interpolator.reset(seg, radius * 0.25); let numCircles = 0; if (interpolator.length <= 0.5 * radius) { numCircles = 1; } else { numCircles = Math.ceil(interpolator.paddedLength / circleDist) + 1; } for (let i = 0; i < numCircles; i++) { const t = i / Math.max(numCircles - 1, 1); const circlePosition = interpolator.lerp(t); const centerX = circlePosition.x + viewportPadding; const centerY = circlePosition.y + viewportPadding; placedCollisionCircles.push(centerX, centerY, radius, 0); const x1 = centerX - radius; const y1 = centerY - radius; const x2 = centerX + radius; const y2 = centerY + radius; entirelyOffscreen = entirelyOffscreen && this.isOffscreen(x1, y1, x2, y2); inGrid = inGrid || this.isInsideGrid(x1, y1, x2, y2); if (!allowOverlap) { if (this.grid.hitTestCircle(centerX, centerY, radius, collisionGroupPredicate)) { collisionDetected = true; if (!showCollisionCircles) { return { circles: [], offscreen: false, collisionDetected }; } } } } } } return { circles: !showCollisionCircles && collisionDetected || !inGrid ? [] : placedCollisionCircles, offscreen: entirelyOffscreen, collisionDetected }; } queryRenderedSymbols(viewportQueryGeometry) { if (viewportQueryGeometry.length === 0 || this.grid.keysLength() === 0 && this.ignoredGrid.keysLength() === 0) { return {}; } const query = []; let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; for (const point of viewportQueryGeometry) { const gridPoint = new ref_properties.Point(point.x + viewportPadding, point.y + viewportPadding); minX = Math.min(minX, gridPoint.x); minY = Math.min(minY, gridPoint.y); maxX = Math.max(maxX, gridPoint.x); maxY = Math.max(maxY, gridPoint.y); query.push(gridPoint); } const features = this.grid.query(minX, minY, maxX, maxY).concat(this.ignoredGrid.query(minX, minY, maxX, maxY)); const seenFeatures = {}; const result = {}; for (const feature of features) { const featureKey = feature.key; if (seenFeatures[featureKey.bucketInstanceId] === undefined) { seenFeatures[featureKey.bucketInstanceId] = {}; } if (seenFeatures[featureKey.bucketInstanceId][featureKey.featureIndex]) { continue; } const bbox = [ new ref_properties.Point(feature.x1, feature.y1), new ref_properties.Point(feature.x2, feature.y1), new ref_properties.Point(feature.x2, feature.y2), new ref_properties.Point(feature.x1, feature.y2) ]; if (!ref_properties.polygonIntersectsPolygon(query, bbox)) { continue; } seenFeatures[featureKey.bucketInstanceId][featureKey.featureIndex] = true; if (result[featureKey.bucketInstanceId] === undefined) { result[featureKey.bucketInstanceId] = []; } result[featureKey.bucketInstanceId].push(featureKey.featureIndex); } return result; } insertCollisionBox(collisionBox, ignorePlacement, bucketInstanceId, featureIndex, collisionGroupID) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; const key = { bucketInstanceId, featureIndex, collisionGroupID }; grid.insert(key, collisionBox[0], collisionBox[1], collisionBox[2], collisionBox[3]); } insertCollisionCircles(collisionCircles, ignorePlacement, bucketInstanceId, featureIndex, collisionGroupID) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; const key = { bucketInstanceId, featureIndex, collisionGroupID }; for (let k = 0; k < collisionCircles.length; k += 4) { grid.insertCircle(key, collisionCircles[k], collisionCircles[k + 1], collisionCircles[k + 2]); } } projectAndGetPerspectiveRatio(posMatrix, x, y, elevation) { const p = [ x, y, elevation || 0, 1 ]; let aboveHorizon = false; if (elevation || this.transform.pitch > 0) { ref_properties.transformMat4(p, p, posMatrix); aboveHorizon = p[2] > p[3]; } else { xyTransformMat4(p, p, posMatrix); } const a = new ref_properties.Point((p[0] / p[3] + 1) / 2 * this.transform.width + viewportPadding, (-p[1] / p[3] + 1) / 2 * this.transform.height + viewportPadding); return { point: a, perspectiveRatio: Math.min(0.5 + 0.5 * (this.transform.cameraToCenterDistance / p[3]), 1.5), signedDistanceFromCamera: p[3], aboveHorizon }; } isOffscreen(x1, y1, x2, y2) { return x2 < viewportPadding || x1 >= this.screenRightBoundary || y2 < viewportPadding || y1 > this.screenBottomBoundary; } isInsideGrid(x1, y1, x2, y2) { return x2 >= 0 && x1 < this.gridRightBoundary && y2 >= 0 && y1 < this.gridBottomBoundary; } getViewportMatrix() { const m = ref_properties.identity([]); ref_properties.translate(m, m, [ -viewportPadding, -viewportPadding, 0 ]); return m; } } class OpacityState { constructor(prevState, increment, placed, skipFade) { if (prevState) { this.opacity = Math.max(0, Math.min(1, prevState.opacity + (prevState.placed ? increment : -increment))); } else { this.opacity = skipFade && placed ? 1 : 0; } this.placed = placed; } isHidden() { return this.opacity === 0 && !this.placed; } } class JointOpacityState { constructor(prevState, increment, placedText, placedIcon, skipFade) { this.text = new OpacityState(prevState ? prevState.text : null, increment, placedText, skipFade); this.icon = new OpacityState(prevState ? prevState.icon : null, increment, placedIcon, skipFade); } isHidden() { return this.text.isHidden() && this.icon.isHidden(); } } class JointPlacement { constructor(text, icon, skipFade) { this.text = text; this.icon = icon; this.skipFade = skipFade; } } class CollisionCircleArray { constructor() { this.invProjMatrix = ref_properties.create(); this.viewportMatrix = ref_properties.create(); this.circles = []; } } class RetainedQueryData { constructor(bucketInstanceId, featureIndex, sourceLayerIndex, bucketIndex, tileID) { this.bucketInstanceId = bucketInstanceId; this.featureIndex = featureIndex; this.sourceLayerIndex = sourceLayerIndex; this.bucketIndex = bucketIndex; this.tileID = tileID; } } class CollisionGroups { constructor(crossSourceCollisions) { this.crossSourceCollisions = crossSourceCollisions; this.maxGroupID = 0; this.collisionGroups = {}; } get(sourceID) { if (!this.crossSourceCollisions) { if (!this.collisionGroups[sourceID]) { const nextGroupID = ++this.maxGroupID; this.collisionGroups[sourceID] = { ID: nextGroupID, predicate: key => { return key.collisionGroupID === nextGroupID; } }; } return this.collisionGroups[sourceID]; } else { return { ID: 0, predicate: null }; } } } function calculateVariableLayoutShift(anchor, width, height, textOffset, textBoxScale) { const {horizontalAlign, verticalAlign} = ref_properties.getAnchorAlignment(anchor); const shiftX = -(horizontalAlign - 0.5) * width; const shiftY = -(verticalAlign - 0.5) * height; const offset = ref_properties.evaluateVariableOffset(anchor, textOffset); return new ref_properties.Point(shiftX + offset[0] * textBoxScale, shiftY + offset[1] * textBoxScale); } function offsetShift(shiftX, shiftY, rotateWithMap, pitchWithMap, angle) { const shift = new ref_properties.Point(shiftX, shiftY); if (rotateWithMap) { shift._rotate(pitchWithMap ? angle : -angle); } return shift; } class Placement { constructor(transform, fadeDuration, crossSourceCollisions, prevPlacement) { this.transform = transform.clone(); this.collisionIndex = new CollisionIndex(this.transform); this.placements = {}; this.opacities = {}; this.variableOffsets = {}; this.stale = false; this.commitTime = 0; this.fadeDuration = fadeDuration; this.retainedQueryData = {}; this.collisionGroups = new CollisionGroups(crossSourceCollisions); this.collisionCircleArrays = {}; this.prevPlacement = prevPlacement; if (prevPlacement) { prevPlacement.prevPlacement = undefined; } this.placedOrientations = {}; } getBucketParts(results, styleLayer, tile, sortAcrossTiles) { const symbolBucket = tile.getBucket(styleLayer); const bucketFeatureIndex = tile.latestFeatureIndex; if (!symbolBucket || !bucketFeatureIndex || styleLayer.id !== symbolBucket.layerIds[0]) return; const collisionBoxArray = tile.collisionBoxArray; const layout = symbolBucket.layers[0].layout; const scale = Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ); const textPixelRatio = tile.tileSize / ref_properties.EXTENT; const posMatrix = this.transform.calculatePosMatrix(tile.tileID.toUnwrapped()); const pitchWithMap = layout.get('text-pitch-alignment') === 'map'; const rotateWithMap = layout.get('text-rotation-alignment') === 'map'; const pixelsToTiles = pixelsToTileUnits(tile, 1, this.transform.zoom); const textLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, pitchWithMap, rotateWithMap, this.transform, pixelsToTiles); let labelToScreenMatrix = null; if (pitchWithMap) { const glMatrix = getGlCoordMatrix(posMatrix, pitchWithMap, rotateWithMap, this.transform, pixelsToTiles); labelToScreenMatrix = ref_properties.multiply([], this.transform.labelPlaneMatrix, glMatrix); } this.retainedQueryData[symbolBucket.bucketInstanceId] = new RetainedQueryData(symbolBucket.bucketInstanceId, bucketFeatureIndex, symbolBucket.sourceLayerIndex, symbolBucket.index, tile.tileID); const parameters = { bucket: symbolBucket, layout, posMatrix, textLabelPlaneMatrix, labelToScreenMatrix, scale, textPixelRatio, holdingForFade: tile.holdingForFade(), collisionBoxArray, partiallyEvaluatedTextSize: ref_properties.evaluateSizeForZoom(symbolBucket.textSizeData, this.transform.zoom), partiallyEvaluatedIconSize: ref_properties.evaluateSizeForZoom(symbolBucket.iconSizeData, this.transform.zoom), collisionGroup: this.collisionGroups.get(symbolBucket.sourceID) }; if (sortAcrossTiles) { for (const range of symbolBucket.sortKeyRanges) { const {sortKey, symbolInstanceStart, symbolInstanceEnd} = range; results.push({ sortKey, symbolInstanceStart, symbolInstanceEnd, parameters }); } } else { results.push({ symbolInstanceStart: 0, symbolInstanceEnd: symbolBucket.symbolInstances.length, parameters }); } } attemptAnchorPlacement(anchor, textBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, posMatrix, collisionGroup, textAllowOverlap, symbolInstance, symbolIndex, bucket, orientation, iconBox, textSize, iconSize) { const textOffset = [ symbolInstance.textOffset0, symbolInstance.textOffset1 ]; const textScale = bucket.getSymbolInstanceTextSize(textSize, symbolInstance, this.transform.zoom, symbolIndex); const shift = calculateVariableLayoutShift(anchor, width, height, textOffset, textBoxScale); const placedGlyphBoxes = this.collisionIndex.placeCollisionBox(textScale, textBox, offsetShift(shift.x, shift.y, rotateWithMap, pitchWithMap, this.transform.angle), textAllowOverlap, textPixelRatio, posMatrix, collisionGroup.predicate); if (iconBox) { const placedIconBoxes = this.collisionIndex.placeCollisionBox(bucket.getSymbolInstanceIconSize(iconSize, this.transform.zoom, symbolIndex), iconBox, offsetShift(shift.x, shift.y, rotateWithMap, pitchWithMap, this.transform.angle), textAllowOverlap, textPixelRatio, posMatrix, collisionGroup.predicate); if (placedIconBoxes.box.length === 0) return; } if (placedGlyphBoxes.box.length > 0) { let prevAnchor; if (this.prevPlacement && this.prevPlacement.variableOffsets[symbolInstance.crossTileID] && this.prevPlacement.placements[symbolInstance.crossTileID] && this.prevPlacement.placements[symbolInstance.crossTileID].text) { prevAnchor = this.prevPlacement.variableOffsets[symbolInstance.crossTileID].anchor; } this.variableOffsets[symbolInstance.crossTileID] = { textOffset, width, height, anchor, textBoxScale, prevAnchor }; this.markUsedJustification(bucket, anchor, symbolInstance, orientation); if (bucket.allowVerticalPlacement) { this.markUsedOrientation(bucket, orientation, symbolInstance); this.placedOrientations[symbolInstance.crossTileID] = orientation; } return { shift, placedGlyphBoxes }; } } placeLayerBucketPart(bucketPart, seenCrossTileIDs, showCollisionBoxes) { const {bucket, layout, posMatrix, textLabelPlaneMatrix, labelToScreenMatrix, textPixelRatio, holdingForFade, collisionBoxArray, partiallyEvaluatedTextSize, partiallyEvaluatedIconSize, collisionGroup} = bucketPart.parameters; const textOptional = layout.get('text-optional'); const iconOptional = layout.get('icon-optional'); const textAllowOverlap = layout.get('text-allow-overlap'); const iconAllowOverlap = layout.get('icon-allow-overlap'); const rotateWithMap = layout.get('text-rotation-alignment') === 'map'; const pitchWithMap = layout.get('text-pitch-alignment') === 'map'; const hasIconTextFit = layout.get('icon-text-fit') !== 'none'; const zOrderByViewportY = layout.get('symbol-z-order') === 'viewport-y'; const alwaysShowText = textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || iconOptional); const alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || textOptional); if (!bucket.collisionArrays && collisionBoxArray) { bucket.deserializeCollisionBoxes(collisionBoxArray); } if (showCollisionBoxes) { bucket.updateCollisionDebugBuffers(this.transform.zoom, collisionBoxArray); } const placeSymbol = (symbolInstance, symbolIndex, collisionArrays) => { if (seenCrossTileIDs[symbolInstance.crossTileID]) return; if (holdingForFade) { this.placements[symbolInstance.crossTileID] = new JointPlacement(false, false, false); return; } let placeText = false; let placeIcon = false; let offscreen = true; let shift = null; let placed = { box: null, offscreen: null }; let placedVerticalText = { box: null, offscreen: null }; let placedGlyphBoxes = null; let placedGlyphCircles = null; let placedIconBoxes = null; let textFeatureIndex = 0; let verticalTextFeatureIndex = 0; let iconFeatureIndex = 0; if (collisionArrays.textFeatureIndex) { textFeatureIndex = collisionArrays.textFeatureIndex; } else if (symbolInstance.useRuntimeCollisionCircles) { textFeatureIndex = symbolInstance.featureIndex; } if (collisionArrays.verticalTextFeatureIndex) { verticalTextFeatureIndex = collisionArrays.verticalTextFeatureIndex; } const updateElevation = box => { if (!this.transform.elevation && !box.elevation) return; box.elevation = this.transform.elevation ? this.transform.elevation.getAtTileOffset(this.retainedQueryData[bucket.bucketInstanceId].tileID, box.anchorPointX, box.anchorPointY) : 0; }; const textBox = collisionArrays.textBox; if (textBox) { updateElevation(textBox); const updatePreviousOrientationIfNotPlaced = isPlaced => { let previousOrientation = ref_properties.WritingMode.horizontal; if (bucket.allowVerticalPlacement && !isPlaced && this.prevPlacement) { const prevPlacedOrientation = this.prevPlacement.placedOrientations[symbolInstance.crossTileID]; if (prevPlacedOrientation) { this.placedOrientations[symbolInstance.crossTileID] = prevPlacedOrientation; previousOrientation = prevPlacedOrientation; this.markUsedOrientation(bucket, previousOrientation, symbolInstance); } } return previousOrientation; }; const placeTextForPlacementModes = (placeHorizontalFn, placeVerticalFn) => { if (bucket.allowVerticalPlacement && symbolInstance.numVerticalGlyphVertices > 0 && collisionArrays.verticalTextBox) { for (const placementMode of bucket.writingModes) { if (placementMode === ref_properties.WritingMode.vertical) { placed = placeVerticalFn(); placedVerticalText = placed; } else { placed = placeHorizontalFn(); } if (placed && placed.box && placed.box.length) break; } } else { placed = placeHorizontalFn(); } }; if (!layout.get('text-variable-anchor')) { const placeBox = (collisionTextBox, orientation) => { const textScale = bucket.getSymbolInstanceTextSize(partiallyEvaluatedTextSize, symbolInstance, this.transform.zoom, symbolIndex); const placedFeature = this.collisionIndex.placeCollisionBox(textScale, collisionTextBox, new ref_properties.Point(0, 0), textAllowOverlap, textPixelRatio, posMatrix, collisionGroup.predicate); if (placedFeature && placedFeature.box && placedFeature.box.length) { this.markUsedOrientation(bucket, orientation, symbolInstance); this.placedOrientations[symbolInstance.crossTileID] = orientation; } return placedFeature; }; const placeHorizontal = () => { return placeBox(textBox, ref_properties.WritingMode.horizontal); }; const placeVertical = () => { const verticalTextBox = collisionArrays.verticalTextBox; if (bucket.allowVerticalPlacement && symbolInstance.numVerticalGlyphVertices > 0 && verticalTextBox) { updateElevation(verticalTextBox); return placeBox(verticalTextBox, ref_properties.WritingMode.vertical); } return { box: null, offscreen: null }; }; placeTextForPlacementModes(placeHorizontal, placeVertical); updatePreviousOrientationIfNotPlaced(placed && placed.box && placed.box.length); } else { let anchors = layout.get('text-variable-anchor'); if (this.prevPlacement && this.prevPlacement.variableOffsets[symbolInstance.crossTileID]) { const prevOffsets = this.prevPlacement.variableOffsets[symbolInstance.crossTileID]; if (anchors.indexOf(prevOffsets.anchor) > 0) { anchors = anchors.filter(anchor => anchor !== prevOffsets.anchor); anchors.unshift(prevOffsets.anchor); } } const placeBoxForVariableAnchors = (collisionTextBox, collisionIconBox, orientation) => { const textBoxScale = symbolInstance.textBoxScale; const width = (collisionTextBox.x2 - collisionTextBox.x1) * textBoxScale + 2 * collisionTextBox.padding; const height = (collisionTextBox.y2 - collisionTextBox.y1) * textBoxScale + 2 * collisionTextBox.padding; const variableIconBox = hasIconTextFit && !iconAllowOverlap ? collisionIconBox : null; if (variableIconBox) updateElevation(variableIconBox); let placedBox = { box: [], offscreen: false }; const placementAttempts = textAllowOverlap ? anchors.length * 2 : anchors.length; for (let i = 0; i < placementAttempts; ++i) { const anchor = anchors[i % anchors.length]; const allowOverlap = i >= anchors.length; const result = this.attemptAnchorPlacement(anchor, collisionTextBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, posMatrix, collisionGroup, allowOverlap, symbolInstance, symbolIndex, bucket, orientation, variableIconBox, partiallyEvaluatedTextSize, partiallyEvaluatedIconSize); if (result) { placedBox = result.placedGlyphBoxes; if (placedBox && placedBox.box && placedBox.box.length) { placeText = true; shift = result.shift; break; } } } return placedBox; }; const placeHorizontal = () => { return placeBoxForVariableAnchors(textBox, collisionArrays.iconBox, ref_properties.WritingMode.horizontal); }; const placeVertical = () => { const verticalTextBox = collisionArrays.verticalTextBox; if (verticalTextBox) updateElevation(verticalTextBox); const wasPlaced = placed && placed.box && placed.box.length; if (bucket.allowVerticalPlacement && !wasPlaced && symbolInstance.numVerticalGlyphVertices > 0 && verticalTextBox) { return placeBoxForVariableAnchors(verticalTextBox, collisionArrays.verticalIconBox, ref_properties.WritingMode.vertical); } return { box: null, offscreen: null }; }; placeTextForPlacementModes(placeHorizontal, placeVertical); if (placed) { placeText = placed.box; offscreen = placed.offscreen; } const prevOrientation = updatePreviousOrientationIfNotPlaced(placed && placed.box); if (!placeText && this.prevPlacement) { const prevOffset = this.prevPlacement.variableOffsets[symbolInstance.crossTileID]; if (prevOffset) { this.variableOffsets[symbolInstance.crossTileID] = prevOffset; this.markUsedJustification(bucket, prevOffset.anchor, symbolInstance, prevOrientation); } } } } placedGlyphBoxes = placed; placeText = placedGlyphBoxes && placedGlyphBoxes.box && placedGlyphBoxes.box.length > 0; offscreen = placedGlyphBoxes && placedGlyphBoxes.offscreen; if (symbolInstance.useRuntimeCollisionCircles) { const placedSymbol = bucket.text.placedSymbolArray.get(symbolInstance.centerJustifiedTextSymbolIndex); const fontSize = ref_properties.evaluateSizeForFeature(bucket.textSizeData, partiallyEvaluatedTextSize, placedSymbol); const textPixelPadding = layout.get('text-padding'); const circlePixelDiameter = symbolInstance.collisionCircleDiameter; placedGlyphCircles = this.collisionIndex.placeCollisionCircles(textAllowOverlap, placedSymbol, bucket.lineVertexArray, bucket.glyphOffsetArray, fontSize, posMatrix, textLabelPlaneMatrix, labelToScreenMatrix, showCollisionBoxes, pitchWithMap, collisionGroup.predicate, circlePixelDiameter, textPixelPadding, this.retainedQueryData[bucket.bucketInstanceId].tileID); placeText = textAllowOverlap || placedGlyphCircles.circles.length > 0 && !placedGlyphCircles.collisionDetected; offscreen = offscreen && placedGlyphCircles.offscreen; } if (collisionArrays.iconFeatureIndex) { iconFeatureIndex = collisionArrays.iconFeatureIndex; } if (collisionArrays.iconBox) { const placeIconFeature = iconBox => { updateElevation(iconBox); const shiftPoint = hasIconTextFit && shift ? offsetShift(shift.x, shift.y, rotateWithMap, pitchWithMap, this.transform.angle) : new ref_properties.Point(0, 0); const iconScale = bucket.getSymbolInstanceIconSize(partiallyEvaluatedIconSize, this.transform.zoom, symbolIndex); return this.collisionIndex.placeCollisionBox(iconScale, iconBox, shiftPoint, iconAllowOverlap, textPixelRatio, posMatrix, collisionGroup.predicate); }; if (placedVerticalText && placedVerticalText.box && placedVerticalText.box.length && collisionArrays.verticalIconBox) { placedIconBoxes = placeIconFeature(collisionArrays.verticalIconBox); placeIcon = placedIconBoxes.box.length > 0; } else { placedIconBoxes = placeIconFeature(collisionArrays.iconBox); placeIcon = placedIconBoxes.box.length > 0; } offscreen = offscreen && placedIconBoxes.offscreen; } const iconWithoutText = textOptional || symbolInstance.numHorizontalGlyphVertices === 0 && symbolInstance.numVerticalGlyphVertices === 0; const textWithoutIcon = iconOptional || symbolInstance.numIconVertices === 0; if (!iconWithoutText && !textWithoutIcon) { placeIcon = placeText = placeIcon && placeText; } else if (!textWithoutIcon) { placeText = placeIcon && placeText; } else if (!iconWithoutText) { placeIcon = placeIcon && placeText; } if (placeText && placedGlyphBoxes && placedGlyphBoxes.box) { if (placedVerticalText && placedVerticalText.box && verticalTextFeatureIndex) { this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, layout.get('text-ignore-placement'), bucket.bucketInstanceId, verticalTextFeatureIndex, collisionGroup.ID); } else { this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, layout.get('text-ignore-placement'), bucket.bucketInstanceId, textFeatureIndex, collisionGroup.ID); } } if (placeIcon && placedIconBoxes) { this.collisionIndex.insertCollisionBox(placedIconBoxes.box, layout.get('icon-ignore-placement'), bucket.bucketInstanceId, iconFeatureIndex, collisionGroup.ID); } if (placedGlyphCircles) { if (placeText) { this.collisionIndex.insertCollisionCircles(placedGlyphCircles.circles, layout.get('text-ignore-placement'), bucket.bucketInstanceId, textFeatureIndex, collisionGroup.ID); } if (showCollisionBoxes) { const id = bucket.bucketInstanceId; let circleArray = this.collisionCircleArrays[id]; if (circleArray === undefined) circleArray = this.collisionCircleArrays[id] = new CollisionCircleArray(); for (let i = 0; i < placedGlyphCircles.circles.length; i += 4) { circleArray.circles.push(placedGlyphCircles.circles[i + 0]); circleArray.circles.push(placedGlyphCircles.circles[i + 1]); circleArray.circles.push(placedGlyphCircles.circles[i + 2]); circleArray.circles.push(placedGlyphCircles.collisionDetected ? 1 : 0); } } } this.placements[symbolInstance.crossTileID] = new JointPlacement(placeText || alwaysShowText, placeIcon || alwaysShowIcon, offscreen || bucket.justReloaded); seenCrossTileIDs[symbolInstance.crossTileID] = true; }; if (zOrderByViewportY) { const symbolIndexes = bucket.getSortedSymbolIndexes(this.transform.angle); for (let i = symbolIndexes.length - 1; i >= 0; --i) { const symbolIndex = symbolIndexes[i]; placeSymbol(bucket.symbolInstances.get(symbolIndex), symbolIndex, bucket.collisionArrays[symbolIndex]); } } else { for (let i = bucketPart.symbolInstanceStart; i < bucketPart.symbolInstanceEnd; i++) { placeSymbol(bucket.symbolInstances.get(i), i, bucket.collisionArrays[i]); } } if (showCollisionBoxes && bucket.bucketInstanceId in this.collisionCircleArrays) { const circleArray = this.collisionCircleArrays[bucket.bucketInstanceId]; ref_properties.invert(circleArray.invProjMatrix, posMatrix); circleArray.viewportMatrix = this.collisionIndex.getViewportMatrix(); } bucket.justReloaded = false; } markUsedJustification(bucket, placedAnchor, symbolInstance, orientation) { const justifications = { 'left': symbolInstance.leftJustifiedTextSymbolIndex, 'center': symbolInstance.centerJustifiedTextSymbolIndex, 'right': symbolInstance.rightJustifiedTextSymbolIndex }; let autoIndex; if (orientation === ref_properties.WritingMode.vertical) { autoIndex = symbolInstance.verticalPlacedTextSymbolIndex; } else { autoIndex = justifications[ref_properties.getAnchorJustification(placedAnchor)]; } const indexes = [ symbolInstance.leftJustifiedTextSymbolIndex, symbolInstance.centerJustifiedTextSymbolIndex, symbolInstance.rightJustifiedTextSymbolIndex, symbolInstance.verticalPlacedTextSymbolIndex ]; for (const index of indexes) { if (index >= 0) { if (autoIndex >= 0 && index !== autoIndex) { bucket.text.placedSymbolArray.get(index).crossTileID = 0; } else { bucket.text.placedSymbolArray.get(index).crossTileID = symbolInstance.crossTileID; } } } } markUsedOrientation(bucket, orientation, symbolInstance) { const horizontal = orientation === ref_properties.WritingMode.horizontal || orientation === ref_properties.WritingMode.horizontalOnly ? orientation : 0; const vertical = orientation === ref_properties.WritingMode.vertical ? orientation : 0; const horizontalIndexes = [ symbolInstance.leftJustifiedTextSymbolIndex, symbolInstance.centerJustifiedTextSymbolIndex, symbolInstance.rightJustifiedTextSymbolIndex ]; for (const index of horizontalIndexes) { bucket.text.placedSymbolArray.get(index).placedOrientation = horizontal; } if (symbolInstance.verticalPlacedTextSymbolIndex) { bucket.text.placedSymbolArray.get(symbolInstance.verticalPlacedTextSymbolIndex).placedOrientation = vertical; } } commit(now) { this.commitTime = now; this.zoomAtLastRecencyCheck = this.transform.zoom; const prevPlacement = this.prevPlacement; let placementChanged = false; this.prevZoomAdjustment = prevPlacement ? prevPlacement.zoomAdjustment(this.transform.zoom) : 0; const increment = prevPlacement ? prevPlacement.symbolFadeChange(now) : 1; const prevOpacities = prevPlacement ? prevPlacement.opacities : {}; const prevOffsets = prevPlacement ? prevPlacement.variableOffsets : {}; const prevOrientations = prevPlacement ? prevPlacement.placedOrientations : {}; for (const crossTileID in this.placements) { const jointPlacement = this.placements[crossTileID]; const prevOpacity = prevOpacities[crossTileID]; if (prevOpacity) { this.opacities[crossTileID] = new JointOpacityState(prevOpacity, increment, jointPlacement.text, jointPlacement.icon); placementChanged = placementChanged || jointPlacement.text !== prevOpacity.text.placed || jointPlacement.icon !== prevOpacity.icon.placed; } else { this.opacities[crossTileID] = new JointOpacityState(null, increment, jointPlacement.text, jointPlacement.icon, jointPlacement.skipFade); placementChanged = placementChanged || jointPlacement.text || jointPlacement.icon; } } for (const crossTileID in prevOpacities) { const prevOpacity = prevOpacities[crossTileID]; if (!this.opacities[crossTileID]) { const jointOpacity = new JointOpacityState(prevOpacity, increment, false, false); if (!jointOpacity.isHidden()) { this.opacities[crossTileID] = jointOpacity; placementChanged = placementChanged || prevOpacity.text.placed || prevOpacity.icon.placed; } } } for (const crossTileID in prevOffsets) { if (!this.variableOffsets[crossTileID] && this.opacities[crossTileID] && !this.opacities[crossTileID].isHidden()) { this.variableOffsets[crossTileID] = prevOffsets[crossTileID]; } } for (const crossTileID in prevOrientations) { if (!this.placedOrientations[crossTileID] && this.opacities[crossTileID] && !this.opacities[crossTileID].isHidden()) { this.placedOrientations[crossTileID] = prevOrientations[crossTileID]; } } if (placementChanged) { this.lastPlacementChangeTime = now; } else if (typeof this.lastPlacementChangeTime !== 'number') { this.lastPlacementChangeTime = prevPlacement ? prevPlacement.lastPlacementChangeTime : now; } } updateLayerOpacities(styleLayer, tiles) { const seenCrossTileIDs = {}; for (const tile of tiles) { const symbolBucket = tile.getBucket(styleLayer); if (symbolBucket && tile.latestFeatureIndex && styleLayer.id === symbolBucket.layerIds[0]) { this.updateBucketOpacities(symbolBucket, seenCrossTileIDs, tile.collisionBoxArray); } } } updateBucketOpacities(bucket, seenCrossTileIDs, collisionBoxArray) { if (bucket.hasTextData()) bucket.text.opacityVertexArray.clear(); if (bucket.hasIconData()) bucket.icon.opacityVertexArray.clear(); if (bucket.hasIconCollisionBoxData()) bucket.iconCollisionBox.collisionVertexArray.clear(); if (bucket.hasTextCollisionBoxData()) bucket.textCollisionBox.collisionVertexArray.clear(); const layout = bucket.layers[0].layout; const duplicateOpacityState = new JointOpacityState(null, 0, false, false, true); const textAllowOverlap = layout.get('text-allow-overlap'); const iconAllowOverlap = layout.get('icon-allow-overlap'); const variablePlacement = layout.get('text-variable-anchor'); const rotateWithMap = layout.get('text-rotation-alignment') === 'map'; const pitchWithMap = layout.get('text-pitch-alignment') === 'map'; const hasIconTextFit = layout.get('icon-text-fit') !== 'none'; const defaultOpacityState = new JointOpacityState(null, 0, textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || layout.get('icon-optional')), iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || layout.get('text-optional')), true); if (!bucket.collisionArrays && collisionBoxArray && (bucket.hasIconCollisionBoxData() || bucket.hasTextCollisionBoxData())) { bucket.deserializeCollisionBoxes(collisionBoxArray); } const addOpacities = (iconOrText, numVertices, opacity) => { for (let i = 0; i < numVertices / 4; i++) { iconOrText.opacityVertexArray.emplaceBack(opacity); } }; for (let s = 0; s < bucket.symbolInstances.length; s++) { const symbolInstance = bucket.symbolInstances.get(s); const {numHorizontalGlyphVertices, numVerticalGlyphVertices, crossTileID} = symbolInstance; const isDuplicate = seenCrossTileIDs[crossTileID]; let opacityState = this.opacities[crossTileID]; if (isDuplicate) { opacityState = duplicateOpacityState; } else if (!opacityState) { opacityState = defaultOpacityState; this.opacities[crossTileID] = opacityState; } seenCrossTileIDs[crossTileID] = true; const hasText = numHorizontalGlyphVertices > 0 || numVerticalGlyphVertices > 0; const hasIcon = symbolInstance.numIconVertices > 0; const placedOrientation = this.placedOrientations[symbolInstance.crossTileID]; const horizontalHidden = placedOrientation === ref_properties.WritingMode.vertical; const verticalHidden = placedOrientation === ref_properties.WritingMode.horizontal || placedOrientation === ref_properties.WritingMode.horizontalOnly; if (hasText) { const packedOpacity = packOpacity(opacityState.text); const horizontalOpacity = horizontalHidden ? PACKED_HIDDEN_OPACITY : packedOpacity; addOpacities(bucket.text, numHorizontalGlyphVertices, horizontalOpacity); const verticalOpacity = verticalHidden ? PACKED_HIDDEN_OPACITY : packedOpacity; addOpacities(bucket.text, numVerticalGlyphVertices, verticalOpacity); const symbolHidden = opacityState.text.isHidden(); [ symbolInstance.rightJustifiedTextSymbolIndex, symbolInstance.centerJustifiedTextSymbolIndex, symbolInstance.leftJustifiedTextSymbolIndex ].forEach(index => { if (index >= 0) { bucket.text.placedSymbolArray.get(index).hidden = symbolHidden || horizontalHidden ? 1 : 0; } }); if (symbolInstance.verticalPlacedTextSymbolIndex >= 0) { bucket.text.placedSymbolArray.get(symbolInstance.verticalPlacedTextSymbolIndex).hidden = symbolHidden || verticalHidden ? 1 : 0; } const prevOffset = this.variableOffsets[symbolInstance.crossTileID]; if (prevOffset) { this.markUsedJustification(bucket, prevOffset.anchor, symbolInstance, placedOrientation); } const prevOrientation = this.placedOrientations[symbolInstance.crossTileID]; if (prevOrientation) { this.markUsedJustification(bucket, 'left', symbolInstance, prevOrientation); this.markUsedOrientation(bucket, prevOrientation, symbolInstance); } } if (hasIcon) { const packedOpacity = packOpacity(opacityState.icon); const useHorizontal = !(hasIconTextFit && symbolInstance.verticalPlacedIconSymbolIndex && horizontalHidden); if (symbolInstance.placedIconSymbolIndex >= 0) { const horizontalOpacity = useHorizontal ? packedOpacity : PACKED_HIDDEN_OPACITY; addOpacities(bucket.icon, symbolInstance.numIconVertices, horizontalOpacity); bucket.icon.placedSymbolArray.get(symbolInstance.placedIconSymbolIndex).hidden = opacityState.icon.isHidden(); } if (symbolInstance.verticalPlacedIconSymbolIndex >= 0) { const verticalOpacity = !useHorizontal ? packedOpacity : PACKED_HIDDEN_OPACITY; addOpacities(bucket.icon, symbolInstance.numVerticalIconVertices, verticalOpacity); bucket.icon.placedSymbolArray.get(symbolInstance.verticalPlacedIconSymbolIndex).hidden = opacityState.icon.isHidden(); } } if (bucket.hasIconCollisionBoxData() || bucket.hasTextCollisionBoxData()) { const collisionArrays = bucket.collisionArrays[s]; if (collisionArrays) { let shift = new ref_properties.Point(0, 0); if (collisionArrays.textBox || collisionArrays.verticalTextBox) { let used = true; if (variablePlacement) { const variableOffset = this.variableOffsets[crossTileID]; if (variableOffset) { shift = calculateVariableLayoutShift(variableOffset.anchor, variableOffset.width, variableOffset.height, variableOffset.textOffset, variableOffset.textBoxScale); if (rotateWithMap) { shift._rotate(pitchWithMap ? this.transform.angle : -this.transform.angle); } } else { used = false; } } if (collisionArrays.textBox) { updateCollisionVertices(bucket.textCollisionBox.collisionVertexArray, opacityState.text.placed, !used || horizontalHidden, shift.x, shift.y); } if (collisionArrays.verticalTextBox) { updateCollisionVertices(bucket.textCollisionBox.collisionVertexArray, opacityState.text.placed, !used || verticalHidden, shift.x, shift.y); } } const verticalIconUsed = Boolean(!verticalHidden && collisionArrays.verticalIconBox); if (collisionArrays.iconBox) { updateCollisionVertices(bucket.iconCollisionBox.collisionVertexArray, opacityState.icon.placed, verticalIconUsed, hasIconTextFit ? shift.x : 0, hasIconTextFit ? shift.y : 0); } if (collisionArrays.verticalIconBox) { updateCollisionVertices(bucket.iconCollisionBox.collisionVertexArray, opacityState.icon.placed, !verticalIconUsed, hasIconTextFit ? shift.x : 0, hasIconTextFit ? shift.y : 0); } } } } bucket.sortFeatures(this.transform.angle); if (this.retainedQueryData[bucket.bucketInstanceId]) { this.retainedQueryData[bucket.bucketInstanceId].featureSortOrder = bucket.featureSortOrder; } if (bucket.hasTextData() && bucket.text.opacityVertexBuffer) { bucket.text.opacityVertexBuffer.updateData(bucket.text.opacityVertexArray); } if (bucket.hasIconData() && bucket.icon.opacityVertexBuffer) { bucket.icon.opacityVertexBuffer.updateData(bucket.icon.opacityVertexArray); } if (bucket.hasIconCollisionBoxData() && bucket.iconCollisionBox.collisionVertexBuffer) { bucket.iconCollisionBox.collisionVertexBuffer.updateData(bucket.iconCollisionBox.collisionVertexArray); } if (bucket.hasTextCollisionBoxData() && bucket.textCollisionBox.collisionVertexBuffer) { bucket.textCollisionBox.collisionVertexBuffer.updateData(bucket.textCollisionBox.collisionVertexArray); } if (bucket.bucketInstanceId in this.collisionCircleArrays) { const instance = this.collisionCircleArrays[bucket.bucketInstanceId]; bucket.placementInvProjMatrix = instance.invProjMatrix; bucket.placementViewportMatrix = instance.viewportMatrix; bucket.collisionCircleArray = instance.circles; delete this.collisionCircleArrays[bucket.bucketInstanceId]; } } symbolFadeChange(now) { return this.fadeDuration === 0 ? 1 : (now - this.commitTime) / this.fadeDuration + this.prevZoomAdjustment; } zoomAdjustment(zoom) { return Math.max(0, (this.transform.zoom - zoom) / 1.5); } hasTransitions(now) { return this.stale || now - this.lastPlacementChangeTime < this.fadeDuration; } stillRecent(now, zoom) { const durationAdjustment = this.zoomAtLastRecencyCheck === zoom ? 1 - this.zoomAdjustment(zoom) : 1; this.zoomAtLastRecencyCheck = zoom; return this.commitTime + this.fadeDuration * durationAdjustment > now; } setStale() { this.stale = true; } } function updateCollisionVertices(collisionVertexArray, placed, notUsed, shiftX, shiftY) { collisionVertexArray.emplaceBack(placed ? 1 : 0, notUsed ? 1 : 0, shiftX || 0, shiftY || 0); collisionVertexArray.emplaceBack(placed ? 1 : 0, notUsed ? 1 : 0, shiftX || 0, shiftY || 0); collisionVertexArray.emplaceBack(placed ? 1 : 0, notUsed ? 1 : 0, shiftX || 0, shiftY || 0); collisionVertexArray.emplaceBack(placed ? 1 : 0, notUsed ? 1 : 0, shiftX || 0, shiftY || 0); } const shift25 = Math.pow(2, 25); const shift24 = Math.pow(2, 24); const shift17 = Math.pow(2, 17); const shift16 = Math.pow(2, 16); const shift9 = Math.pow(2, 9); const shift8 = Math.pow(2, 8); const shift1 = Math.pow(2, 1); function packOpacity(opacityState) { if (opacityState.opacity === 0 && !opacityState.placed) { return 0; } else if (opacityState.opacity === 1 && opacityState.placed) { return 4294967295; } const targetBit = opacityState.placed ? 1 : 0; const opacityBits = Math.floor(opacityState.opacity * 127); return opacityBits * shift25 + targetBit * shift24 + opacityBits * shift17 + targetBit * shift16 + opacityBits * shift9 + targetBit * shift8 + opacityBits * shift1 + targetBit; } const PACKED_HIDDEN_OPACITY = 0; class LayerPlacement { constructor(styleLayer) { this._sortAcrossTiles = styleLayer.layout.get('symbol-z-order') !== 'viewport-y' && styleLayer.layout.get('symbol-sort-key').constantOr(1) !== undefined; this._currentTileIndex = 0; this._currentPartIndex = 0; this._seenCrossTileIDs = {}; this._bucketParts = []; } continuePlacement(tiles, placement, showCollisionBoxes, styleLayer, shouldPausePlacement) { const bucketParts = this._bucketParts; while (this._currentTileIndex < tiles.length) { const tile = tiles[this._currentTileIndex]; placement.getBucketParts(bucketParts, styleLayer, tile, this._sortAcrossTiles); this._currentTileIndex++; if (shouldPausePlacement()) { return true; } } if (this._sortAcrossTiles) { this._sortAcrossTiles = false; bucketParts.sort((a, b) => a.sortKey - b.sortKey); } while (this._currentPartIndex < bucketParts.length) { const bucketPart = bucketParts[this._currentPartIndex]; placement.placeLayerBucketPart(bucketPart, this._seenCrossTileIDs, showCollisionBoxes); this._currentPartIndex++; if (shouldPausePlacement()) { return true; } } return false; } } class PauseablePlacement { constructor(transform, order, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions, prevPlacement) { this.placement = new Placement(transform, fadeDuration, crossSourceCollisions, prevPlacement); this._currentPlacementIndex = order.length - 1; this._forceFullPlacement = forceFullPlacement; this._showCollisionBoxes = showCollisionBoxes; this._done = false; } isDone() { return this._done; } continuePlacement(order, layers, layerTiles) { const startTime = ref_properties.browser.now(); const shouldPausePlacement = () => { const elapsedTime = ref_properties.browser.now() - startTime; return this._forceFullPlacement ? false : elapsedTime > 2; }; while (this._currentPlacementIndex >= 0) { const layerId = order[this._currentPlacementIndex]; const layer = layers[layerId]; const placementZoom = this.placement.collisionIndex.transform.zoom; if (layer.type === 'symbol' && (!layer.minzoom || layer.minzoom <= placementZoom) && (!layer.maxzoom || layer.maxzoom > placementZoom)) { if (!this._inProgressLayer) { this._inProgressLayer = new LayerPlacement(layer); } const pausePlacement = this._inProgressLayer.continuePlacement(layerTiles[layer.source], this.placement, this._showCollisionBoxes, layer, shouldPausePlacement); if (pausePlacement) { return; } delete this._inProgressLayer; } this._currentPlacementIndex--; } this._done = true; } commit(now) { this.placement.commit(now); return this.placement; } } const roundingFactor = 512 / ref_properties.EXTENT / 2; class TileLayerIndex { constructor(tileID, symbolInstances, bucketInstanceId) { this.tileID = tileID; this.indexedSymbolInstances = {}; this.bucketInstanceId = bucketInstanceId; for (let i = 0; i < symbolInstances.length; i++) { const symbolInstance = symbolInstances.get(i); const key = symbolInstance.key; if (!this.indexedSymbolInstances[key]) { this.indexedSymbolInstances[key] = []; } this.indexedSymbolInstances[key].push({ crossTileID: symbolInstance.crossTileID, coord: this.getScaledCoordinates(symbolInstance, tileID) }); } } getScaledCoordinates(symbolInstance, childTileID) { const zDifference = childTileID.canonical.z - this.tileID.canonical.z; const scale = roundingFactor / Math.pow(2, zDifference); return { x: Math.floor((childTileID.canonical.x * ref_properties.EXTENT + symbolInstance.anchorX) * scale), y: Math.floor((childTileID.canonical.y * ref_properties.EXTENT + symbolInstance.anchorY) * scale) }; } findMatches(symbolInstances, newTileID, zoomCrossTileIDs) { const tolerance = this.tileID.canonical.z < newTileID.canonical.z ? 1 : Math.pow(2, this.tileID.canonical.z - newTileID.canonical.z); for (let i = 0; i < symbolInstances.length; i++) { const symbolInstance = symbolInstances.get(i); if (symbolInstance.crossTileID) { continue; } const indexedInstances = this.indexedSymbolInstances[symbolInstance.key]; if (!indexedInstances) { continue; } const scaledSymbolCoord = this.getScaledCoordinates(symbolInstance, newTileID); for (const thisTileSymbol of indexedInstances) { if (Math.abs(thisTileSymbol.coord.x - scaledSymbolCoord.x) <= tolerance && Math.abs(thisTileSymbol.coord.y - scaledSymbolCoord.y) <= tolerance && !zoomCrossTileIDs[thisTileSymbol.crossTileID]) { zoomCrossTileIDs[thisTileSymbol.crossTileID] = true; symbolInstance.crossTileID = thisTileSymbol.crossTileID; break; } } } } } class CrossTileIDs { constructor() { this.maxCrossTileID = 0; } generate() { return ++this.maxCrossTileID; } } class CrossTileSymbolLayerIndex { constructor() { this.indexes = {}; this.usedCrossTileIDs = {}; this.lng = 0; } handleWrapJump(lng) { const wrapDelta = Math.round((lng - this.lng) / 360); if (wrapDelta !== 0) { for (const zoom in this.indexes) { const zoomIndexes = this.indexes[zoom]; const newZoomIndex = {}; for (const key in zoomIndexes) { const index = zoomIndexes[key]; index.tileID = index.tileID.unwrapTo(index.tileID.wrap + wrapDelta); newZoomIndex[index.tileID.key] = index; } this.indexes[zoom] = newZoomIndex; } } this.lng = lng; } addBucket(tileID, bucket, crossTileIDs) { if (this.indexes[tileID.overscaledZ] && this.indexes[tileID.overscaledZ][tileID.key]) { if (this.indexes[tileID.overscaledZ][tileID.key].bucketInstanceId === bucket.bucketInstanceId) { return false; } else { this.removeBucketCrossTileIDs(tileID.overscaledZ, this.indexes[tileID.overscaledZ][tileID.key]); } } for (let i = 0; i < bucket.symbolInstances.length; i++) { const symbolInstance = bucket.symbolInstances.get(i); symbolInstance.crossTileID = 0; } if (!this.usedCrossTileIDs[tileID.overscaledZ]) { this.usedCrossTileIDs[tileID.overscaledZ] = {}; } const zoomCrossTileIDs = this.usedCrossTileIDs[tileID.overscaledZ]; for (const zoom in this.indexes) { const zoomIndexes = this.indexes[zoom]; if (Number(zoom) > tileID.overscaledZ) { for (const id in zoomIndexes) { const childIndex = zoomIndexes[id]; if (childIndex.tileID.isChildOf(tileID)) { childIndex.findMatches(bucket.symbolInstances, tileID, zoomCrossTileIDs); } } } else { const parentCoord = tileID.scaledTo(Number(zoom)); const parentIndex = zoomIndexes[parentCoord.key]; if (parentIndex) { parentIndex.findMatches(bucket.symbolInstances, tileID, zoomCrossTileIDs); } } } for (let i = 0; i < bucket.symbolInstances.length; i++) { const symbolInstance = bucket.symbolInstances.get(i); if (!symbolInstance.crossTileID) { symbolInstance.crossTileID = crossTileIDs.generate(); zoomCrossTileIDs[symbolInstance.crossTileID] = true; } } if (this.indexes[tileID.overscaledZ] === undefined) { this.indexes[tileID.overscaledZ] = {}; } this.indexes[tileID.overscaledZ][tileID.key] = new TileLayerIndex(tileID, bucket.symbolInstances, bucket.bucketInstanceId); return true; } removeBucketCrossTileIDs(zoom, removedBucket) { for (const key in removedBucket.indexedSymbolInstances) { for (const symbolInstance of removedBucket.indexedSymbolInstances[key]) { delete this.usedCrossTileIDs[zoom][symbolInstance.crossTileID]; } } } removeStaleBuckets(currentIDs) { let tilesChanged = false; for (const z in this.indexes) { const zoomIndexes = this.indexes[z]; for (const tileKey in zoomIndexes) { if (!currentIDs[zoomIndexes[tileKey].bucketInstanceId]) { this.removeBucketCrossTileIDs(z, zoomIndexes[tileKey]); delete zoomIndexes[tileKey]; tilesChanged = true; } } } return tilesChanged; } } class CrossTileSymbolIndex { constructor() { this.layerIndexes = {}; this.crossTileIDs = new CrossTileIDs(); this.maxBucketInstanceId = 0; this.bucketsInCurrentPlacement = {}; } addLayer(styleLayer, tiles, lng) { let layerIndex = this.layerIndexes[styleLayer.id]; if (layerIndex === undefined) { layerIndex = this.layerIndexes[styleLayer.id] = new CrossTileSymbolLayerIndex(); } let symbolBucketsChanged = false; const currentBucketIDs = {}; layerIndex.handleWrapJump(lng); for (const tile of tiles) { const symbolBucket = tile.getBucket(styleLayer); if (!symbolBucket || styleLayer.id !== symbolBucket.layerIds[0]) continue; if (!symbolBucket.bucketInstanceId) { symbolBucket.bucketInstanceId = ++this.maxBucketInstanceId; } if (layerIndex.addBucket(tile.tileID, symbolBucket, this.crossTileIDs)) { symbolBucketsChanged = true; } currentBucketIDs[symbolBucket.bucketInstanceId] = true; } if (layerIndex.removeStaleBuckets(currentBucketIDs)) { symbolBucketsChanged = true; } return symbolBucketsChanged; } pruneUnusedLayers(usedLayers) { const usedLayerMap = {}; usedLayers.forEach(usedLayer => { usedLayerMap[usedLayer] = true; }); for (const layerId in this.layerIndexes) { if (!usedLayerMap[layerId]) { delete this.layerIndexes[layerId]; } } } } const emitValidationErrors = (evented, errors) => ref_properties.emitValidationErrors(evented, errors && errors.filter(error => error.identifier !== 'source.canvas')); const supportedDiffOperations = ref_properties.pick(operations, [ 'addLayer', 'removeLayer', 'setPaintProperty', 'setLayoutProperty', 'setFilter', 'addSource', 'removeSource', 'setLayerZoomRange', 'setLight', 'setTransition', 'setGeoJSONSourceData', 'setTerrain' ]); const ignoredDiffOperations = ref_properties.pick(operations, [ 'setCenter', 'setZoom', 'setBearing', 'setPitch' ]); const empty = emptyStyle(); const drapedLayers = { 'fill': true, 'line': true, 'background': true, 'hillshade': true, 'raster': true }; class Style extends ref_properties.Evented { constructor(map, options = {}) { super(); this.map = map; this.dispatcher = new Dispatcher(getGlobalWorkerPool(), this); this.imageManager = new ImageManager(); this.imageManager.setEventedParent(this); this.glyphManager = new ref_properties.GlyphManager(map._requestManager, options.localFontFamily ? ref_properties.LocalGlyphMode.all : options.localIdeographFontFamily ? ref_properties.LocalGlyphMode.ideographs : ref_properties.LocalGlyphMode.none, options.localFontFamily || options.localIdeographFontFamily); this.lineAtlas = new LineAtlas(256, 512); this.crossTileSymbolIndex = new CrossTileSymbolIndex(); this._layers = {}; this._num3DLayers = 0; this._numSymbolLayers = 0; this._numCircleLayers = 0; this._serializedLayers = {}; this._sourceCaches = {}; this._otherSourceCaches = {}; this._symbolSourceCaches = {}; this.zoomHistory = new ref_properties.ZoomHistory(); this._loaded = false; this._availableImages = []; this._order = []; this._drapedFirstOrder = []; this._resetUpdates(); this.dispatcher.broadcast('setReferrer', ref_properties.getReferrer()); const self = this; this._rtlTextPluginCallback = Style.registerForPluginStateChange(event => { const state = { pluginStatus: event.pluginStatus, pluginURL: event.pluginURL }; self.dispatcher.broadcast('syncRTLPluginState', state, (err, results) => { ref_properties.triggerPluginCompletionEvent(err); if (results) { const allComplete = results.every(elem => elem); if (allComplete) { for (const id in self._sourceCaches) { const sourceCache = self._sourceCaches[id]; const sourceCacheType = sourceCache.getSource().type; if (sourceCacheType === 'vector' || sourceCacheType === 'geojson') { sourceCache.reload(); } } } } }); }); this.on('data', event => { if (event.dataType !== 'source' || event.sourceDataType !== 'metadata') { return; } const source = this.getSource(event.sourceId); if (!source || !source.vectorLayerIds) { return; } for (const layerId in this._layers) { const layer = this._layers[layerId]; if (layer.source === source.id) { this._validateLayer(layer); } } }); } loadURL(url, options = {}) { this.fire(new ref_properties.Event('dataloading', { dataType: 'style' })); const validate = typeof options.validate === 'boolean' ? options.validate : !ref_properties.isMapboxURL(url); url = this.map._requestManager.normalizeStyleURL(url, options.accessToken); const request = this.map._requestManager.transformRequest(url, ref_properties.ResourceType.Style); this._request = ref_properties.getJSON(request, (error, json) => { this._request = null; if (error) { this.fire(new ref_properties.ErrorEvent(error)); } else if (json) { this._load(json, validate); } }); } loadJSON(json, options = {}) { this.fire(new ref_properties.Event('dataloading', { dataType: 'style' })); this._request = ref_properties.browser.frame(() => { this._request = null; this._load(json, options.validate !== false); }); } loadEmpty() { this.fire(new ref_properties.Event('dataloading', { dataType: 'style' })); this._load(empty, false); } _updateLayerCount(layer, add) { const count = add ? 1 : -1; if (layer.is3D()) { this._num3DLayers += count; } if (layer.type === 'circle') { this._numCircleLayers += count; } if (layer.type === 'symbol') { this._numSymbolLayers += count; } } _load(json, validate) { if (validate && emitValidationErrors(this, ref_properties.validateStyle(json))) { return; } this._loaded = true; this.stylesheet = json; for (const id in json.sources) { this.addSource(id, json.sources[id], { validate: false }); } this._changed = false; if (json.sprite) { this._loadSprite(json.sprite); } else { this.imageManager.setLoaded(true); this.dispatcher.broadcast('spriteLoaded', true); } this.glyphManager.setURL(json.glyphs); const layers = derefLayers(this.stylesheet.layers); this._order = layers.map(layer => layer.id); this._layers = {}; this._serializedLayers = {}; for (let layer of layers) { layer = ref_properties.createStyleLayer(layer); layer.setEventedParent(this, { layer: { id: layer.id } }); this._layers[layer.id] = layer; this._serializedLayers[layer.id] = layer.serialize(); this._updateLayerCount(layer, true); } this.dispatcher.broadcast('setLayers', this._serializeLayers(this._order)); this.light = new Light(this.stylesheet.light); if (this.stylesheet.terrain) { this._createTerrain(this.stylesheet.terrain); } this._updateDrapeFirstLayers(); this.fire(new ref_properties.Event('data', { dataType: 'style' })); this.fire(new ref_properties.Event('style.load')); } _loadSprite(url) { this._spriteRequest = loadSprite(url, this.map._requestManager, (err, images) => { this._spriteRequest = null; if (err) { this.fire(new ref_properties.ErrorEvent(err)); } else if (images) { for (const id in images) { this.imageManager.addImage(id, images[id]); } } this.imageManager.setLoaded(true); this._availableImages = this.imageManager.listImages(); this.dispatcher.broadcast('setImages', this._availableImages); this.dispatcher.broadcast('spriteLoaded', true); this.fire(new ref_properties.Event('data', { dataType: 'style' })); }); } _validateLayer(layer) { const source = this.getSource(layer.source); if (!source) { return; } const sourceLayer = layer.sourceLayer; if (!sourceLayer) { return; } if (source.type === 'geojson' || source.vectorLayerIds && source.vectorLayerIds.indexOf(sourceLayer) === -1) { this.fire(new ref_properties.ErrorEvent(new Error(`Source layer "${ sourceLayer }" ` + `does not exist on source "${ source.id }" ` + `as specified by style layer "${ layer.id }"`))); } } loaded() { if (!this._loaded) return false; if (Object.keys(this._updatedSources).length) return false; for (const id in this._sourceCaches) if (!this._sourceCaches[id].loaded()) return false; if (!this.imageManager.isLoaded()) return false; return true; } _serializeLayers(ids) { const serializedLayers = []; for (const id of ids) { const layer = this._layers[id]; if (layer.type !== 'custom') { serializedLayers.push(layer.serialize()); } } return serializedLayers; } hasTransitions() { if (this.light && this.light.hasTransition()) { return true; } for (const id in this._sourceCaches) { if (this._sourceCaches[id].hasTransition()) { return true; } } for (const id in this._layers) { if (this._layers[id].hasTransition()) { return true; } } return false; } get order() { if (this.map._optimizeForTerrain && this.terrain) { return this._drapedFirstOrder; } return this._order; } isLayerDraped(layer) { if (!this.terrain) return false; return drapedLayers[layer.type]; } _checkLoaded() { if (!this._loaded) { throw new Error('Style is not done loading'); } } update(parameters) { if (!this._loaded) { return; } const changed = this._changed; if (this._changed) { const updatedIds = Object.keys(this._updatedLayers); const removedIds = Object.keys(this._removedLayers); if (updatedIds.length || removedIds.length) { this._updateWorkerLayers(updatedIds, removedIds); } for (const id in this._updatedSources) { const action = this._updatedSources[id]; if (action === 'reload') { this._reloadSource(id); } else if (action === 'clear') { this._clearSource(id); } } this._updateTilesForChangedImages(); for (const id in this._updatedPaintProps) { this._layers[id].updateTransitions(parameters); } this.light.updateTransitions(parameters); this._resetUpdates(); } const sourcesUsedBefore = {}; for (const sourceId in this._sourceCaches) { const sourceCache = this._sourceCaches[sourceId]; sourcesUsedBefore[sourceId] = sourceCache.used; sourceCache.used = false; } for (const layerId of this._order) { const layer = this._layers[layerId]; layer.recalculate(parameters, this._availableImages); if (!layer.isHidden(parameters.zoom)) { const sourceCache = this._getLayerSourceCache(layer); if (sourceCache) sourceCache.used = true; } const painter = this.map.painter; if (painter) { const programIds = layer.getProgramIds(); if (!programIds) continue; const programConfiguration = layer.getProgramConfiguration(parameters.zoom); for (const programId of programIds) { painter.useProgram(programId, programConfiguration); } } } for (const sourceId in sourcesUsedBefore) { const sourceCache = this._sourceCaches[sourceId]; if (sourcesUsedBefore[sourceId] !== sourceCache.used) { sourceCache.getSource().fire(new ref_properties.Event('data', { sourceDataType: 'visibility', dataType: 'source', sourceId: sourceCache.getSource().id })); } } this.light.recalculate(parameters); if (this.terrain) { this.terrain.recalculate(parameters); } this.z = parameters.zoom; if (changed) { this.fire(new ref_properties.Event('data', { dataType: 'style' })); } } _updateTilesForChangedImages() { const changedImages = Object.keys(this._changedImages); if (changedImages.length) { for (const name in this._sourceCaches) { this._sourceCaches[name].reloadTilesForDependencies([ 'icons', 'patterns' ], changedImages); } this._changedImages = {}; } } _updateWorkerLayers(updatedIds, removedIds) { this.dispatcher.broadcast('updateLayers', { layers: this._serializeLayers(updatedIds), removedIds }); } _resetUpdates() { this._changed = false; this._updatedLayers = {}; this._removedLayers = {}; this._updatedSources = {}; this._updatedPaintProps = {}; this._changedImages = {}; } setState(nextState) { this._checkLoaded(); if (emitValidationErrors(this, ref_properties.validateStyle(nextState))) return false; nextState = ref_properties.clone$1(nextState); nextState.layers = derefLayers(nextState.layers); const changes = diffStyles(this.serialize(), nextState).filter(op => !(op.command in ignoredDiffOperations)); if (changes.length === 0) { return false; } const unimplementedOps = changes.filter(op => !(op.command in supportedDiffOperations)); if (unimplementedOps.length > 0) { throw new Error(`Unimplemented: ${ unimplementedOps.map(op => op.command).join(', ') }.`); } changes.forEach(op => { if (op.command === 'setTransition') { return; } this[op.command].apply(this, op.args); }); this.stylesheet = nextState; return true; } addImage(id, image) { if (this.getImage(id)) { return this.fire(new ref_properties.ErrorEvent(new Error('An image with this name already exists.'))); } this.imageManager.addImage(id, image); this._afterImageUpdated(id); } updateImage(id, image) { this.imageManager.updateImage(id, image); } getImage(id) { return this.imageManager.getImage(id); } removeImage(id) { if (!this.getImage(id)) { return this.fire(new ref_properties.ErrorEvent(new Error('No image with this name exists.'))); } this.imageManager.removeImage(id); this._afterImageUpdated(id); } _afterImageUpdated(id) { this._availableImages = this.imageManager.listImages(); this._changedImages[id] = true; this._changed = true; this.dispatcher.broadcast('setImages', this._availableImages); this.fire(new ref_properties.Event('data', { dataType: 'style' })); } listImages() { this._checkLoaded(); return this.imageManager.listImages(); } addSource(id, source, options = {}) { this._checkLoaded(); if (this.getSource(id) !== undefined) { throw new Error('There is already a source with this ID'); } if (!source.type) { throw new Error(`The type property must be defined, but only the following properties were given: ${ Object.keys(source).join(', ') }.`); } const builtIns = [ 'vector', 'raster', 'geojson', 'video', 'image' ]; const shouldValidate = builtIns.indexOf(source.type) >= 0; if (shouldValidate && this._validate(ref_properties.validateStyle.source, `sources.${ id }`, source, null, options)) return; if (this.map && this.map._collectResourceTiming) source.collectResourceTiming = true; const sourceInstance = create(id, source, this.dispatcher, this); sourceInstance.setEventedParent(this, () => ({ isSourceLoaded: this.loaded(), source: sourceInstance.serialize(), sourceId: id })); const addSourceCache = onlySymbols => { const sourceCacheId = (onlySymbols ? 'symbol:' : 'other:') + id; const sourceCache = this._sourceCaches[sourceCacheId] = new ref_properties.SourceCache(sourceCacheId, sourceInstance, onlySymbols); (onlySymbols ? this._symbolSourceCaches : this._otherSourceCaches)[id] = sourceCache; sourceCache.style = this; sourceCache.onAdd(this.map); }; addSourceCache(false); if (source.type === 'vector' || source.type === 'geojson') { addSourceCache(true); } if (sourceInstance.onAdd) sourceInstance.onAdd(this.map); this._changed = true; } removeSource(id) { this._checkLoaded(); const source = this.getSource(id); if (source === undefined) { throw new Error('There is no source with this ID'); } for (const layerId in this._layers) { if (this._layers[layerId].source === id) { return this.fire(new ref_properties.ErrorEvent(new Error(`Source "${ id }" cannot be removed while layer "${ layerId }" is using it.`))); } } if (this.terrain && this.terrain.get().source === id) { return this.fire(new ref_properties.ErrorEvent(new Error(`Source "${ id }" cannot be removed while terrain is using it.`))); } const sourceCaches = this._getSourceCaches(id); for (const sourceCache of sourceCaches) { delete this._sourceCaches[sourceCache.id]; delete this._updatedSources[sourceCache.id]; sourceCache.fire(new ref_properties.Event('data', { sourceDataType: 'metadata', dataType: 'source', sourceId: sourceCache.getSource().id })); sourceCache.setEventedParent(null); sourceCache.clearTiles(); } delete this._otherSourceCaches[id]; delete this._symbolSourceCaches[id]; source.setEventedParent(null); if (source.onRemove) { source.onRemove(this.map); } this._changed = true; } setGeoJSONSourceData(id, data) { this._checkLoaded(); const geojsonSource = this.getSource(id); geojsonSource.setData(data); this._changed = true; } getSource(id) { const sourceCache = this._getSourceCache(id); return sourceCache && sourceCache.getSource(); } addLayer(layerObject, before, options = {}) { this._checkLoaded(); const id = layerObject.id; if (this.getLayer(id)) { this.fire(new ref_properties.ErrorEvent(new Error(`Layer with id "${ id }" already exists on this map`))); return; } let layer; if (layerObject.type === 'custom') { if (emitValidationErrors(this, ref_properties.validateCustomStyleLayer(layerObject))) return; layer = ref_properties.createStyleLayer(layerObject); } else { if (typeof layerObject.source === 'object') { this.addSource(id, layerObject.source); layerObject = ref_properties.clone$1(layerObject); layerObject = ref_properties.extend(layerObject, { source: id }); } if (this._validate(ref_properties.validateStyle.layer, `layers.${ id }`, layerObject, { arrayIndex: -1 }, options)) return; layer = ref_properties.createStyleLayer(layerObject); this._validateLayer(layer); layer.setEventedParent(this, { layer: { id } }); this._serializedLayers[layer.id] = layer.serialize(); this._updateLayerCount(layer, true); } const index = before ? this._order.indexOf(before) : this._order.length; if (before && index === -1) { this.fire(new ref_properties.ErrorEvent(new Error(`Layer with id "${ before }" does not exist on this map.`))); return; } this._order.splice(index, 0, id); this._layerOrderChanged = true; this._layers[id] = layer; const sourceCache = this._getLayerSourceCache(layer); if (this._removedLayers[id] && layer.source && sourceCache && layer.type !== 'custom') { const removed = this._removedLayers[id]; delete this._removedLayers[id]; if (removed.type !== layer.type) { this._updatedSources[layer.source] = 'clear'; } else { this._updatedSources[layer.source] = 'reload'; sourceCache.pause(); } } this._updateLayer(layer); if (layer.onAdd) { layer.onAdd(this.map); } this._updateDrapeFirstLayers(); } moveLayer(id, before) { this._checkLoaded(); this._changed = true; const layer = this._layers[id]; if (!layer) { this.fire(new ref_properties.ErrorEvent(new Error(`The layer '${ id }' does not exist in the map's style and cannot be moved.`))); return; } if (id === before) { return; } const index = this._order.indexOf(id); this._order.splice(index, 1); const newIndex = before ? this._order.indexOf(before) : this._order.length; if (before && newIndex === -1) { this.fire(new ref_properties.ErrorEvent(new Error(`Layer with id "${ before }" does not exist on this map.`))); return; } this._order.splice(newIndex, 0, id); this._layerOrderChanged = true; this._updateDrapeFirstLayers(); } removeLayer(id) { this._checkLoaded(); const layer = this._layers[id]; if (!layer) { this.fire(new ref_properties.ErrorEvent(new Error(`The layer '${ id }' does not exist in the map's style and cannot be removed.`))); return; } layer.setEventedParent(null); this._updateLayerCount(layer, false); const index = this._order.indexOf(id); this._order.splice(index, 1); this._layerOrderChanged = true; this._changed = true; this._removedLayers[id] = layer; delete this._layers[id]; delete this._serializedLayers[id]; delete this._updatedLayers[id]; delete this._updatedPaintProps[id]; if (layer.onRemove) { layer.onRemove(this.map); } this._updateDrapeFirstLayers(); } getLayer(id) { return this._layers[id]; } hasLayer(id) { return id in this._layers; } hasLayerType(type) { for (const layerId in this._layers) { const layer = this._layers[layerId]; if (layer.type === type) { return true; } } return false; } setLayerZoomRange(layerId, minzoom, maxzoom) { this._checkLoaded(); const layer = this.getLayer(layerId); if (!layer) { this.fire(new ref_properties.ErrorEvent(new Error(`The layer '${ layerId }' does not exist in the map's style and cannot have zoom extent.`))); return; } if (layer.minzoom === minzoom && layer.maxzoom === maxzoom) return; if (minzoom != null) { layer.minzoom = minzoom; } if (maxzoom != null) { layer.maxzoom = maxzoom; } this._updateLayer(layer); } setFilter(layerId, filter, options = {}) { this._checkLoaded(); const layer = this.getLayer(layerId); if (!layer) { this.fire(new ref_properties.ErrorEvent(new Error(`The layer '${ layerId }' does not exist in the map's style and cannot be filtered.`))); return; } if (ref_properties.deepEqual(layer.filter, filter)) { return; } if (filter === null || filter === undefined) { layer.filter = undefined; this._updateLayer(layer); return; } if (this._validate(ref_properties.validateStyle.filter, `layers.${ layer.id }.filter`, filter, null, options)) { return; } layer.filter = ref_properties.clone$1(filter); this._updateLayer(layer); } getFilter(layer) { return ref_properties.clone$1(this.getLayer(layer).filter); } setLayoutProperty(layerId, name, value, options = {}) { this._checkLoaded(); const layer = this.getLayer(layerId); if (!layer) { this.fire(new ref_properties.ErrorEvent(new Error(`The layer '${ layerId }' does not exist in the map's style and cannot be styled.`))); return; } if (ref_properties.deepEqual(layer.getLayoutProperty(name), value)) return; layer.setLayoutProperty(name, value, options); this._updateLayer(layer); } getLayoutProperty(layerId, name) { const layer = this.getLayer(layerId); if (!layer) { this.fire(new ref_properties.ErrorEvent(new Error(`The layer '${ layerId }' does not exist in the map's style.`))); return; } return layer.getLayoutProperty(name); } setPaintProperty(layerId, name, value, options = {}) { this._checkLoaded(); const layer = this.getLayer(layerId); if (!layer) { this.fire(new ref_properties.ErrorEvent(new Error(`The layer '${ layerId }' does not exist in the map's style and cannot be styled.`))); return; } if (ref_properties.deepEqual(layer.getPaintProperty(name), value)) return; const requiresRelayout = layer.setPaintProperty(name, value, options); if (requiresRelayout) { this._updateLayer(layer); } this._changed = true; this._updatedPaintProps[layerId] = true; } getPaintProperty(layer, name) { return this.getLayer(layer).getPaintProperty(name); } setFeatureState(target, state) { this._checkLoaded(); const sourceId = target.source; const sourceLayer = target.sourceLayer; const source = this.getSource(sourceId); if (source === undefined) { this.fire(new ref_properties.ErrorEvent(new Error(`The source '${ sourceId }' does not exist in the map's style.`))); return; } const sourceType = source.type; if (sourceType === 'geojson' && sourceLayer) { this.fire(new ref_properties.ErrorEvent(new Error(`GeoJSON sources cannot have a sourceLayer parameter.`))); return; } if (sourceType === 'vector' && !sourceLayer) { this.fire(new ref_properties.ErrorEvent(new Error(`The sourceLayer parameter must be provided for vector source types.`))); return; } if (target.id === undefined) { this.fire(new ref_properties.ErrorEvent(new Error(`The feature id parameter must be provided.`))); } const sourceCaches = this._getSourceCaches(sourceId); for (const sourceCache of sourceCaches) { sourceCache.setFeatureState(sourceLayer, target.id, state); } } removeFeatureState(target, key) { this._checkLoaded(); const sourceId = target.source; const source = this.getSource(sourceId); if (source === undefined) { this.fire(new ref_properties.ErrorEvent(new Error(`The source '${ sourceId }' does not exist in the map's style.`))); return; } const sourceType = source.type; const sourceLayer = sourceType === 'vector' ? target.sourceLayer : undefined; if (sourceType === 'vector' && !sourceLayer) { this.fire(new ref_properties.ErrorEvent(new Error(`The sourceLayer parameter must be provided for vector source types.`))); return; } if (key && (typeof target.id !== 'string' && typeof target.id !== 'number')) { this.fire(new ref_properties.ErrorEvent(new Error(`A feature id is required to remove its specific state property.`))); return; } const sourceCaches = this._getSourceCaches(sourceId); for (const sourceCache of sourceCaches) { sourceCache.removeFeatureState(sourceLayer, target.id, key); } } getFeatureState(target) { this._checkLoaded(); const sourceId = target.source; const sourceLayer = target.sourceLayer; const source = this.getSource(sourceId); if (source === undefined) { this.fire(new ref_properties.ErrorEvent(new Error(`The source '${ sourceId }' does not exist in the map's style.`))); return; } const sourceType = source.type; if (sourceType === 'vector' && !sourceLayer) { this.fire(new ref_properties.ErrorEvent(new Error(`The sourceLayer parameter must be provided for vector source types.`))); return; } if (target.id === undefined) { this.fire(new ref_properties.ErrorEvent(new Error(`The feature id parameter must be provided.`))); } const sourceCaches = this._getSourceCaches(sourceId); return sourceCaches[0].getFeatureState(sourceLayer, target.id); } getTransition() { return ref_properties.extend({ duration: 300, delay: 0 }, this.stylesheet && this.stylesheet.transition); } serialize() { const sources = {}; for (const cacheId in this._sourceCaches) { const source = this._sourceCaches[cacheId].getSource(); if (!sources[source.id]) { sources[source.id] = source.serialize(); } } return ref_properties.filterObject({ version: this.stylesheet.version, name: this.stylesheet.name, metadata: this.stylesheet.metadata, light: this.stylesheet.light, terrain: this.stylesheet.terrain, center: this.stylesheet.center, zoom: this.stylesheet.zoom, bearing: this.stylesheet.bearing, pitch: this.stylesheet.pitch, sprite: this.stylesheet.sprite, glyphs: this.stylesheet.glyphs, transition: this.stylesheet.transition, sources, layers: this._serializeLayers(this._order) }, value => { return value !== undefined; }); } _updateLayer(layer) { this._updatedLayers[layer.id] = true; const sourceCache = this._getLayerSourceCache(layer); if (layer.source && !this._updatedSources[layer.source] && sourceCache && sourceCache.getSource().type !== 'raster') { this._updatedSources[layer.source] = 'reload'; sourceCache.pause(); } this._changed = true; } _flattenAndSortRenderedFeatures(sourceResults) { const isLayer3D = layerId => this._layers[layerId].type === 'fill-extrusion'; const layerIndex = {}; const features3D = []; for (let l = this._order.length - 1; l >= 0; l--) { const layerId = this._order[l]; if (isLayer3D(layerId)) { layerIndex[layerId] = l; for (const sourceResult of sourceResults) { const layerFeatures = sourceResult[layerId]; if (layerFeatures) { for (const featureWrapper of layerFeatures) { features3D.push(featureWrapper); } } } } } features3D.sort((a, b) => { return b.intersectionZ - a.intersectionZ; }); const features = []; for (let l = this._order.length - 1; l >= 0; l--) { const layerId = this._order[l]; if (isLayer3D(layerId)) { for (let i = features3D.length - 1; i >= 0; i--) { const topmost3D = features3D[i].feature; if (layerIndex[topmost3D.layer.id] < l) break; features.push(topmost3D); features3D.pop(); } } else { for (const sourceResult of sourceResults) { const layerFeatures = sourceResult[layerId]; if (layerFeatures) { for (const featureWrapper of layerFeatures) { features.push(featureWrapper.feature); } } } } } return features; } queryRenderedFeatures(queryGeometry, params, transform) { if (params && params.filter) { this._validate(ref_properties.validateStyle.filter, 'queryRenderedFeatures.filter', params.filter, null, params); } const includedSources = {}; if (params && params.layers) { if (!Array.isArray(params.layers)) { this.fire(new ref_properties.ErrorEvent(new Error('parameters.layers must be an Array.'))); return []; } for (const layerId of params.layers) { const layer = this._layers[layerId]; if (!layer) { this.fire(new ref_properties.ErrorEvent(new Error(`The layer '${ layerId }' does not exist in the map's style and cannot be queried for features.`))); return []; } includedSources[layer.source] = true; } } const sourceResults = []; params.availableImages = this._availableImages; const has3DLayer = params && params.layers ? params.layers.some(layerId => { const layer = this.getLayer(layerId); return layer && layer.is3D(); }) : this.has3DLayers(); const queryGeometryStruct = QueryGeometry.createFromScreenPoints(queryGeometry, transform); for (const id in this._sourceCaches) { const sourceId = this._sourceCaches[id].getSource().id; if (params.layers && !includedSources[sourceId]) continue; sourceResults.push(queryRenderedFeatures(this._sourceCaches[id], this._layers, this._serializedLayers, queryGeometryStruct, params, transform, has3DLayer, !!this.map._showQueryGeometry)); } if (this.placement) { sourceResults.push(queryRenderedSymbols(this._layers, this._serializedLayers, this._getLayerSourceCache.bind(this), queryGeometryStruct.screenGeometry, params, this.placement.collisionIndex, this.placement.retainedQueryData)); } return this._flattenAndSortRenderedFeatures(sourceResults); } querySourceFeatures(sourceID, params) { if (params && params.filter) { this._validate(ref_properties.validateStyle.filter, 'querySourceFeatures.filter', params.filter, null, params); } const sourceCaches = this._getSourceCaches(sourceID); let results = []; for (const sourceCache of sourceCaches) { results = results.concat(querySourceFeatures(sourceCache, params)); } return results; } addSourceType(name, SourceType, callback) { if (Style.getSourceType(name)) { return callback(new Error(`A source type called "${ name }" already exists.`)); } Style.setSourceType(name, SourceType); if (!SourceType.workerSourceURL) { return callback(null, null); } this.dispatcher.broadcast('loadWorkerSource', { name, url: SourceType.workerSourceURL }, callback); } getLight() { return this.light.getLight(); } setLight(lightOptions, options = {}) { this._checkLoaded(); const light = this.light.getLight(); let _update = false; for (const key in lightOptions) { if (!ref_properties.deepEqual(lightOptions[key], light[key])) { _update = true; break; } } if (!_update) return; const parameters = { now: ref_properties.browser.now(), transition: ref_properties.extend({ duration: 300, delay: 0 }, this.stylesheet.transition) }; this.light.setLight(lightOptions, options); this.light.updateTransitions(parameters); } setTerrain(terrainOptions) { this._checkLoaded(); if (!terrainOptions) { delete this.terrain; delete this.stylesheet.terrain; this.dispatcher.broadcast('enableTerrain', false); this._force3DLayerUpdate(); return; } if (typeof terrainOptions.source === 'object') { const id = 'terrain-dem-src'; this.addSource(id, terrainOptions.source); terrainOptions = ref_properties.clone$1(terrainOptions); terrainOptions = ref_properties.extend(terrainOptions, { source: id }); } if (this._validate(ref_properties.validateStyle.terrain, 'terrain', terrainOptions)) return; if (!this.terrain) { this._createTerrain(terrainOptions); } else { const terrain = this.terrain; const currSpec = terrain.get(); for (const key in terrainOptions) { if (!ref_properties.deepEqual(terrainOptions[key], currSpec[key])) { terrain.set(terrainOptions); this.stylesheet.terrain = terrainOptions; const parameters = { now: ref_properties.browser.now(), transition: ref_properties.extend({ duration: 0 }, this.stylesheet.transition) }; terrain.updateTransitions(parameters); break; } } } this._updateDrapeFirstLayers(); } _updateDrapeFirstLayers() { if (!this.map._optimizeForTerrain || !this.terrain) { return; } const draped = this._order.filter(id => { return this.isLayerDraped(this._layers[id]); }); const nonDraped = this._order.filter(id => { return !this.isLayerDraped(this._layers[id]); }); this._drapedFirstOrder = []; this._drapedFirstOrder.push(...draped); this._drapedFirstOrder.push(...nonDraped); } _createTerrain(terrainOptions) { const terrain = this.terrain = new Terrain(terrainOptions); this.stylesheet.terrain = terrainOptions; this.dispatcher.broadcast('enableTerrain', true); this._force3DLayerUpdate(); const parameters = { now: ref_properties.browser.now(), transition: ref_properties.extend({ duration: 0 }, this.stylesheet.transition) }; terrain.updateTransitions(parameters); } _force3DLayerUpdate() { for (const layerId in this._layers) { const layer = this._layers[layerId]; if (layer.type === 'fill-extrusion') { this._updateLayer(layer); } } } _validate(validate, key, value, props, options = {}) { if (options && options.validate === false) { return false; } return emitValidationErrors(this, validate.call(ref_properties.validateStyle, ref_properties.extend({ key, style: this.serialize(), value, styleSpec: ref_properties.styleSpec }, props))); } _remove() { if (this._request) { this._request.cancel(); this._request = null; } if (this._spriteRequest) { this._spriteRequest.cancel(); this._spriteRequest = null; } ref_properties.evented.off('pluginStateChange', this._rtlTextPluginCallback); for (const layerId in this._layers) { const layer = this._layers[layerId]; layer.setEventedParent(null); } for (const id in this._sourceCaches) { this._sourceCaches[id].clearTiles(); this._sourceCaches[id].setEventedParent(null); } this.imageManager.setEventedParent(null); this.setEventedParent(null); this.dispatcher.remove(); } _clearSource(id) { const sourceCaches = this._getSourceCaches(id); for (const sourceCache of sourceCaches) { sourceCache.clearTiles(); } } _reloadSource(id) { const sourceCaches = this._getSourceCaches(id); for (const sourceCache of sourceCaches) { sourceCache.resume(); sourceCache.reload(); } } _updateSources(transform) { for (const id in this._sourceCaches) { this._sourceCaches[id].update(transform); } } _generateCollisionBoxes() { for (const id in this._sourceCaches) { const sourceCache = this._sourceCaches[id]; sourceCache.resume(); sourceCache.reload(); } } _updatePlacement(transform, showCollisionBoxes, fadeDuration, crossSourceCollisions, forceFullPlacement = false) { let symbolBucketsChanged = false; let placementCommitted = false; const layerTiles = {}; for (const layerID of this._order) { const styleLayer = this._layers[layerID]; if (styleLayer.type !== 'symbol') continue; if (!layerTiles[styleLayer.source]) { const sourceCache = this._getLayerSourceCache(styleLayer); if (!sourceCache) continue; layerTiles[styleLayer.source] = sourceCache.getRenderableIds(true).map(id => sourceCache.getTileByID(id)).sort((a, b) => b.tileID.overscaledZ - a.tileID.overscaledZ || (a.tileID.isLessThan(b.tileID) ? -1 : 1)); } const layerBucketsChanged = this.crossTileSymbolIndex.addLayer(styleLayer, layerTiles[styleLayer.source], transform.center.lng); symbolBucketsChanged = symbolBucketsChanged || layerBucketsChanged; } this.crossTileSymbolIndex.pruneUnusedLayers(this._order); forceFullPlacement = forceFullPlacement || this._layerOrderChanged || fadeDuration === 0; if (this._layerOrderChanged) { this.fire(new ref_properties.Event('neworder')); } if (forceFullPlacement || !this.pauseablePlacement || this.pauseablePlacement.isDone() && !this.placement.stillRecent(ref_properties.browser.now(), transform.zoom)) { this.pauseablePlacement = new PauseablePlacement(transform, this._order, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions, this.placement); this._layerOrderChanged = false; } if (this.pauseablePlacement.isDone()) { this.placement.setStale(); } else { this.pauseablePlacement.continuePlacement(this._order, this._layers, layerTiles); if (this.pauseablePlacement.isDone()) { this.placement = this.pauseablePlacement.commit(ref_properties.browser.now()); placementCommitted = true; } if (symbolBucketsChanged) { this.pauseablePlacement.placement.setStale(); } } if (placementCommitted || symbolBucketsChanged) { for (const layerID of this._order) { const styleLayer = this._layers[layerID]; if (styleLayer.type !== 'symbol') continue; this.placement.updateLayerOpacities(styleLayer, layerTiles[styleLayer.source]); } } const needsRerender = !this.pauseablePlacement.isDone() || this.placement.hasTransitions(ref_properties.browser.now()); return needsRerender; } _releaseSymbolFadeTiles() { for (const id in this._sourceCaches) { this._sourceCaches[id].releaseSymbolFadeTiles(); } } getImages(mapId, params, callback) { this.imageManager.getImages(params.icons, callback); this._updateTilesForChangedImages(); const setDependencies = sourceCache => { if (sourceCache) { sourceCache.setDependencies(params.tileID.key, params.type, params.icons); } }; setDependencies(this._otherSourceCaches[params.source]); setDependencies(this._symbolSourceCaches[params.source]); } getGlyphs(mapId, params, callback) { this.glyphManager.getGlyphs(params.stacks, callback); } getResource(mapId, params, callback) { return ref_properties.makeRequest(params, callback); } _getSourceCache(source) { return this._otherSourceCaches[source]; } _getLayerSourceCache(layer) { return layer.type === 'symbol' ? this._symbolSourceCaches[layer.source] : this._otherSourceCaches[layer.source]; } _getSourceCaches(source) { const sourceCaches = []; if (this._otherSourceCaches[source]) { sourceCaches.push(this._otherSourceCaches[source]); } if (this._symbolSourceCaches[source]) { sourceCaches.push(this._symbolSourceCaches[source]); } return sourceCaches; } has3DLayers() { return this._num3DLayers > 0; } hasSymbolLayers() { return this._numSymbolLayers > 0; } hasCircleLayers() { return this._numCircleLayers > 0; } } Style.getSourceType = getType; Style.setSourceType = setType; Style.registerForPluginStateChange = ref_properties.registerForPluginStateChange; var preludeFrag = "#ifdef GL_ES\nprecision mediump float;\n#else\n#if !defined(lowp)\n#define lowp\n#endif\n#if !defined(mediump)\n#define mediump\n#endif\n#if !defined(highp)\n#define highp\n#endif\n#endif\nconst float PI=3.141592653589793;"; var preludeVert = "#ifdef GL_ES\nprecision highp float;\n#else\n#if !defined(lowp)\n#define lowp\n#endif\n#if !defined(mediump)\n#define mediump\n#endif\n#if !defined(highp)\n#define highp\n#endif\n#endif\nvec2 unpack_float(const float packedValue) {int packedIntValue=int(packedValue);int v0=packedIntValue/256;return vec2(v0,packedIntValue-v0*256);}vec2 unpack_opacity(const float packedOpacity) {int intOpacity=int(packedOpacity)/2;return vec2(float(intOpacity)/127.0,mod(packedOpacity,2.0));}vec4 decode_color(const vec2 encodedColor) {return vec4(\nunpack_float(encodedColor[0])/255.0,unpack_float(encodedColor[1])/255.0\n);}float unpack_mix_vec2(const vec2 packedValue,const float t) {return mix(packedValue[0],packedValue[1],t);}vec4 unpack_mix_color(const vec4 packedColors,const float t) {vec4 minColor=decode_color(vec2(packedColors[0],packedColors[1]));vec4 maxColor=decode_color(vec2(packedColors[2],packedColors[3]));return mix(minColor,maxColor,t);}vec2 get_pattern_pos(const vec2 pixel_coord_upper,const vec2 pixel_coord_lower,const vec2 pattern_size,const float tile_units_to_pixels,const vec2 pos) {vec2 offset=mod(mod(mod(pixel_coord_upper,pattern_size)*256.0,pattern_size)*256.0+pixel_coord_lower,pattern_size);return (tile_units_to_pixels*pos+offset)/pattern_size;}const float PI=3.141592653589793;const vec4 AWAY=vec4(-1000.0,-1000.0,-1000.0,1);//Normalized device coordinate that is not rendered."; var backgroundFrag = "uniform vec4 u_color;uniform float u_opacity;void main() {gl_FragColor=u_color*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var backgroundVert = "attribute vec2 a_pos;uniform mat4 u_matrix;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);}"; var backgroundPatternFrag = "uniform vec2 u_pattern_tl_a;uniform vec2 u_pattern_br_a;uniform vec2 u_pattern_tl_b;uniform vec2 u_pattern_br_b;uniform vec2 u_texsize;uniform float u_mix;uniform float u_opacity;uniform sampler2D u_image;varying vec2 v_pos_a;varying vec2 v_pos_b;void main() {vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(u_pattern_tl_a/u_texsize,u_pattern_br_a/u_texsize,imagecoord);vec4 color1=texture2D(u_image,pos);vec2 imagecoord_b=mod(v_pos_b,1.0);vec2 pos2=mix(u_pattern_tl_b/u_texsize,u_pattern_br_b/u_texsize,imagecoord_b);vec4 color2=texture2D(u_image,pos2);gl_FragColor=mix(color1,color2,u_mix)*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var backgroundPatternVert = "uniform mat4 u_matrix;uniform vec2 u_pattern_size_a;uniform vec2 u_pattern_size_b;uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform float u_scale_a;uniform float u_scale_b;uniform float u_tile_units_to_pixels;attribute vec2 a_pos;varying vec2 v_pos_a;varying vec2 v_pos_b;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,u_scale_a*u_pattern_size_a,u_tile_units_to_pixels,a_pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,u_scale_b*u_pattern_size_b,u_tile_units_to_pixels,a_pos);}"; var circleFrag = "varying vec3 v_data;varying float v_visibility;\n#pragma mapbox: define highp vec4 color\n#pragma mapbox: define mediump float radius\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define highp vec4 stroke_color\n#pragma mapbox: define mediump float stroke_width\n#pragma mapbox: define lowp float stroke_opacity\nvoid main() {\n#pragma mapbox: initialize highp vec4 color\n#pragma mapbox: initialize mediump float radius\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize highp vec4 stroke_color\n#pragma mapbox: initialize mediump float stroke_width\n#pragma mapbox: initialize lowp float stroke_opacity\nvec2 extrude=v_data.xy;float extrude_length=length(extrude);lowp float antialiasblur=v_data.z;float antialiased_blur=-max(blur,antialiasblur);float opacity_t=smoothstep(0.0,antialiased_blur,extrude_length-1.0);float color_t=stroke_width < 0.01 ? 0.0 : smoothstep(\nantialiased_blur,0.0,extrude_length-radius/(radius+stroke_width)\n);gl_FragColor=v_visibility*opacity_t*mix(color*opacity,stroke_color*stroke_opacity,color_t);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var circleVert = "#define NUM_VISIBILITY_RINGS 2\n#define INV_SQRT2 0.70710678\n#define ELEVATION_BIAS 0.0001\n#define NUM_SAMPLES_PER_RING 16\nuniform mat4 u_matrix;uniform vec2 u_extrude_scale;uniform lowp float u_device_pixel_ratio;uniform highp float u_camera_to_center_distance;attribute vec2 a_pos;varying vec3 v_data;varying float v_visibility;\n#pragma mapbox: define highp vec4 color\n#pragma mapbox: define mediump float radius\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define highp vec4 stroke_color\n#pragma mapbox: define mediump float stroke_width\n#pragma mapbox: define lowp float stroke_opacity\nvec2 calc_offset(vec2 extrusion,float radius,float stroke_width, float view_scale) {return extrusion*(radius+stroke_width)*u_extrude_scale*view_scale;}float cantilevered_elevation(vec2 pos,float radius,float stroke_width,float view_scale) {vec2 c1=pos+calc_offset(vec2(-1,-1),radius,stroke_width,view_scale);vec2 c2=pos+calc_offset(vec2(1,-1),radius,stroke_width,view_scale);vec2 c3=pos+calc_offset(vec2(1,1),radius,stroke_width,view_scale);vec2 c4=pos+calc_offset(vec2(-1,1),radius,stroke_width,view_scale);float h1=elevation(c1)+ELEVATION_BIAS;float h2=elevation(c2)+ELEVATION_BIAS;float h3=elevation(c3)+ELEVATION_BIAS;float h4=elevation(c4)+ELEVATION_BIAS;return max(h4,max(h3,max(h1,h2)));}float circle_elevation(vec2 pos) {\n#if defined(TERRAIN)\nreturn elevation(pos)+ELEVATION_BIAS;\n#else\nreturn 0.0;\n#endif\n}vec4 project_vertex(vec2 extrusion,vec4 world_center,vec4 projected_center,float radius,float stroke_width, float view_scale) {vec2 sample_offset=calc_offset(extrusion,radius,stroke_width,view_scale);\n#ifdef PITCH_WITH_MAP\nreturn u_matrix*( world_center+vec4(sample_offset,0,0) );\n#else\nreturn projected_center+vec4(sample_offset,0,0);\n#endif\n}float get_sample_step() {\n#ifdef PITCH_WITH_MAP\nreturn 2.0*PI/float(NUM_SAMPLES_PER_RING);\n#else\nreturn PI/float(NUM_SAMPLES_PER_RING);\n#endif\n}void main(void) {\n#pragma mapbox: initialize highp vec4 color\n#pragma mapbox: initialize mediump float radius\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize highp vec4 stroke_color\n#pragma mapbox: initialize mediump float stroke_width\n#pragma mapbox: initialize lowp float stroke_opacity\nvec2 extrude=vec2(mod(a_pos,2.0)*2.0-1.0);vec2 circle_center=floor(a_pos*0.5);float height=circle_elevation(circle_center);vec4 world_center=vec4(circle_center,height,1);vec4 projected_center=u_matrix*world_center;float view_scale=0.0;\n#ifdef PITCH_WITH_MAP\n#ifdef SCALE_WITH_MAP\nview_scale=1.0;\n#else\nview_scale=projected_center.w/u_camera_to_center_distance;\n#endif\n#else\n#ifdef SCALE_WITH_MAP\nview_scale=u_camera_to_center_distance;\n#else\nview_scale=projected_center.w;\n#endif\n#endif\ngl_Position=project_vertex(extrude,world_center,projected_center,radius,stroke_width,view_scale);float visibility=0.0;\n#ifdef TERRAIN\nfloat step=get_sample_step();\n#ifdef PITCH_WITH_MAP\nfloat cantilevered_height=cantilevered_elevation(circle_center,radius,stroke_width,view_scale);vec4 occlusion_world_center=vec4(circle_center,cantilevered_height,1);vec4 occlusion_projected_center=u_matrix*occlusion_world_center;\n#else\nvec4 occlusion_world_center=world_center;vec4 occlusion_projected_center=projected_center;\n#endif\nfor(int ring=0; ring < NUM_VISIBILITY_RINGS; ring++) {float scale=(float(ring)+1.0)/float(NUM_VISIBILITY_RINGS);for(int i=0; i < NUM_SAMPLES_PER_RING; i++) {vec2 extrusion=vec2(cos(step*float(i)),-sin(step*float(i)))*scale;vec4 frag_pos=project_vertex(extrusion,occlusion_world_center,occlusion_projected_center,radius,stroke_width,view_scale);visibility+=float(!isOccluded(frag_pos));}}visibility/=float(NUM_VISIBILITY_RINGS)*float(NUM_SAMPLES_PER_RING);\n#else\nvisibility=1.0;\n#endif\nv_visibility=visibility;lowp float antialiasblur=1.0/u_device_pixel_ratio/(radius+stroke_width);v_data=vec3(extrude.x,extrude.y,antialiasblur);}"; var clippingMaskFrag = "void main() {gl_FragColor=vec4(1.0);}"; var clippingMaskVert = "attribute vec2 a_pos;uniform mat4 u_matrix;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);}"; var heatmapFrag = "uniform highp float u_intensity;varying vec2 v_extrude;\n#pragma mapbox: define highp float weight\n#define GAUSS_COEF 0.3989422804014327\nvoid main() {\n#pragma mapbox: initialize highp float weight\nfloat d=-0.5*3.0*3.0*dot(v_extrude,v_extrude);float val=weight*u_intensity*GAUSS_COEF*exp(d);gl_FragColor=vec4(val,1.0,1.0,1.0);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var heatmapVert = "uniform mat4 u_matrix;uniform float u_extrude_scale;uniform float u_opacity;uniform float u_intensity;attribute vec2 a_pos;varying vec2 v_extrude;\n#pragma mapbox: define highp float weight\n#pragma mapbox: define mediump float radius\nconst highp float ZERO=1.0/255.0/16.0;\n#define GAUSS_COEF 0.3989422804014327\nvoid main(void) {\n#pragma mapbox: initialize highp float weight\n#pragma mapbox: initialize mediump float radius\nvec2 unscaled_extrude=vec2(mod(a_pos,2.0)*2.0-1.0);float S=sqrt(-2.0*log(ZERO/weight/u_intensity/GAUSS_COEF))/3.0;v_extrude=S*unscaled_extrude;vec2 extrude=v_extrude*radius*u_extrude_scale;vec4 pos=vec4(floor(a_pos*0.5)+extrude,elevation(floor(a_pos*0.5)),1);gl_Position=u_matrix*pos;}"; var heatmapTextureFrag = "uniform sampler2D u_image;uniform sampler2D u_color_ramp;uniform float u_opacity;varying vec2 v_pos;void main() {float t=texture2D(u_image,v_pos).r;vec4 color=texture2D(u_color_ramp,vec2(t,0.5));gl_FragColor=color*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(0.0);\n#endif\n}"; var heatmapTextureVert = "uniform mat4 u_matrix;uniform vec2 u_world;attribute vec2 a_pos;varying vec2 v_pos;void main() {gl_Position=u_matrix*vec4(a_pos*u_world,0,1);v_pos.x=a_pos.x;v_pos.y=1.0-a_pos.y;}"; var collisionBoxFrag = "varying float v_placed;varying float v_notUsed;void main() {vec4 red =vec4(1.0,0.0,0.0,1.0);vec4 blue=vec4(0.0,0.0,1.0,0.5);gl_FragColor =mix(red,blue,step(0.5,v_placed))*0.5;gl_FragColor*=mix(1.0,0.1,step(0.5,v_notUsed));}"; var collisionBoxVert = "attribute vec2 a_pos;attribute vec2 a_anchor_pos;attribute vec2 a_extrude;attribute vec2 a_placed;attribute vec2 a_shift;attribute float a_size_scale;attribute vec2 a_padding;uniform mat4 u_matrix;uniform vec2 u_extrude_scale;uniform float u_camera_to_center_distance;varying float v_placed;varying float v_notUsed;void main() {vec4 projectedPoint=u_matrix*vec4(a_anchor_pos,elevation(a_anchor_pos),1);highp float camera_to_anchor_distance=projectedPoint.w;highp float collision_perspective_ratio=clamp(\n0.5+0.5*(u_camera_to_center_distance/camera_to_anchor_distance),0.0,1.5);gl_Position=u_matrix*vec4(a_pos,elevation(a_pos),1.0);gl_Position.xy+=(a_extrude*a_size_scale+a_shift+a_padding)*u_extrude_scale*gl_Position.w*collision_perspective_ratio;v_placed=a_placed.x;v_notUsed=a_placed.y;}"; var collisionCircleFrag = "varying float v_radius;varying vec2 v_extrude;varying float v_perspective_ratio;varying float v_collision;void main() {float alpha=0.5*min(v_perspective_ratio,1.0);float stroke_radius=0.9*max(v_perspective_ratio,1.0);float distance_to_center=length(v_extrude);float distance_to_edge=abs(distance_to_center-v_radius);float opacity_t=smoothstep(-stroke_radius,0.0,-distance_to_edge);vec4 color=mix(vec4(0.0,0.0,1.0,0.5),vec4(1.0,0.0,0.0,1.0),v_collision);gl_FragColor=color*alpha*opacity_t;}"; var collisionCircleVert = "attribute vec2 a_pos_2f;attribute float a_radius;attribute vec2 a_flags;uniform mat4 u_matrix;uniform mat4 u_inv_matrix;uniform vec2 u_viewport_size;uniform float u_camera_to_center_distance;varying float v_radius;varying vec2 v_extrude;varying float v_perspective_ratio;varying float v_collision;vec3 toTilePosition(vec2 screenPos) {vec4 rayStart=u_inv_matrix*vec4(screenPos,-1.0,1.0);vec4 rayEnd =u_inv_matrix*vec4(screenPos, 1.0,1.0);rayStart.xyz/=rayStart.w;rayEnd.xyz /=rayEnd.w;highp float t=(0.0-rayStart.z)/(rayEnd.z-rayStart.z);return mix(rayStart.xyz,rayEnd.xyz,t);}void main() {vec2 quadCenterPos=a_pos_2f;float radius=a_radius;float collision=a_flags.x;float vertexIdx=a_flags.y;vec2 quadVertexOffset=vec2(\nmix(-1.0,1.0,float(vertexIdx >=2.0)),mix(-1.0,1.0,float(vertexIdx >=1.0 && vertexIdx <=2.0)));vec2 quadVertexExtent=quadVertexOffset*radius;vec3 tilePos=toTilePosition(quadCenterPos);vec4 clipPos=u_matrix*vec4(tilePos,1.0);highp float camera_to_anchor_distance=clipPos.w;highp float collision_perspective_ratio=clamp(\n0.5+0.5*(u_camera_to_center_distance/camera_to_anchor_distance),0.0,4.0);float padding_factor=1.2;v_radius=radius;v_extrude=quadVertexExtent*padding_factor;v_perspective_ratio=collision_perspective_ratio;v_collision=collision;gl_Position=vec4(clipPos.xyz/clipPos.w,1.0)+vec4(quadVertexExtent*padding_factor/u_viewport_size*2.0,0.0,0.0);}"; var debugFrag = "uniform highp vec4 u_color;uniform sampler2D u_overlay;varying vec2 v_uv;void main() {vec4 overlay_color=texture2D(u_overlay,v_uv);gl_FragColor=mix(u_color,overlay_color,overlay_color.a);}"; var debugVert = "attribute vec2 a_pos;varying vec2 v_uv;uniform mat4 u_matrix;uniform float u_overlay_scale;void main() {float h=elevation(a_pos);v_uv=a_pos/8192.0;gl_Position=u_matrix*vec4(a_pos*u_overlay_scale,h,1);}"; var fillFrag = "#pragma mapbox: define highp vec4 color\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize highp vec4 color\n#pragma mapbox: initialize lowp float opacity\ngl_FragColor=color*opacity;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var fillVert = "attribute vec2 a_pos;uniform mat4 u_matrix;\n#pragma mapbox: define highp vec4 color\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize highp vec4 color\n#pragma mapbox: initialize lowp float opacity\ngl_Position=u_matrix*vec4(a_pos,0,1);}"; var fillOutlineFrag = "varying vec2 v_pos;\n#pragma mapbox: define highp vec4 outline_color\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize highp vec4 outline_color\n#pragma mapbox: initialize lowp float opacity\nfloat dist=length(v_pos-gl_FragCoord.xy);float alpha=1.0-smoothstep(0.0,1.0,dist);gl_FragColor=outline_color*(alpha*opacity);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var fillOutlineVert = "attribute vec2 a_pos;uniform mat4 u_matrix;uniform vec2 u_world;varying vec2 v_pos;\n#pragma mapbox: define highp vec4 outline_color\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize highp vec4 outline_color\n#pragma mapbox: initialize lowp float opacity\ngl_Position=u_matrix*vec4(a_pos,0,1);v_pos=(gl_Position.xy/gl_Position.w+1.0)/2.0*u_world;}"; var fillOutlinePatternFrag = "uniform vec2 u_texsize;uniform sampler2D u_image;uniform float u_fade;varying vec2 v_pos_a;varying vec2 v_pos_b;varying vec2 v_pos;\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp vec4 pattern_from\n#pragma mapbox: define lowp vec4 pattern_to\nvoid main() {\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize mediump vec4 pattern_from\n#pragma mapbox: initialize mediump vec4 pattern_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(pattern_tl_a/u_texsize,pattern_br_a/u_texsize,imagecoord);vec4 color1=texture2D(u_image,pos);vec2 imagecoord_b=mod(v_pos_b,1.0);vec2 pos2=mix(pattern_tl_b/u_texsize,pattern_br_b/u_texsize,imagecoord_b);vec4 color2=texture2D(u_image,pos2);float dist=length(v_pos-gl_FragCoord.xy);float alpha=1.0-smoothstep(0.0,1.0,dist);gl_FragColor=mix(color1,color2,u_fade)*alpha*opacity;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var fillOutlinePatternVert = "uniform mat4 u_matrix;uniform vec2 u_world;uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;attribute vec2 a_pos;varying vec2 v_pos_a;varying vec2 v_pos_b;varying vec2 v_pos;\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp vec4 pattern_from\n#pragma mapbox: define lowp vec4 pattern_to\n#pragma mapbox: define lowp float pixel_ratio_from\n#pragma mapbox: define lowp float pixel_ratio_to\nvoid main() {\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize mediump vec4 pattern_from\n#pragma mapbox: initialize mediump vec4 pattern_to\n#pragma mapbox: initialize lowp float pixel_ratio_from\n#pragma mapbox: initialize lowp float pixel_ratio_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;float tileRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;gl_Position=u_matrix*vec4(a_pos,0,1);vec2 display_size_a=(pattern_br_a-pattern_tl_a)/pixel_ratio_from;vec2 display_size_b=(pattern_br_b-pattern_tl_b)/pixel_ratio_to;v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,fromScale*display_size_a,tileRatio,a_pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,toScale*display_size_b,tileRatio,a_pos);v_pos=(gl_Position.xy/gl_Position.w+1.0)/2.0*u_world;}"; var fillPatternFrag = "uniform vec2 u_texsize;uniform float u_fade;uniform sampler2D u_image;varying vec2 v_pos_a;varying vec2 v_pos_b;\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp vec4 pattern_from\n#pragma mapbox: define lowp vec4 pattern_to\nvoid main() {\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize mediump vec4 pattern_from\n#pragma mapbox: initialize mediump vec4 pattern_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(pattern_tl_a/u_texsize,pattern_br_a/u_texsize,imagecoord);vec4 color1=texture2D(u_image,pos);vec2 imagecoord_b=mod(v_pos_b,1.0);vec2 pos2=mix(pattern_tl_b/u_texsize,pattern_br_b/u_texsize,imagecoord_b);vec4 color2=texture2D(u_image,pos2);gl_FragColor=mix(color1,color2,u_fade)*opacity;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var fillPatternVert = "uniform mat4 u_matrix;uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;attribute vec2 a_pos;varying vec2 v_pos_a;varying vec2 v_pos_b;\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp vec4 pattern_from\n#pragma mapbox: define lowp vec4 pattern_to\n#pragma mapbox: define lowp float pixel_ratio_from\n#pragma mapbox: define lowp float pixel_ratio_to\nvoid main() {\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize mediump vec4 pattern_from\n#pragma mapbox: initialize mediump vec4 pattern_to\n#pragma mapbox: initialize lowp float pixel_ratio_from\n#pragma mapbox: initialize lowp float pixel_ratio_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;float tileZoomRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;vec2 display_size_a=(pattern_br_a-pattern_tl_a)/pixel_ratio_from;vec2 display_size_b=(pattern_br_b-pattern_tl_b)/pixel_ratio_to;gl_Position=u_matrix*vec4(a_pos,0,1);v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,fromScale*display_size_a,tileZoomRatio,a_pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,toScale*display_size_b,tileZoomRatio,a_pos);}"; var fillExtrusionFrag = "varying vec4 v_color;void main() {gl_FragColor=v_color;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var fillExtrusionVert = "uniform mat4 u_matrix;uniform vec3 u_lightcolor;uniform lowp vec3 u_lightpos;uniform lowp float u_lightintensity;uniform float u_vertical_gradient;uniform lowp float u_opacity;attribute vec4 a_pos_normal_ed;attribute vec2 a_centroid_pos;varying vec4 v_color;\n#pragma mapbox: define highp float base\n#pragma mapbox: define highp float height\n#pragma mapbox: define highp vec4 color\nvoid main() {\n#pragma mapbox: initialize highp float base\n#pragma mapbox: initialize highp float height\n#pragma mapbox: initialize highp vec4 color\nvec3 pos_nx=floor(a_pos_normal_ed.xyz*0.5);mediump vec3 top_up_ny=a_pos_normal_ed.xyz-2.0*pos_nx;float x_normal=pos_nx.z/8192.0;vec3 normal=top_up_ny.y==1.0 ? vec3(0.0,0.0,1.0) : normalize(vec3(x_normal,(2.0*top_up_ny.z-1.0)*(1.0-abs(x_normal)),0.0));base=max(0.0,base);height=max(0.0,height);float t=top_up_ny.x;\n#ifdef TERRAIN\nvec2 centroid_pos=a_centroid_pos;bool flat_roof=centroid_pos.x !=0.0;float ele=elevation(pos_nx.xy);float hidden=float(centroid_pos.x==0.0 && centroid_pos.y==1.0);float c_ele=flat_roof ? centroid_pos.y==0.0 ? elevationFromUint16(centroid_pos.x) : flatElevation(centroid_pos) : ele;float h=flat_roof ? max(c_ele+height,ele+base+2.0) : ele+(t > 0.0 ? height : base==0.0 ?-5.0 : base);gl_Position=mix(u_matrix*vec4(pos_nx.xy,h,1),AWAY,hidden);\n#else\ngl_Position=u_matrix*vec4(pos_nx.xy,t > 0.0 ? height : base,1);\n#endif\nfloat colorvalue=color.r*0.2126+color.g*0.7152+color.b*0.0722;v_color=vec4(0.0,0.0,0.0,1.0);vec4 ambientlight=vec4(0.03,0.03,0.03,1.0);color+=ambientlight;float directional=clamp(dot(normal,u_lightpos),0.0,1.0);directional=mix((1.0-u_lightintensity),max((1.0-colorvalue+u_lightintensity),1.0),directional);if (normal.y !=0.0) {directional*=(\n(1.0-u_vertical_gradient)+(u_vertical_gradient*clamp((t+base)*pow(height/150.0,0.5),mix(0.7,0.98,1.0-u_lightintensity),1.0)));}v_color.r+=clamp(color.r*directional*u_lightcolor.r,mix(0.0,0.3,1.0-u_lightcolor.r),1.0);v_color.g+=clamp(color.g*directional*u_lightcolor.g,mix(0.0,0.3,1.0-u_lightcolor.g),1.0);v_color.b+=clamp(color.b*directional*u_lightcolor.b,mix(0.0,0.3,1.0-u_lightcolor.b),1.0);v_color*=u_opacity;}"; var fillExtrusionPatternFrag = "uniform vec2 u_texsize;uniform float u_fade;uniform sampler2D u_image;varying vec2 v_pos_a;varying vec2 v_pos_b;varying vec4 v_lighting;\n#pragma mapbox: define lowp float base\n#pragma mapbox: define lowp float height\n#pragma mapbox: define lowp vec4 pattern_from\n#pragma mapbox: define lowp vec4 pattern_to\n#pragma mapbox: define lowp float pixel_ratio_from\n#pragma mapbox: define lowp float pixel_ratio_to\nvoid main() {\n#pragma mapbox: initialize lowp float base\n#pragma mapbox: initialize lowp float height\n#pragma mapbox: initialize mediump vec4 pattern_from\n#pragma mapbox: initialize mediump vec4 pattern_to\n#pragma mapbox: initialize lowp float pixel_ratio_from\n#pragma mapbox: initialize lowp float pixel_ratio_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;vec2 imagecoord=mod(v_pos_a,1.0);vec2 pos=mix(pattern_tl_a/u_texsize,pattern_br_a/u_texsize,imagecoord);vec4 color1=texture2D(u_image,pos);vec2 imagecoord_b=mod(v_pos_b,1.0);vec2 pos2=mix(pattern_tl_b/u_texsize,pattern_br_b/u_texsize,imagecoord_b);vec4 color2=texture2D(u_image,pos2);vec4 mixedColor=mix(color1,color2,u_fade);gl_FragColor=mixedColor*v_lighting;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var fillExtrusionPatternVert = "uniform mat4 u_matrix;uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform float u_height_factor;uniform vec3 u_scale;uniform float u_vertical_gradient;uniform lowp float u_opacity;uniform vec3 u_lightcolor;uniform lowp vec3 u_lightpos;uniform lowp float u_lightintensity;attribute vec4 a_pos_normal_ed;attribute vec2 a_centroid_pos;varying vec2 v_pos_a;varying vec2 v_pos_b;varying vec4 v_lighting;\n#pragma mapbox: define lowp float base\n#pragma mapbox: define lowp float height\n#pragma mapbox: define lowp vec4 pattern_from\n#pragma mapbox: define lowp vec4 pattern_to\n#pragma mapbox: define lowp float pixel_ratio_from\n#pragma mapbox: define lowp float pixel_ratio_to\nvoid main() {\n#pragma mapbox: initialize lowp float base\n#pragma mapbox: initialize lowp float height\n#pragma mapbox: initialize mediump vec4 pattern_from\n#pragma mapbox: initialize mediump vec4 pattern_to\n#pragma mapbox: initialize lowp float pixel_ratio_from\n#pragma mapbox: initialize lowp float pixel_ratio_to\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;float tileRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;vec3 pos_nx=floor(a_pos_normal_ed.xyz*0.5);mediump vec3 top_up_ny=a_pos_normal_ed.xyz-2.0*pos_nx;float x_normal=pos_nx.z/8192.0;vec3 normal=top_up_ny.y==1.0 ? vec3(0.0,0.0,1.0) : normalize(vec3(x_normal,(2.0*top_up_ny.z-1.0)*(1.0-abs(x_normal)),0.0));float edgedistance=a_pos_normal_ed.w;vec2 display_size_a=(pattern_br_a-pattern_tl_a)/pixel_ratio_from;vec2 display_size_b=(pattern_br_b-pattern_tl_b)/pixel_ratio_to;base=max(0.0,base);height=max(0.0,height);float t=top_up_ny.x;float z=t > 0.0 ? height : base;\n#ifdef TERRAIN\nvec2 centroid_pos=a_centroid_pos;bool flat_roof=centroid_pos.x !=0.0;float ele=elevation(pos_nx.xy);float hidden=float(centroid_pos.x==0.0 && centroid_pos.y==1.0);float c_ele=flat_roof ? centroid_pos.y==0.0 ? elevationFromUint16(centroid_pos.x) : flatElevation(centroid_pos) : ele;float h=flat_roof ? max(c_ele+height,ele+base+2.0) : ele+(t > 0.0 ? height : base==0.0 ?-5.0 : base);gl_Position=mix(u_matrix*vec4(pos_nx.xy,h,1),AWAY,hidden);\n#else\ngl_Position=u_matrix*vec4(pos_nx.xy,z,1);\n#endif\nvec2 pos=normal.z==1.0\n? pos_nx.xy\n: vec2(edgedistance,z*u_height_factor);v_pos_a=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,fromScale*display_size_a,tileRatio,pos);v_pos_b=get_pattern_pos(u_pixel_coord_upper,u_pixel_coord_lower,toScale*display_size_b,tileRatio,pos);v_lighting=vec4(0.0,0.0,0.0,1.0);float directional=clamp(dot(normal,u_lightpos),0.0,1.0);directional=mix((1.0-u_lightintensity),max((0.5+u_lightintensity),1.0),directional);if (normal.y !=0.0) {directional*=(\n(1.0-u_vertical_gradient)+(u_vertical_gradient*clamp((t+base)*pow(height/150.0,0.5),mix(0.7,0.98,1.0-u_lightintensity),1.0)));}v_lighting.rgb+=clamp(directional*u_lightcolor,mix(vec3(0.0),vec3(0.3),1.0-u_lightcolor),vec3(1.0));v_lighting*=u_opacity;}"; var hillshadePrepareFrag = "#ifdef GL_ES\nprecision highp float;\n#endif\nuniform sampler2D u_image;varying vec2 v_pos;uniform vec2 u_dimension;uniform float u_zoom;uniform vec4 u_unpack;float getElevation(vec2 coord,float bias) {vec4 data=texture2D(u_image,coord)*255.0;data.a=-1.0;return dot(data,u_unpack)/4.0;}void main() {vec2 epsilon=1.0/u_dimension;float a=getElevation(v_pos+vec2(-epsilon.x,-epsilon.y),0.0);float b=getElevation(v_pos+vec2(0,-epsilon.y),0.0);float c=getElevation(v_pos+vec2(epsilon.x,-epsilon.y),0.0);float d=getElevation(v_pos+vec2(-epsilon.x,0),0.0);float e=getElevation(v_pos,0.0);float f=getElevation(v_pos+vec2(epsilon.x,0),0.0);float g=getElevation(v_pos+vec2(-epsilon.x,epsilon.y),0.0);float h=getElevation(v_pos+vec2(0,epsilon.y),0.0);float i=getElevation(v_pos+vec2(epsilon.x,epsilon.y),0.0);float exaggerationFactor=u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3;float exaggeration=u_zoom < 15.0 ? (u_zoom-15.0)*exaggerationFactor : 0.0;vec2 deriv=vec2(\n(c+f+f+i)-(a+d+d+g),(g+h+h+i)-(a+b+b+c)\n)/pow(2.0,exaggeration+(19.2562-u_zoom));gl_FragColor=clamp(vec4(\nderiv.x/2.0+0.5,deriv.y/2.0+0.5,1.0,1.0),0.0,1.0);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var hillshadePrepareVert = "uniform mat4 u_matrix;uniform vec2 u_dimension;attribute vec2 a_pos;attribute vec2 a_texture_pos;varying vec2 v_pos;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);highp vec2 epsilon=1.0/u_dimension;float scale=(u_dimension.x-2.0)/u_dimension.x;v_pos=(a_texture_pos/8192.0)*scale+epsilon;}"; var hillshadeFrag = "uniform sampler2D u_image;varying vec2 v_pos;uniform vec2 u_latrange;uniform vec2 u_light;uniform vec4 u_shadow;uniform vec4 u_highlight;uniform vec4 u_accent;void main() {vec4 pixel=texture2D(u_image,v_pos);vec2 deriv=((pixel.rg*2.0)-1.0);float scaleFactor=cos(radians((u_latrange[0]-u_latrange[1])*(1.0-v_pos.y)+u_latrange[1]));float slope=atan(1.25*length(deriv)/scaleFactor);float aspect=deriv.x !=0.0 ? atan(deriv.y,-deriv.x) : PI/2.0*(deriv.y > 0.0 ? 1.0 :-1.0);float intensity=u_light.x;float azimuth=u_light.y+PI;float base=1.875-intensity*1.75;float maxValue=0.5*PI;float scaledSlope=intensity !=0.5 ? ((pow(base,slope)-1.0)/(pow(base,maxValue)-1.0))*maxValue : slope;float accent=cos(scaledSlope);vec4 accent_color=(1.0-accent)*u_accent*clamp(intensity*2.0,0.0,1.0);float shade=abs(mod((aspect+azimuth)/PI+0.5,2.0)-1.0);vec4 shade_color=mix(u_shadow,u_highlight,shade)*sin(scaledSlope)*clamp(intensity*2.0,0.0,1.0);gl_FragColor=accent_color*(1.0-shade_color.a)+shade_color;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var hillshadeVert = "uniform mat4 u_matrix;attribute vec2 a_pos;attribute vec2 a_texture_pos;varying vec2 v_pos;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);v_pos=a_texture_pos/8192.0;}"; var lineFrag = "uniform lowp float u_device_pixel_ratio;varying vec2 v_width2;varying vec2 v_normal;varying float v_gamma_scale;\n#pragma mapbox: define highp vec4 color\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize highp vec4 color\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\nfloat dist=length(v_normal)*v_width2.s;float blur2=(blur+1.0/u_device_pixel_ratio)*v_gamma_scale;float alpha=clamp(min(dist-(v_width2.t-blur2),v_width2.s-dist)/blur2,0.0,1.0);gl_FragColor=color*(alpha*opacity);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var lineVert = "\n#define scale 0.015873016\nattribute vec2 a_pos_normal;attribute vec4 a_data;uniform mat4 u_matrix;uniform mediump float u_ratio;uniform vec2 u_units_to_pixels;uniform lowp float u_device_pixel_ratio;varying vec2 v_normal;varying vec2 v_width2;varying float v_gamma_scale;\n#pragma mapbox: define highp vec4 color\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define mediump float gapwidth\n#pragma mapbox: define lowp float offset\n#pragma mapbox: define mediump float width\nvoid main() {\n#pragma mapbox: initialize highp vec4 color\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize mediump float gapwidth\n#pragma mapbox: initialize lowp float offset\n#pragma mapbox: initialize mediump float width\nfloat ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;normal.y=normal.y*2.0-1.0;v_normal=normal;gapwidth=gapwidth/2.0;float halfwidth=width/2.0;offset=-1.0*offset;float inset=gapwidth+(gapwidth > 0.0 ? ANTIALIASING : 0.0);float outset=gapwidth+halfwidth*(gapwidth > 0.0 ? 2.0 : 1.0)+(halfwidth==0.0 ? 0.0 : ANTIALIASING);mediump vec2 dist=outset*a_extrude*scale;mediump float u=0.5*a_direction;mediump float t=1.0-abs(u);mediump vec2 offset2=offset*a_extrude*scale*normal.y*mat2(t,-u,u,t);vec4 projected_extrude=u_matrix*vec4(dist/u_ratio,0.0,0.0);gl_Position=u_matrix*vec4(pos+offset2/u_ratio,0.0,1.0)+projected_extrude;\n#ifndef RENDER_TO_TEXTURE\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length(projected_extrude.xy/gl_Position.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#else\nv_gamma_scale=1.0;\n#endif\nv_width2=vec2(outset,inset);}"; var lineGradientFrag = "uniform lowp float u_device_pixel_ratio;uniform sampler2D u_image;varying vec2 v_width2;varying vec2 v_normal;varying float v_gamma_scale;varying highp vec2 v_uv;\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\nfloat dist=length(v_normal)*v_width2.s;float blur2=(blur+1.0/u_device_pixel_ratio)*v_gamma_scale;float alpha=clamp(min(dist-(v_width2.t-blur2),v_width2.s-dist)/blur2,0.0,1.0);vec4 color=texture2D(u_image,v_uv);gl_FragColor=color*(alpha*opacity);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var lineGradientVert = "\n#define scale 0.015873016\nattribute vec2 a_pos_normal;attribute vec4 a_data;attribute float a_uv_x;attribute float a_split_index;uniform mat4 u_matrix;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;uniform vec2 u_units_to_pixels;uniform float u_image_height;varying vec2 v_normal;varying vec2 v_width2;varying float v_gamma_scale;varying highp vec2 v_uv;\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define mediump float gapwidth\n#pragma mapbox: define lowp float offset\n#pragma mapbox: define mediump float width\nvoid main() {\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize mediump float gapwidth\n#pragma mapbox: initialize lowp float offset\n#pragma mapbox: initialize mediump float width\nfloat ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;highp float texel_height=1.0/u_image_height;highp float half_texel_height=0.5*texel_height;v_uv=vec2(a_uv_x,a_split_index*texel_height-half_texel_height);vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;normal.y=normal.y*2.0-1.0;v_normal=normal;gapwidth=gapwidth/2.0;float halfwidth=width/2.0;offset=-1.0*offset;float inset=gapwidth+(gapwidth > 0.0 ? ANTIALIASING : 0.0);float outset=gapwidth+halfwidth*(gapwidth > 0.0 ? 2.0 : 1.0)+(halfwidth==0.0 ? 0.0 : ANTIALIASING);mediump vec2 dist=outset*a_extrude*scale;mediump float u=0.5*a_direction;mediump float t=1.0-abs(u);mediump vec2 offset2=offset*a_extrude*scale*normal.y*mat2(t,-u,u,t);vec4 projected_extrude=u_matrix*vec4(dist/u_ratio,0.0,0.0);gl_Position=u_matrix*vec4(pos+offset2/u_ratio,0.0,1.0)+projected_extrude;\n#ifndef RENDER_TO_TEXTURE\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length(projected_extrude.xy/gl_Position.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#else\nv_gamma_scale=1.0;\n#endif\nv_width2=vec2(outset,inset);}"; var linePatternFrag = "uniform lowp float u_device_pixel_ratio;uniform vec2 u_texsize;uniform float u_fade;uniform mediump vec3 u_scale;uniform sampler2D u_image;varying vec2 v_normal;varying vec2 v_width2;varying float v_linesofar;varying float v_gamma_scale;varying float v_width;\n#pragma mapbox: define lowp vec4 pattern_from\n#pragma mapbox: define lowp vec4 pattern_to\n#pragma mapbox: define lowp float pixel_ratio_from\n#pragma mapbox: define lowp float pixel_ratio_to\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize mediump vec4 pattern_from\n#pragma mapbox: initialize mediump vec4 pattern_to\n#pragma mapbox: initialize lowp float pixel_ratio_from\n#pragma mapbox: initialize lowp float pixel_ratio_to\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\nvec2 pattern_tl_a=pattern_from.xy;vec2 pattern_br_a=pattern_from.zw;vec2 pattern_tl_b=pattern_to.xy;vec2 pattern_br_b=pattern_to.zw;float tileZoomRatio=u_scale.x;float fromScale=u_scale.y;float toScale=u_scale.z;vec2 display_size_a=(pattern_br_a-pattern_tl_a)/pixel_ratio_from;vec2 display_size_b=(pattern_br_b-pattern_tl_b)/pixel_ratio_to;vec2 pattern_size_a=vec2(display_size_a.x*fromScale/tileZoomRatio,display_size_a.y);vec2 pattern_size_b=vec2(display_size_b.x*toScale/tileZoomRatio,display_size_b.y);float aspect_a=display_size_a.y/v_width;float aspect_b=display_size_b.y/v_width;float dist=length(v_normal)*v_width2.s;float blur2=(blur+1.0/u_device_pixel_ratio)*v_gamma_scale;float alpha=clamp(min(dist-(v_width2.t-blur2),v_width2.s-dist)/blur2,0.0,1.0);float x_a=mod(v_linesofar/pattern_size_a.x*aspect_a,1.0);float x_b=mod(v_linesofar/pattern_size_b.x*aspect_b,1.0);float y=0.5*v_normal.y+0.5;vec2 texel_size=1.0/u_texsize;vec2 pos_a=mix(pattern_tl_a*texel_size-texel_size,pattern_br_a*texel_size+texel_size,vec2(x_a,y));vec2 pos_b=mix(pattern_tl_b*texel_size-texel_size,pattern_br_b*texel_size+texel_size,vec2(x_b,y));vec4 color=mix(texture2D(u_image,pos_a),texture2D(u_image,pos_b),u_fade);gl_FragColor=color*alpha*opacity;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var linePatternVert = "\n#define scale 0.015873016\nattribute vec2 a_pos_normal;attribute vec4 a_data;attribute float a_linesofar;uniform mat4 u_matrix;uniform vec2 u_units_to_pixels;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;varying vec2 v_normal;varying vec2 v_width2;varying float v_linesofar;varying float v_gamma_scale;varying float v_width;\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp float offset\n#pragma mapbox: define mediump float gapwidth\n#pragma mapbox: define mediump float width\n#pragma mapbox: define lowp float floorwidth\n#pragma mapbox: define lowp vec4 pattern_from\n#pragma mapbox: define lowp vec4 pattern_to\n#pragma mapbox: define lowp float pixel_ratio_from\n#pragma mapbox: define lowp float pixel_ratio_to\nvoid main() {\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize lowp float offset\n#pragma mapbox: initialize mediump float gapwidth\n#pragma mapbox: initialize mediump float width\n#pragma mapbox: initialize lowp float floorwidth\n#pragma mapbox: initialize mediump vec4 pattern_from\n#pragma mapbox: initialize mediump vec4 pattern_to\n#pragma mapbox: initialize lowp float pixel_ratio_from\n#pragma mapbox: initialize lowp float pixel_ratio_to\nfloat ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;normal.y=normal.y*2.0-1.0;v_normal=normal;gapwidth=gapwidth/2.0;float halfwidth=width/2.0;offset=-1.0*offset;float inset=gapwidth+(gapwidth > 0.0 ? ANTIALIASING : 0.0);float outset=gapwidth+halfwidth*(gapwidth > 0.0 ? 2.0 : 1.0)+(halfwidth==0.0 ? 0.0 : ANTIALIASING);mediump vec2 dist=outset*a_extrude*scale;mediump float u=0.5*a_direction;mediump float t=1.0-abs(u);mediump vec2 offset2=offset*a_extrude*scale*normal.y*mat2(t,-u,u,t);vec4 projected_extrude=u_matrix*vec4(dist/u_ratio,0.0,0.0);gl_Position=u_matrix*vec4(pos+offset2/u_ratio,0.0,1.0)+projected_extrude;\n#ifndef RENDER_TO_TEXTURE\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length(projected_extrude.xy/gl_Position.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#else\nv_gamma_scale=1.0;\n#endif\nv_linesofar=a_linesofar;v_width2=vec2(outset,inset);v_width=floorwidth;}"; var lineSDFFrag = "uniform lowp float u_device_pixel_ratio;uniform sampler2D u_image;uniform float u_sdfgamma;uniform float u_mix;varying vec2 v_normal;varying vec2 v_width2;varying vec2 v_tex_a;varying vec2 v_tex_b;varying float v_gamma_scale;\n#pragma mapbox: define highp vec4 color\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define mediump float width\n#pragma mapbox: define lowp float floorwidth\nvoid main() {\n#pragma mapbox: initialize highp vec4 color\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize mediump float width\n#pragma mapbox: initialize lowp float floorwidth\nfloat dist=length(v_normal)*v_width2.s;float blur2=(blur+1.0/u_device_pixel_ratio)*v_gamma_scale;float alpha=clamp(min(dist-(v_width2.t-blur2),v_width2.s-dist)/blur2,0.0,1.0);float sdfdist_a=texture2D(u_image,v_tex_a).a;float sdfdist_b=texture2D(u_image,v_tex_b).a;float sdfdist=mix(sdfdist_a,sdfdist_b,u_mix);alpha*=smoothstep(0.5-u_sdfgamma/floorwidth,0.5+u_sdfgamma/floorwidth,sdfdist);gl_FragColor=color*(alpha*opacity);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var lineSDFVert = "\n#define scale 0.015873016\nattribute vec2 a_pos_normal;attribute vec4 a_data;attribute float a_linesofar;uniform mat4 u_matrix;uniform mediump float u_ratio;uniform lowp float u_device_pixel_ratio;uniform vec2 u_patternscale_a;uniform float u_tex_y_a;uniform vec2 u_patternscale_b;uniform float u_tex_y_b;uniform vec2 u_units_to_pixels;varying vec2 v_normal;varying vec2 v_width2;varying vec2 v_tex_a;varying vec2 v_tex_b;varying float v_gamma_scale;\n#pragma mapbox: define highp vec4 color\n#pragma mapbox: define lowp float blur\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define mediump float gapwidth\n#pragma mapbox: define lowp float offset\n#pragma mapbox: define mediump float width\n#pragma mapbox: define lowp float floorwidth\nvoid main() {\n#pragma mapbox: initialize highp vec4 color\n#pragma mapbox: initialize lowp float blur\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize mediump float gapwidth\n#pragma mapbox: initialize lowp float offset\n#pragma mapbox: initialize mediump float width\n#pragma mapbox: initialize lowp float floorwidth\nfloat ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=a_data.xy-128.0;float a_direction=mod(a_data.z,4.0)-1.0;vec2 pos=floor(a_pos_normal*0.5);mediump vec2 normal=a_pos_normal-2.0*pos;normal.y=normal.y*2.0-1.0;v_normal=normal;gapwidth=gapwidth/2.0;float halfwidth=width/2.0;offset=-1.0*offset;float inset=gapwidth+(gapwidth > 0.0 ? ANTIALIASING : 0.0);float outset=gapwidth+halfwidth*(gapwidth > 0.0 ? 2.0 : 1.0)+(halfwidth==0.0 ? 0.0 : ANTIALIASING);mediump vec2 dist=outset*a_extrude*scale;mediump float u=0.5*a_direction;mediump float t=1.0-abs(u);mediump vec2 offset2=offset*a_extrude*scale*normal.y*mat2(t,-u,u,t);vec4 projected_extrude=u_matrix*vec4(dist/u_ratio,0.0,0.0);gl_Position=u_matrix*vec4(pos+offset2/u_ratio,0.0,1.0)+projected_extrude;\n#ifndef RENDER_TO_TEXTURE\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length(projected_extrude.xy/gl_Position.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#else\nv_gamma_scale=1.0;\n#endif\nv_tex_a=vec2(a_linesofar*u_patternscale_a.x/floorwidth,normal.y*u_patternscale_a.y+u_tex_y_a);v_tex_b=vec2(a_linesofar*u_patternscale_b.x/floorwidth,normal.y*u_patternscale_b.y+u_tex_y_b);v_width2=vec2(outset,inset);}"; var rasterFrag = "uniform float u_fade_t;uniform float u_opacity;uniform sampler2D u_image0;uniform sampler2D u_image1;varying vec2 v_pos0;varying vec2 v_pos1;uniform float u_brightness_low;uniform float u_brightness_high;uniform float u_saturation_factor;uniform float u_contrast_factor;uniform vec3 u_spin_weights;void main() {vec4 color0=texture2D(u_image0,v_pos0);vec4 color1=texture2D(u_image1,v_pos1);if (color0.a > 0.0) {color0.rgb=color0.rgb/color0.a;}if (color1.a > 0.0) {color1.rgb=color1.rgb/color1.a;}vec4 color=mix(color0,color1,u_fade_t);color.a*=u_opacity;vec3 rgb=color.rgb;rgb=vec3(\ndot(rgb,u_spin_weights.xyz),dot(rgb,u_spin_weights.zxy),dot(rgb,u_spin_weights.yzx));float average=(color.r+color.g+color.b)/3.0;rgb+=(average-rgb)*u_saturation_factor;rgb=(rgb-0.5)*u_contrast_factor+0.5;vec3 u_high_vec=vec3(u_brightness_low,u_brightness_low,u_brightness_low);vec3 u_low_vec=vec3(u_brightness_high,u_brightness_high,u_brightness_high);gl_FragColor=vec4(mix(u_high_vec,u_low_vec,rgb)*color.a,color.a);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var rasterVert = "uniform mat4 u_matrix;uniform vec2 u_tl_parent;uniform float u_scale_parent;uniform float u_buffer_scale;attribute vec2 a_pos;attribute vec2 a_texture_pos;varying vec2 v_pos0;varying vec2 v_pos1;void main() {gl_Position=u_matrix*vec4(a_pos,0,1);v_pos0=(((a_texture_pos/8192.0)-0.5)/u_buffer_scale )+0.5;v_pos1=(v_pos0*u_scale_parent)+u_tl_parent;}"; var symbolIconFrag = "uniform sampler2D u_texture;varying vec2 v_tex;varying float v_fade_opacity;\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize lowp float opacity\nlowp float alpha=opacity*v_fade_opacity;gl_FragColor=texture2D(u_texture,v_tex)*alpha;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var symbolIconVert = "attribute vec4 a_pos_offset;attribute vec4 a_data;attribute vec4 a_pixeloffset;attribute vec3 a_projected_pos;attribute float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform highp float u_camera_to_center_distance;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform float u_fade_change;uniform mat4 u_matrix;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform vec2 u_texsize;varying vec2 v_tex;varying float v_fade_opacity;\n#pragma mapbox: define lowp float opacity\nvoid main() {\n#pragma mapbox: initialize lowp float opacity\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);vec2 a_pxoffset=a_pixeloffset.xy;vec2 a_minFontScale=a_pixeloffset.zw/256.0;highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}float h=elevation(a_pos);vec4 projectedPoint=u_matrix*vec4(a_pos,h,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ?\ncamera_to_anchor_distance/u_camera_to_center_distance :\nu_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(\n0.5+0.5*distance_ratio,0.0,1.5);size*=perspective_ratio;float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=u_matrix*vec4(a_pos+vec2(1,0),h,1);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy,h,1.0);float z=0.0;vec2 offset=rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset/16.0);\n#ifdef PITCH_WITH_MAP_TERRAIN\nvec4 tile_pos=u_label_plane_matrix_inv*vec4(a_projected_pos.xy+offset,0.0,1.0);z=elevation(tile_pos.xy);\n#endif\nfloat occlusion_fade=occlusionFade(projectedPoint);gl_Position=mix(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+offset,z,1.0),AWAY,float(projectedPoint.w <=0.0 || occlusion_fade==0.0));v_tex=a_tex/u_texsize;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;v_fade_opacity=max(0.0,min(occlusion_fade,fade_opacity[0]+fade_change));}"; var symbolSDFFrag = "#define SDF_PX 8.0\nuniform bool u_is_halo;uniform sampler2D u_texture;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;uniform bool u_is_text;varying vec2 v_data0;varying vec3 v_data1;\n#pragma mapbox: define highp vec4 fill_color\n#pragma mapbox: define highp vec4 halo_color\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp float halo_width\n#pragma mapbox: define lowp float halo_blur\nvoid main() {\n#pragma mapbox: initialize highp vec4 fill_color\n#pragma mapbox: initialize highp vec4 halo_color\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize lowp float halo_width\n#pragma mapbox: initialize lowp float halo_blur\nfloat EDGE_GAMMA=0.105/u_device_pixel_ratio;vec2 tex=v_data0.xy;float gamma_scale=v_data1.x;float size=v_data1.y;float fade_opacity=v_data1[2];float fontScale=u_is_text ? size/24.0 : size;lowp vec4 color=fill_color;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float buff=(256.0-64.0)/256.0;if (u_is_halo) {color=halo_color;gamma=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);buff=(6.0-halo_width/fontScale)/SDF_PX;}lowp float dist=texture2D(u_texture,tex).a;highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(buff-gamma_scaled,buff+gamma_scaled,dist);gl_FragColor=color*(alpha*opacity*fade_opacity);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var symbolSDFVert = "attribute vec4 a_pos_offset;attribute vec4 a_data;attribute vec4 a_pixeloffset;attribute vec3 a_projected_pos;attribute float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform mat4 u_matrix;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform highp float u_camera_to_center_distance;uniform float u_fade_change;uniform vec2 u_texsize;varying vec2 v_data0;varying vec3 v_data1;\n#pragma mapbox: define highp vec4 fill_color\n#pragma mapbox: define highp vec4 halo_color\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp float halo_width\n#pragma mapbox: define lowp float halo_blur\nvoid main() {\n#pragma mapbox: initialize highp vec4 fill_color\n#pragma mapbox: initialize highp vec4 halo_color\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize lowp float halo_width\n#pragma mapbox: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);vec2 a_pxoffset=a_pixeloffset.xy;highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}float h=elevation(a_pos);vec4 projectedPoint=u_matrix*vec4(a_pos,h,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ?\ncamera_to_anchor_distance/u_camera_to_center_distance :\nu_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(\n0.5+0.5*distance_ratio,0.0,1.5);size*=perspective_ratio;float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=u_matrix*vec4(a_pos+vec2(1,0),h,1);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy,h,1.0);float z=0.0;vec2 offset=rotation_matrix*(a_offset/32.0*fontScale+a_pxoffset);\n#ifdef PITCH_WITH_MAP_TERRAIN\nvec4 tile_pos=u_label_plane_matrix_inv*vec4(a_projected_pos.xy+offset,0.0,1.0);z=elevation(tile_pos.xy);\n#endif\nfloat occlusion_fade=occlusionFade(projectedPoint);gl_Position=mix(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+offset,z,1.0),AWAY,float(projectedPoint.w <=0.0 || occlusion_fade==0.0));float gamma_scale=gl_Position.w;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float interpolated_fade_opacity=max(0.0,min(occlusion_fade,fade_opacity[0]+fade_change));v_data0=a_tex/u_texsize;v_data1=vec3(gamma_scale,size,interpolated_fade_opacity);}"; var symbolTextAndIconFrag = "#define SDF_PX 8.0\n#define SDF 1.0\n#define ICON 0.0\nuniform bool u_is_halo;uniform sampler2D u_texture;uniform sampler2D u_texture_icon;uniform highp float u_gamma_scale;uniform lowp float u_device_pixel_ratio;varying vec4 v_data0;varying vec4 v_data1;\n#pragma mapbox: define highp vec4 fill_color\n#pragma mapbox: define highp vec4 halo_color\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp float halo_width\n#pragma mapbox: define lowp float halo_blur\nvoid main() {\n#pragma mapbox: initialize highp vec4 fill_color\n#pragma mapbox: initialize highp vec4 halo_color\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize lowp float halo_width\n#pragma mapbox: initialize lowp float halo_blur\nfloat fade_opacity=v_data1[2];if (v_data1.w==ICON) {vec2 tex_icon=v_data0.zw;lowp float alpha=opacity*fade_opacity;gl_FragColor=texture2D(u_texture_icon,tex_icon)*alpha;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\nreturn;}vec2 tex=v_data0.xy;float EDGE_GAMMA=0.105/u_device_pixel_ratio;float gamma_scale=v_data1.x;float size=v_data1.y;float fontScale=size/24.0;lowp vec4 color=fill_color;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float buff=(256.0-64.0)/256.0;if (u_is_halo) {color=halo_color;gamma=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);buff=(6.0-halo_width/fontScale)/SDF_PX;}lowp float dist=texture2D(u_texture,tex).a;highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(buff-gamma_scaled,buff+gamma_scaled,dist);gl_FragColor=color*(alpha*opacity*fade_opacity);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var symbolTextAndIconVert = "attribute vec4 a_pos_offset;attribute vec4 a_data;attribute vec3 a_projected_pos;attribute float a_fade_opacity;uniform bool u_is_size_zoom_constant;uniform bool u_is_size_feature_constant;uniform highp float u_size_t;uniform highp float u_size;uniform mat4 u_matrix;uniform mat4 u_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform highp float u_pitch;uniform bool u_rotate_symbol;uniform highp float u_aspect_ratio;uniform highp float u_camera_to_center_distance;uniform float u_fade_change;uniform vec2 u_texsize;uniform vec2 u_texsize_icon;varying vec4 v_data0;varying vec4 v_data1;\n#pragma mapbox: define highp vec4 fill_color\n#pragma mapbox: define highp vec4 halo_color\n#pragma mapbox: define lowp float opacity\n#pragma mapbox: define lowp float halo_width\n#pragma mapbox: define lowp float halo_blur\nvoid main() {\n#pragma mapbox: initialize highp vec4 fill_color\n#pragma mapbox: initialize highp vec4 halo_color\n#pragma mapbox: initialize lowp float opacity\n#pragma mapbox: initialize lowp float halo_width\n#pragma mapbox: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=a_data.xy;vec2 a_size=a_data.zw;float a_size_min=floor(a_size[0]*0.5);float is_sdf=a_size[0]-2.0*a_size_min;highp float segment_angle=-a_projected_pos[2];float size;if (!u_is_size_zoom_constant && !u_is_size_feature_constant) {size=mix(a_size_min,a_size[1],u_size_t)/128.0;} else if (u_is_size_zoom_constant && !u_is_size_feature_constant) {size=a_size_min/128.0;} else {size=u_size;}float h=elevation(a_pos);vec4 projectedPoint=u_matrix*vec4(a_pos,h,1);highp float camera_to_anchor_distance=projectedPoint.w;highp float distance_ratio=u_pitch_with_map ?\ncamera_to_anchor_distance/u_camera_to_center_distance :\nu_camera_to_center_distance/camera_to_anchor_distance;highp float perspective_ratio=clamp(\n0.5+0.5*distance_ratio,0.0,1.5);size*=perspective_ratio;float fontScale=size/24.0;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=u_matrix*vec4(a_pos+vec2(1,0),h,1);vec2 a=projectedPoint.xy/projectedPoint.w;vec2 b=offsetProjectedPoint.xy/offsetProjectedPoint.w;symbol_rotation=atan((b.y-a.y)/u_aspect_ratio,b.x-a.x);}highp float angle_sin=sin(segment_angle+symbol_rotation);highp float angle_cos=cos(segment_angle+symbol_rotation);mat2 rotation_matrix=mat2(angle_cos,-1.0*angle_sin,angle_sin,angle_cos);vec4 projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy,h,1.0);float z=0.0;vec2 offset=rotation_matrix*(a_offset/32.0*fontScale);\n#ifdef PITCH_WITH_MAP_TERRAIN\nvec4 tile_pos=u_label_plane_matrix_inv*vec4(a_projected_pos.xy+offset,0.0,1.0);z=elevation(tile_pos.xy);\n#endif\nfloat occlusion_fade=occlusionFade(projectedPoint);gl_Position=mix(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+offset,z,1.0),AWAY,float(projectedPoint.w <=0.0 || occlusion_fade==0.0));float gamma_scale=gl_Position.w;vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_fade_change :-u_fade_change;float interpolated_fade_opacity=max(0.0,min(occlusion_fade,fade_opacity[0]+fade_change));v_data0.xy=a_tex/u_texsize;v_data0.zw=a_tex/u_texsize_icon;v_data1=vec4(gamma_scale,size,interpolated_fade_opacity,is_sdf);}"; var skyboxFrag = "\nvarying lowp vec3 v_uv;uniform lowp samplerCube u_cubemap;uniform lowp float u_opacity;uniform highp float u_temporal_offset;uniform highp vec3 u_sun_direction;highp vec3 hash(highp vec2 p) {highp vec3 p3=fract(vec3(p.xyx)*vec3(443.8975,397.2973,491.1871));p3+=dot(p3,p3.yxz+19.19);return fract(vec3((p3.x+p3.y)*p3.z,(p3.x+p3.z)*p3.y,(p3.y+p3.z)*p3.x));}vec3 dither(vec3 color,highp vec2 seed) {vec3 rnd=hash(seed)+hash(seed+0.59374)-0.5;color.rgb+=rnd/255.0;return color;}float sun_disk(highp vec3 ray_direction,highp vec3 sun_direction) {highp float cos_angle=dot(normalize(ray_direction),sun_direction);const highp float cos_sun_angular_diameter=0.99996192306;const highp float smoothstep_delta=1e-5;return smoothstep(\ncos_sun_angular_diameter-smoothstep_delta,cos_sun_angular_diameter+smoothstep_delta,cos_angle);}float map(float value,float start,float end,float new_start,float new_end) {return ((value-start)*(new_end-new_start))/(end-start)+new_start;}void main() {vec3 uv=v_uv;const float y_bias=0.015;uv.y+=y_bias;uv.y=pow(abs(uv.y),1.0/5.0);uv.y=map(uv.y,0.0,1.0,-1.0,1.0);vec3 sky_color=textureCube(u_cubemap,uv).rgb;sky_color.rgb=dither(sky_color.rgb,gl_FragCoord.xy+u_temporal_offset);sky_color+=0.1*sun_disk(v_uv,u_sun_direction);gl_FragColor=vec4(sky_color*u_opacity,u_opacity);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var skyboxGradientFrag = "varying highp vec3 v_uv;uniform lowp sampler2D u_color_ramp;uniform lowp vec3 u_center_direction;uniform lowp float u_radius;uniform lowp float u_opacity;uniform highp float u_temporal_offset;highp vec3 hash(highp vec2 p) {highp vec3 p3=fract(vec3(p.xyx)*vec3(443.8975,397.2973,491.1871));p3+=dot(p3,p3.yxz+19.19);return fract(vec3((p3.x+p3.y)*p3.z,(p3.x+p3.z)*p3.y,(p3.y+p3.z)*p3.x));}vec3 dither(vec3 color,highp vec2 seed) {vec3 rnd=hash(seed)+hash(seed+0.59374)-0.5;color.rgb+=rnd/255.0;return color;}void main() {float progress=acos(dot(normalize(v_uv),u_center_direction))/u_radius;vec4 color=texture2D(u_color_ramp,vec2(progress,0.5))*u_opacity;color.rgb=dither(color.rgb,gl_FragCoord.xy+u_temporal_offset);gl_FragColor=color;\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var skyboxVert = "attribute highp vec3 a_pos_3f;uniform lowp mat4 u_matrix;varying highp vec3 v_uv;void main() {const mat3 half_neg_pi_around_x=mat3(1.0,0.0, 0.0,0.0,0.0,-1.0,0.0,1.0, 0.0);v_uv=half_neg_pi_around_x*a_pos_3f;vec4 pos=u_matrix*vec4(a_pos_3f,1.0);gl_Position=pos.xyww;}"; var terrainRasterFrag = "uniform sampler2D u_image0;varying vec2 v_pos0;void main() {gl_FragColor=texture2D(u_image0,v_pos0);\n#ifdef OVERDRAW_INSPECTOR\ngl_FragColor=vec4(1.0);\n#endif\n}"; var terrainRasterVert = "uniform mat4 u_matrix;uniform float u_skirt_height;attribute vec2 a_pos;attribute vec2 a_texture_pos;varying vec2 v_pos0;const float skirtOffset=24575.0;void main() {v_pos0=a_texture_pos/8192.0;float skirt=float(a_pos.x >=skirtOffset);float elevation=elevation(a_texture_pos)-skirt*u_skirt_height;vec2 decodedPos=a_pos-vec2(skirt*skirtOffset,0.0);gl_Position=u_matrix*vec4(decodedPos,elevation,1.0);}"; var terrainDepthFrag = "#ifdef GL_ES\nprecision highp float;\n#endif\nvec4 pack_depth(float ndc_z) {float depth=ndc_z*0.5+0.5;const vec4 bit_shift=vec4(256.0*256.0*256.0,256.0*256.0,256.0,1.0);const vec4 bit_mask =vec4(0.0,1.0/256.0,1.0/256.0,1.0/256.0);vec4 res=fract(depth*bit_shift);res-=res.xxyz*bit_mask;return res;}varying float v_depth;void main() {gl_FragColor=pack_depth(v_depth);}"; var terrainDepthVert = "uniform mat4 u_matrix;attribute vec2 a_pos;attribute vec2 a_texture_pos;varying float v_depth;void main() {float elevation=elevation(a_texture_pos);gl_Position=u_matrix*vec4(a_pos,elevation,1.0);v_depth=gl_Position.z/gl_Position.w;}"; var preludeTerrainVert = "\n#define ELEVATION_SCALE 7.3\n#ifdef TERRAIN\nuniform sampler2D u_dem;uniform sampler2D u_dem_prev;uniform vec4 u_dem_unpack;uniform vec2 u_dem_tl;uniform vec2 u_dem_tl_prev;uniform float u_dem_scale;uniform float u_dem_scale_prev;uniform float u_dem_size;uniform float u_dem_lerp;uniform float u_exaggeration;uniform float u_meter_to_dem;uniform mat4 u_label_plane_matrix_inv;uniform sampler2D u_depth;uniform vec2 u_depth_size_inv;vec4 tileUvToDemSample(vec2 uv,float dem_size,float dem_scale,vec2 dem_tl) {vec2 pos=dem_size*(uv*dem_scale+dem_tl)+1.0;vec2 f=fract(pos);return vec4((pos-f+0.5)/(dem_size+2.0),f);}float decodeElevation(vec4 v) {return dot(vec4(v.xyz*255.0,-1.0),u_dem_unpack);}float currentElevation(vec2 apos) {float dd=1.0/(u_dem_size+2.0);vec4 r=tileUvToDemSample(apos/8192.0,u_dem_size,u_dem_scale,u_dem_tl);vec2 pos=r.xy;vec2 f=r.zw;float tl=decodeElevation(texture2D(u_dem,pos));float tr=decodeElevation(texture2D(u_dem,pos+vec2(dd,0.0)));float bl=decodeElevation(texture2D(u_dem,pos+vec2(0.0,dd)));float br=decodeElevation(texture2D(u_dem,pos+vec2(dd,dd)));return u_exaggeration*mix(mix(tl,tr,f.x),mix(bl,br,f.x),f.y);}float prevElevation(vec2 apos) {float dd=1.0/(u_dem_size+2.0);vec4 r=tileUvToDemSample(apos/8192.0,u_dem_size,u_dem_scale_prev,u_dem_tl_prev);vec2 pos=r.xy;vec2 f=r.zw;float tl=decodeElevation(texture2D(u_dem_prev,pos));float tr=decodeElevation(texture2D(u_dem_prev,pos+vec2(dd,0.0)));float bl=decodeElevation(texture2D(u_dem_prev,pos+vec2(0.0,dd)));float br=decodeElevation(texture2D(u_dem_prev,pos+vec2(dd,dd)));return u_exaggeration*mix(mix(tl,tr,f.x),mix(bl,br,f.x),f.y);}\n#ifdef TERRAIN_VERTEX_MORPHING\nfloat elevation(vec2 apos) {float nextElevation=currentElevation(apos);float prevElevation=prevElevation(apos);return mix(prevElevation,nextElevation,u_dem_lerp);}\n#else\nfloat elevation(vec2 apos) {return currentElevation(apos);}\n#endif\nfloat unpack_depth(vec4 rgba_depth)\n{const vec4 bit_shift=vec4(1.0/(256.0*256.0*256.0),1.0/(256.0*256.0),1.0/256.0,1.0);return dot(rgba_depth,bit_shift)*2.0-1.0;}bool isOccluded(vec4 frag) {vec3 coord=frag.xyz/frag.w;float depth=unpack_depth(texture2D(u_depth,(coord.xy+1.0)*0.5));return coord.z > depth+0.0005;}float occlusionFade(vec4 frag) {vec3 coord=frag.xyz/frag.w;vec3 df=vec3(5.0*u_depth_size_inv,0.0);vec2 uv=0.5*coord.xy+0.5;vec4 depth=vec4(\nunpack_depth(texture2D(u_depth,uv-df.xz)),unpack_depth(texture2D(u_depth,uv+df.xz)),unpack_depth(texture2D(u_depth,uv-df.zy)),unpack_depth(texture2D(u_depth,uv+df.zy))\n);return dot(vec4(0.25),vec4(1.0)-clamp(300.0*(vec4(coord.z-0.001)-depth),0.0,1.0));}vec4 fourSample(vec2 pos,vec2 off) {vec4 demtl=vec4(texture2D(u_dem,pos).xyz*255.0,-1.0);float tl=dot(demtl,u_dem_unpack);vec4 demtr=vec4(texture2D(u_dem,pos+vec2(off.x,0.0)).xyz*255.0,-1.0);float tr=dot(demtr,u_dem_unpack);vec4 dembl=vec4(texture2D(u_dem,pos+vec2(0.0,off.y)).xyz*255.0,-1.0);float bl=dot(dembl,u_dem_unpack);vec4 dembr=vec4(texture2D(u_dem,pos+off).xyz*255.0,-1.0);float br=dot(dembr,u_dem_unpack);return vec4(tl,tr,bl,br);}float flatElevation(vec2 pack) {vec2 apos=floor(pack/8.0);vec2 span=10.0*(pack-apos*8.0);vec2 uvTex=(apos-vec2(1.0,1.0))/8190.0;float size=u_dem_size+2.0;float dd=1.0/size;vec2 pos=u_dem_size*(uvTex*u_dem_scale+u_dem_tl)+1.0;vec2 f=fract(pos);pos=(pos-f+0.5)*dd;vec4 h=fourSample(pos,vec2(dd));float z=mix(mix(h.x,h.y,f.x),mix(h.z,h.w,f.x),f.y);vec2 w=floor(0.5*(span*u_meter_to_dem-1.0));vec2 d=dd*w;vec4 bounds=vec4(d,vec2(1.0)-d);h=fourSample(pos-d,2.0*d+vec2(dd));vec4 diff=abs(h.xzxy-h.ywzw);vec2 slope=min(vec2(0.25),u_meter_to_dem*0.5*(diff.xz+diff.yw)/(2.0*w+vec2(1.0)));vec2 fix=slope*span;float base=z+max(fix.x,fix.y);return u_exaggeration*base;}float elevationFromUint16(float word) {return u_exaggeration*word/ELEVATION_SCALE;}\n#else\nfloat elevation(vec2 pos) { return 0.0; }bool isOccluded(vec4 frag) { return false; }float occlusionFade(vec4 frag) { return 1.0; }\n#endif"; var skyboxCaptureFrag = "\nvarying highp vec3 v_position;uniform highp float u_sun_intensity;uniform highp float u_luminance;uniform lowp vec3 u_sun_direction;uniform highp vec4 u_color_tint_r;uniform highp vec4 u_color_tint_m;\n#ifdef GL_ES\nprecision highp float;\n#endif\n#define BETA_R vec3(5.5e-6,13.0e-6,22.4e-6)\n#define BETA_M vec3(21e-6,21e-6,21e-6)\n#define MIE_G 0.76\n#define DENSITY_HEIGHT_SCALE_R 8000.0\n#define DENSITY_HEIGHT_SCALE_M 1200.0\n#define PLANET_RADIUS 6360e3\n#define ATMOSPHERE_RADIUS 6420e3\n#define SAMPLE_STEPS 10\n#define DENSITY_STEPS 4\nfloat ray_sphere_exit(vec3 orig,vec3 dir,float radius) {float a=dot(dir,dir);float b=2.0*dot(dir,orig);float c=dot(orig,orig)-radius*radius;float d=sqrt(b*b-4.0*a*c);return (-b+d)/(2.0*a);}vec3 extinction(vec2 density) {return exp(-vec3(BETA_R*u_color_tint_r.a*density.x+BETA_M*u_color_tint_m.a*density.y));}vec2 local_density(vec3 point) {float height=max(length(point)-PLANET_RADIUS,0.0);float exp_r=exp(-height/DENSITY_HEIGHT_SCALE_R);float exp_m=exp(-height/DENSITY_HEIGHT_SCALE_M);return vec2(exp_r,exp_m);}float phase_ray(float cos_angle) {return (3.0/(16.0*PI))*(1.0+cos_angle*cos_angle);}float phase_mie(float cos_angle) {return (3.0/(8.0*PI))*((1.0-MIE_G*MIE_G)*(1.0+cos_angle*cos_angle))/((2.0+MIE_G*MIE_G)*pow(1.0+MIE_G*MIE_G-2.0*MIE_G*cos_angle,1.5));}vec2 density_to_atmosphere(vec3 point,vec3 light_dir) {float ray_len=ray_sphere_exit(point,light_dir,ATMOSPHERE_RADIUS);float step_len=ray_len/float(DENSITY_STEPS);vec2 density_point_to_atmosphere=vec2(0.0);for (int i=0; i < DENSITY_STEPS;++i) {vec3 point_on_ray=point+light_dir*((float(i)+0.5)*step_len);density_point_to_atmosphere+=local_density(point_on_ray)*step_len;;}return density_point_to_atmosphere;}vec3 atmosphere(vec3 ray_dir,vec3 sun_direction,float sun_intensity) {vec2 density_orig_to_point=vec2(0.0);vec3 scatter_r=vec3(0.0);vec3 scatter_m=vec3(0.0);vec3 origin=vec3(0.0,PLANET_RADIUS,0.0);float ray_len=ray_sphere_exit(origin,ray_dir,ATMOSPHERE_RADIUS);float step_len=ray_len/float(SAMPLE_STEPS);for (int i=0; i < SAMPLE_STEPS;++i) {vec3 point_on_ray=origin+ray_dir*((float(i)+0.5)*step_len);vec2 density=local_density(point_on_ray)*step_len;density_orig_to_point+=density;vec2 density_point_to_atmosphere=density_to_atmosphere(point_on_ray,sun_direction);vec2 density_orig_to_atmosphere=density_orig_to_point+density_point_to_atmosphere;vec3 extinction=extinction(density_orig_to_atmosphere);scatter_r+=density.x*extinction;scatter_m+=density.y*extinction;}float cos_angle=dot(ray_dir,sun_direction);float phase_r=phase_ray(cos_angle);float phase_m=phase_mie(cos_angle);vec3 beta_r=BETA_R*u_color_tint_r.rgb*u_color_tint_r.a;vec3 beta_m=BETA_M*u_color_tint_m.rgb*u_color_tint_m.a;return (scatter_r*phase_r*beta_r+scatter_m*phase_m*beta_m)*sun_intensity;}const float A=0.15;const float B=0.50;const float C=0.10;const float D=0.20;const float E=0.02;const float F=0.30;vec3 uncharted2_tonemap(vec3 x) {return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;}void main() {vec3 ray_direction=v_position;ray_direction.y=pow(ray_direction.y,5.0);const float y_bias=0.015;ray_direction.y+=y_bias;vec3 color=atmosphere(normalize(ray_direction),u_sun_direction,u_sun_intensity);float white_scale=1.0748724675633854;color=uncharted2_tonemap((log2(2.0/pow(u_luminance,4.0)))*color)*white_scale;gl_FragColor=vec4(color,1.0);}"; var skyboxCaptureVert = "attribute highp vec3 a_pos_3f;uniform mat3 u_matrix_3f;varying highp vec3 v_position;float map(float value,float start,float end,float new_start,float new_end) {return ((value-start)*(new_end-new_start))/(end-start)+new_start;}void main() {vec4 pos=vec4(u_matrix_3f*a_pos_3f,1.0);v_position=pos.xyz;v_position.y*=-1.0;v_position.y=map(v_position.y,-1.0,1.0,0.0,1.0);gl_Position=vec4(a_pos_3f.xy,0.0,1.0);}"; let preludeTerrain = {}; preludeTerrain = compile('', preludeTerrainVert, true); const prelude = compile(preludeFrag, preludeVert); const background = compile(backgroundFrag, backgroundVert); const backgroundPattern = compile(backgroundPatternFrag, backgroundPatternVert); const circle = compile(circleFrag, circleVert); const clippingMask = compile(clippingMaskFrag, clippingMaskVert); const heatmap = compile(heatmapFrag, heatmapVert); const heatmapTexture = compile(heatmapTextureFrag, heatmapTextureVert); const collisionBox = compile(collisionBoxFrag, collisionBoxVert); const collisionCircle = compile(collisionCircleFrag, collisionCircleVert); const debug = compile(debugFrag, debugVert); const fill = compile(fillFrag, fillVert); const fillOutline = compile(fillOutlineFrag, fillOutlineVert); const fillOutlinePattern = compile(fillOutlinePatternFrag, fillOutlinePatternVert); const fillPattern = compile(fillPatternFrag, fillPatternVert); const fillExtrusion = compile(fillExtrusionFrag, fillExtrusionVert); const fillExtrusionPattern = compile(fillExtrusionPatternFrag, fillExtrusionPatternVert); const hillshadePrepare = compile(hillshadePrepareFrag, hillshadePrepareVert); const hillshade = compile(hillshadeFrag, hillshadeVert); const line = compile(lineFrag, lineVert); const lineGradient = compile(lineGradientFrag, lineGradientVert); const linePattern = compile(linePatternFrag, linePatternVert); const lineSDF = compile(lineSDFFrag, lineSDFVert); const raster = compile(rasterFrag, rasterVert); const symbolIcon = compile(symbolIconFrag, symbolIconVert); const symbolSDF = compile(symbolSDFFrag, symbolSDFVert); const symbolTextAndIcon = compile(symbolTextAndIconFrag, symbolTextAndIconVert); const terrainRaster = compile(terrainRasterFrag, terrainRasterVert); const terrainDepth = compile(terrainDepthFrag, terrainDepthVert); const skybox = compile(skyboxFrag, skyboxVert); const skyboxGradient = compile(skyboxGradientFrag, skyboxVert); const skyboxCapture = compile(skyboxCaptureFrag, skyboxCaptureVert); function compile(fragmentSource, vertexSource, isPreludeTerrainShader) { const re = /#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g; const staticAttributes = vertexSource.match(/attribute (highp |mediump |lowp )?([\w]+) ([\w]+)/g); const fragmentUniforms = fragmentSource.match(/uniform (highp |mediump |lowp )?([\w]+) ([\w]+)([\s]*)([\w]*)/g); const vertexUniforms = vertexSource.match(/uniform (highp |mediump |lowp )?([\w]+) ([\w]+)([\s]*)([\w]*)/g); let staticUniforms = vertexUniforms ? vertexUniforms.concat(fragmentUniforms) : fragmentUniforms; if (!isPreludeTerrainShader) { staticUniforms = preludeTerrain.staticUniforms.concat(staticUniforms); } const fragmentPragmas = {}; fragmentSource = fragmentSource.replace(re, (match, operation, precision, type, name) => { fragmentPragmas[name] = true; if (operation === 'define') { return ` #ifndef HAS_UNIFORM_u_${ name } varying ${ precision } ${ type } ${ name }; #else uniform ${ precision } ${ type } u_${ name }; #endif `; } else { return ` #ifdef HAS_UNIFORM_u_${ name } ${ precision } ${ type } ${ name } = u_${ name }; #endif `; } }); vertexSource = vertexSource.replace(re, (match, operation, precision, type, name) => { const attrType = type === 'float' ? 'vec2' : 'vec4'; const unpackType = name.match(/color/) ? 'color' : attrType; if (fragmentPragmas[name]) { if (operation === 'define') { return ` #ifndef HAS_UNIFORM_u_${ name } uniform lowp float u_${ name }_t; attribute ${ precision } ${ attrType } a_${ name }; varying ${ precision } ${ type } ${ name }; #else uniform ${ precision } ${ type } u_${ name }; #endif `; } else { if (unpackType === 'vec4') { return ` #ifndef HAS_UNIFORM_u_${ name } ${ name } = a_${ name }; #else ${ precision } ${ type } ${ name } = u_${ name }; #endif `; } else { return ` #ifndef HAS_UNIFORM_u_${ name } ${ name } = unpack_mix_${ unpackType }(a_${ name }, u_${ name }_t); #else ${ precision } ${ type } ${ name } = u_${ name }; #endif `; } } } else { if (operation === 'define') { return ` #ifndef HAS_UNIFORM_u_${ name } uniform lowp float u_${ name }_t; attribute ${ precision } ${ attrType } a_${ name }; #else uniform ${ precision } ${ type } u_${ name }; #endif `; } else { if (unpackType === 'vec4') { return ` #ifndef HAS_UNIFORM_u_${ name } ${ precision } ${ type } ${ name } = a_${ name }; #else ${ precision } ${ type } ${ name } = u_${ name }; #endif `; } else { return ` #ifndef HAS_UNIFORM_u_${ name } ${ precision } ${ type } ${ name } = unpack_mix_${ unpackType }(a_${ name }, u_${ name }_t); #else ${ precision } ${ type } ${ name } = u_${ name }; #endif `; } } } }); return { fragmentSource, vertexSource, staticAttributes, staticUniforms }; } var shaders = /*#__PURE__*/Object.freeze({ __proto__: null, get preludeTerrain () { return preludeTerrain; }, prelude: prelude, background: background, backgroundPattern: backgroundPattern, circle: circle, clippingMask: clippingMask, heatmap: heatmap, heatmapTexture: heatmapTexture, collisionBox: collisionBox, collisionCircle: collisionCircle, debug: debug, fill: fill, fillOutline: fillOutline, fillOutlinePattern: fillOutlinePattern, fillPattern: fillPattern, fillExtrusion: fillExtrusion, fillExtrusionPattern: fillExtrusionPattern, hillshadePrepare: hillshadePrepare, hillshade: hillshade, line: line, lineGradient: lineGradient, linePattern: linePattern, lineSDF: lineSDF, raster: raster, symbolIcon: symbolIcon, symbolSDF: symbolSDF, symbolTextAndIcon: symbolTextAndIcon, terrainRaster: terrainRaster, terrainDepth: terrainDepth, skybox: skybox, skyboxGradient: skyboxGradient, skyboxCapture: skyboxCapture }); class VertexArrayObject { constructor() { this.boundProgram = null; this.boundLayoutVertexBuffer = null; this.boundPaintVertexBuffers = []; this.boundIndexBuffer = null; this.boundVertexOffset = null; this.boundDynamicVertexBuffer = null; this.vao = null; } bind(context, program, layoutVertexBuffer, paintVertexBuffers, indexBuffer, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2) { this.context = context; let paintBuffersDiffer = this.boundPaintVertexBuffers.length !== paintVertexBuffers.length; for (let i = 0; !paintBuffersDiffer && i < paintVertexBuffers.length; i++) { if (this.boundPaintVertexBuffers[i] !== paintVertexBuffers[i]) { paintBuffersDiffer = true; } } const isFreshBindRequired = !this.vao || this.boundProgram !== program || this.boundLayoutVertexBuffer !== layoutVertexBuffer || paintBuffersDiffer || this.boundIndexBuffer !== indexBuffer || this.boundVertexOffset !== vertexOffset || this.boundDynamicVertexBuffer !== dynamicVertexBuffer || this.boundDynamicVertexBuffer2 !== dynamicVertexBuffer2; if (!context.extVertexArrayObject || isFreshBindRequired) { this.freshBind(program, layoutVertexBuffer, paintVertexBuffers, indexBuffer, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2); } else { context.bindVertexArrayOES.set(this.vao); if (dynamicVertexBuffer) { dynamicVertexBuffer.bind(); } if (indexBuffer && indexBuffer.dynamicDraw) { indexBuffer.bind(); } if (dynamicVertexBuffer2) { dynamicVertexBuffer2.bind(); } } } freshBind(program, layoutVertexBuffer, paintVertexBuffers, indexBuffer, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2) { let numPrevAttributes; const numNextAttributes = program.numAttributes; const context = this.context; const gl = context.gl; if (context.extVertexArrayObject) { if (this.vao) this.destroy(); this.vao = context.extVertexArrayObject.createVertexArrayOES(); context.bindVertexArrayOES.set(this.vao); numPrevAttributes = 0; this.boundProgram = program; this.boundLayoutVertexBuffer = layoutVertexBuffer; this.boundPaintVertexBuffers = paintVertexBuffers; this.boundIndexBuffer = indexBuffer; this.boundVertexOffset = vertexOffset; this.boundDynamicVertexBuffer = dynamicVertexBuffer; this.boundDynamicVertexBuffer2 = dynamicVertexBuffer2; } else { numPrevAttributes = context.currentNumAttributes || 0; for (let i = numNextAttributes; i < numPrevAttributes; i++) { gl.disableVertexAttribArray(i); } } layoutVertexBuffer.enableAttributes(gl, program); for (const vertexBuffer of paintVertexBuffers) { vertexBuffer.enableAttributes(gl, program); } if (dynamicVertexBuffer) { dynamicVertexBuffer.enableAttributes(gl, program); } if (dynamicVertexBuffer2) { dynamicVertexBuffer2.enableAttributes(gl, program); } layoutVertexBuffer.bind(); layoutVertexBuffer.setVertexAttribPointers(gl, program, vertexOffset); for (const vertexBuffer of paintVertexBuffers) { vertexBuffer.bind(); vertexBuffer.setVertexAttribPointers(gl, program, vertexOffset); } if (dynamicVertexBuffer) { dynamicVertexBuffer.bind(); dynamicVertexBuffer.setVertexAttribPointers(gl, program, vertexOffset); } if (indexBuffer) { indexBuffer.bind(); } if (dynamicVertexBuffer2) { dynamicVertexBuffer2.bind(); dynamicVertexBuffer2.setVertexAttribPointers(gl, program, vertexOffset); } context.currentNumAttributes = numNextAttributes; } destroy() { if (this.vao) { this.context.extVertexArrayObject.deleteVertexArrayOES(this.vao); this.vao = null; } } } const hillshadeUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_latrange': new ref_properties.Uniform2f(context, locations.u_latrange), 'u_light': new ref_properties.Uniform2f(context, locations.u_light), 'u_shadow': new ref_properties.UniformColor(context, locations.u_shadow), 'u_highlight': new ref_properties.UniformColor(context, locations.u_highlight), 'u_accent': new ref_properties.UniformColor(context, locations.u_accent) }); const hillshadePrepareUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_dimension': new ref_properties.Uniform2f(context, locations.u_dimension), 'u_zoom': new ref_properties.Uniform1f(context, locations.u_zoom), 'u_unpack': new ref_properties.Uniform4f(context, locations.u_unpack) }); const hillshadeUniformValues = (painter, tile, layer, matrix) => { const shadow = layer.paint.get('hillshade-shadow-color'); const highlight = layer.paint.get('hillshade-highlight-color'); const accent = layer.paint.get('hillshade-accent-color'); let azimuthal = layer.paint.get('hillshade-illumination-direction') * (Math.PI / 180); if (layer.paint.get('hillshade-illumination-anchor') === 'viewport') { azimuthal -= painter.transform.angle; } const align = !painter.options.moving; return { 'u_matrix': matrix ? matrix : painter.transform.calculatePosMatrix(tile.tileID.toUnwrapped(), align), 'u_image': 0, 'u_latrange': getTileLatRange(painter, tile.tileID), 'u_light': [ layer.paint.get('hillshade-exaggeration'), azimuthal ], 'u_shadow': shadow, 'u_highlight': highlight, 'u_accent': accent }; }; const hillshadeUniformPrepareValues = (tileID, dem) => { const stride = dem.stride; const matrix = ref_properties.create(); ref_properties.ortho(matrix, 0, ref_properties.EXTENT, -ref_properties.EXTENT, 0, 0, 1); ref_properties.translate(matrix, matrix, [ 0, -ref_properties.EXTENT, 0 ]); return { 'u_matrix': matrix, 'u_image': 1, 'u_dimension': [ stride, stride ], 'u_zoom': tileID.overscaledZ, 'u_unpack': dem.unpackVector }; }; function getTileLatRange(painter, tileID) { const tilesAtZoom = Math.pow(2, tileID.canonical.z); const y = tileID.canonical.y; return [ new ref_properties.MercatorCoordinate(0, y / tilesAtZoom).toLngLat().lat, new ref_properties.MercatorCoordinate(0, (y + 1) / tilesAtZoom).toLngLat().lat ]; } function drawHillshade(painter, sourceCache, layer, tileIDs) { if (painter.renderPass !== 'offscreen' && painter.renderPass !== 'translucent') return; const context = painter.context; const depthMode = painter.depthModeForSublayer(0, ref_properties.DepthMode.ReadOnly); const colorMode = painter.colorModeForRenderPass(); const renderingToTexture = painter.terrain && painter.terrain.renderingToTexture; const [stencilModes, coords] = painter.renderPass === 'translucent' && !renderingToTexture ? painter.stencilConfigForOverlap(tileIDs) : [ {}, tileIDs ]; for (const coord of coords) { const tile = sourceCache.getTile(coord); if (tile.needsHillshadePrepare && painter.renderPass === 'offscreen') { prepareHillshade(painter, tile, layer, depthMode, ref_properties.StencilMode.disabled, colorMode); } else if (painter.renderPass === 'translucent') { const stencilMode = renderingToTexture && painter.terrain ? painter.terrain.stencilModeForRTTOverlap(coord) : stencilModes[coord.overscaledZ]; renderHillshade(painter, coord, tile, layer, depthMode, stencilMode, colorMode); } } context.viewport.set([ 0, 0, painter.width, painter.height ]); } function renderHillshade(painter, coord, tile, layer, depthMode, stencilMode, colorMode) { const context = painter.context; const gl = context.gl; const fbo = tile.fbo; if (!fbo) return; painter.prepareDrawTile(coord); const program = painter.useProgram('hillshade'); context.activeTexture.set(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); const uniformValues = hillshadeUniformValues(painter, tile, layer, painter.terrain ? coord.posMatrix : null); program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.disabled, uniformValues, layer.id, painter.rasterBoundsBuffer, painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); } function prepareDEMTexture(painter, tile, dem) { if (!tile.needsDEMTextureUpload) return; const context = painter.context; const gl = context.gl; context.pixelStoreUnpackPremultiplyAlpha.set(false); const textureStride = dem.stride; tile.demTexture = tile.demTexture || painter.getTileTexture(textureStride); const pixelData = dem.getPixels(); if (tile.demTexture) { tile.demTexture.update(pixelData, { premultiply: false }); } else { tile.demTexture = new ref_properties.Texture(context, pixelData, gl.RGBA, { premultiply: false }); } tile.needsDEMTextureUpload = false; } function prepareHillshade(painter, tile, layer, depthMode, stencilMode, colorMode) { const context = painter.context; const gl = context.gl; if (!tile.dem) return; const dem = tile.dem; context.activeTexture.set(gl.TEXTURE1); prepareDEMTexture(painter, tile, dem); if (!tile.demTexture) return; tile.demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); const tileSize = dem.dim; context.activeTexture.set(gl.TEXTURE0); let fbo = tile.fbo; if (!fbo) { const renderTexture = new ref_properties.Texture(context, { width: tileSize, height: tileSize, data: null }, gl.RGBA); renderTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); fbo = tile.fbo = context.createFramebuffer(tileSize, tileSize, true); fbo.colorAttachment.set(renderTexture.texture); } context.bindFramebuffer.set(fbo.framebuffer); context.viewport.set([ 0, 0, tileSize, tileSize ]); painter.useProgram('hillshadePrepare').draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.disabled, hillshadeUniformPrepareValues(tile.tileID, dem), layer.id, painter.rasterBoundsBuffer, painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); tile.needsHillshadePrepare = false; } const terrainRasterUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_image0': new ref_properties.Uniform1i(context, locations.u_image0), 'u_skirt_height': new ref_properties.Uniform1f(context, locations.u_skirt_height) }); const terrainRasterUniformValues = (matrix, skirtHeight) => ({ 'u_matrix': matrix, 'u_image0': 0, 'u_skirt_height': skirtHeight }); class VertexMorphing { constructor() { this.operations = {}; } newMorphing(key, from, to, now, duration) { if (key in this.operations) { const op = this.operations[key]; if (op.to.tileID.key !== to.tileID.key) op.queued = to; } else { this.operations[key] = { startTime: now, phase: 0, duration, from, to, queued: null }; } } getMorphValuesForProxy(key) { if (!(key in this.operations)) return null; const op = this.operations[key]; const from = op.from; const to = op.to; return { from, to, phase: op.phase }; } update(now) { for (const key in this.operations) { const op = this.operations[key]; op.phase = (now - op.startTime) / op.duration; while (op.phase >= 1 || !this._validOp(op)) { if (!this._nextOp(op, now)) { delete this.operations[key]; break; } } } } _nextOp(op, now) { if (!op.queued) return false; op.from = op.to; op.to = op.queued; op.queued = null; op.phase = 0; op.startTime = now; return true; } _validOp(op) { return op.from.hasData() && op.to.hasData(); } } function demTileChanged(prev, next) { if (prev == null || next == null) return false; if (!prev.hasData() || !next.hasData()) return false; if (prev.demTexture == null || next.demTexture == null) return false; return prev.tileID.key !== next.tileID.key; } const vertexMorphing = new VertexMorphing(); const SHADER_DEFAULT = 0; const SHADER_MORPHING = 1; const defaultDuration = 250; const shaderDefines = { '0': null, '1': 'TERRAIN_VERTEX_MORPHING' }; function drawTerrainRaster(painter, terrain, sourceCache, tileIDs, now) { const context = painter.context; const gl = context.gl; let program = painter.useProgram('terrainRaster'); let programMode = SHADER_DEFAULT; const setShaderMode = mode => { if (programMode === mode) return; program = painter.useProgram('terrainRaster', null, shaderDefines[mode]); programMode = mode; }; const colorMode = painter.colorModeForRenderPass(); const depthMode = new ref_properties.DepthMode(gl.LEQUAL, ref_properties.DepthMode.ReadWrite, painter.depthRangeFor3D); vertexMorphing.update(now); const tr = painter.transform; const skirt = skirtHeight(tr.zoom) * terrain.exaggeration(); for (const coord of tileIDs) { const tile = sourceCache.getTile(coord); const stencilMode = ref_properties.StencilMode.disabled; const prevDemTile = terrain.prevTerrainTileForTile[coord.key]; const nextDemTile = terrain.terrainTileForTile[coord.key]; if (demTileChanged(prevDemTile, nextDemTile)) { vertexMorphing.newMorphing(coord.key, prevDemTile, nextDemTile, now, defaultDuration); } context.activeTexture.set(gl.TEXTURE0); tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); const morph = vertexMorphing.getMorphValuesForProxy(coord.key); const shaderMode = morph ? SHADER_MORPHING : SHADER_DEFAULT; let elevationOptions; if (morph) { elevationOptions = { morphing: { srcDemTile: morph.from, dstDemTile: morph.to, phase: ref_properties.easeCubicInOut(morph.phase) } }; } const uniformValues = terrainRasterUniformValues(coord.posMatrix, isEdgeTile(coord.canonical, tr.renderWorldCopies) ? skirt / 10 : skirt); setShaderMode(shaderMode); terrain.setupElevationDraw(tile, program, elevationOptions); program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.backCCW, uniformValues, 'terrain_raster', terrain.gridBuffer, terrain.gridIndexBuffer, terrain.gridSegments); } } function drawTerrainDepth(painter, terrain, sourceCache, tileIDs) { const context = painter.context; const gl = context.gl; context.clear({ depth: 1 }); const program = painter.useProgram('terrainDepth'); const depthMode = new ref_properties.DepthMode(gl.LESS, ref_properties.DepthMode.ReadWrite, painter.depthRangeFor3D); for (const coord of tileIDs) { const tile = sourceCache.getTile(coord); const uniformValues = terrainRasterUniformValues(coord.posMatrix, 0); terrain.setupElevationDraw(tile, program); program.draw(context, gl.TRIANGLES, depthMode, ref_properties.StencilMode.disabled, ref_properties.ColorMode.unblended, ref_properties.CullFaceMode.backCCW, uniformValues, 'terrain_depth', terrain.gridBuffer, terrain.gridIndexBuffer, terrain.gridNoSkirtSegments); } } function skirtHeight(zoom) { return 6 * Math.pow(1.5, 22 - zoom); } function isEdgeTile(cid, renderWorldCopies) { const numTiles = 1 << cid.z; return !renderWorldCopies && (cid.x === 0 || cid.x === numTiles - 1) || cid.y === 0 || cid.y === numTiles - 1; } const clippingMaskUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix) }); const clippingMaskUniformValues = matrix => ({ 'u_matrix': matrix }); function rasterFade(tile, parentTile, sourceCache, transform, fadeDuration) { if (fadeDuration > 0) { const now = ref_properties.browser.now(); const sinceTile = (now - tile.timeAdded) / fadeDuration; const sinceParent = parentTile ? (now - parentTile.timeAdded) / fadeDuration : -1; const source = sourceCache.getSource(); const idealZ = transform.coveringZoomLevel({ tileSize: source.tileSize, roundZoom: source.roundZoom }); const fadeIn = !parentTile || Math.abs(parentTile.tileID.overscaledZ - idealZ) > Math.abs(tile.tileID.overscaledZ - idealZ); const childOpacity = fadeIn && tile.refreshedUponExpiration ? 1 : ref_properties.clamp(fadeIn ? sinceTile : 1 - sinceParent, 0, 1); if (tile.refreshedUponExpiration && sinceTile >= 1) tile.refreshedUponExpiration = false; if (parentTile) { return { opacity: 1, mix: 1 - childOpacity }; } else { return { opacity: childOpacity, mix: 0 }; } } else { return { opacity: 1, mix: 0 }; } } const GRID_DIM = 128; const FBO_POOL_SIZE = 5; const RENDER_CACHE_MAX_SIZE = 50; class ProxySourceCache extends ref_properties.SourceCache { constructor(map) { const source = create('proxy', { type: 'geojson', maxzoom: map.transform.maxZoom }, new Dispatcher(getGlobalWorkerPool(), null), map.style); super('proxy', source, false); source.setEventedParent(this); this.map = this.getSource().map = map; this.used = this._sourceLoaded = true; this.renderCache = []; this.renderCachePool = []; this.proxyCachedFBO = {}; } update(transform, tileSize, updateForTerrain) { if (transform.freezeTileCoverage) { return; } this.transform = transform; const idealTileIDs = transform.coveringTiles({ tileSize: this._source.tileSize, minzoom: this._source.minzoom, maxzoom: this._source.maxzoom, roundZoom: this._source.roundZoom, reparseOverscaled: this._source.reparseOverscaled, useElevationData: true }); const incoming = idealTileIDs.reduce((acc, tileID) => { acc[tileID.key] = ''; if (!this._tiles[tileID.key]) { const tile = new ref_properties.Tile(tileID, this._source.tileSize * tileID.overscaleFactor(), transform.tileZoom); tile.state = 'loaded'; this._tiles[tileID.key] = tile; } return acc; }, {}); for (const id in this._tiles) { if (!(id in incoming)) { this.freeFBO(id); this._tiles[id].state = 'unloaded'; delete this._tiles[id]; } } } freeFBO(id) { const fbos = this.proxyCachedFBO[id]; if (fbos !== undefined) { const fboIds = Object.values(fbos); this.renderCachePool.push(...fboIds); delete this.proxyCachedFBO[id]; } } deallocRenderCache() { this.renderCache.forEach(fbo => fbo.fb.destroy()); this.renderCache = []; this.renderCachePool = []; this.proxyCachedFBO = {}; } } class ProxiedTileID extends ref_properties.OverscaledTileID { constructor(tileID, proxyTileKey, posMatrix) { super(tileID.overscaledZ, tileID.wrap, tileID.canonical.z, tileID.canonical.x, tileID.canonical.y); this.proxyTileKey = proxyTileKey; this.posMatrix = posMatrix; } } class Terrain$1 extends ref_properties.Elevation { constructor(painter, style) { super(); this.painter = painter; this.terrainTileForTile = {}; this.prevTerrainTileForTile = {}; const [triangleGridArray, triangleGridIndices, skirtIndicesOffset] = createGrid(GRID_DIM + 1); const context = painter.context; this.gridBuffer = context.createVertexBuffer(triangleGridArray, rasterBoundsAttributes.members); this.gridIndexBuffer = context.createIndexBuffer(triangleGridIndices); this.gridSegments = ref_properties.SegmentVector.simpleSegment(0, 0, triangleGridArray.length, triangleGridIndices.length); this.gridNoSkirtSegments = ref_properties.SegmentVector.simpleSegment(0, 0, triangleGridArray.length, skirtIndicesOffset); this.proxyCoords = []; this.proxiedCoords = {}; this._visibleDemTiles = []; this._drapedRenderBatches = []; this._sourceTilesOverlap = {}; this.proxySourceCache = new ProxySourceCache(style.map); this.orthoMatrix = ref_properties.create(); ref_properties.ortho(this.orthoMatrix, 0, ref_properties.EXTENT, 0, ref_properties.EXTENT, 0, 1); const gl = context.gl; this._overlapStencilMode = new ref_properties.StencilMode({ func: gl.GEQUAL, mask: 255 }, 0, 255, gl.KEEP, gl.KEEP, gl.REPLACE); this._previousZoom = painter.transform.zoom; this.pool = []; this._findCoveringTileCache = {}; this._tilesDirty = {}; this.style = style; this._useVertexMorphing = true; } set style(style) { style.on('data', this._onStyleDataEvent.bind(this)); style.on('neworder', this._checkRenderCacheEfficiency.bind(this)); this._style = style; this._checkRenderCacheEfficiency(); } update(style, transform, cameraChanging) { if (style && style.terrain) { if (this._style !== style) { this.style = style; } this.enabled = true; const terrainProps = style.terrain.properties; this.sourceCache = style._getSourceCache(terrainProps.get('source')); this._exaggeration = terrainProps.get('exaggeration'); const updateSourceCache = () => { if (this.sourceCache.used) { ref_properties.warnOnce(`Raster DEM source '${ this.sourceCache.id }' is used both for terrain and as layer source.\n` + 'This leads to lower resolution of hillshade. For full hillshade resolution but higher memory consumption, define another raster DEM source.'); } const demScale = this.sourceCache.getSource().tileSize / GRID_DIM; const proxyTileSize = this.proxySourceCache.getSource().tileSize; this.sourceCache.update(transform, demScale * proxyTileSize, true); this._findCoveringTileCache[this.sourceCache.id] = {}; }; if (!this.sourceCache.usedForTerrain) { this._findCoveringTileCache[this.sourceCache.id] = {}; this.sourceCache.usedForTerrain = true; updateSourceCache(); this._initializing = true; } updateSourceCache(); transform.updateElevation(!cameraChanging); this._findCoveringTileCache[this.proxySourceCache.id] = {}; this.proxySourceCache.update(transform); this._emptyDEMTextureDirty = true; } else { this._disable(); } } _checkRenderCacheEfficiency() { const renderCacheInfo = this.renderCacheEfficiency(this._style); if (this._style.map._optimizeForTerrain) ; else if (renderCacheInfo.efficiency !== 100) { ref_properties.warnOnce(`Terrain render cache efficiency is not optimal (${ renderCacheInfo.efficiency }%) and performance may be affected negatively, consider placing all background, fill and line layers before layer with id '${ renderCacheInfo.firstUndrapedLayer }' or create a map using optimizeForTerrain: true option.`); } } _onStyleDataEvent(event) { if (event.coord && event.dataType === 'source') { this._clearRenderCacheForTile(event.sourceCacheId, event.coord); } else if (event.dataType === 'style') { this._invalidateRenderCache = true; } } _disable() { if (!this.enabled) return; this.enabled = false; this.proxySourceCache.deallocRenderCache(); if (this._style) { for (const id in this._style._sourceCaches) { this._style._sourceCaches[id].usedForTerrain = false; } } } destroy() { this._disable(); if (this._emptyDEMTexture) this._emptyDEMTexture.destroy(); this.pool.forEach(fbo => fbo.fb.destroy()); this.pool = []; if (this._depthFBO) { this._depthFBO.destroy(); delete this._depthFBO; delete this._depthTexture; } } _source() { return this.enabled ? this.sourceCache : null; } exaggeration() { return this._exaggeration; } get visibleDemTiles() { return this._visibleDemTiles; } get drapeBufferSize() { const extent = this.proxySourceCache.getSource().tileSize * 2; return [ extent, extent ]; } set useVertexMorphing(enable) { this._useVertexMorphing = enable; } updateTileBinding(sourcesCoords) { if (!this.enabled) return; this.prevTerrainTileForTile = this.terrainTileForTile; const psc = this.proxySourceCache; const tr = this.painter.transform; if (this._initializing) { this._initializing = tr._centerAltitude === 0 && this.getAtPoint(ref_properties.MercatorCoordinate.fromLngLat(tr.center), -1) === -1; this._emptyDEMTextureDirty = !this._initializing; } const options = this.painter.options; this.renderCached = (options.zooming || options.moving || options.rotating || !!this.forceRenderCached) && !this._invalidateRenderCache; this._invalidateRenderCache = false; const coords = this.proxyCoords = psc.getIds().map(id => { const tileID = psc.getTileByID(id).tileID; tileID.posMatrix = tr.calculatePosMatrix(tileID.toUnwrapped()); return tileID; }); sortByDistanceToCamera(coords, this.painter); this._previousZoom = tr.zoom; const previousProxyToSource = this.proxyToSource || {}; this.proxyToSource = {}; coords.forEach(tileID => { this.proxyToSource[tileID.key] = {}; }); this.terrainTileForTile = {}; const sourceCaches = this._style._sourceCaches; for (const id in sourceCaches) { const sourceCache = sourceCaches[id]; if (!sourceCache.used) continue; if (sourceCache !== this.sourceCache) this._findCoveringTileCache[sourceCache.id] = {}; this._setupProxiedCoordsForOrtho(sourceCache, sourcesCoords[id], previousProxyToSource); if (sourceCache.usedForTerrain) continue; const coordinates = sourcesCoords[id]; if (sourceCache.getSource().reparseOverscaled) { this._assignTerrainTiles(coordinates); } } this.proxiedCoords[psc.id] = coords.map(tileID => new ProxiedTileID(tileID, tileID.key, this.orthoMatrix)); this._assignTerrainTiles(coords); this._prepareDEMTextures(); this._setupDrapedRenderBatches(); this._setupRenderCache(previousProxyToSource); this.renderingToTexture = false; this._initFBOPool(); this._updateTimestamp = ref_properties.browser.now(); const visibleKeys = {}; this._visibleDemTiles = []; for (const id of this.proxyCoords) { const demTile = this.terrainTileForTile[id.key]; if (!demTile) continue; const key = demTile.tileID.key; if (key in visibleKeys) continue; this._visibleDemTiles.push(demTile); visibleKeys[key] = key; } } _assignTerrainTiles(coords) { if (this._initializing) return; coords.forEach(tileID => { if (this.terrainTileForTile[tileID.key]) return; const demTile = this._findTileCoveringTileID(tileID, this.sourceCache); if (demTile) this.terrainTileForTile[tileID.key] = demTile; }); } _prepareDEMTextures() { const context = this.painter.context; const gl = context.gl; for (const key in this.terrainTileForTile) { const tile = this.terrainTileForTile[key]; const dem = tile.dem; if (dem && (!tile.demTexture || tile.needsDEMTextureUpload)) { context.activeTexture.set(gl.TEXTURE1); prepareDEMTexture(this.painter, tile, dem); } } } _prepareDemTileUniforms(proxyTile, demTile, uniforms, uniformSuffix) { if (!demTile || demTile.demTexture == null) return false; const proxyId = proxyTile.tileID.canonical; const demId = demTile.tileID.canonical; const demScaleBy = Math.pow(2, demId.z - proxyId.z); const suffix = uniformSuffix || ''; uniforms[`u_dem_tl${ suffix }`] = [ proxyId.x * demScaleBy % 1, proxyId.y * demScaleBy % 1 ]; uniforms[`u_dem_scale${ suffix }`] = demScaleBy; return true; } get emptyDEMTexture() { return !this._emptyDEMTextureDirty && this._emptyDEMTexture ? this._emptyDEMTexture : this._updateEmptyDEMTexture(); } _getLoadedAreaMinimum() { let nonzero = 0; const min = this._visibleDemTiles.reduce((acc, tile) => { if (!tile.dem) return acc; const m = tile.dem.tree.minimums[0]; acc += m; if (m > 0) nonzero++; return acc; }, 0); return nonzero ? min / nonzero : 0; } _updateEmptyDEMTexture() { const context = this.painter.context; const gl = context.gl; context.activeTexture.set(gl.TEXTURE2); const min = this._getLoadedAreaMinimum(); const image = { width: 1, height: 1, data: new Uint8Array(ref_properties.DEMData.pack(min, this.sourceCache.getSource().encoding)) }; this._emptyDEMTextureDirty = false; let texture = this._emptyDEMTexture; if (!texture) { texture = this._emptyDEMTexture = new ref_properties.Texture(context, image, gl.RGBA, { premultiply: false }); } else { texture.update(image, { premultiply: false }); } return texture; } setupElevationDraw(tile, program, options) { const context = this.painter.context; const gl = context.gl; const uniforms = defaultTerrainUniforms(this.sourceCache.getSource().encoding); uniforms['u_dem_size'] = this.sourceCache.getSource().tileSize; uniforms['u_exaggeration'] = this.exaggeration(); let demTile = null; let prevDemTile = null; let morphingPhase = 1; if (options && options.morphing && this._useVertexMorphing) { const srcTile = options.morphing.srcDemTile; const dstTile = options.morphing.dstDemTile; morphingPhase = options.morphing.phase; if (srcTile && dstTile) { if (this._prepareDemTileUniforms(tile, srcTile, uniforms, '_prev')) prevDemTile = srcTile; if (this._prepareDemTileUniforms(tile, dstTile, uniforms)) demTile = dstTile; } } if (prevDemTile && demTile) { context.activeTexture.set(gl.TEXTURE2); demTile.demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE, gl.NEAREST); context.activeTexture.set(gl.TEXTURE4); prevDemTile.demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE, gl.NEAREST); uniforms['u_dem_lerp'] = morphingPhase; } else { demTile = this.terrainTileForTile[tile.tileID.key]; context.activeTexture.set(gl.TEXTURE2); const demTexture = this._prepareDemTileUniforms(tile, demTile, uniforms) ? demTile.demTexture : this.emptyDEMTexture; demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE, gl.NEAREST); } if (options && options.useDepthForOcclusion) { context.activeTexture.set(gl.TEXTURE3); this._depthTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE, gl.NEAREST); uniforms['u_depth_size_inv'] = [ 1 / this._depthFBO.width, 1 / this._depthFBO.height ]; } if (options && options.useMeterToDem && demTile) { const meterToDEM = (1 << demTile.tileID.canonical.z) * ref_properties.mercatorZfromAltitude(1, this.painter.transform.center.lat) * this.sourceCache.getSource().tileSize; uniforms['u_meter_to_dem'] = meterToDEM; } if (options && options.labelPlaneMatrixInv) { uniforms['u_label_plane_matrix_inv'] = options.labelPlaneMatrixInv; } program.setTerrainUniformValues(context, uniforms); } renderBatch(startLayerIndex) { if (this._drapedRenderBatches.length === 0) { return startLayerIndex + 1; } this.renderingToTexture = true; const painter = this.painter; const context = this.painter.context; const psc = this.proxySourceCache; const proxies = this.proxiedCoords[psc.id]; const setupRenderToScreen = () => { context.bindFramebuffer.set(null); context.viewport.set([ 0, 0, painter.width, painter.height ]); this.renderingToTexture = false; }; const drapedLayerBatch = this._drapedRenderBatches.shift(); let drawAsRasterCoords = []; const layerIds = painter.style.order; let poolIndex = 0; for (let i = 0; i < proxies.length; i++) { const proxy = proxies[i]; const tile = psc.getTileByID(proxy.proxyTileKey); const renderCacheIndex = psc.proxyCachedFBO[proxy.key] ? psc.proxyCachedFBO[proxy.key][startLayerIndex] : undefined; let fbo; if (renderCacheIndex !== undefined) { fbo = this.currentFBO = psc.renderCache[renderCacheIndex]; } else { fbo = this.currentFBO = this.pool[poolIndex++]; } tile.texture = fbo.tex; if (renderCacheIndex !== undefined && !fbo.dirty) { drawAsRasterCoords.push(tile.tileID); continue; } context.bindFramebuffer.set(fbo.fb.framebuffer); this.renderedToTile = false; if (fbo.dirty) { context.clear({ color: ref_properties.Color.transparent }); fbo.dirty = false; } let currentStencilSource; for (let j = drapedLayerBatch.start; j <= drapedLayerBatch.end; ++j) { const layer = painter.style._layers[layerIds[j]]; const hidden = layer.isHidden(painter.transform.zoom); if (hidden) continue; const sourceCache = painter.style._getLayerSourceCache(layer); const proxiedCoords = sourceCache ? this.proxyToSource[proxy.key][sourceCache.id] : [proxy]; if (!proxiedCoords) continue; const coords = proxiedCoords; context.viewport.set([ 0, 0, fbo.fb.width, fbo.fb.height ]); if (currentStencilSource !== (sourceCache ? sourceCache.id : null)) { this._setupStencil(proxiedCoords, layer, sourceCache); currentStencilSource = sourceCache ? sourceCache.id : null; } painter.renderLayer(painter, sourceCache, layer, coords); } fbo.dirty = this.renderedToTile; if (this.renderedToTile) drawAsRasterCoords.push(tile.tileID); if (poolIndex === FBO_POOL_SIZE) { poolIndex = 0; if (drawAsRasterCoords.length > 0) { setupRenderToScreen(); drawTerrainRaster(painter, this, psc, drawAsRasterCoords, this._updateTimestamp); this.renderingToTexture = true; drawAsRasterCoords = []; } } } setupRenderToScreen(); if (drawAsRasterCoords.length > 0) { drawTerrainRaster(painter, this, psc, drawAsRasterCoords, this._updateTimestamp); } return drapedLayerBatch.end + 1; } postRender() { } renderCacheEfficiency(style) { const layerCount = style.order.length; if (layerCount === 0) { return { efficiency: 100 }; } let uncacheableLayerCount = 0; let drapedLayerCount = 0; let reachedUndrapedLayer = false; let firstUndrapedLayer; for (let i = 0; i < layerCount; ++i) { const layer = style._layers[style.order[i]]; if (!this._style.isLayerDraped(layer)) { if (!reachedUndrapedLayer) { reachedUndrapedLayer = true; firstUndrapedLayer = layer.id; } } else { if (reachedUndrapedLayer) { ++uncacheableLayerCount; } ++drapedLayerCount; } } if (drapedLayerCount === 0) { return { efficiency: 100 }; } return { efficiency: (1 - uncacheableLayerCount / drapedLayerCount) * 100, firstUndrapedLayer }; } raycast(pos, dir, exaggeration) { if (!this._visibleDemTiles) return null; const preparedTiles = this._visibleDemTiles.filter(tile => tile.dem).map(tile => { const id = tile.tileID; const tiles = Math.pow(2, id.overscaledZ); const {x, y} = id.canonical; const minx = x / tiles; const maxx = (x + 1) / tiles; const miny = y / tiles; const maxy = (y + 1) / tiles; const tree = tile.dem.tree; return { minx, miny, maxx, maxy, t: tree.raycastRoot(minx, miny, maxx, maxy, pos, dir, exaggeration), tile }; }); preparedTiles.sort((a, b) => { const at = a.t !== null ? a.t : Number.MAX_VALUE; const bt = b.t !== null ? b.t : Number.MAX_VALUE; return at - bt; }); for (const obj of preparedTiles) { if (obj.t == null) return null; const tree = obj.tile.dem.tree; const t = tree.raycast(obj.minx, obj.miny, obj.maxx, obj.maxy, pos, dir, exaggeration); if (t != null) return t; } return null; } _createFBO() { const painter = this.painter; const context = painter.context; const gl = context.gl; const bufferSize = this.drapeBufferSize; context.activeTexture.set(gl.TEXTURE0); const tex = new ref_properties.Texture(context, { width: bufferSize[0], height: bufferSize[1], data: null }, gl.RGBA); tex.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); const fb = context.createFramebuffer(bufferSize[0], bufferSize[1], false); fb.colorAttachment.set(tex.texture); if (context.extTextureFilterAnisotropic && !context.extTextureFilterAnisotropicForceOff) { gl.texParameterf(gl.TEXTURE_2D, context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, context.extTextureFilterAnisotropicMax); } return { fb, tex, dirty: false, ref: 1 }; } _initFBOPool() { while (this.pool.length < Math.min(FBO_POOL_SIZE, this.proxyCoords.length)) { this.pool.push(this._createFBO()); } } _shouldDisableRenderCache() { const isCrossFading = id => { const layer = this._style._layers[id]; const isHidden = !layer.isHidden(this.painter.transform.zoom); const crossFade = layer.getCrossfadeParameters(); const isFading = !!crossFade && crossFade.t !== 1; return layer.type !== 'custom' && !isHidden && isFading; }; return !this.renderCached || this._style.order.some(isCrossFading); } _clearRasterFadeFromRenderCache() { let hasRasterSource = false; for (const id in this._style._sourceCaches) { if (this._style._sourceCaches[id]._source instanceof RasterTileSource) { hasRasterSource = true; break; } } if (!hasRasterSource) { return; } for (let i = 0; i < this._style.order.length; ++i) { const layer = this._style._layers[this._style.order[i]]; const isHidden = layer.isHidden(this.painter.transform.zoom); const sourceCache = this._style._getLayerSourceCache(layer); if (layer.type !== 'raster' || isHidden || !sourceCache) { continue; } const rasterLayer = layer; const fadeDuration = rasterLayer.paint.get('raster-fade-duration'); for (const proxy of this.proxyCoords) { const proxiedCoords = this.proxyToSource[proxy.key][sourceCache.id]; const coords = proxiedCoords; if (!coords) { continue; } for (const coord of coords) { const tile = sourceCache.getTile(coord); const parent = sourceCache.findLoadedParent(coord, 0); const fade = rasterFade(tile, parent, sourceCache, this.painter.transform, fadeDuration); const isFading = fade.opacity !== 1 || fade.mix !== 0; if (isFading) { this._clearRenderCacheForTile(sourceCache.id, coord); } } } } } _setupDrapedRenderBatches() { const layerIds = this._style.order; const layerCount = layerIds.length; if (layerCount === 0) { return; } const batches = []; let currentLayer = 0; let layer = this._style._layers[layerIds[currentLayer]]; while (!this._style.isLayerDraped(layer) && layer.isHidden(this.painter.transform.zoom) && ++currentLayer < layerCount) { layer = this._style._layers[layerIds[currentLayer]]; } let batchStart; for (; currentLayer < layerCount; ++currentLayer) { const layer = this._style._layers[layerIds[currentLayer]]; if (layer.isHidden(this.painter.transform.zoom)) { continue; } if (!this._style.isLayerDraped(layer)) { if (batchStart !== undefined) { batches.push({ start: batchStart, end: currentLayer - 1 }); batchStart = undefined; } continue; } if (batchStart === undefined) { batchStart = currentLayer; } } if (batchStart !== undefined) { batches.push({ start: batchStart, end: currentLayer - 1 }); } if (this._style.map._optimizeForTerrain) ; this._drapedRenderBatches = batches; } _setupRenderCache(previousProxyToSource) { const psc = this.proxySourceCache; if (this._shouldDisableRenderCache()) { if (psc.renderCache.length > psc.renderCachePool.length) { const used = Object.values(psc.proxyCachedFBO); psc.proxyCachedFBO = {}; for (let i = 0; i < used.length; ++i) { const fbos = Object.values(used[i]); psc.renderCachePool.push(...fbos); } } return; } this._clearRasterFadeFromRenderCache(); const coords = this.proxyCoords; const dirty = this._tilesDirty; for (let i = coords.length - 1; i >= 0; i--) { const proxy = coords[i]; const tile = psc.getTileByID(proxy.key); if (psc.proxyCachedFBO[proxy.key] !== undefined) { const prev = previousProxyToSource[proxy.key]; const current = this.proxyToSource[proxy.key]; let equal = 0; for (const source in current) { const tiles = current[source]; const prevTiles = prev[source]; if (!prevTiles || prevTiles.length !== tiles.length || tiles.some((t, index) => t !== prevTiles[index] || dirty[source] && dirty[source].hasOwnProperty(t.key))) { equal = -1; break; } ++equal; } for (const proxyFBO in psc.proxyCachedFBO[proxy.key]) { psc.renderCache[psc.proxyCachedFBO[proxy.key][proxyFBO]].dirty = equal < 0 || equal !== Object.values(prev).length; } } else { for (let j = 0; j < this._drapedRenderBatches.length; ++j) { const batch = this._drapedRenderBatches[j]; let index = psc.renderCachePool.pop(); if (index === undefined && psc.renderCache.length < RENDER_CACHE_MAX_SIZE) { index = psc.renderCache.length; psc.renderCache.push(this._createFBO()); } if (index !== undefined) { if (psc.proxyCachedFBO[proxy.key] === undefined) psc.proxyCachedFBO[proxy.key] = {}; psc.proxyCachedFBO[proxy.key][batch.start] = index; psc.renderCache[index].dirty = true; } } } } this._tilesDirty = {}; } _setupStencil(proxiedCoords, layer, sourceCache) { if (!sourceCache || !this._sourceTilesOverlap[sourceCache.id]) { if (this._overlapStencilType) this._overlapStencilType = false; return; } const context = this.painter.context; const gl = context.gl; if (proxiedCoords.length <= 1) { this._overlapStencilType = false; return; } const fbo = this.currentFBO; const fb = fbo.fb; let stencilRange; if (layer.isTileClipped()) { stencilRange = proxiedCoords.length; this._overlapStencilMode.test = { func: gl.EQUAL, mask: 255 }; this._overlapStencilType = 'Clip'; } else if (proxiedCoords[0].overscaledZ > proxiedCoords[proxiedCoords.length - 1].overscaledZ) { stencilRange = 1; this._overlapStencilMode.test = { func: gl.GREATER, mask: 255 }; this._overlapStencilType = 'Mask'; } else { this._overlapStencilType = false; return; } if (!fb.depthAttachment) { const renderbuffer = context.createRenderbuffer(context.gl.DEPTH_STENCIL, fb.width, fb.height); fb.depthAttachment = new ref_properties.DepthStencilAttachment(context, fb.framebuffer); fb.depthAttachment.set(renderbuffer); context.clear({ stencil: 0 }); } if (fbo.ref + stencilRange > 255) { context.clear({ stencil: 0 }); fbo.ref = 0; } fbo.ref += stencilRange; this._overlapStencilMode.ref = fbo.ref; if (layer.isTileClipped()) { this._renderTileClippingMasks(proxiedCoords, this._overlapStencilMode.ref); } } stencilModeForRTTOverlap(id) { if (!this.renderingToTexture || !this._overlapStencilType) { return ref_properties.StencilMode.disabled; } if (this._overlapStencilType === 'Clip') { this._overlapStencilMode.ref = this.painter._tileClippingMaskIDs[id.key]; } return this._overlapStencilMode; } _renderTileClippingMasks(proxiedCoords, ref) { const painter = this.painter; const context = this.painter.context; const gl = context.gl; painter._tileClippingMaskIDs = {}; context.setColorMode(ref_properties.ColorMode.disabled); context.setDepthMode(ref_properties.DepthMode.disabled); const program = painter.useProgram('clippingMask'); for (const tileID of proxiedCoords) { const id = painter._tileClippingMaskIDs[tileID.key] = --ref; program.draw(context, gl.TRIANGLES, ref_properties.DepthMode.disabled, new ref_properties.StencilMode({ func: gl.ALWAYS, mask: 0 }, id, 255, gl.KEEP, gl.KEEP, gl.REPLACE), ref_properties.ColorMode.disabled, ref_properties.CullFaceMode.disabled, clippingMaskUniformValues(tileID.posMatrix), '$clipping', painter.tileExtentBuffer, painter.quadTriangleIndexBuffer, painter.tileExtentSegments); } } pointCoordinate(screenPoint) { const transform = this.painter.transform; if (screenPoint.x < 0 || screenPoint.x > transform.width || screenPoint.y < 0 || screenPoint.y > transform.height) { return null; } const far = [ screenPoint.x, screenPoint.y, 1, 1 ]; ref_properties.transformMat4(far, far, transform.pixelMatrixInverse); ref_properties.scale$1(far, far, 1 / far[3]); far[0] /= transform.worldSize; far[1] /= transform.worldSize; const camera = transform._camera.position; const mercatorZScale = ref_properties.mercatorZfromAltitude(1, transform.center.lat); const p = [ camera[0], camera[1], camera[2] / mercatorZScale, 0 ]; const dir = ref_properties.subtract([], far.slice(0, 3), p); ref_properties.normalize(dir, dir); const distanceAlongRay = this.raycast(p, dir, this._exaggeration); if (distanceAlongRay === null || !distanceAlongRay) return null; ref_properties.scaleAndAdd(p, p, dir, distanceAlongRay); p[3] = p[2]; p[2] *= mercatorZScale; return p; } drawDepth() { const painter = this.painter; const context = painter.context; const psc = this.proxySourceCache; const width = Math.ceil(painter.width), height = Math.ceil(painter.height); if (this._depthFBO && (this._depthFBO.width !== width || this._depthFBO.height !== height)) { this._depthFBO.destroy(); delete this._depthFBO; delete this._depthTexture; } if (!this._depthFBO) { const gl = context.gl; const fbo = context.createFramebuffer(width, height, true); context.activeTexture.set(gl.TEXTURE0); const texture = new ref_properties.Texture(context, { width, height, data: null }, gl.RGBA); texture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); fbo.colorAttachment.set(texture.texture); const renderbuffer = context.createRenderbuffer(context.gl.DEPTH_COMPONENT16, width, height); fbo.depthAttachment.set(renderbuffer); this._depthFBO = fbo; this._depthTexture = texture; } context.bindFramebuffer.set(this._depthFBO.framebuffer); context.viewport.set([ 0, 0, width, height ]); drawTerrainDepth(painter, this, psc, this.proxyCoords); } _setupProxiedCoordsForOrtho(sourceCache, sourceCoords, previousProxyToSource) { if (sourceCache.getSource() instanceof ImageSource) { return this._setupProxiedCoordsForImageSource(sourceCache, sourceCoords, previousProxyToSource); } this._findCoveringTileCache[sourceCache.id] = this._findCoveringTileCache[sourceCache.id] || {}; const coords = this.proxiedCoords[sourceCache.id] = []; const proxys = this.proxyCoords; for (let i = 0; i < proxys.length; i++) { const proxyTileID = proxys[i]; const proxied = this._findTileCoveringTileID(proxyTileID, sourceCache); if (proxied) { const id = this._createProxiedId(proxyTileID, proxied, previousProxyToSource[proxyTileID.key] && previousProxyToSource[proxyTileID.key][sourceCache.id]); coords.push(id); this.proxyToSource[proxyTileID.key][sourceCache.id] = [id]; } } let hasOverlap = false; for (let i = 0; i < sourceCoords.length; i++) { const tile = sourceCache.getTile(sourceCoords[i]); if (!tile || !tile.hasData()) continue; const proxy = this._findTileCoveringTileID(tile.tileID, this.proxySourceCache); if (proxy && proxy.tileID.canonical.z !== tile.tileID.canonical.z) { const array = this.proxyToSource[proxy.tileID.key][sourceCache.id]; const id = this._createProxiedId(proxy.tileID, tile, previousProxyToSource[proxy.tileID.key] && previousProxyToSource[proxy.tileID.key][sourceCache.id]); if (!array) { this.proxyToSource[proxy.tileID.key][sourceCache.id] = [id]; } else { array.splice(array.length - 1, 0, id); } coords.push(id); hasOverlap = true; } } this._sourceTilesOverlap[sourceCache.id] = hasOverlap; } _setupProxiedCoordsForImageSource(sourceCache, sourceCoords, previousProxyToSource) { if (!sourceCache.getSource().loaded()) return; const coords = this.proxiedCoords[sourceCache.id] = []; const proxys = this.proxyCoords; const imageSource = sourceCache.getSource(); const anchor = new ref_properties.Point(imageSource.tileID.x, imageSource.tileID.y)._div(1 << imageSource.tileID.z); const aabb = imageSource.coordinates.map(ref_properties.MercatorCoordinate.fromLngLat).reduce((acc, coord) => { acc.min.x = Math.min(acc.min.x, coord.x - anchor.x); acc.min.y = Math.min(acc.min.y, coord.y - anchor.y); acc.max.x = Math.max(acc.max.x, coord.x - anchor.x); acc.max.y = Math.max(acc.max.y, coord.y - anchor.y); return acc; }, { min: new ref_properties.Point(Number.MAX_VALUE, Number.MAX_VALUE), max: new ref_properties.Point(-Number.MAX_VALUE, -Number.MAX_VALUE) }); const tileOutsideImage = (tileID, imageTileID) => { const x = tileID.wrap + tileID.canonical.x / (1 << tileID.canonical.z); const y = tileID.canonical.y / (1 << tileID.canonical.z); const d = ref_properties.EXTENT / (1 << tileID.canonical.z); const ix = imageTileID.wrap + imageTileID.canonical.x / (1 << imageTileID.canonical.z); const iy = imageTileID.canonical.y / (1 << imageTileID.canonical.z); return x + d < ix + aabb.min.x || x > ix + aabb.max.x || y + d < iy + aabb.min.y || y > iy + aabb.max.y; }; for (let i = 0; i < proxys.length; i++) { const proxyTileID = proxys[i]; for (let j = 0; j < sourceCoords.length; j++) { const tile = sourceCache.getTile(sourceCoords[j]); if (!tile || !tile.hasData()) continue; if (tileOutsideImage(proxyTileID, tile.tileID)) continue; const id = this._createProxiedId(proxyTileID, tile, previousProxyToSource[proxyTileID.key] && previousProxyToSource[proxyTileID.key][sourceCache.id]); const array = this.proxyToSource[proxyTileID.key][sourceCache.id]; if (!array) { this.proxyToSource[proxyTileID.key][sourceCache.id] = [id]; } else { array.push(id); } coords.push(id); } } } _createProxiedId(proxyTileID, tile, recycle) { let matrix = this.orthoMatrix; if (recycle) { const recycled = recycle.find(proxied => proxied.key === tile.tileID.key); if (recycled) return recycled; } if (tile.tileID.key !== proxyTileID.key) { const scale = proxyTileID.canonical.z - tile.tileID.canonical.z; matrix = ref_properties.create(); let size, xOffset, yOffset; const wrap = tile.tileID.wrap - proxyTileID.wrap << proxyTileID.overscaledZ; if (scale > 0) { size = ref_properties.EXTENT >> scale; xOffset = size * ((tile.tileID.canonical.x << scale) - proxyTileID.canonical.x + wrap); yOffset = size * ((tile.tileID.canonical.y << scale) - proxyTileID.canonical.y); } else { size = ref_properties.EXTENT << -scale; xOffset = ref_properties.EXTENT * (tile.tileID.canonical.x - (proxyTileID.canonical.x + wrap << -scale)); yOffset = ref_properties.EXTENT * (tile.tileID.canonical.y - (proxyTileID.canonical.y << -scale)); } ref_properties.ortho(matrix, 0, size, 0, size, 0, 1); ref_properties.translate(matrix, matrix, [ xOffset, yOffset, 0 ]); } return new ProxiedTileID(tile.tileID, proxyTileID.key, matrix); } _findTileCoveringTileID(tileID, sourceCache) { let tile = sourceCache.getTile(tileID); if (tile && tile.hasData()) return tile; const lookup = this._findCoveringTileCache[sourceCache.id]; const key = lookup[tileID.key]; tile = key ? sourceCache.getTileByID(key) : null; if (tile && tile.hasData() || key === null) return tile; let sourceTileID = tile ? tile.tileID : tileID; let z = sourceTileID.overscaledZ; const minzoom = sourceCache.getSource().minzoom; const path = []; if (!key) { const maxzoom = sourceCache.getSource().maxzoom; if (tileID.canonical.z >= maxzoom) { const downscale = tileID.canonical.z - maxzoom; if (sourceCache.getSource().reparseOverscaled) { z = Math.max(tileID.canonical.z + 2, sourceCache.transform.tileZoom); sourceTileID = new ref_properties.OverscaledTileID(z, tileID.wrap, maxzoom, tileID.canonical.x >> downscale, tileID.canonical.y >> downscale); } else if (downscale !== 0) { z = maxzoom; sourceTileID = new ref_properties.OverscaledTileID(z, tileID.wrap, maxzoom, tileID.canonical.x >> downscale, tileID.canonical.y >> downscale); } } if (sourceTileID.key !== tileID.key) { path.push(sourceTileID.key); tile = sourceCache.getTile(sourceTileID); } } const pathToLookup = key => { path.forEach(id => { lookup[id] = key; }); path.length = 0; }; for (z = z - 1; z >= minzoom && !(tile && tile.hasData()); z--) { if (tile) { pathToLookup(tile.tileID.key); } const id = sourceTileID.calculateScaledKey(z); tile = sourceCache.getTileByID(id); if (tile && tile.hasData()) break; const key = lookup[id]; if (key === null) { break; } else if (key !== undefined) { tile = sourceCache.getTileByID(key); continue; } path.push(id); } pathToLookup(tile ? tile.tileID.key : null); return tile && tile.hasData() ? tile : null; } findDEMTileFor(tileID) { return this.enabled ? this._findTileCoveringTileID(tileID, this.sourceCache) : null; } prepareDrawTile(coord) { if (!this.renderedToTile) { this.renderedToTile = true; } } _clearRenderCacheForTile(source, coord) { let sourceTiles = this._tilesDirty[source]; if (!sourceTiles) sourceTiles = this._tilesDirty[source] = {}; sourceTiles[coord.key] = true; } } function sortByDistanceToCamera(tileIDs, painter) { const cameraCoordinate = painter.transform.pointCoordinate(painter.transform.getCameraPoint()); const cameraPoint = new ref_properties.Point(cameraCoordinate.x, cameraCoordinate.y); tileIDs.sort((a, b) => { if (b.overscaledZ - a.overscaledZ) return b.overscaledZ - a.overscaledZ; const aPoint = new ref_properties.Point(a.canonical.x + (1 << a.canonical.z) * a.wrap, a.canonical.y); const bPoint = new ref_properties.Point(b.canonical.x + (1 << b.canonical.z) * b.wrap, b.canonical.y); const cameraScaled = cameraPoint.mult(1 << a.canonical.z); cameraScaled.x -= 0.5; cameraScaled.y -= 0.5; return cameraScaled.distSqr(aPoint) - cameraScaled.distSqr(bPoint); }); } function createGrid(count) { const boundsArray = new ref_properties.StructArrayLayout4i8(); const indexArray = new ref_properties.StructArrayLayout3ui6(); const size = count + 2; boundsArray.reserve(size * size); indexArray.reserve((size - 1) * (size - 1) * 2); const step = ref_properties.EXTENT / (count - 1); const gridBound = ref_properties.EXTENT + step / 2; const bound = gridBound + step; const skirtOffset = 24575; for (let y = -step; y < bound; y += step) { for (let x = -step; x < bound; x += step) { const offset = x < 0 || x > gridBound || y < 0 || y > gridBound ? skirtOffset : 0; const xi = ref_properties.clamp(Math.round(x), 0, ref_properties.EXTENT); const yi = ref_properties.clamp(Math.round(y), 0, ref_properties.EXTENT); boundsArray.emplaceBack(xi + offset, yi, xi, yi); } } const skirtIndicesOffset = (size - 3) * (size - 3) * 2; const quad = (i, j) => { const index = j * size + i; indexArray.emplaceBack(index + 1, index, index + size); indexArray.emplaceBack(index + size, index + size + 1, index + 1); }; for (let j = 1; j < size - 2; j++) { for (let i = 1; i < size - 2; i++) { quad(i, j); } } [ 0, size - 2 ].forEach(j => { for (let i = 0; i < size - 1; i++) { quad(i, j); quad(j, i); } }); return [ boundsArray, indexArray, skirtIndicesOffset ]; } const terrainUniforms = (context, locations) => ({ 'u_dem': new ref_properties.Uniform1i(context, locations.u_dem), 'u_dem_prev': new ref_properties.Uniform1i(context, locations.u_dem_prev), 'u_dem_unpack': new ref_properties.Uniform4f(context, locations.u_dem_unpack), 'u_dem_tl': new ref_properties.Uniform2f(context, locations.u_dem_tl), 'u_dem_scale': new ref_properties.Uniform1f(context, locations.u_dem_scale), 'u_dem_tl_prev': new ref_properties.Uniform2f(context, locations.u_dem_tl_prev), 'u_dem_scale_prev': new ref_properties.Uniform1f(context, locations.u_dem_scale_prev), 'u_dem_size': new ref_properties.Uniform1f(context, locations.u_dem_size), 'u_dem_lerp': new ref_properties.Uniform1f(context, locations.u_dem_lerp), 'u_exaggeration': new ref_properties.Uniform1f(context, locations.u_exaggeration), 'u_depth': new ref_properties.Uniform1i(context, locations.u_depth), 'u_depth_size_inv': new ref_properties.Uniform2f(context, locations.u_depth_size_inv), 'u_meter_to_dem': new ref_properties.Uniform1f(context, locations.u_meter_to_dem), 'u_label_plane_matrix_inv': new ref_properties.UniformMatrix4f(context, locations.u_label_plane_matrix_inv) }); function defaultTerrainUniforms(encoding) { return { 'u_dem': 2, 'u_dem_prev': 4, 'u_dem_unpack': ref_properties.DEMData.getUnpackVector(encoding), 'u_dem_tl': [ 0, 0 ], 'u_dem_tl_prev': [ 0, 0 ], 'u_dem_scale': 0, 'u_dem_scale_prev': 0, 'u_dem_size': 0, 'u_dem_lerp': 1, 'u_depth': 3, 'u_depth_size_inv': [ 0, 0 ], 'u_exaggeration': 0 }; } function getTokenizedAttributesAndUniforms(array) { const result = []; for (let i = 0; i < array.length; i++) { if (array[i] === null) continue; const token = array[i].split(' '); result.push(token.pop()); } return result; } class Program { static cacheKey(name, defines, programConfiguration) { let key = `${ name }${ programConfiguration ? programConfiguration.cacheKey : '' }`; for (const define of defines) { key += `/${ define }`; } return key; } constructor(context, name, source, configuration, fixedUniforms, fixedDefines) { const gl = context.gl; this.program = gl.createProgram(); const staticAttrInfo = getTokenizedAttributesAndUniforms(source.staticAttributes); const dynamicAttrInfo = configuration ? configuration.getBinderAttributes() : []; const allAttrInfo = staticAttrInfo.concat(dynamicAttrInfo); const staticUniformsInfo = source.staticUniforms ? getTokenizedAttributesAndUniforms(source.staticUniforms) : []; const dynamicUniformsInfo = configuration ? configuration.getBinderUniforms() : []; const uniformList = staticUniformsInfo.concat(dynamicUniformsInfo); const allUniformsInfo = []; for (const uniform of uniformList) { if (allUniformsInfo.indexOf(uniform) < 0) allUniformsInfo.push(uniform); } let defines = configuration ? configuration.defines() : []; defines = defines.concat(fixedDefines.map(define => `#define ${ define }`)); const fragmentSource = defines.concat(prelude.fragmentSource, source.fragmentSource).join('\n'); const vertexSource = defines.concat(prelude.vertexSource, preludeTerrain.vertexSource, source.vertexSource).join('\n'); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); if (gl.isContextLost()) { this.failedToCreate = true; return; } gl.shaderSource(fragmentShader, fragmentSource); gl.compileShader(fragmentShader); gl.attachShader(this.program, fragmentShader); const vertexShader = gl.createShader(gl.VERTEX_SHADER); if (gl.isContextLost()) { this.failedToCreate = true; return; } gl.shaderSource(vertexShader, vertexSource); gl.compileShader(vertexShader); gl.attachShader(this.program, vertexShader); this.attributes = {}; const uniformLocations = {}; this.numAttributes = allAttrInfo.length; for (let i = 0; i < this.numAttributes; i++) { if (allAttrInfo[i]) { gl.bindAttribLocation(this.program, i, allAttrInfo[i]); this.attributes[allAttrInfo[i]] = i; } } gl.linkProgram(this.program); gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); for (let it = 0; it < allUniformsInfo.length; it++) { const uniform = allUniformsInfo[it]; if (uniform && !uniformLocations[uniform]) { const uniformLocation = gl.getUniformLocation(this.program, uniform); if (uniformLocation) { uniformLocations[uniform] = uniformLocation; } } } this.fixedUniforms = fixedUniforms(context, uniformLocations); this.binderUniforms = configuration ? configuration.getUniforms(context, uniformLocations) : []; if (fixedDefines.indexOf('TERRAIN') !== -1) { this.terrainUniforms = terrainUniforms(context, uniformLocations); } } setTerrainUniformValues(context, terrainUnformValues) { if (!this.terrainUniforms) return; const uniforms = this.terrainUniforms; if (this.failedToCreate) return; context.program.set(this.program); for (const name in terrainUnformValues) { uniforms[name].set(terrainUnformValues[name]); } } draw(context, drawMode, depthMode, stencilMode, colorMode, cullFaceMode, uniformValues, layerID, layoutVertexBuffer, indexBuffer, segments, currentProperties, zoom, configuration, dynamicLayoutBuffer, dynamicLayoutBuffer2) { const gl = context.gl; if (this.failedToCreate) return; context.program.set(this.program); context.setDepthMode(depthMode); context.setStencilMode(stencilMode); context.setColorMode(colorMode); context.setCullFace(cullFaceMode); for (const name in this.fixedUniforms) { this.fixedUniforms[name].set(uniformValues[name]); } if (configuration) { configuration.setUniforms(context, this.binderUniforms, currentProperties, { zoom: zoom }); } const primitiveSize = { [gl.LINES]: 2, [gl.TRIANGLES]: 3, [gl.LINE_STRIP]: 1 }[drawMode]; for (const segment of segments.get()) { const vaos = segment.vaos || (segment.vaos = {}); const vao = vaos[layerID] || (vaos[layerID] = new VertexArrayObject()); vao.bind(context, this, layoutVertexBuffer, configuration ? configuration.getPaintVertexBuffers() : [], indexBuffer, segment.vertexOffset, dynamicLayoutBuffer, dynamicLayoutBuffer2); gl.drawElements(drawMode, segment.primitiveLength * primitiveSize, gl.UNSIGNED_SHORT, segment.primitiveOffset * primitiveSize * 2); } } } function patternUniformValues(crossfade, painter, tile) { const tileRatio = 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom); const numTiles = Math.pow(2, tile.tileID.overscaledZ); const tileSizeAtNearestZoom = tile.tileSize * Math.pow(2, painter.transform.tileZoom) / numTiles; const pixelX = tileSizeAtNearestZoom * (tile.tileID.canonical.x + tile.tileID.wrap * numTiles); const pixelY = tileSizeAtNearestZoom * tile.tileID.canonical.y; return { 'u_image': 0, 'u_texsize': tile.imageAtlasTexture.size, 'u_scale': [ tileRatio, crossfade.fromScale, crossfade.toScale ], 'u_fade': crossfade.t, 'u_pixel_coord_upper': [ pixelX >> 16, pixelY >> 16 ], 'u_pixel_coord_lower': [ pixelX & 65535, pixelY & 65535 ] }; } function bgPatternUniformValues(image, crossfade, painter, tile) { const imagePosA = painter.imageManager.getPattern(image.from.toString()); const imagePosB = painter.imageManager.getPattern(image.to.toString()); const {width, height} = painter.imageManager.getPixelSize(); const numTiles = Math.pow(2, tile.tileID.overscaledZ); const tileSizeAtNearestZoom = tile.tileSize * Math.pow(2, painter.transform.tileZoom) / numTiles; const pixelX = tileSizeAtNearestZoom * (tile.tileID.canonical.x + tile.tileID.wrap * numTiles); const pixelY = tileSizeAtNearestZoom * tile.tileID.canonical.y; return { 'u_image': 0, 'u_pattern_tl_a': imagePosA.tl, 'u_pattern_br_a': imagePosA.br, 'u_pattern_tl_b': imagePosB.tl, 'u_pattern_br_b': imagePosB.br, 'u_texsize': [ width, height ], 'u_mix': crossfade.t, 'u_pattern_size_a': imagePosA.displaySize, 'u_pattern_size_b': imagePosB.displaySize, 'u_scale_a': crossfade.fromScale, 'u_scale_b': crossfade.toScale, 'u_tile_units_to_pixels': 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom), 'u_pixel_coord_upper': [ pixelX >> 16, pixelY >> 16 ], 'u_pixel_coord_lower': [ pixelX & 65535, pixelY & 65535 ] }; } const fillExtrusionUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_lightpos': new ref_properties.Uniform3f(context, locations.u_lightpos), 'u_lightintensity': new ref_properties.Uniform1f(context, locations.u_lightintensity), 'u_lightcolor': new ref_properties.Uniform3f(context, locations.u_lightcolor), 'u_vertical_gradient': new ref_properties.Uniform1f(context, locations.u_vertical_gradient), 'u_opacity': new ref_properties.Uniform1f(context, locations.u_opacity) }); const fillExtrusionPatternUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_lightpos': new ref_properties.Uniform3f(context, locations.u_lightpos), 'u_lightintensity': new ref_properties.Uniform1f(context, locations.u_lightintensity), 'u_lightcolor': new ref_properties.Uniform3f(context, locations.u_lightcolor), 'u_vertical_gradient': new ref_properties.Uniform1f(context, locations.u_vertical_gradient), 'u_height_factor': new ref_properties.Uniform1f(context, locations.u_height_factor), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_texsize': new ref_properties.Uniform2f(context, locations.u_texsize), 'u_pixel_coord_upper': new ref_properties.Uniform2f(context, locations.u_pixel_coord_upper), 'u_pixel_coord_lower': new ref_properties.Uniform2f(context, locations.u_pixel_coord_lower), 'u_scale': new ref_properties.Uniform3f(context, locations.u_scale), 'u_fade': new ref_properties.Uniform1f(context, locations.u_fade), 'u_opacity': new ref_properties.Uniform1f(context, locations.u_opacity) }); const fillExtrusionUniformValues = (matrix, painter, shouldUseVerticalGradient, opacity) => { const light = painter.style.light; const _lp = light.properties.get('position'); const lightPos = [ _lp.x, _lp.y, _lp.z ]; const lightMat = ref_properties.create$1(); const anchor = light.properties.get('anchor'); if (anchor === 'viewport') { ref_properties.fromRotation(lightMat, -painter.transform.angle); ref_properties.transformMat3(lightPos, lightPos, lightMat); } const lightColor = light.properties.get('color'); return { 'u_matrix': matrix, 'u_lightpos': lightPos, 'u_lightintensity': light.properties.get('intensity'), 'u_lightcolor': [ lightColor.r, lightColor.g, lightColor.b ], 'u_vertical_gradient': +shouldUseVerticalGradient, 'u_opacity': opacity }; }; const fillExtrusionPatternUniformValues = (matrix, painter, shouldUseVerticalGradient, opacity, coord, crossfade, tile) => { return ref_properties.extend(fillExtrusionUniformValues(matrix, painter, shouldUseVerticalGradient, opacity), patternUniformValues(crossfade, painter, tile), { 'u_height_factor': -Math.pow(2, coord.overscaledZ) / tile.tileSize / 8 }); }; const fillUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix) }); const fillPatternUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_texsize': new ref_properties.Uniform2f(context, locations.u_texsize), 'u_pixel_coord_upper': new ref_properties.Uniform2f(context, locations.u_pixel_coord_upper), 'u_pixel_coord_lower': new ref_properties.Uniform2f(context, locations.u_pixel_coord_lower), 'u_scale': new ref_properties.Uniform3f(context, locations.u_scale), 'u_fade': new ref_properties.Uniform1f(context, locations.u_fade) }); const fillOutlineUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_world': new ref_properties.Uniform2f(context, locations.u_world) }); const fillOutlinePatternUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_world': new ref_properties.Uniform2f(context, locations.u_world), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_texsize': new ref_properties.Uniform2f(context, locations.u_texsize), 'u_pixel_coord_upper': new ref_properties.Uniform2f(context, locations.u_pixel_coord_upper), 'u_pixel_coord_lower': new ref_properties.Uniform2f(context, locations.u_pixel_coord_lower), 'u_scale': new ref_properties.Uniform3f(context, locations.u_scale), 'u_fade': new ref_properties.Uniform1f(context, locations.u_fade) }); const fillUniformValues = matrix => ({ 'u_matrix': matrix }); const fillPatternUniformValues = (matrix, painter, crossfade, tile) => ref_properties.extend(fillUniformValues(matrix), patternUniformValues(crossfade, painter, tile)); const fillOutlineUniformValues = (matrix, drawingBufferSize) => ({ 'u_matrix': matrix, 'u_world': drawingBufferSize }); const fillOutlinePatternUniformValues = (matrix, painter, crossfade, tile, drawingBufferSize) => ref_properties.extend(fillPatternUniformValues(matrix, painter, crossfade, tile), { 'u_world': drawingBufferSize }); const circleUniforms = (context, locations) => ({ 'u_camera_to_center_distance': new ref_properties.Uniform1f(context, locations.u_camera_to_center_distance), 'u_extrude_scale': new ref_properties.Uniform2f(context, locations.u_extrude_scale), 'u_device_pixel_ratio': new ref_properties.Uniform1f(context, locations.u_device_pixel_ratio), 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix) }); const circleUniformValues = (painter, coord, tile, layer) => { const transform = painter.transform; let extrudeScale; if (layer.paint.get('circle-pitch-alignment') === 'map') { const pixelRatio = pixelsToTileUnits(tile, 1, transform.zoom); extrudeScale = [ pixelRatio, pixelRatio ]; } else { extrudeScale = transform.pixelsToGLUnits; } return { 'u_camera_to_center_distance': transform.cameraToCenterDistance, 'u_matrix': painter.translatePosMatrix(coord.posMatrix, tile, layer.paint.get('circle-translate'), layer.paint.get('circle-translate-anchor')), 'u_device_pixel_ratio': ref_properties.browser.devicePixelRatio, 'u_extrude_scale': extrudeScale }; }; const circleDefinesValues = layer => { const values = []; if (layer.paint.get('circle-pitch-alignment') === 'map') values.push('PITCH_WITH_MAP'); if (layer.paint.get('circle-pitch-scale') === 'map') values.push('SCALE_WITH_MAP'); return values; }; const collisionUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_camera_to_center_distance': new ref_properties.Uniform1f(context, locations.u_camera_to_center_distance), 'u_extrude_scale': new ref_properties.Uniform2f(context, locations.u_extrude_scale) }); const collisionCircleUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_inv_matrix': new ref_properties.UniformMatrix4f(context, locations.u_inv_matrix), 'u_camera_to_center_distance': new ref_properties.Uniform1f(context, locations.u_camera_to_center_distance), 'u_viewport_size': new ref_properties.Uniform2f(context, locations.u_viewport_size) }); const collisionUniformValues = (matrix, transform, tile) => { const pixelRatio = ref_properties.EXTENT / tile.tileSize; return { 'u_matrix': matrix, 'u_camera_to_center_distance': transform.cameraToCenterDistance, 'u_extrude_scale': [ transform.pixelsToGLUnits[0] / pixelRatio, transform.pixelsToGLUnits[1] / pixelRatio ] }; }; const collisionCircleUniformValues = (matrix, invMatrix, transform) => { return { 'u_matrix': matrix, 'u_inv_matrix': invMatrix, 'u_camera_to_center_distance': transform.cameraToCenterDistance, 'u_viewport_size': [ transform.width, transform.height ] }; }; const debugUniforms = (context, locations) => ({ 'u_color': new ref_properties.UniformColor(context, locations.u_color), 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_overlay': new ref_properties.Uniform1i(context, locations.u_overlay), 'u_overlay_scale': new ref_properties.Uniform1f(context, locations.u_overlay_scale) }); const debugUniformValues = (matrix, color, scaleRatio = 1) => ({ 'u_matrix': matrix, 'u_color': color, 'u_overlay': 0, 'u_overlay_scale': scaleRatio }); const heatmapUniforms = (context, locations) => ({ 'u_extrude_scale': new ref_properties.Uniform1f(context, locations.u_extrude_scale), 'u_intensity': new ref_properties.Uniform1f(context, locations.u_intensity), 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix) }); const heatmapTextureUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_world': new ref_properties.Uniform2f(context, locations.u_world), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_color_ramp': new ref_properties.Uniform1i(context, locations.u_color_ramp), 'u_opacity': new ref_properties.Uniform1f(context, locations.u_opacity) }); const heatmapUniformValues = (matrix, tile, zoom, intensity) => ({ 'u_matrix': matrix, 'u_extrude_scale': pixelsToTileUnits(tile, 1, zoom), 'u_intensity': intensity }); const heatmapTextureUniformValues = (painter, layer, textureUnit, colorRampUnit) => { const matrix = ref_properties.create(); ref_properties.ortho(matrix, 0, painter.width, painter.height, 0, 0, 1); const gl = painter.context.gl; return { 'u_matrix': matrix, 'u_world': [ gl.drawingBufferWidth, gl.drawingBufferHeight ], 'u_image': textureUnit, 'u_color_ramp': colorRampUnit, 'u_opacity': layer.paint.get('heatmap-opacity') }; }; const lineUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_ratio': new ref_properties.Uniform1f(context, locations.u_ratio), 'u_device_pixel_ratio': new ref_properties.Uniform1f(context, locations.u_device_pixel_ratio), 'u_units_to_pixels': new ref_properties.Uniform2f(context, locations.u_units_to_pixels) }); const lineGradientUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_ratio': new ref_properties.Uniform1f(context, locations.u_ratio), 'u_device_pixel_ratio': new ref_properties.Uniform1f(context, locations.u_device_pixel_ratio), 'u_units_to_pixels': new ref_properties.Uniform2f(context, locations.u_units_to_pixels), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_image_height': new ref_properties.Uniform1f(context, locations.u_image_height) }); const linePatternUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_texsize': new ref_properties.Uniform2f(context, locations.u_texsize), 'u_ratio': new ref_properties.Uniform1f(context, locations.u_ratio), 'u_device_pixel_ratio': new ref_properties.Uniform1f(context, locations.u_device_pixel_ratio), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_units_to_pixels': new ref_properties.Uniform2f(context, locations.u_units_to_pixels), 'u_scale': new ref_properties.Uniform3f(context, locations.u_scale), 'u_fade': new ref_properties.Uniform1f(context, locations.u_fade) }); const lineSDFUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_ratio': new ref_properties.Uniform1f(context, locations.u_ratio), 'u_device_pixel_ratio': new ref_properties.Uniform1f(context, locations.u_device_pixel_ratio), 'u_units_to_pixels': new ref_properties.Uniform2f(context, locations.u_units_to_pixels), 'u_patternscale_a': new ref_properties.Uniform2f(context, locations.u_patternscale_a), 'u_patternscale_b': new ref_properties.Uniform2f(context, locations.u_patternscale_b), 'u_sdfgamma': new ref_properties.Uniform1f(context, locations.u_sdfgamma), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_tex_y_a': new ref_properties.Uniform1f(context, locations.u_tex_y_a), 'u_tex_y_b': new ref_properties.Uniform1f(context, locations.u_tex_y_b), 'u_mix': new ref_properties.Uniform1f(context, locations.u_mix) }); const lineUniformValues = (painter, tile, layer, matrix) => { const transform = painter.transform; return { 'u_matrix': calculateMatrix(painter, tile, layer, matrix), 'u_ratio': 1 / pixelsToTileUnits(tile, 1, transform.zoom), 'u_device_pixel_ratio': ref_properties.browser.devicePixelRatio, 'u_units_to_pixels': [ 1 / transform.pixelsToGLUnits[0], 1 / transform.pixelsToGLUnits[1] ] }; }; const lineGradientUniformValues = (painter, tile, layer, matrix, imageHeight) => { return ref_properties.extend(lineUniformValues(painter, tile, layer, matrix), { 'u_image': 0, 'u_image_height': imageHeight }); }; const linePatternUniformValues = (painter, tile, layer, crossfade, matrix) => { const transform = painter.transform; const tileZoomRatio = calculateTileRatio(tile, transform); return { 'u_matrix': calculateMatrix(painter, tile, layer, matrix), 'u_texsize': tile.imageAtlasTexture.size, 'u_ratio': 1 / pixelsToTileUnits(tile, 1, transform.zoom), 'u_device_pixel_ratio': ref_properties.browser.devicePixelRatio, 'u_image': 0, 'u_scale': [ tileZoomRatio, crossfade.fromScale, crossfade.toScale ], 'u_fade': crossfade.t, 'u_units_to_pixels': [ 1 / transform.pixelsToGLUnits[0], 1 / transform.pixelsToGLUnits[1] ] }; }; const lineSDFUniformValues = (painter, tile, layer, dasharray, crossfade, matrix) => { const transform = painter.transform; const lineAtlas = painter.lineAtlas; const tileRatio = calculateTileRatio(tile, transform); const round = layer.layout.get('line-cap') === 'round'; const posA = lineAtlas.getDash(dasharray.from, round); const posB = lineAtlas.getDash(dasharray.to, round); const widthA = posA.width * crossfade.fromScale; const widthB = posB.width * crossfade.toScale; return ref_properties.extend(lineUniformValues(painter, tile, layer, matrix), { 'u_patternscale_a': [ tileRatio / widthA, -posA.height / 2 ], 'u_patternscale_b': [ tileRatio / widthB, -posB.height / 2 ], 'u_sdfgamma': lineAtlas.width / (Math.min(widthA, widthB) * 256 * ref_properties.browser.devicePixelRatio) / 2, 'u_image': 0, 'u_tex_y_a': posA.y, 'u_tex_y_b': posB.y, 'u_mix': crossfade.t }); }; function calculateTileRatio(tile, transform) { return 1 / pixelsToTileUnits(tile, 1, transform.tileZoom); } function calculateMatrix(painter, tile, layer, matrix) { return painter.translatePosMatrix(matrix ? matrix : tile.tileID.posMatrix, tile, layer.paint.get('line-translate'), layer.paint.get('line-translate-anchor')); } const rasterUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_tl_parent': new ref_properties.Uniform2f(context, locations.u_tl_parent), 'u_scale_parent': new ref_properties.Uniform1f(context, locations.u_scale_parent), 'u_buffer_scale': new ref_properties.Uniform1f(context, locations.u_buffer_scale), 'u_fade_t': new ref_properties.Uniform1f(context, locations.u_fade_t), 'u_opacity': new ref_properties.Uniform1f(context, locations.u_opacity), 'u_image0': new ref_properties.Uniform1i(context, locations.u_image0), 'u_image1': new ref_properties.Uniform1i(context, locations.u_image1), 'u_brightness_low': new ref_properties.Uniform1f(context, locations.u_brightness_low), 'u_brightness_high': new ref_properties.Uniform1f(context, locations.u_brightness_high), 'u_saturation_factor': new ref_properties.Uniform1f(context, locations.u_saturation_factor), 'u_contrast_factor': new ref_properties.Uniform1f(context, locations.u_contrast_factor), 'u_spin_weights': new ref_properties.Uniform3f(context, locations.u_spin_weights) }); const rasterUniformValues = (matrix, parentTL, parentScaleBy, fade, layer) => ({ 'u_matrix': matrix, 'u_tl_parent': parentTL, 'u_scale_parent': parentScaleBy, 'u_buffer_scale': 1, 'u_fade_t': fade.mix, 'u_opacity': fade.opacity * layer.paint.get('raster-opacity'), 'u_image0': 0, 'u_image1': 1, 'u_brightness_low': layer.paint.get('raster-brightness-min'), 'u_brightness_high': layer.paint.get('raster-brightness-max'), 'u_saturation_factor': saturationFactor(layer.paint.get('raster-saturation')), 'u_contrast_factor': contrastFactor(layer.paint.get('raster-contrast')), 'u_spin_weights': spinWeights(layer.paint.get('raster-hue-rotate')) }); function spinWeights(angle) { angle *= Math.PI / 180; const s = Math.sin(angle); const c = Math.cos(angle); return [ (2 * c + 1) / 3, (-Math.sqrt(3) * s - c + 1) / 3, (Math.sqrt(3) * s - c + 1) / 3 ]; } function contrastFactor(contrast) { return contrast > 0 ? 1 / (1 - contrast) : 1 + contrast; } function saturationFactor(saturation) { return saturation > 0 ? 1 - 1 / (1.001 - saturation) : -saturation; } const symbolIconUniforms = (context, locations) => ({ 'u_is_size_zoom_constant': new ref_properties.Uniform1i(context, locations.u_is_size_zoom_constant), 'u_is_size_feature_constant': new ref_properties.Uniform1i(context, locations.u_is_size_feature_constant), 'u_size_t': new ref_properties.Uniform1f(context, locations.u_size_t), 'u_size': new ref_properties.Uniform1f(context, locations.u_size), 'u_camera_to_center_distance': new ref_properties.Uniform1f(context, locations.u_camera_to_center_distance), 'u_pitch': new ref_properties.Uniform1f(context, locations.u_pitch), 'u_rotate_symbol': new ref_properties.Uniform1i(context, locations.u_rotate_symbol), 'u_aspect_ratio': new ref_properties.Uniform1f(context, locations.u_aspect_ratio), 'u_fade_change': new ref_properties.Uniform1f(context, locations.u_fade_change), 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_label_plane_matrix': new ref_properties.UniformMatrix4f(context, locations.u_label_plane_matrix), 'u_coord_matrix': new ref_properties.UniformMatrix4f(context, locations.u_coord_matrix), 'u_is_text': new ref_properties.Uniform1i(context, locations.u_is_text), 'u_pitch_with_map': new ref_properties.Uniform1i(context, locations.u_pitch_with_map), 'u_texsize': new ref_properties.Uniform2f(context, locations.u_texsize), 'u_texture': new ref_properties.Uniform1i(context, locations.u_texture) }); const symbolSDFUniforms = (context, locations) => ({ 'u_is_size_zoom_constant': new ref_properties.Uniform1i(context, locations.u_is_size_zoom_constant), 'u_is_size_feature_constant': new ref_properties.Uniform1i(context, locations.u_is_size_feature_constant), 'u_size_t': new ref_properties.Uniform1f(context, locations.u_size_t), 'u_size': new ref_properties.Uniform1f(context, locations.u_size), 'u_camera_to_center_distance': new ref_properties.Uniform1f(context, locations.u_camera_to_center_distance), 'u_pitch': new ref_properties.Uniform1f(context, locations.u_pitch), 'u_rotate_symbol': new ref_properties.Uniform1i(context, locations.u_rotate_symbol), 'u_aspect_ratio': new ref_properties.Uniform1f(context, locations.u_aspect_ratio), 'u_fade_change': new ref_properties.Uniform1f(context, locations.u_fade_change), 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_label_plane_matrix': new ref_properties.UniformMatrix4f(context, locations.u_label_plane_matrix), 'u_coord_matrix': new ref_properties.UniformMatrix4f(context, locations.u_coord_matrix), 'u_is_text': new ref_properties.Uniform1i(context, locations.u_is_text), 'u_pitch_with_map': new ref_properties.Uniform1i(context, locations.u_pitch_with_map), 'u_texsize': new ref_properties.Uniform2f(context, locations.u_texsize), 'u_texture': new ref_properties.Uniform1i(context, locations.u_texture), 'u_gamma_scale': new ref_properties.Uniform1f(context, locations.u_gamma_scale), 'u_device_pixel_ratio': new ref_properties.Uniform1f(context, locations.u_device_pixel_ratio), 'u_is_halo': new ref_properties.Uniform1i(context, locations.u_is_halo) }); const symbolTextAndIconUniforms = (context, locations) => ({ 'u_is_size_zoom_constant': new ref_properties.Uniform1i(context, locations.u_is_size_zoom_constant), 'u_is_size_feature_constant': new ref_properties.Uniform1i(context, locations.u_is_size_feature_constant), 'u_size_t': new ref_properties.Uniform1f(context, locations.u_size_t), 'u_size': new ref_properties.Uniform1f(context, locations.u_size), 'u_camera_to_center_distance': new ref_properties.Uniform1f(context, locations.u_camera_to_center_distance), 'u_pitch': new ref_properties.Uniform1f(context, locations.u_pitch), 'u_rotate_symbol': new ref_properties.Uniform1i(context, locations.u_rotate_symbol), 'u_aspect_ratio': new ref_properties.Uniform1f(context, locations.u_aspect_ratio), 'u_fade_change': new ref_properties.Uniform1f(context, locations.u_fade_change), 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_label_plane_matrix': new ref_properties.UniformMatrix4f(context, locations.u_label_plane_matrix), 'u_coord_matrix': new ref_properties.UniformMatrix4f(context, locations.u_coord_matrix), 'u_is_text': new ref_properties.Uniform1i(context, locations.u_is_text), 'u_pitch_with_map': new ref_properties.Uniform1i(context, locations.u_pitch_with_map), 'u_texsize': new ref_properties.Uniform2f(context, locations.u_texsize), 'u_texsize_icon': new ref_properties.Uniform2f(context, locations.u_texsize_icon), 'u_texture': new ref_properties.Uniform1i(context, locations.u_texture), 'u_texture_icon': new ref_properties.Uniform1i(context, locations.u_texture_icon), 'u_gamma_scale': new ref_properties.Uniform1f(context, locations.u_gamma_scale), 'u_device_pixel_ratio': new ref_properties.Uniform1f(context, locations.u_device_pixel_ratio), 'u_is_halo': new ref_properties.Uniform1i(context, locations.u_is_halo) }); const symbolIconUniformValues = (functionType, size, rotateInShader, pitchWithMap, painter, matrix, labelPlaneMatrix, glCoordMatrix, isText, texSize) => { const transform = painter.transform; return { 'u_is_size_zoom_constant': +(functionType === 'constant' || functionType === 'source'), 'u_is_size_feature_constant': +(functionType === 'constant' || functionType === 'camera'), 'u_size_t': size ? size.uSizeT : 0, 'u_size': size ? size.uSize : 0, 'u_camera_to_center_distance': transform.cameraToCenterDistance, 'u_pitch': transform.pitch / 360 * 2 * Math.PI, 'u_rotate_symbol': +rotateInShader, 'u_aspect_ratio': transform.width / transform.height, 'u_fade_change': painter.options.fadeDuration ? painter.symbolFadeChange : 1, 'u_matrix': matrix, 'u_label_plane_matrix': labelPlaneMatrix, 'u_coord_matrix': glCoordMatrix, 'u_is_text': +isText, 'u_pitch_with_map': +pitchWithMap, 'u_texsize': texSize, 'u_texture': 0 }; }; const symbolSDFUniformValues = (functionType, size, rotateInShader, pitchWithMap, painter, matrix, labelPlaneMatrix, glCoordMatrix, isText, texSize, isHalo) => { const {cameraToCenterDistance, _pitch} = painter.transform; return ref_properties.extend(symbolIconUniformValues(functionType, size, rotateInShader, pitchWithMap, painter, matrix, labelPlaneMatrix, glCoordMatrix, isText, texSize), { 'u_gamma_scale': pitchWithMap ? cameraToCenterDistance * Math.cos(painter.terrain ? 0 : _pitch) : 1, 'u_device_pixel_ratio': ref_properties.browser.devicePixelRatio, 'u_is_halo': +isHalo }); }; const symbolTextAndIconUniformValues = (functionType, size, rotateInShader, pitchWithMap, painter, matrix, labelPlaneMatrix, glCoordMatrix, texSizeSDF, texSizeIcon) => { return ref_properties.extend(symbolSDFUniformValues(functionType, size, rotateInShader, pitchWithMap, painter, matrix, labelPlaneMatrix, glCoordMatrix, true, texSizeSDF, true), { 'u_texsize_icon': texSizeIcon, 'u_texture_icon': 1 }); }; const backgroundUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_opacity': new ref_properties.Uniform1f(context, locations.u_opacity), 'u_color': new ref_properties.UniformColor(context, locations.u_color) }); const backgroundPatternUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_opacity': new ref_properties.Uniform1f(context, locations.u_opacity), 'u_image': new ref_properties.Uniform1i(context, locations.u_image), 'u_pattern_tl_a': new ref_properties.Uniform2f(context, locations.u_pattern_tl_a), 'u_pattern_br_a': new ref_properties.Uniform2f(context, locations.u_pattern_br_a), 'u_pattern_tl_b': new ref_properties.Uniform2f(context, locations.u_pattern_tl_b), 'u_pattern_br_b': new ref_properties.Uniform2f(context, locations.u_pattern_br_b), 'u_texsize': new ref_properties.Uniform2f(context, locations.u_texsize), 'u_mix': new ref_properties.Uniform1f(context, locations.u_mix), 'u_pattern_size_a': new ref_properties.Uniform2f(context, locations.u_pattern_size_a), 'u_pattern_size_b': new ref_properties.Uniform2f(context, locations.u_pattern_size_b), 'u_scale_a': new ref_properties.Uniform1f(context, locations.u_scale_a), 'u_scale_b': new ref_properties.Uniform1f(context, locations.u_scale_b), 'u_pixel_coord_upper': new ref_properties.Uniform2f(context, locations.u_pixel_coord_upper), 'u_pixel_coord_lower': new ref_properties.Uniform2f(context, locations.u_pixel_coord_lower), 'u_tile_units_to_pixels': new ref_properties.Uniform1f(context, locations.u_tile_units_to_pixels) }); const backgroundUniformValues = (matrix, opacity, color) => ({ 'u_matrix': matrix, 'u_opacity': opacity, 'u_color': color }); const backgroundPatternUniformValues = (matrix, opacity, painter, image, tile, crossfade) => ref_properties.extend(bgPatternUniformValues(image, crossfade, painter, tile), { 'u_matrix': matrix, 'u_opacity': opacity }); const skyboxUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_sun_direction': new ref_properties.Uniform3f(context, locations.u_sun_direction), 'u_cubemap': new ref_properties.Uniform1i(context, locations.u_cubemap), 'u_opacity': new ref_properties.Uniform1f(context, locations.u_opacity), 'u_temporal_offset': new ref_properties.Uniform1f(context, locations.u_temporal_offset) }); const skyboxUniformValues = (matrix, sunDirection, cubemap, opacity, temporalOffset) => ({ 'u_matrix': matrix, 'u_sun_direction': sunDirection, 'u_cubemap': cubemap, 'u_opacity': opacity, 'u_temporal_offset': temporalOffset }); const skyboxGradientUniforms = (context, locations) => ({ 'u_matrix': new ref_properties.UniformMatrix4f(context, locations.u_matrix), 'u_color_ramp': new ref_properties.Uniform1i(context, locations.u_color_ramp), 'u_center_direction': new ref_properties.Uniform3f(context, locations.u_center_direction), 'u_radius': new ref_properties.Uniform1f(context, locations.u_radius), 'u_opacity': new ref_properties.Uniform1f(context, locations.u_opacity), 'u_temporal_offset': new ref_properties.Uniform1f(context, locations.u_temporal_offset) }); const skyboxGradientUniformValues = (matrix, centerDirection, radius, opacity, temporalOffset) => { return { 'u_matrix': matrix, 'u_color_ramp': 0, 'u_center_direction': centerDirection, 'u_radius': ref_properties.degToRad(radius), 'u_opacity': opacity, 'u_temporal_offset': temporalOffset }; }; const skyboxCaptureUniforms = (context, locations) => ({ 'u_matrix_3f': new ref_properties.UniformMatrix3f(context, locations.u_matrix_3f), 'u_sun_direction': new ref_properties.Uniform3f(context, locations.u_sun_direction), 'u_sun_intensity': new ref_properties.Uniform1f(context, locations.u_sun_intensity), 'u_color_tint_r': new ref_properties.Uniform4f(context, locations.u_color_tint_r), 'u_color_tint_m': new ref_properties.Uniform4f(context, locations.u_color_tint_m), 'u_luminance': new ref_properties.Uniform1f(context, locations.u_luminance) }); const skyboxCaptureUniformValues = (matrix, sunDirection, sunIntensity, atmosphereColor, atmosphereHaloColor) => ({ 'u_matrix_3f': matrix, 'u_sun_direction': sunDirection, 'u_sun_intensity': sunIntensity, 'u_color_tint_r': [ atmosphereColor.r, atmosphereColor.g, atmosphereColor.b, atmosphereColor.a ], 'u_color_tint_m': [ atmosphereHaloColor.r, atmosphereHaloColor.g, atmosphereHaloColor.b, atmosphereHaloColor.a ], 'u_luminance': 0.00005 }); const programUniforms = { fillExtrusion: fillExtrusionUniforms, fillExtrusionPattern: fillExtrusionPatternUniforms, fill: fillUniforms, fillPattern: fillPatternUniforms, fillOutline: fillOutlineUniforms, fillOutlinePattern: fillOutlinePatternUniforms, circle: circleUniforms, collisionBox: collisionUniforms, collisionCircle: collisionCircleUniforms, debug: debugUniforms, clippingMask: clippingMaskUniforms, heatmap: heatmapUniforms, heatmapTexture: heatmapTextureUniforms, hillshade: hillshadeUniforms, hillshadePrepare: hillshadePrepareUniforms, line: lineUniforms, lineGradient: lineGradientUniforms, linePattern: linePatternUniforms, lineSDF: lineSDFUniforms, raster: rasterUniforms, symbolIcon: symbolIconUniforms, symbolSDF: symbolSDFUniforms, symbolTextAndIcon: symbolTextAndIconUniforms, background: backgroundUniforms, backgroundPattern: backgroundPatternUniforms, terrainRaster: terrainRasterUniforms, terrainDepth: terrainRasterUniforms, skybox: skyboxUniforms, skyboxGradient: skyboxGradientUniforms, skyboxCapture: skyboxCaptureUniforms }; let quadTriangles; function drawCollisionDebug(painter, sourceCache, layer, coords, translate, translateAnchor, isText) { const context = painter.context; const gl = context.gl; const program = painter.useProgram('collisionBox'); const tileBatches = []; let circleCount = 0; let circleOffset = 0; for (let i = 0; i < coords.length; i++) { const coord = coords[i]; const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) continue; let posMatrix = coord.posMatrix; if (translate[0] !== 0 || translate[1] !== 0) { posMatrix = painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor); } const buffers = isText ? bucket.textCollisionBox : bucket.iconCollisionBox; const circleArray = bucket.collisionCircleArray; if (circleArray.length > 0) { const invTransform = ref_properties.create(); const transform = posMatrix; ref_properties.mul(invTransform, bucket.placementInvProjMatrix, painter.transform.glCoordMatrix); ref_properties.mul(invTransform, invTransform, bucket.placementViewportMatrix); tileBatches.push({ circleArray, circleOffset, transform, invTransform }); circleCount += circleArray.length / 4; circleOffset = circleCount; } if (!buffers) continue; if (painter.terrain) painter.terrain.setupElevationDraw(tile, program); program.draw(context, gl.LINES, ref_properties.DepthMode.disabled, ref_properties.StencilMode.disabled, painter.colorModeForRenderPass(), ref_properties.CullFaceMode.disabled, collisionUniformValues(posMatrix, painter.transform, tile), layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer, buffers.segments, null, painter.transform.zoom, null, buffers.collisionVertexBuffer, buffers.collisionVertexBufferExt); } if (!isText || !tileBatches.length) { return; } const circleProgram = painter.useProgram('collisionCircle'); const vertexData = new ref_properties.StructArrayLayout2f1f2i16(); vertexData.resize(circleCount * 4); vertexData._trim(); let vertexOffset = 0; for (const batch of tileBatches) { for (let i = 0; i < batch.circleArray.length / 4; i++) { const circleIdx = i * 4; const x = batch.circleArray[circleIdx + 0]; const y = batch.circleArray[circleIdx + 1]; const radius = batch.circleArray[circleIdx + 2]; const collision = batch.circleArray[circleIdx + 3]; vertexData.emplace(vertexOffset++, x, y, radius, collision, 0); vertexData.emplace(vertexOffset++, x, y, radius, collision, 1); vertexData.emplace(vertexOffset++, x, y, radius, collision, 2); vertexData.emplace(vertexOffset++, x, y, radius, collision, 3); } } if (!quadTriangles || quadTriangles.length < circleCount * 2) { quadTriangles = createQuadTriangles(circleCount); } const indexBuffer = context.createIndexBuffer(quadTriangles, true); const vertexBuffer = context.createVertexBuffer(vertexData, ref_properties.collisionCircleLayout.members, true); for (const batch of tileBatches) { const uniforms = collisionCircleUniformValues(batch.transform, batch.invTransform, painter.transform); circleProgram.draw(context, gl.TRIANGLES, ref_properties.DepthMode.disabled, ref_properties.StencilMode.disabled, painter.colorModeForRenderPass(), ref_properties.CullFaceMode.disabled, uniforms, layer.id, vertexBuffer, indexBuffer, ref_properties.SegmentVector.simpleSegment(0, batch.circleOffset * 2, batch.circleArray.length, batch.circleArray.length / 2), null, painter.transform.zoom, null, null, null); } vertexBuffer.destroy(); indexBuffer.destroy(); } function createQuadTriangles(quadCount) { const triCount = quadCount * 2; const array = new ref_properties.StructArrayLayout3ui6(); array.resize(triCount); array._trim(); for (let i = 0; i < triCount; i++) { const idx = i * 6; array.uint16[idx + 0] = i * 4 + 0; array.uint16[idx + 1] = i * 4 + 1; array.uint16[idx + 2] = i * 4 + 2; array.uint16[idx + 3] = i * 4 + 2; array.uint16[idx + 4] = i * 4 + 3; array.uint16[idx + 5] = i * 4 + 0; } return array; } const identityMat4 = ref_properties.identity(new Float32Array(16)); function drawSymbols(painter, sourceCache, layer, coords, variableOffsets) { if (painter.renderPass !== 'translucent') return; const stencilMode = ref_properties.StencilMode.disabled; const colorMode = painter.colorModeForRenderPass(); const variablePlacement = layer.layout.get('text-variable-anchor'); if (variablePlacement) { updateVariableAnchors(coords, painter, layer, sourceCache, layer.layout.get('text-rotation-alignment'), layer.layout.get('text-pitch-alignment'), variableOffsets); } if (layer.paint.get('icon-opacity').constantOr(1) !== 0) { drawLayerSymbols(painter, sourceCache, layer, coords, false, layer.paint.get('icon-translate'), layer.paint.get('icon-translate-anchor'), layer.layout.get('icon-rotation-alignment'), layer.layout.get('icon-pitch-alignment'), layer.layout.get('icon-keep-upright'), stencilMode, colorMode); } if (layer.paint.get('text-opacity').constantOr(1) !== 0) { drawLayerSymbols(painter, sourceCache, layer, coords, true, layer.paint.get('text-translate'), layer.paint.get('text-translate-anchor'), layer.layout.get('text-rotation-alignment'), layer.layout.get('text-pitch-alignment'), layer.layout.get('text-keep-upright'), stencilMode, colorMode); } if (sourceCache.map.showCollisionBoxes) { drawCollisionDebug(painter, sourceCache, layer, coords, layer.paint.get('text-translate'), layer.paint.get('text-translate-anchor'), true); drawCollisionDebug(painter, sourceCache, layer, coords, layer.paint.get('icon-translate'), layer.paint.get('icon-translate-anchor'), false); } } function calculateVariableRenderShift(anchor, width, height, textOffset, textBoxScale, renderTextSize) { const {horizontalAlign, verticalAlign} = ref_properties.getAnchorAlignment(anchor); const shiftX = -(horizontalAlign - 0.5) * width; const shiftY = -(verticalAlign - 0.5) * height; const variableOffset = ref_properties.evaluateVariableOffset(anchor, textOffset); return new ref_properties.Point((shiftX / textBoxScale + variableOffset[0]) * renderTextSize, (shiftY / textBoxScale + variableOffset[1]) * renderTextSize); } function updateVariableAnchors(coords, painter, layer, sourceCache, rotationAlignment, pitchAlignment, variableOffsets) { const tr = painter.transform; const rotateWithMap = rotationAlignment === 'map'; const pitchWithMap = pitchAlignment === 'map'; for (const coord of coords) { const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket || !bucket.text || !bucket.text.segments.get().length) continue; const sizeData = bucket.textSizeData; const size = ref_properties.evaluateSizeForZoom(sizeData, tr.zoom); const pixelToTileScale = pixelsToTileUnits(tile, 1, painter.transform.zoom); const labelPlaneMatrix = getLabelPlaneMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, pixelToTileScale); const updateTextFitIcon = layer.layout.get('icon-text-fit') !== 'none' && bucket.hasIconData(); if (size) { const tileScale = Math.pow(2, tr.zoom - tile.tileID.overscaledZ); const elevation = tr.elevation; const getElevation = elevation ? p => elevation.getAtTileOffset(coord, p.x, p.y) : _ => 0; updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, ref_properties.symbolSize, tr, labelPlaneMatrix, coord.posMatrix, tileScale, size, updateTextFitIcon, getElevation); } } } function updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, symbolSize, transform, labelPlaneMatrix, posMatrix, tileScale, size, updateTextFitIcon, getElevation) { const placedSymbols = bucket.text.placedSymbolArray; const dynamicTextLayoutVertexArray = bucket.text.dynamicLayoutVertexArray; const dynamicIconLayoutVertexArray = bucket.icon.dynamicLayoutVertexArray; const placedTextShifts = {}; dynamicTextLayoutVertexArray.clear(); for (let s = 0; s < placedSymbols.length; s++) { const symbol = placedSymbols.get(s); const skipOrientation = bucket.allowVerticalPlacement && !symbol.placedOrientation; const variableOffset = !symbol.hidden && symbol.crossTileID && !skipOrientation ? variableOffsets[symbol.crossTileID] : null; if (!variableOffset) { hideGlyphs(symbol.numGlyphs, dynamicTextLayoutVertexArray); } else { const tileAnchor = new ref_properties.Point(symbol.anchorX, symbol.anchorY); const elevation = getElevation(tileAnchor); const projectedAnchor = project(tileAnchor, pitchWithMap ? posMatrix : labelPlaneMatrix, elevation); const perspectiveRatio = getPerspectiveRatio(transform.cameraToCenterDistance, projectedAnchor.signedDistanceFromCamera); let renderTextSize = symbolSize.evaluateSizeForFeature(bucket.textSizeData, size, symbol) * perspectiveRatio / ref_properties.ONE_EM; if (pitchWithMap) { renderTextSize *= bucket.tilePixelRatio / tileScale; } const {width, height, anchor, textOffset, textBoxScale} = variableOffset; const shift = calculateVariableRenderShift(anchor, width, height, textOffset, textBoxScale, renderTextSize); const shiftedAnchor = pitchWithMap ? project(tileAnchor.add(shift), labelPlaneMatrix, elevation).point : projectedAnchor.point.add(rotateWithMap ? shift.rotate(-transform.angle) : shift); const angle = bucket.allowVerticalPlacement && symbol.placedOrientation === ref_properties.WritingMode.vertical ? Math.PI / 2 : 0; for (let g = 0; g < symbol.numGlyphs; g++) { ref_properties.addDynamicAttributes(dynamicTextLayoutVertexArray, shiftedAnchor, angle); } if (updateTextFitIcon && symbol.associatedIconIndex >= 0) { placedTextShifts[symbol.associatedIconIndex] = { shiftedAnchor, angle }; } } } if (updateTextFitIcon) { dynamicIconLayoutVertexArray.clear(); const placedIcons = bucket.icon.placedSymbolArray; for (let i = 0; i < placedIcons.length; i++) { const placedIcon = placedIcons.get(i); if (placedIcon.hidden) { hideGlyphs(placedIcon.numGlyphs, dynamicIconLayoutVertexArray); } else { const shift = placedTextShifts[i]; if (!shift) { hideGlyphs(placedIcon.numGlyphs, dynamicIconLayoutVertexArray); } else { for (let g = 0; g < placedIcon.numGlyphs; g++) { ref_properties.addDynamicAttributes(dynamicIconLayoutVertexArray, shift.shiftedAnchor, shift.angle); } } } } bucket.icon.dynamicLayoutVertexBuffer.updateData(dynamicIconLayoutVertexArray); } bucket.text.dynamicLayoutVertexBuffer.updateData(dynamicTextLayoutVertexArray); } function getSymbolProgramName(isSDF, isText, bucket) { if (bucket.iconsInText && isText) { return 'symbolTextAndIcon'; } else if (isSDF) { return 'symbolSDF'; } else { return 'symbolIcon'; } } function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate, translateAnchor, rotationAlignment, pitchAlignment, keepUpright, stencilMode, colorMode) { const context = painter.context; const gl = context.gl; const tr = painter.transform; const rotateWithMap = rotationAlignment === 'map'; const pitchWithMap = pitchAlignment === 'map'; const alongLine = rotateWithMap && layer.layout.get('symbol-placement') !== 'point'; const rotateInShader = rotateWithMap && !pitchWithMap && !alongLine; const hasSortKey = layer.layout.get('symbol-sort-key').constantOr(1) !== undefined; let sortFeaturesByKey = false; const depthMode = painter.depthModeForSublayer(0, ref_properties.DepthMode.ReadOnly); const variablePlacement = layer.layout.get('text-variable-anchor'); const tileRenderState = []; const defines = painter.terrain && pitchWithMap ? ['PITCH_WITH_MAP_TERRAIN'] : null; for (const coord of coords) { const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) continue; const buffers = isText ? bucket.text : bucket.icon; if (!buffers || !buffers.segments.get().length) continue; const programConfiguration = buffers.programConfigurations.get(layer.id); const isSDF = isText || bucket.sdfIcons; const sizeData = isText ? bucket.textSizeData : bucket.iconSizeData; const transformed = pitchWithMap || tr.pitch !== 0; const program = painter.useProgram(getSymbolProgramName(isSDF, isText, bucket), programConfiguration, defines); const size = ref_properties.evaluateSizeForZoom(sizeData, tr.zoom); let texSize; let texSizeIcon = [ 0, 0 ]; let atlasTexture; let atlasInterpolation; let atlasTextureIcon = null; let atlasInterpolationIcon; if (isText) { atlasTexture = tile.glyphAtlasTexture; atlasInterpolation = gl.LINEAR; texSize = tile.glyphAtlasTexture.size; if (bucket.iconsInText) { texSizeIcon = tile.imageAtlasTexture.size; atlasTextureIcon = tile.imageAtlasTexture; const zoomDependentSize = sizeData.kind === 'composite' || sizeData.kind === 'camera'; atlasInterpolationIcon = transformed || painter.options.rotating || painter.options.zooming || zoomDependentSize ? gl.LINEAR : gl.NEAREST; } } else { const iconScaled = layer.layout.get('icon-size').constantOr(0) !== 1 || bucket.iconsNeedLinear; atlasTexture = tile.imageAtlasTexture; atlasInterpolation = isSDF || painter.options.rotating || painter.options.zooming || iconScaled || transformed ? gl.LINEAR : gl.NEAREST; texSize = tile.imageAtlasTexture.size; } const s = pixelsToTileUnits(tile, 1, painter.transform.zoom); const labelPlaneMatrix = getLabelPlaneMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s); const labelPlaneMatrixInv = painter.terrain && pitchWithMap && alongLine ? ref_properties.invert(new Float32Array(16), labelPlaneMatrix) : identityMat4; const glCoordMatrix = getGlCoordMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s); const hasVariableAnchors = variablePlacement && bucket.hasTextData(); const updateTextFitIcon = layer.layout.get('icon-text-fit') !== 'none' && hasVariableAnchors && bucket.hasIconData(); if (alongLine) { const elevation = tr.elevation; const getElevation = elevation ? p => elevation.getAtTileOffset(coord, p.x, p.y) : null; updateLineLabels(bucket, coord.posMatrix, painter, isText, labelPlaneMatrix, glCoordMatrix, pitchWithMap, keepUpright, getElevation); } const matrix = painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor), uLabelPlaneMatrix = alongLine || isText && variablePlacement || updateTextFitIcon ? identityMat4 : labelPlaneMatrix, uglCoordMatrix = painter.translatePosMatrix(glCoordMatrix, tile, translate, translateAnchor, true); const hasHalo = isSDF && layer.paint.get(isText ? 'text-halo-width' : 'icon-halo-width').constantOr(1) !== 0; let uniformValues; if (isSDF) { if (!bucket.iconsInText) { uniformValues = symbolSDFUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, painter, matrix, uLabelPlaneMatrix, uglCoordMatrix, isText, texSize, true); } else { uniformValues = symbolTextAndIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, painter, matrix, uLabelPlaneMatrix, uglCoordMatrix, texSize, texSizeIcon); } } else { uniformValues = symbolIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, painter, matrix, uLabelPlaneMatrix, uglCoordMatrix, isText, texSize); } const state = { program, buffers, uniformValues, atlasTexture, atlasTextureIcon, atlasInterpolation, atlasInterpolationIcon, isSDF, hasHalo, tile, labelPlaneMatrixInv }; if (hasSortKey && bucket.canOverlap) { sortFeaturesByKey = true; const oldSegments = buffers.segments.get(); for (const segment of oldSegments) { tileRenderState.push({ segments: new ref_properties.SegmentVector([segment]), sortKey: segment.sortKey, state }); } } else { tileRenderState.push({ segments: buffers.segments, sortKey: 0, state }); } } if (sortFeaturesByKey) { tileRenderState.sort((a, b) => a.sortKey - b.sortKey); } for (const segmentState of tileRenderState) { const state = segmentState.state; if (painter.terrain) painter.terrain.setupElevationDraw(state.tile, state.program, { useDepthForOcclusion: true, labelPlaneMatrixInv: state.labelPlaneMatrixInv }); context.activeTexture.set(gl.TEXTURE0); state.atlasTexture.bind(state.atlasInterpolation, gl.CLAMP_TO_EDGE); if (state.atlasTextureIcon) { context.activeTexture.set(gl.TEXTURE1); if (state.atlasTextureIcon) { state.atlasTextureIcon.bind(state.atlasInterpolationIcon, gl.CLAMP_TO_EDGE); } } if (state.isSDF) { const uniformValues = state.uniformValues; if (state.hasHalo) { uniformValues['u_is_halo'] = 1; drawSymbolElements(state.buffers, segmentState.segments, layer, painter, state.program, depthMode, stencilMode, colorMode, uniformValues); } uniformValues['u_is_halo'] = 0; } drawSymbolElements(state.buffers, segmentState.segments, layer, painter, state.program, depthMode, stencilMode, colorMode, state.uniformValues); } } function drawSymbolElements(buffers, segments, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues) { const context = painter.context; const gl = context.gl; program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.disabled, uniformValues, layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer, segments, layer.paint, painter.transform.zoom, buffers.programConfigurations.get(layer.id), buffers.dynamicLayoutVertexBuffer, buffers.opacityVertexBuffer); } function drawCircles(painter, sourceCache, layer, coords) { if (painter.renderPass !== 'translucent') return; const opacity = layer.paint.get('circle-opacity'); const strokeWidth = layer.paint.get('circle-stroke-width'); const strokeOpacity = layer.paint.get('circle-stroke-opacity'); const sortFeaturesByKey = layer.layout.get('circle-sort-key').constantOr(1) !== undefined; if (opacity.constantOr(1) === 0 && (strokeWidth.constantOr(1) === 0 || strokeOpacity.constantOr(1) === 0)) { return; } const context = painter.context; const gl = context.gl; const depthMode = painter.depthModeForSublayer(0, ref_properties.DepthMode.ReadOnly); const stencilMode = ref_properties.StencilMode.disabled; const colorMode = painter.colorModeForRenderPass(); const segmentsRenderStates = []; for (let i = 0; i < coords.length; i++) { const coord = coords[i]; const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) continue; const programConfiguration = bucket.programConfigurations.get(layer.id); const definesValues = circleDefinesValues(layer); const program = painter.useProgram('circle', programConfiguration, definesValues); const layoutVertexBuffer = bucket.layoutVertexBuffer; const indexBuffer = bucket.indexBuffer; const uniformValues = circleUniformValues(painter, coord, tile, layer); const state = { programConfiguration, program, layoutVertexBuffer, indexBuffer, uniformValues, tile }; if (sortFeaturesByKey) { const oldSegments = bucket.segments.get(); for (const segment of oldSegments) { segmentsRenderStates.push({ segments: new ref_properties.SegmentVector([segment]), sortKey: segment.sortKey, state }); } } else { segmentsRenderStates.push({ segments: bucket.segments, sortKey: 0, state }); } } if (sortFeaturesByKey) { segmentsRenderStates.sort((a, b) => a.sortKey - b.sortKey); } for (const segmentsState of segmentsRenderStates) { const {programConfiguration, program, layoutVertexBuffer, indexBuffer, uniformValues, tile} = segmentsState.state; const segments = segmentsState.segments; if (painter.terrain) painter.terrain.setupElevationDraw(tile, program, { useDepthForOcclusion: true }); program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.disabled, uniformValues, layer.id, layoutVertexBuffer, indexBuffer, segments, layer.paint, painter.transform.zoom, programConfiguration); } } function drawHeatmap(painter, sourceCache, layer, coords) { if (layer.paint.get('heatmap-opacity') === 0) { return; } if (painter.renderPass === 'offscreen') { const context = painter.context; const gl = context.gl; const stencilMode = ref_properties.StencilMode.disabled; const colorMode = new ref_properties.ColorMode([ gl.ONE, gl.ONE ], ref_properties.Color.transparent, [ true, true, true, true ]); bindFramebuffer(context, painter, layer); context.clear({ color: ref_properties.Color.transparent }); for (let i = 0; i < coords.length; i++) { const coord = coords[i]; if (sourceCache.hasRenderableParent(coord)) continue; const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) continue; const programConfiguration = bucket.programConfigurations.get(layer.id); const program = painter.useProgram('heatmap', programConfiguration); const {zoom} = painter.transform; if (painter.terrain) painter.terrain.setupElevationDraw(tile, program); program.draw(context, gl.TRIANGLES, ref_properties.DepthMode.disabled, stencilMode, colorMode, ref_properties.CullFaceMode.disabled, heatmapUniformValues(coord.posMatrix, tile, zoom, layer.paint.get('heatmap-intensity')), layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, layer.paint, painter.transform.zoom, programConfiguration); } context.viewport.set([ 0, 0, painter.width, painter.height ]); } else if (painter.renderPass === 'translucent') { painter.context.setColorMode(painter.colorModeForRenderPass()); renderTextureToMap(painter, layer); } } function bindFramebuffer(context, painter, layer) { const gl = context.gl; context.activeTexture.set(gl.TEXTURE1); context.viewport.set([ 0, 0, painter.width / 4, painter.height / 4 ]); let fbo = layer.heatmapFbo; if (!fbo) { const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); fbo = layer.heatmapFbo = context.createFramebuffer(painter.width / 4, painter.height / 4, false); bindTextureToFramebuffer(context, painter, texture, fbo); } else { gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); context.bindFramebuffer.set(fbo.framebuffer); } } function bindTextureToFramebuffer(context, painter, texture, fbo) { const gl = context.gl; const internalFormat = context.extRenderToTextureHalfFloat ? context.extTextureHalfFloat.HALF_FLOAT_OES : gl.UNSIGNED_BYTE; gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, painter.width / 4, painter.height / 4, 0, gl.RGBA, internalFormat, null); fbo.colorAttachment.set(texture); } function renderTextureToMap(painter, layer) { const context = painter.context; const gl = context.gl; const fbo = layer.heatmapFbo; if (!fbo) return; context.activeTexture.set(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); context.activeTexture.set(gl.TEXTURE1); let colorRampTexture = layer.colorRampTexture; if (!colorRampTexture) { colorRampTexture = layer.colorRampTexture = new ref_properties.Texture(context, layer.colorRamp, gl.RGBA); } colorRampTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); painter.useProgram('heatmapTexture').draw(context, gl.TRIANGLES, ref_properties.DepthMode.disabled, ref_properties.StencilMode.disabled, painter.colorModeForRenderPass(), ref_properties.CullFaceMode.disabled, heatmapTextureUniformValues(painter, layer, 0, 1), layer.id, painter.viewportBuffer, painter.quadTriangleIndexBuffer, painter.viewportSegments, layer.paint, painter.transform.zoom); } function drawLine(painter, sourceCache, layer, coords) { if (painter.renderPass !== 'translucent') return; const opacity = layer.paint.get('line-opacity'); const width = layer.paint.get('line-width'); if (opacity.constantOr(1) === 0 || width.constantOr(1) === 0) return; const depthMode = painter.depthModeForSublayer(0, ref_properties.DepthMode.ReadOnly); const colorMode = painter.colorModeForRenderPass(); const dasharray = layer.paint.get('line-dasharray'); const patternProperty = layer.paint.get('line-pattern'); const image = patternProperty.constantOr(1); const gradient = layer.paint.get('line-gradient'); const crossfade = layer.getCrossfadeParameters(); const programId = image ? 'linePattern' : dasharray ? 'lineSDF' : gradient ? 'lineGradient' : 'line'; const context = painter.context; const gl = context.gl; let firstTile = true; for (const coord of coords) { const tile = sourceCache.getTile(coord); if (image && !tile.patternsLoaded()) continue; const bucket = tile.getBucket(layer); if (!bucket) continue; painter.prepareDrawTile(coord); const programConfiguration = bucket.programConfigurations.get(layer.id); const prevProgram = painter.context.program.get(); const program = painter.useProgram(programId, programConfiguration); const programChanged = firstTile || program.program !== prevProgram; const constantPattern = patternProperty.constantOr(null); if (constantPattern && tile.imageAtlas) { const atlas = tile.imageAtlas; const posTo = atlas.patternPositions[constantPattern.to.toString()]; const posFrom = atlas.patternPositions[constantPattern.from.toString()]; if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); } const matrix = painter.terrain ? coord.posMatrix : null; const uniformValues = image ? linePatternUniformValues(painter, tile, layer, crossfade, matrix) : dasharray ? lineSDFUniformValues(painter, tile, layer, dasharray, crossfade, matrix) : gradient ? lineGradientUniformValues(painter, tile, layer, matrix, bucket.lineClipsArray.length) : lineUniformValues(painter, tile, layer, matrix); if (image) { context.activeTexture.set(gl.TEXTURE0); tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); programConfiguration.updatePaintBuffers(crossfade); } else if (dasharray && (programChanged || painter.lineAtlas.dirty)) { context.activeTexture.set(gl.TEXTURE0); painter.lineAtlas.bind(context); } else if (gradient) { const layerGradient = bucket.gradients[layer.id]; let gradientTexture = layerGradient.texture; if (layer.gradientVersion !== layerGradient.version) { let textureResolution = 256; if (layer.stepInterpolant) { const sourceMaxZoom = sourceCache.getSource().maxzoom; const potentialOverzoom = coord.canonical.z === sourceMaxZoom ? Math.ceil(1 << painter.transform.maxZoom - coord.canonical.z) : 1; const lineLength = bucket.maxLineLength / ref_properties.EXTENT; const maxTilePixelSize = 1024; const maxTextureCoverage = lineLength * maxTilePixelSize * potentialOverzoom; textureResolution = ref_properties.clamp(ref_properties.nextPowerOfTwo(maxTextureCoverage), 256, context.maxTextureSize); } layerGradient.gradient = ref_properties.renderColorRamp({ expression: layer.gradientExpression(), evaluationKey: 'lineProgress', resolution: textureResolution, image: layerGradient.gradient || undefined, clips: bucket.lineClipsArray }); if (layerGradient.texture) { layerGradient.texture.update(layerGradient.gradient); } else { layerGradient.texture = new ref_properties.Texture(context, layerGradient.gradient, gl.RGBA); } layerGradient.version = layer.gradientVersion; gradientTexture = layerGradient.texture; } context.activeTexture.set(gl.TEXTURE0); gradientTexture.bind(layer.stepInterpolant ? gl.NEAREST : gl.LINEAR, gl.CLAMP_TO_EDGE); } program.draw(context, gl.TRIANGLES, depthMode, painter.stencilModeForClipping(coord), colorMode, ref_properties.CullFaceMode.disabled, uniformValues, layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, layer.paint, painter.transform.zoom, programConfiguration, bucket.layoutVertexBuffer2); firstTile = false; } } function drawFill(painter, sourceCache, layer, coords) { const color = layer.paint.get('fill-color'); const opacity = layer.paint.get('fill-opacity'); if (opacity.constantOr(1) === 0) { return; } const colorMode = painter.colorModeForRenderPass(); const pattern = layer.paint.get('fill-pattern'); const pass = painter.opaquePassEnabledForLayer() && (!pattern.constantOr(1) && color.constantOr(ref_properties.Color.transparent).a === 1 && opacity.constantOr(0) === 1) ? 'opaque' : 'translucent'; if (painter.renderPass === pass) { const depthMode = painter.depthModeForSublayer(1, painter.renderPass === 'opaque' ? ref_properties.DepthMode.ReadWrite : ref_properties.DepthMode.ReadOnly); drawFillTiles(painter, sourceCache, layer, coords, depthMode, colorMode, false); } if (painter.renderPass === 'translucent' && layer.paint.get('fill-antialias')) { const depthMode = painter.depthModeForSublayer(layer.getPaintProperty('fill-outline-color') ? 2 : 0, ref_properties.DepthMode.ReadOnly); drawFillTiles(painter, sourceCache, layer, coords, depthMode, colorMode, true); } } function drawFillTiles(painter, sourceCache, layer, coords, depthMode, colorMode, isOutline) { const gl = painter.context.gl; const patternProperty = layer.paint.get('fill-pattern'); const image = patternProperty && patternProperty.constantOr(1); const crossfade = layer.getCrossfadeParameters(); let drawMode, programName, uniformValues, indexBuffer, segments; if (!isOutline) { programName = image ? 'fillPattern' : 'fill'; drawMode = gl.TRIANGLES; } else { programName = image && !layer.getPaintProperty('fill-outline-color') ? 'fillOutlinePattern' : 'fillOutline'; drawMode = gl.LINES; } for (const coord of coords) { const tile = sourceCache.getTile(coord); if (image && !tile.patternsLoaded()) continue; const bucket = tile.getBucket(layer); if (!bucket) continue; painter.prepareDrawTile(coord); const programConfiguration = bucket.programConfigurations.get(layer.id); const program = painter.useProgram(programName, programConfiguration); if (image) { painter.context.activeTexture.set(gl.TEXTURE0); tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); programConfiguration.updatePaintBuffers(crossfade); } const constantPattern = patternProperty.constantOr(null); if (constantPattern && tile.imageAtlas) { const atlas = tile.imageAtlas; const posTo = atlas.patternPositions[constantPattern.to.toString()]; const posFrom = atlas.patternPositions[constantPattern.from.toString()]; if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); } const tileMatrix = painter.translatePosMatrix(coord.posMatrix, tile, layer.paint.get('fill-translate'), layer.paint.get('fill-translate-anchor')); if (!isOutline) { indexBuffer = bucket.indexBuffer; segments = bucket.segments; uniformValues = image ? fillPatternUniformValues(tileMatrix, painter, crossfade, tile) : fillUniformValues(tileMatrix); } else { indexBuffer = bucket.indexBuffer2; segments = bucket.segments2; const drawingBufferSize = painter.terrain && painter.terrain.renderingToTexture ? painter.terrain.drapeBufferSize : [ gl.drawingBufferWidth, gl.drawingBufferHeight ]; uniformValues = programName === 'fillOutlinePattern' && image ? fillOutlinePatternUniformValues(tileMatrix, painter, crossfade, tile, drawingBufferSize) : fillOutlineUniformValues(tileMatrix, drawingBufferSize); } program.draw(painter.context, drawMode, depthMode, painter.stencilModeForClipping(coord), colorMode, ref_properties.CullFaceMode.disabled, uniformValues, layer.id, bucket.layoutVertexBuffer, indexBuffer, segments, layer.paint, painter.transform.zoom, programConfiguration); } } function draw(painter, source, layer, coords) { const opacity = layer.paint.get('fill-extrusion-opacity'); if (opacity === 0) { return; } if (painter.renderPass === 'translucent') { const depthMode = new ref_properties.DepthMode(painter.context.gl.LEQUAL, ref_properties.DepthMode.ReadWrite, painter.depthRangeFor3D); if (opacity === 1 && !layer.paint.get('fill-extrusion-pattern').constantOr(1)) { const colorMode = painter.colorModeForRenderPass(); drawExtrusionTiles(painter, source, layer, coords, depthMode, ref_properties.StencilMode.disabled, colorMode); } else { drawExtrusionTiles(painter, source, layer, coords, depthMode, ref_properties.StencilMode.disabled, ref_properties.ColorMode.disabled); drawExtrusionTiles(painter, source, layer, coords, depthMode, painter.stencilModeFor3D(), painter.colorModeForRenderPass()); } } } function drawExtrusionTiles(painter, source, layer, coords, depthMode, stencilMode, colorMode) { const context = painter.context; const gl = context.gl; const patternProperty = layer.paint.get('fill-extrusion-pattern'); const image = patternProperty.constantOr(1); const crossfade = layer.getCrossfadeParameters(); const opacity = layer.paint.get('fill-extrusion-opacity'); for (const coord of coords) { const tile = source.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) continue; const programConfiguration = bucket.programConfigurations.get(layer.id); const program = painter.useProgram(image ? 'fillExtrusionPattern' : 'fillExtrusion', programConfiguration); if (painter.terrain) { const terrain = painter.terrain; if (!bucket.enableTerrain) continue; terrain.setupElevationDraw(tile, program, { useMeterToDem: true }); flatRoofsUpdate(context, source, coord, bucket, layer, terrain); if (!bucket.centroidVertexBuffer) { const attrIndex = program.attributes['a_centroid_pos']; if (attrIndex !== undefined) gl.vertexAttrib2f(attrIndex, 0, 0); } } if (image) { painter.context.activeTexture.set(gl.TEXTURE0); tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); programConfiguration.updatePaintBuffers(crossfade); } const constantPattern = patternProperty.constantOr(null); if (constantPattern && tile.imageAtlas) { const atlas = tile.imageAtlas; const posTo = atlas.patternPositions[constantPattern.to.toString()]; const posFrom = atlas.patternPositions[constantPattern.from.toString()]; if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); } const matrix = painter.translatePosMatrix(coord.posMatrix, tile, layer.paint.get('fill-extrusion-translate'), layer.paint.get('fill-extrusion-translate-anchor')); const shouldUseVerticalGradient = layer.paint.get('fill-extrusion-vertical-gradient'); const uniformValues = image ? fillExtrusionPatternUniformValues(matrix, painter, shouldUseVerticalGradient, opacity, coord, crossfade, tile) : fillExtrusionUniformValues(matrix, painter, shouldUseVerticalGradient, opacity); program.draw(context, context.gl.TRIANGLES, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.backCCW, uniformValues, layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, layer.paint, painter.transform.zoom, programConfiguration, painter.terrain ? bucket.centroidVertexBuffer : null); } } function flatRoofsUpdate(context, source, coord, bucket, layer, terrain) { const neighborCoord = [ coord => { let x = coord.canonical.x - 1; let w = coord.wrap; if (x < 0) { x = (1 << coord.canonical.z) - 1; w--; } return new ref_properties.OverscaledTileID(coord.overscaledZ, w, coord.canonical.z, x, coord.canonical.y); }, coord => { let x = coord.canonical.x + 1; let w = coord.wrap; if (x === 1 << coord.canonical.z) { x = 0; w++; } return new ref_properties.OverscaledTileID(coord.overscaledZ, w, coord.canonical.z, x, coord.canonical.y); }, coord => new ref_properties.OverscaledTileID(coord.overscaledZ, coord.wrap, coord.canonical.z, coord.canonical.x, (coord.canonical.y === 0 ? 1 << coord.canonical.z : coord.canonical.y) - 1), coord => new ref_properties.OverscaledTileID(coord.overscaledZ, coord.wrap, coord.canonical.z, coord.canonical.x, coord.canonical.y === (1 << coord.canonical.z) - 1 ? 0 : coord.canonical.y + 1) ]; const getLoadedBucket = nid => { const maxzoom = source.getSource().maxzoom; for (const j of [ 0, -1, 1 ]) { if (nid.overscaledZ + j < maxzoom) continue; if (j > 0 && nid.overscaledZ < maxzoom) continue; const n = source.getTileByID(nid.calculateScaledKey(nid.overscaledZ + j)); if (n && n.hasData()) { const nBucket = n.getBucket(layer); if (nBucket) return nBucket; } } }; const projectedToBorder = [ 0, 0, 0 ]; const xjoin = (a, b) => { projectedToBorder[0] = Math.min(a.min.y, b.min.y); projectedToBorder[1] = Math.max(a.max.y, b.max.y); projectedToBorder[2] = ref_properties.EXTENT - b.min.x > a.max.x ? b.min.x - ref_properties.EXTENT : a.max.x; return projectedToBorder; }; const yjoin = (a, b) => { projectedToBorder[0] = Math.min(a.min.x, b.min.x); projectedToBorder[1] = Math.max(a.max.x, b.max.x); projectedToBorder[2] = ref_properties.EXTENT - b.min.y > a.max.y ? b.min.y - ref_properties.EXTENT : a.max.y; return projectedToBorder; }; const projectCombinedSpanToBorder = [ (a, b) => xjoin(a, b), (a, b) => xjoin(b, a), (a, b) => yjoin(a, b), (a, b) => yjoin(b, a) ]; const centroid = new ref_properties.Point(0, 0); const error = 3; let demTile, neighborDEMTile, neighborTileID; const flatBase = (min, max, edge, verticalEdge, maxOffsetFromBorder) => { const points = [ [ verticalEdge ? edge : min, verticalEdge ? min : edge, 0 ], [ verticalEdge ? edge : max, verticalEdge ? max : edge, 0 ] ]; const coord3 = maxOffsetFromBorder < 0 ? ref_properties.EXTENT + maxOffsetFromBorder : maxOffsetFromBorder; const thirdPoint = [ verticalEdge ? coord3 : (min + max) / 2, verticalEdge ? (min + max) / 2 : coord3, 0 ]; if (edge === 0 && maxOffsetFromBorder < 0 || edge !== 0 && maxOffsetFromBorder > 0) { terrain.getForTilePoints(neighborTileID, [thirdPoint], true, neighborDEMTile); } else { points.push(thirdPoint); } terrain.getForTilePoints(coord, points, true, demTile); return Math.max(points[0][2], points[1][2], thirdPoint[2]) / terrain.exaggeration(); }; for (let i = 0; i < 4; i++) { const a = bucket.borders[i]; if (a.length === 0) { bucket.borderDone[i] = true; } if (bucket.borderDone[i]) continue; const nid = neighborTileID = neighborCoord[i](coord); const nBucket = getLoadedBucket(nid); if (!nBucket || !nBucket.enableTerrain) continue; neighborDEMTile = terrain.findDEMTileFor(nid); if (!neighborDEMTile || !neighborDEMTile.dem) continue; if (!demTile) { const dem = terrain.findDEMTileFor(coord); if (!(dem && dem.dem)) return; demTile = dem; } const j = (i < 2 ? 1 : 5) - i; const b = nBucket.borders[j]; let ib = 0; for (let ia = 0; ia < a.length; ia++) { const parta = bucket.featuresOnBorder[a[ia]]; const partABorderRange = parta.borders[i]; let partb; while (ib < b.length) { partb = nBucket.featuresOnBorder[b[ib]]; const partBBorderRange = partb.borders[j]; if (partBBorderRange[1] > partABorderRange[0] + error) break; if (!nBucket.borderDone[j]) nBucket.encodeCentroid(undefined, partb, false); ib++; } if (partb && ib < b.length) { const saveIb = ib; let count = 0; while (true) { const partBBorderRange = partb.borders[j]; if (partBBorderRange[0] > partABorderRange[1] - error) break; count++; if (++ib === b.length) break; partb = nBucket.featuresOnBorder[b[ib]]; } partb = nBucket.featuresOnBorder[b[saveIb]]; if (parta.intersectsCount() > 1 || partb.intersectsCount() > 1 || count !== 1) { if (count !== 1) { ib = saveIb; } bucket.encodeCentroid(undefined, parta, false); if (!nBucket.borderDone[j]) nBucket.encodeCentroid(undefined, partb, false); continue; } const span = projectCombinedSpanToBorder[i](parta, partb); const edge = i % 2 ? ref_properties.EXTENT - 1 : 0; centroid.x = flatBase(span[0], Math.min(ref_properties.EXTENT - 1, span[1]), edge, i < 2, span[2]); centroid.y = 0; bucket.encodeCentroid(centroid, parta, false); if (!nBucket.borderDone[j]) nBucket.encodeCentroid(centroid, partb, false); } else { bucket.encodeCentroid(undefined, parta, false); } } bucket.borderDone[i] = bucket.needsCentroidUpdate = true; if (!nBucket.borderDone[j]) { nBucket.borderDone[j] = nBucket.needsCentroidUpdate = true; } } if (bucket.needsCentroidUpdate || !bucket.centroidVertexBuffer && bucket.centroidVertexArray.length !== 0) { bucket.uploadCentroid(context); } } function drawRaster(painter, sourceCache, layer, tileIDs, variableOffsets, isInitialLoad) { if (painter.renderPass !== 'translucent') return; if (layer.paint.get('raster-opacity') === 0) return; if (!tileIDs.length) return; const context = painter.context; const gl = context.gl; const source = sourceCache.getSource(); const program = painter.useProgram('raster'); const colorMode = painter.colorModeForRenderPass(); const renderingToTexture = painter.terrain && painter.terrain.renderingToTexture; const [stencilModes, coords] = source instanceof ImageSource || renderingToTexture ? [ {}, tileIDs ] : painter.stencilConfigForOverlap(tileIDs); const minTileZ = coords[coords.length - 1].overscaledZ; const align = !painter.options.moving; for (const coord of coords) { const depthMode = renderingToTexture ? ref_properties.DepthMode.disabled : painter.depthModeForSublayer(coord.overscaledZ - minTileZ, layer.paint.get('raster-opacity') === 1 ? ref_properties.DepthMode.ReadWrite : ref_properties.DepthMode.ReadOnly, gl.LESS); const tile = sourceCache.getTile(coord); if (renderingToTexture && !(tile && tile.hasData())) continue; const posMatrix = renderingToTexture ? coord.posMatrix : painter.transform.calculatePosMatrix(coord.toUnwrapped(), align); const stencilMode = painter.terrain && renderingToTexture ? painter.terrain.stencilModeForRTTOverlap(coord) : stencilModes[coord.overscaledZ]; const rasterFadeDuration = isInitialLoad ? 0 : layer.paint.get('raster-fade-duration'); tile.registerFadeDuration(rasterFadeDuration); const parentTile = sourceCache.findLoadedParent(coord, 0); const fade = rasterFade(tile, parentTile, sourceCache, painter.transform, rasterFadeDuration); if (painter.terrain) painter.terrain.prepareDrawTile(coord); let parentScaleBy, parentTL; const textureFilter = layer.paint.get('raster-resampling') === 'nearest' ? gl.NEAREST : gl.LINEAR; context.activeTexture.set(gl.TEXTURE0); tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); context.activeTexture.set(gl.TEXTURE1); if (parentTile) { parentTile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); parentScaleBy = Math.pow(2, parentTile.tileID.overscaledZ - tile.tileID.overscaledZ); parentTL = [ tile.tileID.canonical.x * parentScaleBy % 1, tile.tileID.canonical.y * parentScaleBy % 1 ]; } else { tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); } const uniformValues = rasterUniformValues(posMatrix, parentTL || [ 0, 0 ], parentScaleBy || 1, fade, layer); if (source instanceof ImageSource) { program.draw(context, gl.TRIANGLES, depthMode, ref_properties.StencilMode.disabled, colorMode, ref_properties.CullFaceMode.disabled, uniformValues, layer.id, source.boundsBuffer, painter.quadTriangleIndexBuffer, source.boundsSegments); } else { program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.disabled, uniformValues, layer.id, painter.rasterBoundsBuffer, painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); } } } function drawBackground(painter, sourceCache, layer, coords) { const color = layer.paint.get('background-color'); const opacity = layer.paint.get('background-opacity'); if (opacity === 0) return; const context = painter.context; const gl = context.gl; const transform = painter.transform; const tileSize = transform.tileSize; const image = layer.paint.get('background-pattern'); if (painter.isPatternMissing(image)) return; const pass = !image && color.a === 1 && opacity === 1 && painter.opaquePassEnabledForLayer() ? 'opaque' : 'translucent'; if (painter.renderPass !== pass) return; const stencilMode = ref_properties.StencilMode.disabled; const depthMode = painter.depthModeForSublayer(0, pass === 'opaque' ? ref_properties.DepthMode.ReadWrite : ref_properties.DepthMode.ReadOnly); const colorMode = painter.colorModeForRenderPass(); const program = painter.useProgram(image ? 'backgroundPattern' : 'background'); const tileIDs = coords ? coords : transform.coveringTiles({ tileSize }); if (image) { context.activeTexture.set(gl.TEXTURE0); painter.imageManager.bind(painter.context); } const crossfade = layer.getCrossfadeParameters(); for (const tileID of tileIDs) { const matrix = coords ? tileID.posMatrix : painter.transform.calculatePosMatrix(tileID.toUnwrapped()); painter.prepareDrawTile(tileID); const uniformValues = image ? backgroundPatternUniformValues(matrix, opacity, painter, image, { tileID, tileSize }, crossfade) : backgroundUniformValues(matrix, opacity, color); program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.disabled, uniformValues, layer.id, painter.tileExtentBuffer, painter.quadTriangleIndexBuffer, painter.tileExtentSegments); } } const topColor = new ref_properties.Color(1, 0, 0, 1); const btmColor = new ref_properties.Color(0, 1, 0, 1); const leftColor = new ref_properties.Color(0, 0, 1, 1); const rightColor = new ref_properties.Color(1, 0, 1, 1); const centerColor = new ref_properties.Color(0, 1, 1, 1); function drawDebugPadding(painter) { const padding = painter.transform.padding; const lineWidth = 3; drawHorizontalLine(painter, painter.transform.height - (padding.top || 0), lineWidth, topColor); drawHorizontalLine(painter, padding.bottom || 0, lineWidth, btmColor); drawVerticalLine(painter, padding.left || 0, lineWidth, leftColor); drawVerticalLine(painter, painter.transform.width - (padding.right || 0), lineWidth, rightColor); const center = painter.transform.centerPoint; drawCrosshair(painter, center.x, painter.transform.height - center.y, centerColor); } function drawCrosshair(painter, x, y, color) { const size = 20; const lineWidth = 2; drawDebugSSRect(painter, x - lineWidth / 2, y - size / 2, lineWidth, size, color); drawDebugSSRect(painter, x - size / 2, y - lineWidth / 2, size, lineWidth, color); } function drawHorizontalLine(painter, y, lineWidth, color) { drawDebugSSRect(painter, 0, y + lineWidth / 2, painter.transform.width, lineWidth, color); } function drawVerticalLine(painter, x, lineWidth, color) { drawDebugSSRect(painter, x - lineWidth / 2, 0, lineWidth, painter.transform.height, color); } function drawDebugSSRect(painter, x, y, width, height, color) { const context = painter.context; const gl = context.gl; gl.enable(gl.SCISSOR_TEST); gl.scissor(x * ref_properties.browser.devicePixelRatio, y * ref_properties.browser.devicePixelRatio, width * ref_properties.browser.devicePixelRatio, height * ref_properties.browser.devicePixelRatio); context.clear({ color }); gl.disable(gl.SCISSOR_TEST); } function drawDebug(painter, sourceCache, coords) { for (let i = 0; i < coords.length; i++) { drawDebugTile(painter, sourceCache, coords[i]); } } function drawDebugTile(painter, sourceCache, coord) { const context = painter.context; const gl = context.gl; const posMatrix = coord.posMatrix; const program = painter.useProgram('debug'); const tile = sourceCache.getTileByID(coord.key); if (painter.terrain) painter.terrain.setupElevationDraw(tile, program); const depthMode = ref_properties.DepthMode.disabled; const stencilMode = ref_properties.StencilMode.disabled; const colorMode = painter.colorModeForRenderPass(); const id = '$debug'; context.activeTexture.set(gl.TEXTURE0); painter.emptyTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); program.draw(context, gl.LINE_STRIP, depthMode, stencilMode, colorMode, ref_properties.CullFaceMode.disabled, debugUniformValues(posMatrix, ref_properties.Color.red), id, painter.debugBuffer, painter.tileBorderIndexBuffer, painter.debugSegments); const tileRawData = tile.latestRawTileData; const tileByteLength = tileRawData && tileRawData.byteLength || 0; const tileSizeKb = Math.floor(tileByteLength / 1024); const tileSize = sourceCache.getTile(coord).tileSize; const scaleRatio = 512 / Math.min(tileSize, 512) * (coord.overscaledZ / painter.transform.zoom) * 0.5; let tileIdText = coord.canonical.toString(); if (coord.overscaledZ !== coord.canonical.z) { tileIdText += ` => ${ coord.overscaledZ }`; } const tileLabel = `${ tileIdText } ${ tileSizeKb }kb`; drawTextToOverlay(painter, tileLabel); program.draw(context, gl.TRIANGLES, depthMode, stencilMode, ref_properties.ColorMode.alphaBlended, ref_properties.CullFaceMode.disabled, debugUniformValues(posMatrix, ref_properties.Color.transparent, scaleRatio), id, painter.debugBuffer, painter.quadTriangleIndexBuffer, painter.debugSegments); } function drawTextToOverlay(painter, text) { painter.initDebugOverlayCanvas(); const canvas = painter.debugOverlayCanvas; const gl = painter.context.gl; const ctx2d = painter.debugOverlayCanvas.getContext('2d'); ctx2d.clearRect(0, 0, canvas.width, canvas.height); ctx2d.shadowColor = 'white'; ctx2d.shadowBlur = 2; ctx2d.lineWidth = 1.5; ctx2d.strokeStyle = 'white'; ctx2d.textBaseline = 'top'; ctx2d.font = `bold ${ 36 }px Open Sans, sans-serif`; ctx2d.fillText(text, 5, 5); ctx2d.strokeText(text, 5, 5); painter.debugOverlayTexture.update(canvas); painter.debugOverlayTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); } function drawCustom(painter, sourceCache, layer) { const context = painter.context; const implementation = layer.implementation; if (painter.renderPass === 'offscreen') { const prerender = implementation.prerender; if (prerender) { painter.setCustomLayerDefaults(); context.setColorMode(painter.colorModeForRenderPass()); prerender.call(implementation, context.gl, painter.transform.customLayerMatrix()); context.setDirty(); painter.setBaseState(); } } else if (painter.renderPass === 'translucent') { painter.setCustomLayerDefaults(); context.setColorMode(painter.colorModeForRenderPass()); context.setStencilMode(ref_properties.StencilMode.disabled); const depthMode = implementation.renderingMode === '3d' ? new ref_properties.DepthMode(painter.context.gl.LEQUAL, ref_properties.DepthMode.ReadWrite, painter.depthRangeFor3D) : painter.depthModeForSublayer(0, ref_properties.DepthMode.ReadOnly); context.setDepthMode(depthMode); implementation.render(context.gl, painter.transform.customLayerMatrix()); context.setDirty(); painter.setBaseState(); context.bindFramebuffer.set(null); } } const skyboxAttributes = ref_properties.createLayout([{ name: 'a_pos_3f', components: 3, type: 'Float32' }]); const {members, size, alignment} = skyboxAttributes; function addVertex(vertexArray, x, y, z) { vertexArray.emplaceBack(x, y, z); } class SkyboxGeometry { constructor(context) { this.vertexArray = new ref_properties.StructArrayLayout3f12(); this.indices = new ref_properties.StructArrayLayout3ui6(); addVertex(this.vertexArray, -1, -1, 1); addVertex(this.vertexArray, 1, -1, 1); addVertex(this.vertexArray, -1, 1, 1); addVertex(this.vertexArray, 1, 1, 1); addVertex(this.vertexArray, -1, -1, -1); addVertex(this.vertexArray, 1, -1, -1); addVertex(this.vertexArray, -1, 1, -1); addVertex(this.vertexArray, 1, 1, -1); this.indices.emplaceBack(5, 1, 3); this.indices.emplaceBack(3, 7, 5); this.indices.emplaceBack(6, 2, 0); this.indices.emplaceBack(0, 4, 6); this.indices.emplaceBack(2, 6, 7); this.indices.emplaceBack(7, 3, 2); this.indices.emplaceBack(5, 4, 0); this.indices.emplaceBack(0, 1, 5); this.indices.emplaceBack(0, 2, 3); this.indices.emplaceBack(3, 1, 0); this.indices.emplaceBack(7, 6, 4); this.indices.emplaceBack(4, 5, 7); this.vertexBuffer = context.createVertexBuffer(this.vertexArray, members); this.indexBuffer = context.createIndexBuffer(this.indices); this.segment = ref_properties.SegmentVector.simpleSegment(0, 0, 36, 12); } } function drawSky(painter, sourceCache, layer) { const opacity = layer.paint.get('sky-opacity'); if (opacity === 0) { return; } const context = painter.context; const type = layer.paint.get('sky-type'); const depthMode = new ref_properties.DepthMode(context.gl.LEQUAL, ref_properties.DepthMode.ReadOnly, [ 0, 1 ]); const temporalOffset = painter.frameCounter / 1000 % 1; if (type === 'atmosphere') { if (painter.renderPass === 'offscreen') { if (layer.needsSkyboxCapture(painter)) { captureSkybox(painter, layer, 32, 32); layer.markSkyboxValid(painter); } } else if (painter.renderPass === 'sky') { drawSkyboxFromCapture(painter, layer, depthMode, opacity, temporalOffset); } } else if (type === 'gradient') { if (painter.renderPass === 'sky') { drawSkyboxGradient(painter, layer, depthMode, opacity, temporalOffset); } } } function drawSkyboxGradient(painter, layer, depthMode, opacity, temporalOffset) { const context = painter.context; const gl = context.gl; const transform = painter.transform; const program = painter.useProgram('skyboxGradient'); if (!layer.skyboxGeometry) { layer.skyboxGeometry = new SkyboxGeometry(context); } context.activeTexture.set(gl.TEXTURE0); let colorRampTexture = layer.colorRampTexture; if (!colorRampTexture) { colorRampTexture = layer.colorRampTexture = new ref_properties.Texture(context, layer.colorRamp, gl.RGBA); } colorRampTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); const uniformValues = skyboxGradientUniformValues(transform.skyboxMatrix, layer.getCenter(painter, false), layer.paint.get('sky-gradient-radius'), opacity, temporalOffset); program.draw(context, gl.TRIANGLES, depthMode, ref_properties.StencilMode.disabled, painter.colorModeForRenderPass(), ref_properties.CullFaceMode.backCW, uniformValues, 'skyboxGradient', layer.skyboxGeometry.vertexBuffer, layer.skyboxGeometry.indexBuffer, layer.skyboxGeometry.segment); } function drawSkyboxFromCapture(painter, layer, depthMode, opacity, temporalOffset) { const context = painter.context; const gl = context.gl; const transform = painter.transform; const program = painter.useProgram('skybox'); context.activeTexture.set(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_CUBE_MAP, layer.skyboxTexture); const uniformValues = skyboxUniformValues(transform.skyboxMatrix, layer.getCenter(painter, false), 0, opacity, temporalOffset); program.draw(context, gl.TRIANGLES, depthMode, ref_properties.StencilMode.disabled, painter.colorModeForRenderPass(), ref_properties.CullFaceMode.backCW, uniformValues, 'skybox', layer.skyboxGeometry.vertexBuffer, layer.skyboxGeometry.indexBuffer, layer.skyboxGeometry.segment); } function drawSkyboxFace(context, layer, program, faceRotate, sunDirection, i) { const gl = context.gl; const atmosphereColor = layer.paint.get('sky-atmosphere-color'); const atmosphereHaloColor = layer.paint.get('sky-atmosphere-halo-color'); const sunIntensity = layer.paint.get('sky-atmosphere-sun-intensity'); const uniformValues = skyboxCaptureUniformValues(ref_properties.fromMat4([], faceRotate), sunDirection, sunIntensity, atmosphereColor, atmosphereHaloColor); const glFace = gl.TEXTURE_CUBE_MAP_POSITIVE_X + i; gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, glFace, layer.skyboxTexture, 0); program.draw(context, gl.TRIANGLES, ref_properties.DepthMode.disabled, ref_properties.StencilMode.disabled, ref_properties.ColorMode.unblended, ref_properties.CullFaceMode.frontCW, uniformValues, 'skyboxCapture', layer.skyboxGeometry.vertexBuffer, layer.skyboxGeometry.indexBuffer, layer.skyboxGeometry.segment); } function captureSkybox(painter, layer, width, height) { const context = painter.context; const gl = context.gl; let fbo = layer.skyboxFbo; if (!fbo) { fbo = layer.skyboxFbo = context.createFramebuffer(width, height, false); layer.skyboxGeometry = new SkyboxGeometry(context); layer.skyboxTexture = context.gl.createTexture(); gl.bindTexture(gl.TEXTURE_CUBE_MAP, layer.skyboxTexture); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); for (let i = 0; i < 6; ++i) { const glFace = gl.TEXTURE_CUBE_MAP_POSITIVE_X + i; gl.texImage2D(glFace, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); } } context.bindFramebuffer.set(fbo.framebuffer); context.viewport.set([ 0, 0, width, height ]); const sunDirection = layer.getCenter(painter, true); const program = painter.useProgram('skyboxCapture'); const faceRotate = new Float64Array(16); ref_properties.identity(faceRotate); ref_properties.rotateY(faceRotate, faceRotate, -Math.PI * 0.5); drawSkyboxFace(context, layer, program, faceRotate, sunDirection, 0); ref_properties.identity(faceRotate); ref_properties.rotateY(faceRotate, faceRotate, Math.PI * 0.5); drawSkyboxFace(context, layer, program, faceRotate, sunDirection, 1); ref_properties.identity(faceRotate); ref_properties.rotateX(faceRotate, faceRotate, -Math.PI * 0.5); drawSkyboxFace(context, layer, program, faceRotate, sunDirection, 2); ref_properties.identity(faceRotate); ref_properties.rotateX(faceRotate, faceRotate, Math.PI * 0.5); drawSkyboxFace(context, layer, program, faceRotate, sunDirection, 3); ref_properties.identity(faceRotate); drawSkyboxFace(context, layer, program, faceRotate, sunDirection, 4); ref_properties.identity(faceRotate); ref_properties.rotateY(faceRotate, faceRotate, Math.PI); drawSkyboxFace(context, layer, program, faceRotate, sunDirection, 5); context.viewport.set([ 0, 0, painter.width, painter.height ]); } const draw$1 = { symbol: drawSymbols, circle: drawCircles, heatmap: drawHeatmap, line: drawLine, fill: drawFill, 'fill-extrusion': draw, hillshade: drawHillshade, raster: drawRaster, background: drawBackground, sky: drawSky, debug: drawDebug, custom: drawCustom }; class Painter { constructor(gl, transform) { this.context = new ref_properties.Context(gl); this.transform = transform; this._tileTextures = {}; this.frameCopies = []; this.loadTimeStamps = []; this.setup(); this.numSublayers = ref_properties.SourceCache.maxUnderzooming + ref_properties.SourceCache.maxOverzooming + 1; this.depthEpsilon = 1 / Math.pow(2, 16); this.crossTileSymbolIndex = new CrossTileSymbolIndex(); this.gpuTimers = {}; this.frameCounter = 0; } updateTerrain(style, cameraChanging) { const enabled = !!style && !!style.terrain; if (!enabled && (!this._terrain || !this._terrain.enabled)) return; if (!this._terrain) { this._terrain = new Terrain$1(this, style); } const terrain = this._terrain; this.transform.elevation = enabled ? terrain : null; terrain.update(style, this.transform, cameraChanging); } get terrain() { return this._terrain && this._terrain.enabled ? this._terrain : null; } resize(width, height) { this.width = width * ref_properties.browser.devicePixelRatio; this.height = height * ref_properties.browser.devicePixelRatio; this.context.viewport.set([ 0, 0, this.width, this.height ]); if (this.style) { for (const layerId of this.style.order) { this.style._layers[layerId].resize(); } } } setup() { const context = this.context; const tileExtentArray = new ref_properties.StructArrayLayout2i4(); tileExtentArray.emplaceBack(0, 0); tileExtentArray.emplaceBack(ref_properties.EXTENT, 0); tileExtentArray.emplaceBack(0, ref_properties.EXTENT); tileExtentArray.emplaceBack(ref_properties.EXTENT, ref_properties.EXTENT); this.tileExtentBuffer = context.createVertexBuffer(tileExtentArray, ref_properties.posAttributes.members); this.tileExtentSegments = ref_properties.SegmentVector.simpleSegment(0, 0, 4, 2); const debugArray = new ref_properties.StructArrayLayout2i4(); debugArray.emplaceBack(0, 0); debugArray.emplaceBack(ref_properties.EXTENT, 0); debugArray.emplaceBack(0, ref_properties.EXTENT); debugArray.emplaceBack(ref_properties.EXTENT, ref_properties.EXTENT); this.debugBuffer = context.createVertexBuffer(debugArray, ref_properties.posAttributes.members); this.debugSegments = ref_properties.SegmentVector.simpleSegment(0, 0, 4, 5); const rasterBoundsArray = new ref_properties.StructArrayLayout4i8(); rasterBoundsArray.emplaceBack(0, 0, 0, 0); rasterBoundsArray.emplaceBack(ref_properties.EXTENT, 0, ref_properties.EXTENT, 0); rasterBoundsArray.emplaceBack(0, ref_properties.EXTENT, 0, ref_properties.EXTENT); rasterBoundsArray.emplaceBack(ref_properties.EXTENT, ref_properties.EXTENT, ref_properties.EXTENT, ref_properties.EXTENT); this.rasterBoundsBuffer = context.createVertexBuffer(rasterBoundsArray, rasterBoundsAttributes.members); this.rasterBoundsSegments = ref_properties.SegmentVector.simpleSegment(0, 0, 4, 2); const viewportArray = new ref_properties.StructArrayLayout2i4(); viewportArray.emplaceBack(0, 0); viewportArray.emplaceBack(1, 0); viewportArray.emplaceBack(0, 1); viewportArray.emplaceBack(1, 1); this.viewportBuffer = context.createVertexBuffer(viewportArray, ref_properties.posAttributes.members); this.viewportSegments = ref_properties.SegmentVector.simpleSegment(0, 0, 4, 2); const tileLineStripIndices = new ref_properties.StructArrayLayout1ui2(); tileLineStripIndices.emplaceBack(0); tileLineStripIndices.emplaceBack(1); tileLineStripIndices.emplaceBack(3); tileLineStripIndices.emplaceBack(2); tileLineStripIndices.emplaceBack(0); this.tileBorderIndexBuffer = context.createIndexBuffer(tileLineStripIndices); const quadTriangleIndices = new ref_properties.StructArrayLayout3ui6(); quadTriangleIndices.emplaceBack(0, 1, 2); quadTriangleIndices.emplaceBack(2, 1, 3); this.quadTriangleIndexBuffer = context.createIndexBuffer(quadTriangleIndices); this.emptyTexture = new ref_properties.Texture(context, { width: 1, height: 1, data: new Uint8Array([ 0, 0, 0, 0 ]) }, context.gl.RGBA); const gl = this.context.gl; this.stencilClearMode = new ref_properties.StencilMode({ func: gl.ALWAYS, mask: 0 }, 0, 255, gl.ZERO, gl.ZERO, gl.ZERO); this.loadTimeStamps.push(ref_properties.window.performance.now()); } clearStencil() { const context = this.context; const gl = context.gl; this.nextStencilID = 1; this.currentStencilSource = undefined; const matrix = ref_properties.create(); ref_properties.ortho(matrix, 0, this.width, this.height, 0, 0, 1); ref_properties.scale(matrix, matrix, [ gl.drawingBufferWidth, gl.drawingBufferHeight, 0 ]); this.useProgram('clippingMask').draw(context, gl.TRIANGLES, ref_properties.DepthMode.disabled, this.stencilClearMode, ref_properties.ColorMode.disabled, ref_properties.CullFaceMode.disabled, clippingMaskUniformValues(matrix), '$clipping', this.viewportBuffer, this.quadTriangleIndexBuffer, this.viewportSegments); } _renderTileClippingMasks(layer, sourceCache, tileIDs) { if (!sourceCache || this.currentStencilSource === sourceCache.id || !layer.isTileClipped() || !tileIDs || !tileIDs.length) return; this.currentStencilSource = sourceCache.id; const context = this.context; const gl = context.gl; if (this.nextStencilID + tileIDs.length > 256) { this.clearStencil(); } context.setColorMode(ref_properties.ColorMode.disabled); context.setDepthMode(ref_properties.DepthMode.disabled); const program = this.useProgram('clippingMask'); this._tileClippingMaskIDs = {}; for (const tileID of tileIDs) { const id = this._tileClippingMaskIDs[tileID.key] = this.nextStencilID++; program.draw(context, gl.TRIANGLES, ref_properties.DepthMode.disabled, new ref_properties.StencilMode({ func: gl.ALWAYS, mask: 0 }, id, 255, gl.KEEP, gl.KEEP, gl.REPLACE), ref_properties.ColorMode.disabled, ref_properties.CullFaceMode.disabled, clippingMaskUniformValues(tileID.posMatrix), '$clipping', this.tileExtentBuffer, this.quadTriangleIndexBuffer, this.tileExtentSegments); } } stencilModeFor3D() { this.currentStencilSource = undefined; if (this.nextStencilID + 1 > 256) { this.clearStencil(); } const id = this.nextStencilID++; const gl = this.context.gl; return new ref_properties.StencilMode({ func: gl.NOTEQUAL, mask: 255 }, id, 255, gl.KEEP, gl.KEEP, gl.REPLACE); } stencilModeForClipping(tileID) { if (this.terrain) return this.terrain.stencilModeForRTTOverlap(tileID); const gl = this.context.gl; return new ref_properties.StencilMode({ func: gl.EQUAL, mask: 255 }, this._tileClippingMaskIDs[tileID.key], 0, gl.KEEP, gl.KEEP, gl.REPLACE); } stencilConfigForOverlap(tileIDs) { const gl = this.context.gl; const coords = tileIDs.sort((a, b) => b.overscaledZ - a.overscaledZ); const minTileZ = coords[coords.length - 1].overscaledZ; const stencilValues = coords[0].overscaledZ - minTileZ + 1; if (stencilValues > 1) { this.currentStencilSource = undefined; if (this.nextStencilID + stencilValues > 256) { this.clearStencil(); } const zToStencilMode = {}; for (let i = 0; i < stencilValues; i++) { zToStencilMode[i + minTileZ] = new ref_properties.StencilMode({ func: gl.GEQUAL, mask: 255 }, i + this.nextStencilID, 255, gl.KEEP, gl.KEEP, gl.REPLACE); } this.nextStencilID += stencilValues; return [ zToStencilMode, coords ]; } return [ { [minTileZ]: ref_properties.StencilMode.disabled }, coords ]; } colorModeForRenderPass() { const gl = this.context.gl; if (this._showOverdrawInspector) { const numOverdrawSteps = 8; const a = 1 / numOverdrawSteps; return new ref_properties.ColorMode([ gl.CONSTANT_COLOR, gl.ONE ], new ref_properties.Color(a, a, a, 0), [ true, true, true, true ]); } else if (this.renderPass === 'opaque') { return ref_properties.ColorMode.unblended; } else { return ref_properties.ColorMode.alphaBlended; } } depthModeForSublayer(n, mask, func) { if (!this.opaquePassEnabledForLayer()) return ref_properties.DepthMode.disabled; const depth = 1 - ((1 + this.currentLayer) * this.numSublayers + n) * this.depthEpsilon; return new ref_properties.DepthMode(func || this.context.gl.LEQUAL, mask, [ depth, depth ]); } opaquePassEnabledForLayer() { return this.currentLayer < this.opaquePassCutoff; } render(style, options) { this.style = style; this.options = options; this.lineAtlas = style.lineAtlas; this.imageManager = style.imageManager; this.glyphManager = style.glyphManager; this.symbolFadeChange = style.placement.symbolFadeChange(ref_properties.browser.now()); this.imageManager.beginFrame(); const layerIds = this.style.order; const sourceCaches = this.style._sourceCaches; for (const id in sourceCaches) { const sourceCache = sourceCaches[id]; if (sourceCache.used) { sourceCache.prepare(this.context); } } const coordsAscending = {}; const coordsDescending = {}; const coordsDescendingSymbol = {}; for (const id in sourceCaches) { const sourceCache = sourceCaches[id]; coordsAscending[id] = sourceCache.getVisibleCoordinates(); coordsDescending[id] = coordsAscending[id].slice().reverse(); coordsDescendingSymbol[id] = sourceCache.getVisibleCoordinates(true).reverse(); } this.opaquePassCutoff = Infinity; for (let i = 0; i < layerIds.length; i++) { const layerId = layerIds[i]; if (this.style._layers[layerId].is3D()) { this.opaquePassCutoff = i; break; } } if (this.terrain) { this.terrain.updateTileBinding(coordsDescendingSymbol); this.opaquePassCutoff = 0; } this.renderPass = 'offscreen'; for (const layerId of layerIds) { const layer = this.style._layers[layerId]; const sourceCache = style._getLayerSourceCache(layer); if (!layer.hasOffscreenPass() || layer.isHidden(this.transform.zoom)) continue; const coords = sourceCache ? coordsDescending[sourceCache.id] : undefined; if (!(layer.type === 'custom' || layer.isSky()) && !(coords && coords.length)) continue; this.renderLayer(this, sourceCache, layer, coords); } this.depthRangeFor3D = [ 0, 1 - (style.order.length + 2) * this.numSublayers * this.depthEpsilon ]; if (this.terrain && (this.style.hasSymbolLayers() || this.style.hasCircleLayers())) { this.terrain.drawDepth(); } this.context.bindFramebuffer.set(null); this.context.viewport.set([ 0, 0, this.width, this.height ]); this.context.clear({ color: options.showOverdrawInspector ? ref_properties.Color.black : ref_properties.Color.transparent, depth: 1 }); this.clearStencil(); this._showOverdrawInspector = options.showOverdrawInspector; this.renderPass = 'opaque'; if (!this.terrain) { for (this.currentLayer = layerIds.length - 1; this.currentLayer >= 0; this.currentLayer--) { const layer = this.style._layers[layerIds[this.currentLayer]]; const sourceCache = style._getLayerSourceCache(layer); if (layer.isSky()) continue; const coords = sourceCache ? coordsDescending[sourceCache.id] : undefined; this._renderTileClippingMasks(layer, sourceCache, coords); this.renderLayer(this, sourceCache, layer, coords); } } this.renderPass = 'sky'; if (this.transform.isHorizonVisible()) { for (this.currentLayer = 0; this.currentLayer < layerIds.length; this.currentLayer++) { const layer = this.style._layers[layerIds[this.currentLayer]]; const sourceCache = style._getLayerSourceCache(layer); if (!layer.isSky()) continue; const coords = sourceCache ? coordsDescending[sourceCache.id] : undefined; this.renderLayer(this, sourceCache, layer, coords); } } this.renderPass = 'translucent'; this.currentLayer = 0; while (this.currentLayer < layerIds.length) { const layer = this.style._layers[layerIds[this.currentLayer]]; const sourceCache = style._getLayerSourceCache(layer); if (layer.isSky()) { ++this.currentLayer; continue; } if (this.terrain && this.style.isLayerDraped(layer)) { if (layer.isHidden(this.transform.zoom)) { ++this.currentLayer; continue; } const terrain = this.terrain; const prevLayer = this.currentLayer; this.currentLayer = terrain.renderBatch(this.currentLayer); continue; } const coords = sourceCache ? (layer.type === 'symbol' ? coordsDescendingSymbol : coordsDescending)[sourceCache.id] : undefined; this._renderTileClippingMasks(layer, sourceCache, sourceCache ? coordsAscending[sourceCache.id] : undefined); this.renderLayer(this, sourceCache, layer, coords); ++this.currentLayer; } if (this.terrain) { this.terrain.postRender(); } if (this.options.showTileBoundaries || this.options.showQueryGeometry) { let selectedSource = null; const layers = ref_properties.values(this.style._layers); layers.forEach(layer => { const sourceCache = style._getLayerSourceCache(layer); if (sourceCache && !layer.isHidden(this.transform.zoom)) { if (!selectedSource || selectedSource.getSource().maxzoom < sourceCache.getSource().maxzoom) { selectedSource = sourceCache; } } }); if (selectedSource) { if (this.options.showTileBoundaries) { draw$1.debug(this, selectedSource, selectedSource.getVisibleCoordinates()); } } } if (this.options.showPadding) { drawDebugPadding(this); } this.context.setDefault(); this.frameCounter = (this.frameCounter + 1) % ref_properties.MAX_SAFE_INTEGER; if (this.tileLoaded && this.options.speedIndexTiming) { this.loadTimeStamps.push(ref_properties.window.performance.now()); this.saveCanvasCopy(); } } renderLayer(painter, sourceCache, layer, coords) { if (layer.isHidden(this.transform.zoom)) return; if (layer.type !== 'background' && layer.type !== 'sky' && layer.type !== 'custom' && !(coords && coords.length)) return; this.id = layer.id; this.gpuTimingStart(layer); draw$1[layer.type](painter, sourceCache, layer, coords, this.style.placement.variableOffsets, this.options.isInitialLoad); this.gpuTimingEnd(); } gpuTimingStart(layer) { if (!this.options.gpuTiming) return; const ext = this.context.extTimerQuery; let layerTimer = this.gpuTimers[layer.id]; if (!layerTimer) { layerTimer = this.gpuTimers[layer.id] = { calls: 0, cpuTime: 0, query: ext.createQueryEXT() }; } layerTimer.calls++; ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, layerTimer.query); } gpuTimingEnd() { if (!this.options.gpuTiming) return; const ext = this.context.extTimerQuery; ext.endQueryEXT(ext.TIME_ELAPSED_EXT); } collectGpuTimers() { const currentLayerTimers = this.gpuTimers; this.gpuTimers = {}; return currentLayerTimers; } queryGpuTimers(gpuTimers) { const layers = {}; for (const layerId in gpuTimers) { const gpuTimer = gpuTimers[layerId]; const ext = this.context.extTimerQuery; const gpuTime = ext.getQueryObjectEXT(gpuTimer.query, ext.QUERY_RESULT_EXT) / (1000 * 1000); ext.deleteQueryEXT(gpuTimer.query); layers[layerId] = gpuTime; } return layers; } translatePosMatrix(matrix, tile, translate, translateAnchor, inViewportPixelUnitsUnits) { if (!translate[0] && !translate[1]) return matrix; const angle = inViewportPixelUnitsUnits ? translateAnchor === 'map' ? this.transform.angle : 0 : translateAnchor === 'viewport' ? -this.transform.angle : 0; if (angle) { const sinA = Math.sin(angle); const cosA = Math.cos(angle); translate = [ translate[0] * cosA - translate[1] * sinA, translate[0] * sinA + translate[1] * cosA ]; } const translation = [ inViewportPixelUnitsUnits ? translate[0] : pixelsToTileUnits(tile, translate[0], this.transform.zoom), inViewportPixelUnitsUnits ? translate[1] : pixelsToTileUnits(tile, translate[1], this.transform.zoom), 0 ]; const translatedMatrix = new Float32Array(16); ref_properties.translate(translatedMatrix, matrix, translation); return translatedMatrix; } saveTileTexture(texture) { const textures = this._tileTextures[texture.size[0]]; if (!textures) { this._tileTextures[texture.size[0]] = [texture]; } else { textures.push(texture); } } getTileTexture(size) { const textures = this._tileTextures[size]; return textures && textures.length > 0 ? textures.pop() : null; } isPatternMissing(image) { if (!image) return false; if (!image.from || !image.to) return true; const imagePosA = this.imageManager.getPattern(image.from.toString()); const imagePosB = this.imageManager.getPattern(image.to.toString()); return !imagePosA || !imagePosB; } currentGlobalDefines() { const terrain = this.terrain && !this.terrain.renderingToTexture; const rtt = this.terrain && this.terrain.renderingToTexture; const defines = []; if (terrain) defines.push('TERRAIN'); if (rtt) defines.push('RENDER_TO_TEXTURE'); if (this._showOverdrawInspector) defines.push('OVERDRAW_INSPECTOR'); return defines; } useProgram(name, programConfiguration, fixedDefines) { this.cache = this.cache || {}; const defines = fixedDefines || []; const globalDefines = this.currentGlobalDefines(); const allDefines = globalDefines.concat(defines); const key = Program.cacheKey(name, allDefines, programConfiguration); if (!this.cache[key]) { this.cache[key] = new Program(this.context, name, shaders[name], programConfiguration, programUniforms[name], allDefines); } return this.cache[key]; } setCustomLayerDefaults() { this.context.unbindVAO(); this.context.cullFace.setDefault(); this.context.activeTexture.setDefault(); this.context.pixelStoreUnpack.setDefault(); this.context.pixelStoreUnpackPremultiplyAlpha.setDefault(); this.context.pixelStoreUnpackFlipY.setDefault(); } setBaseState() { const gl = this.context.gl; this.context.cullFace.set(false); this.context.viewport.set([ 0, 0, this.width, this.height ]); this.context.blendEquation.set(gl.FUNC_ADD); } initDebugOverlayCanvas() { if (this.debugOverlayCanvas == null) { this.debugOverlayCanvas = ref_properties.window.document.createElement('canvas'); this.debugOverlayCanvas.width = 512; this.debugOverlayCanvas.height = 512; const gl = this.context.gl; this.debugOverlayTexture = new ref_properties.Texture(this.context, this.debugOverlayCanvas, gl.RGBA); } } destroy() { if (this._terrain) { this._terrain.destroy(); } this.emptyTexture.destroy(); if (this.debugOverlayTexture) { this.debugOverlayTexture.destroy(); } } prepareDrawTile(tileID) { if (this.terrain) { this.terrain.prepareDrawTile(tileID); } } setTileLoadedFlag(flag) { this.tileLoaded = flag; } saveCanvasCopy() { this.frameCopies.push(this.canvasCopy()); this.tileLoaded = false; } canvasCopy() { const gl = this.context.gl; const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, 0); return texture; } getCanvasCopiesAndTimestamps() { return { canvasCopies: this.frameCopies, timeStamps: this.loadTimeStamps }; } } class EdgeInsets { constructor(top = 0, bottom = 0, left = 0, right = 0) { if (isNaN(top) || top < 0 || isNaN(bottom) || bottom < 0 || isNaN(left) || left < 0 || isNaN(right) || right < 0) { throw new Error('Invalid value for edge-insets, top, bottom, left and right must all be numbers'); } this.top = top; this.bottom = bottom; this.left = left; this.right = right; } interpolate(start, target, t) { if (target.top != null && start.top != null) this.top = ref_properties.number(start.top, target.top, t); if (target.bottom != null && start.bottom != null) this.bottom = ref_properties.number(start.bottom, target.bottom, t); if (target.left != null && start.left != null) this.left = ref_properties.number(start.left, target.left, t); if (target.right != null && start.right != null) this.right = ref_properties.number(start.right, target.right, t); return this; } getCenter(width, height) { const x = ref_properties.clamp((this.left + width - this.right) / 2, 0, width); const y = ref_properties.clamp((this.top + height - this.bottom) / 2, 0, height); return new ref_properties.Point(x, y); } equals(other) { return this.top === other.top && this.bottom === other.bottom && this.left === other.left && this.right === other.right; } clone() { return new EdgeInsets(this.top, this.bottom, this.left, this.right); } toJSON() { return { top: this.top, bottom: this.bottom, left: this.left, right: this.right }; } } 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 updateTransformOrientation(matrix, orientation) { const position = getColumn(matrix, 3); ref_properties.fromQuat(matrix, orientation); setColumn(matrix, 3, position); } function updateTransformPosition(matrix, position) { setColumn(matrix, 3, [ position[0], position[1], position[2], 1 ]); } function wrapCameraPosition(position) { if (!position) return; const mercatorCoordinate = Array.isArray(position) ? new ref_properties.MercatorCoordinate(position[0], position[1], position[2]) : position; mercatorCoordinate.x = ref_properties.wrap(mercatorCoordinate.x, 0, 1); return mercatorCoordinate; } function orientationFromPitchBearing(pitch, bearing) { const orientation = ref_properties.identity$1([]); ref_properties.rotateZ$1(orientation, orientation, -bearing); ref_properties.rotateX$1(orientation, orientation, -pitch); return orientation; } function orientationFromFrame(forward, up) { const xyForward = [ forward[0], forward[1], 0 ]; const xyUp = [ up[0], up[1], 0 ]; const epsilon = 1e-15; if (ref_properties.length(xyForward) >= epsilon) { const xyDir = ref_properties.normalize([], xyForward); ref_properties.scale$2(xyUp, xyDir, ref_properties.dot(xyUp, xyDir)); up[0] = xyUp[0]; up[1] = xyUp[1]; } const right = ref_properties.cross([], up, forward); if (ref_properties.len(right) < epsilon) { return null; } const bearing = Math.atan2(-right[1], right[0]); const pitch = Math.atan2(Math.sqrt(forward[0] * forward[0] + forward[1] * forward[1]), -forward[2]); return orientationFromPitchBearing(pitch, bearing); } class FreeCameraOptions { constructor(position, orientation) { this.position = position; this.orientation = orientation; } get position() { return this._position; } set position(position) { this._position = this._renderWorldCopies ? wrapCameraPosition(position) : position; } lookAtPoint(location, up) { this.orientation = null; if (!this.position) { return; } const altitude = this._elevation ? this._elevation.getAtPoint(ref_properties.MercatorCoordinate.fromLngLat(location)) : 0; const pos = this.position; const target = ref_properties.MercatorCoordinate.fromLngLat(location, altitude); const forward = [ target.x - pos.x, target.y - pos.y, target.z - pos.z ]; if (!up) up = [ 0, 0, 1 ]; up[2] = Math.abs(up[2]); this.orientation = orientationFromFrame(forward, up); } setPitchBearing(pitch, bearing) { this.orientation = orientationFromPitchBearing(ref_properties.degToRad(pitch), ref_properties.degToRad(-bearing)); } } class FreeCamera { constructor(position, orientation) { this._transform = ref_properties.identity([]); this._orientation = ref_properties.identity$1([]); if (orientation) { this._orientation = orientation; updateTransformOrientation(this._transform, this._orientation); } if (position) { updateTransformPosition(this._transform, position); } } get mercatorPosition() { const pos = this.position; return new ref_properties.MercatorCoordinate(pos[0], pos[1], pos[2]); } get position() { const col = getColumn(this._transform, 3); return [ col[0], col[1], col[2] ]; } set position(value) { updateTransformPosition(this._transform, value); } get orientation() { return this._orientation; } set orientation(value) { this._orientation = value; updateTransformOrientation(this._transform, this._orientation); } getPitchBearing() { const f = this.forward(); const r = this.right(); return { bearing: Math.atan2(-r[1], r[0]), pitch: Math.atan2(Math.sqrt(f[0] * f[0] + f[1] * f[1]), -f[2]) }; } setPitchBearing(pitch, bearing) { this._orientation = orientationFromPitchBearing(pitch, bearing); updateTransformOrientation(this._transform, this._orientation); } forward() { const col = getColumn(this._transform, 2); return [ -col[0], -col[1], -col[2] ]; } up() { const col = getColumn(this._transform, 1); return [ -col[0], -col[1], -col[2] ]; } right() { const col = getColumn(this._transform, 0); return [ col[0], col[1], col[2] ]; } getCameraToWorld(worldSize, pixelsPerMeter) { const cameraToWorld = new Float64Array(16); ref_properties.invert(cameraToWorld, this.getWorldToCamera(worldSize, pixelsPerMeter)); return cameraToWorld; } getWorldToCamera(worldSize, pixelsPerMeter) { const matrix = new Float64Array(16); const invOrientation = new Float64Array(4); const invPosition = this.position; ref_properties.conjugate(invOrientation, this._orientation); ref_properties.scale$2(invPosition, invPosition, -worldSize); ref_properties.fromQuat(matrix, invOrientation); ref_properties.translate(matrix, matrix, invPosition); matrix[1] *= -1; matrix[5] *= -1; matrix[9] *= -1; matrix[13] *= -1; matrix[8] *= pixelsPerMeter; matrix[9] *= pixelsPerMeter; matrix[10] *= pixelsPerMeter; matrix[11] *= pixelsPerMeter; return matrix; } getCameraToClipPerspective(fovy, aspectRatio, nearZ, farZ) { const matrix = new Float64Array(16); ref_properties.perspective(matrix, fovy, aspectRatio, nearZ, farZ); return matrix; } clone() { return new FreeCamera([...this.position], [...this.orientation]); } } const NUM_WORLD_COPIES = 3; const DEFAULT_MIN_ZOOM = 0; class Transform { constructor(minZoom, maxZoom, minPitch, maxPitch, renderWorldCopies) { this.tileSize = 512; this.maxValidLatitude = 85.051129; this._renderWorldCopies = renderWorldCopies === undefined ? true : renderWorldCopies; this._minZoom = minZoom || DEFAULT_MIN_ZOOM; this._maxZoom = maxZoom || 22; this._minPitch = minPitch === undefined || minPitch === null ? 0 : minPitch; this._maxPitch = maxPitch === undefined || maxPitch === null ? 60 : maxPitch; this.setMaxBounds(); this.width = 0; this.height = 0; this._center = new ref_properties.LngLat(0, 0); this.zoom = 0; this.angle = 0; this._fov = 0.6435011087932844; this._pitch = 0; this._unmodified = true; this._edgeInsets = new EdgeInsets(); this._posMatrixCache = {}; this._alignedPosMatrixCache = {}; this._camera = new FreeCamera(); this._centerAltitude = 0; this.cameraElevationReference = 'ground'; this._horizonShift = 0.1; } clone() { const clone = new Transform(this._minZoom, this._maxZoom, this._minPitch, this.maxPitch, this._renderWorldCopies); clone._elevation = this._elevation; clone._centerAltitude = this._centerAltitude; clone.tileSize = this.tileSize; clone.latRange = this.latRange; clone.width = this.width; clone.height = this.height; clone.cameraElevationReference = this.cameraElevationReference; clone._center = this._center; clone._setZoom(this.zoom); clone._cameraZoom = this._cameraZoom; clone.angle = this.angle; clone._fov = this._fov; clone._pitch = this._pitch; clone._unmodified = this._unmodified; clone._edgeInsets = this._edgeInsets.clone(); clone._camera = this._camera.clone(); clone._calcMatrices(); clone.freezeTileCoverage = this.freezeTileCoverage; return clone; } get elevation() { return this._elevation; } set elevation(elevation) { if (this._elevation === elevation) return; this._elevation = elevation; if (!elevation) { this._cameraZoom = null; this._centerAltitude = 0; } else { if (this._updateCenterElevation()) this._updateCameraOnTerrain(); } this._calcMatrices(); } updateElevation(constrainCameraOverTerrain) { if (this._terrainEnabled() && this._cameraZoom == null) { if (this._updateCenterElevation()) this._updateCameraOnTerrain(); } if (constrainCameraOverTerrain) { this._constrainCameraAltitude(); } this._calcMatrices(); } get minZoom() { return this._minZoom; } set minZoom(zoom) { if (this._minZoom === zoom) return; this._minZoom = zoom; this.zoom = Math.max(this.zoom, zoom); } get maxZoom() { return this._maxZoom; } set maxZoom(zoom) { if (this._maxZoom === zoom) return; this._maxZoom = zoom; this.zoom = Math.min(this.zoom, zoom); } get minPitch() { return this._minPitch; } set minPitch(pitch) { if (this._minPitch === pitch) return; this._minPitch = pitch; this.pitch = Math.max(this.pitch, pitch); } get maxPitch() { return this._maxPitch; } set maxPitch(pitch) { if (this._maxPitch === pitch) return; this._maxPitch = pitch; this.pitch = Math.min(this.pitch, pitch); } get renderWorldCopies() { return this._renderWorldCopies; } set renderWorldCopies(renderWorldCopies) { if (renderWorldCopies === undefined) { renderWorldCopies = true; } else if (renderWorldCopies === null) { renderWorldCopies = false; } this._renderWorldCopies = renderWorldCopies; } get worldSize() { return this.tileSize * this.scale; } get centerOffset() { return this.centerPoint._sub(this.size._div(2)); } get size() { return new ref_properties.Point(this.width, this.height); } get bearing() { return -this.angle / Math.PI * 180; } set bearing(bearing) { const b = -ref_properties.wrap(bearing, -180, 180) * Math.PI / 180; if (this.angle === b) return; this._unmodified = false; this.angle = b; this._calcMatrices(); this.rotationMatrix = ref_properties.create$2(); ref_properties.rotate(this.rotationMatrix, this.rotationMatrix, this.angle); } get pitch() { return this._pitch / Math.PI * 180; } set pitch(pitch) { const p = ref_properties.clamp(pitch, this.minPitch, this.maxPitch) / 180 * Math.PI; if (this._pitch === p) return; this._unmodified = false; this._pitch = p; this._calcMatrices(); } get fov() { return this._fov / Math.PI * 180; } set fov(fov) { fov = Math.max(0.01, Math.min(60, fov)); if (this._fov === fov) return; this._unmodified = false; this._fov = fov / 180 * Math.PI; this._calcMatrices(); } get zoom() { return this._zoom; } set zoom(zoom) { const z = Math.min(Math.max(zoom, this.minZoom), this.maxZoom); if (this._zoom === z) return; this._unmodified = false; this._setZoom(z); if (this._terrainEnabled()) { this._updateCameraOnTerrain(); } this._constrain(); this._calcMatrices(); } _setZoom(z) { this._zoom = z; this.scale = this.zoomScale(z); this.tileZoom = Math.floor(z); this.zoomFraction = z - this.tileZoom; } _updateCenterElevation() { if (!this._elevation) return false; const elevationAtCenter = this._elevation.getAtPoint(ref_properties.MercatorCoordinate.fromLngLat(this.center), -1); if (elevationAtCenter === -1) { this._cameraZoom = null; return false; } this._centerAltitude = elevationAtCenter; return true; } _updateCameraOnTerrain() { const height = this.cameraToCenterDistance / this.worldSize; const terrainElevation = ref_properties.mercatorZfromAltitude(this._centerAltitude, this.center.lat); this._cameraZoom = this._zoomFromMercatorZ(terrainElevation + height); } get center() { return this._center; } set center(center) { if (center.lat === this._center.lat && center.lng === this._center.lng) return; this._unmodified = false; this._center = center; if (this._terrainEnabled()) { if (this.cameraElevationReference === 'ground') { if (this._updateCenterElevation()) this._updateCameraOnTerrain(); else this._cameraZoom = null; } else { this._updateZoomFromElevation(); } } this._constrain(); this._calcMatrices(); } _updateZoomFromElevation() { if (this._cameraZoom == null || !this._elevation) return; const cameraZoom = this._cameraZoom; const elevationAtCenter = this._elevation.getAtPoint(ref_properties.MercatorCoordinate.fromLngLat(this.center)); const mercatorElevation = ref_properties.mercatorZfromAltitude(elevationAtCenter, this.center.lat); const altitude = this._mercatorZfromZoom(cameraZoom); const minHeight = this._mercatorZfromZoom(this._maxZoom); const height = Math.max(altitude - mercatorElevation, minHeight); this._setZoom(this._zoomFromMercatorZ(height)); } get padding() { return this._edgeInsets.toJSON(); } set padding(padding) { if (this._edgeInsets.equals(padding)) return; this._unmodified = false; this._edgeInsets.interpolate(this._edgeInsets, padding, 1); this._calcMatrices(); } computeZoomRelativeTo(position) { const centerOnTargetAltitude = this.rayIntersectionCoordinate(this.pointRayIntersection(this.centerPoint, position.toAltitude())); let targetPosition; if (position.z < this._camera.position[2]) { targetPosition = [ centerOnTargetAltitude.x, centerOnTargetAltitude.y, centerOnTargetAltitude.z ]; } else { targetPosition = [ position.x, position.y, position.z ]; } const distToTarget = ref_properties.length(ref_properties.sub([], this._camera.position, targetPosition)); return ref_properties.clamp(this._zoomFromMercatorZ(distToTarget), this._minZoom, this._maxZoom); } setFreeCameraOptions(options) { if (!this.height) return; if (!options.position && !options.orientation) return; this._updateCameraState(); let changed = false; if (options.orientation && !ref_properties.exactEquals(options.orientation, this._camera.orientation)) { changed = this._setCameraOrientation(options.orientation); } if (options.position) { const newPosition = [ options.position.x, options.position.y, options.position.z ]; if (!ref_properties.exactEquals$1(newPosition, this._camera.position)) { this._setCameraPosition(newPosition); changed = true; } } if (changed) { this._updateStateFromCamera(); this.recenterOnTerrain(); } } getFreeCameraOptions() { this._updateCameraState(); const pos = this._camera.position; const options = new FreeCameraOptions(); options.position = new ref_properties.MercatorCoordinate(pos[0], pos[1], pos[2]); options.orientation = this._camera.orientation; options._elevation = this.elevation; options._renderWorldCopies = this._renderWorldCopies; return options; } _setCameraOrientation(orientation) { if (!ref_properties.length$1(orientation)) return false; ref_properties.normalize$1(orientation, orientation); const forward = ref_properties.transformQuat([], [ 0, 0, -1 ], orientation); const up = ref_properties.transformQuat([], [ 0, -1, 0 ], orientation); if (up[2] < 0) return false; const updatedOrientation = orientationFromFrame(forward, up); if (!updatedOrientation) return false; this._camera.orientation = updatedOrientation; return true; } _setCameraPosition(position) { const minWorldSize = this.zoomScale(this.minZoom) * this.tileSize; const maxWorldSize = this.zoomScale(this.maxZoom) * this.tileSize; const distToCenter = this.cameraToCenterDistance; position[2] = ref_properties.clamp(position[2], distToCenter / maxWorldSize, distToCenter / minWorldSize); this._camera.position = position; } get centerPoint() { return this._edgeInsets.getCenter(this.width, this.height); } get fovAboveCenter() { return this._fov * (0.5 + this.centerOffset.y / this.height); } isPaddingEqual(padding) { return this._edgeInsets.equals(padding); } interpolatePadding(start, target, t) { this._unmodified = false; this._edgeInsets.interpolate(start, target, t); this._constrain(); this._calcMatrices(); } coveringZoomLevel(options) { const z = (options.roundZoom ? Math.round : Math.floor)(this.zoom + this.scaleZoom(this.tileSize / options.tileSize)); return Math.max(0, z); } getVisibleUnwrappedCoordinates(tileID) { const result = [new ref_properties.UnwrappedTileID(0, tileID)]; if (this._renderWorldCopies) { const utl = this.pointCoordinate(new ref_properties.Point(0, 0)); const utr = this.pointCoordinate(new ref_properties.Point(this.width, 0)); const ubl = this.pointCoordinate(new ref_properties.Point(this.width, this.height)); const ubr = this.pointCoordinate(new ref_properties.Point(0, this.height)); const w0 = Math.floor(Math.min(utl.x, utr.x, ubl.x, ubr.x)); const w1 = Math.floor(Math.max(utl.x, utr.x, ubl.x, ubr.x)); const extraWorldCopy = 1; for (let w = w0 - extraWorldCopy; w <= w1 + extraWorldCopy; w++) { if (w === 0) continue; result.push(new ref_properties.UnwrappedTileID(w, tileID)); } } return result; } coveringTiles(options) { let z = this.coveringZoomLevel(options); const actualZ = z; const useElevationData = !!options.useElevationData; if (options.minzoom !== undefined && z < options.minzoom) return []; if (options.maxzoom !== undefined && z > options.maxzoom) z = options.maxzoom; const centerCoord = ref_properties.MercatorCoordinate.fromLngLat(this.center); const numTiles = 1 << z; const centerPoint = [ numTiles * centerCoord.x, numTiles * centerCoord.y, 0 ]; const cameraFrustum = ref_properties.Frustum.fromInvProjectionMatrix(this.invProjMatrix, this.worldSize, z); const cameraCoord = this.pointCoordinate(this.getCameraPoint()); const meterToTile = numTiles * ref_properties.mercatorZfromAltitude(1, this.center.lat); const cameraAltitude = this._camera.position[2] / ref_properties.mercatorZfromAltitude(1, this.center.lat); const cameraPoint = [ numTiles * cameraCoord.x, numTiles * cameraCoord.y, cameraAltitude ]; const zoomSplitDistance = this.cameraToCenterDistance / options.tileSize * (options.roundZoom ? 1 : 0.502); const minZoom = this.pitch <= 60 && this._edgeInsets.top <= this._edgeInsets.bottom && !this._elevation ? z : 0; const maxRange = this.elevation ? this.elevation.exaggeration() * 10000 : 0; const newRootTile = wrap => { const max = maxRange; const min = -maxRange; return { aabb: new ref_properties.Aabb([ wrap * numTiles, 0, min ], [ (wrap + 1) * numTiles, numTiles, max ]), zoom: 0, x: 0, y: 0, wrap, fullyVisible: false }; }; const stack = []; const result = []; const maxZoom = z; const overscaledZ = options.reparseOverscaled ? actualZ : z; const getAABBFromElevation = (aabb, tileID) => { if (!this._elevation) return; const minmax = this._elevation.getMinMaxForTile(tileID); if (minmax) { aabb.min[2] = minmax.min; aabb.max[2] = minmax.max; aabb.center[2] = (aabb.min[2] + aabb.max[2]) / 2; } }; const square = a => a * a; const cameraHeightSqr = square((cameraAltitude - this._centerAltitude) * meterToTile); const distToSplitScale = (dzSqr, dSqr) => { const acuteAngleThresholdSin = 0.707; const stretchTile = 1.1; if (dSqr * square(acuteAngleThresholdSin) < dzSqr) return 1; const r = Math.sqrt(dSqr / dzSqr); const k = r - 1 / acuteAngleThresholdSin; return r / (1 / acuteAngleThresholdSin + (Math.pow(stretchTile, k + 1) - 1) / (stretchTile - 1) - 1); }; if (this._renderWorldCopies) { for (let i = 1; i <= NUM_WORLD_COPIES; i++) { stack.push(newRootTile(-i)); stack.push(newRootTile(i)); } } stack.push(newRootTile(0)); while (stack.length > 0) { const it = stack.pop(); const x = it.x; const y = it.y; let fullyVisible = it.fullyVisible; if (!fullyVisible) { const intersectResult = it.aabb.intersects(cameraFrustum); if (intersectResult === 0) continue; fullyVisible = intersectResult === 2; } let shouldSplit = true; if (minZoom <= it.zoom && it.zoom < maxZoom) { const dx = it.aabb.distanceX(cameraPoint); const dy = it.aabb.distanceY(cameraPoint); let dzSqr = cameraHeightSqr; if (useElevationData) { dzSqr = square(it.aabb.distanceZ(cameraPoint) * meterToTile); } const distanceSqr = dx * dx + dy * dy + dzSqr; const distToSplit = (1 << maxZoom - it.zoom) * zoomSplitDistance; const distToSplitSqr = square(distToSplit * distToSplitScale(Math.max(dzSqr, cameraHeightSqr), distanceSqr)); shouldSplit = distanceSqr < distToSplitSqr; } if (it.zoom === maxZoom || !shouldSplit) { const tileZoom = it.zoom === maxZoom ? overscaledZ : it.zoom; if (!!options.minzoom && options.minzoom > tileZoom) { continue; } const dx = centerPoint[0] - (0.5 + x + (it.wrap << it.zoom)) * (1 << z - it.zoom); const dy = centerPoint[1] - 0.5 - y; const id = it.tileID ? it.tileID : new ref_properties.OverscaledTileID(tileZoom, it.wrap, it.zoom, x, y); result.push({ tileID: id, distanceSq: dx * dx + dy * dy }); continue; } for (let i = 0; i < 4; i++) { const childX = (x << 1) + i % 2; const childY = (y << 1) + (i >> 1); const aabb = it.aabb.quadrant(i); let tileID = null; if (useElevationData && it.zoom > maxZoom - 6) { tileID = new ref_properties.OverscaledTileID(it.zoom + 1 === maxZoom ? overscaledZ : it.zoom + 1, it.wrap, it.zoom + 1, childX, childY); getAABBFromElevation(aabb, tileID); } stack.push({ aabb, zoom: it.zoom + 1, x: childX, y: childY, wrap: it.wrap, fullyVisible, tileID }); } } const cover = result.sort((a, b) => a.distanceSq - b.distanceSq).map(a => a.tileID); return cover; } resize(width, height) { this.width = width; this.height = height; this.pixelsToGLUnits = [ 2 / width, -2 / height ]; this._constrain(); this._calcMatrices(); } get unmodified() { return this._unmodified; } zoomScale(zoom) { return Math.pow(2, zoom); } scaleZoom(scale) { return Math.log(scale) / Math.LN2; } project(lnglat) { const lat = ref_properties.clamp(lnglat.lat, -this.maxValidLatitude, this.maxValidLatitude); return new ref_properties.Point(ref_properties.mercatorXfromLng(lnglat.lng) * this.worldSize, ref_properties.mercatorYfromLat(lat) * this.worldSize); } unproject(point) { return new ref_properties.MercatorCoordinate(point.x / this.worldSize, point.y / this.worldSize).toLngLat(); } get point() { return this.project(this.center); } setLocationAtPoint(lnglat, point) { const a = this.pointCoordinate(point); const b = this.pointCoordinate(this.centerPoint); const loc = this.locationCoordinate(lnglat); const newCenter = new ref_properties.MercatorCoordinate(loc.x - (a.x - b.x), loc.y - (a.y - b.y)); this.center = this.coordinateLocation(newCenter); if (this._renderWorldCopies) { this.center = this.center.wrap(); } } setLocation(location) { this.center = this.coordinateLocation(location); if (this._renderWorldCopies) { this.center = this.center.wrap(); } } locationPoint(lnglat) { return this._coordinatePoint(this.locationCoordinate(lnglat), false); } locationPoint3D(lnglat) { return this._coordinatePoint(this.locationCoordinate(lnglat), true); } pointLocation(p) { return this.coordinateLocation(this.pointCoordinate(p)); } pointLocation3D(p) { return this.coordinateLocation(this.pointCoordinate3D(p)); } locationCoordinate(lnglat) { return ref_properties.MercatorCoordinate.fromLngLat(lnglat); } coordinateLocation(coord) { return coord.toLngLat(); } pointRayIntersection(p, z) { const targetZ = z !== undefined && z !== null ? z : this._centerAltitude; const p0 = [ p.x, p.y, 0, 1 ]; const p1 = [ p.x, p.y, 1, 1 ]; ref_properties.transformMat4(p0, p0, this.pixelMatrixInverse); ref_properties.transformMat4(p1, p1, this.pixelMatrixInverse); const w0 = p0[3]; const w1 = p1[3]; ref_properties.scale$1(p0, p0, 1 / w0); ref_properties.scale$1(p1, p1, 1 / w1); const z0 = p0[2]; const z1 = p1[2]; const t = z0 === z1 ? 0 : (targetZ - z0) / (z1 - z0); return { p0, p1, t }; } screenPointToMercatorRay(p) { const p0 = [ p.x, p.y, 0, 1 ]; const p1 = [ p.x, p.y, 1, 1 ]; ref_properties.transformMat4(p0, p0, this.pixelMatrixInverse); ref_properties.transformMat4(p1, p1, this.pixelMatrixInverse); ref_properties.scale$1(p0, p0, 1 / p0[3]); ref_properties.scale$1(p1, p1, 1 / p1[3]); p0[2] = ref_properties.mercatorZfromAltitude(p0[2], this._center.lat) * this.worldSize; p1[2] = ref_properties.mercatorZfromAltitude(p1[2], this._center.lat) * this.worldSize; ref_properties.scale$1(p0, p0, 1 / this.worldSize); ref_properties.scale$1(p1, p1, 1 / this.worldSize); return new ref_properties.Ray([ p0[0], p0[1], p0[2] ], ref_properties.normalize([], ref_properties.sub([], p1, p0))); } rayIntersectionCoordinate(rayIntersection) { const {p0, p1, t} = rayIntersection; const z0 = ref_properties.mercatorZfromAltitude(p0[2], this._center.lat); const z1 = ref_properties.mercatorZfromAltitude(p1[2], this._center.lat); return new ref_properties.MercatorCoordinate(ref_properties.number(p0[0], p1[0], t) / this.worldSize, ref_properties.number(p0[1], p1[1], t) / this.worldSize, ref_properties.number(z0, z1, t)); } pointCoordinate(p) { const horizonOffset = this.horizonLineFromTop(false); const clamped = new ref_properties.Point(p.x, Math.max(horizonOffset, p.y)); return this.rayIntersectionCoordinate(this.pointRayIntersection(clamped)); } pointCoordinate3D(p) { if (!this.elevation) return this.pointCoordinate(p); const elevation = this.elevation; let raycast = this.elevation.pointCoordinate(p); if (raycast) return new ref_properties.MercatorCoordinate(raycast[0], raycast[1], raycast[2]); let start = 0, end = this.horizonLineFromTop(); if (p.y > end) return this.pointCoordinate(p); const samples = 10; const threshold = 0.02 * end; const r = p.clone(); for (let i = 0; i < samples && end - start > threshold; i++) { r.y = ref_properties.number(start, end, 0.66); const rCast = elevation.pointCoordinate(r); if (rCast) { end = r.y; raycast = rCast; } else { start = r.y; } } return raycast ? new ref_properties.MercatorCoordinate(raycast[0], raycast[1], raycast[2]) : this.pointCoordinate(p); } isPointAboveHorizon(p) { if (!this.elevation) { const horizon = this.horizonLineFromTop(); return p.y < horizon; } else { return !this.elevation.pointCoordinate(p); } } _coordinatePoint(coord, sampleTerrainIn3D) { const elevation = sampleTerrainIn3D && this.elevation ? this.elevation.getAtPoint(coord, this._centerAltitude) : this._centerAltitude; const p = [ coord.x * this.worldSize, coord.y * this.worldSize, elevation + coord.toAltitude(), 1 ]; ref_properties.transformMat4(p, p, this.pixelMatrix); return p[3] > 0 ? new ref_properties.Point(p[0] / p[3], p[1] / p[3]) : new ref_properties.Point(Number.MAX_VALUE, Number.MAX_VALUE); } getBounds() { if (this._terrainEnabled()) return this._getBounds3D(); return new ref_properties.LngLatBounds().extend(this.pointLocation(new ref_properties.Point(0, 0))).extend(this.pointLocation(new ref_properties.Point(this.width, 0))).extend(this.pointLocation(new ref_properties.Point(this.width, this.height))).extend(this.pointLocation(new ref_properties.Point(0, this.height))); } _getBounds3D() { const elevation = this.elevation; const minmax = elevation.visibleDemTiles.reduce((acc, t) => { if (t.dem) { const tree = t.dem.tree; acc.min = Math.min(acc.min, tree.minimums[0]); acc.max = Math.max(acc.max, tree.maximums[0]); } return acc; }, { min: Number.MAX_VALUE, max: 0 }); minmax.min *= elevation.exaggeration(); minmax.max *= elevation.exaggeration(); const top = this.horizonLineFromTop(); return [ new ref_properties.Point(0, top), new ref_properties.Point(this.width, top), new ref_properties.Point(this.width, this.height), new ref_properties.Point(0, this.height) ].reduce((acc, p) => { return acc.extend(this.coordinateLocation(this.rayIntersectionCoordinate(this.pointRayIntersection(p, minmax.min)))).extend(this.coordinateLocation(this.rayIntersectionCoordinate(this.pointRayIntersection(p, minmax.max)))); }, new ref_properties.LngLatBounds()); } horizonLineFromTop(clampToTop = true) { const h = this.height / 2 / Math.tan(this._fov / 2) / Math.tan(Math.max(this._pitch, 0.1)) + this.centerOffset.y; const horizonEpsilon = 0.03; const offset = this.height / 2 - h * (1 - horizonEpsilon); return clampToTop ? Math.max(0, offset) : offset; } getMaxBounds() { if (!this.latRange || this.latRange.length !== 2 || !this.lngRange || this.lngRange.length !== 2) return null; return new ref_properties.LngLatBounds([ this.lngRange[0], this.latRange[0] ], [ this.lngRange[1], this.latRange[1] ]); } setMaxBounds(bounds) { if (bounds) { this.lngRange = [ bounds.getWest(), bounds.getEast() ]; this.latRange = [ bounds.getSouth(), bounds.getNorth() ]; this._constrain(); } else { this.lngRange = null; this.latRange = [ -this.maxValidLatitude, this.maxValidLatitude ]; } } calculatePosMatrix(unwrappedTileID, aligned = false) { const posMatrixKey = unwrappedTileID.key; const cache = aligned ? this._alignedPosMatrixCache : this._posMatrixCache; if (cache[posMatrixKey]) { return cache[posMatrixKey]; } const canonical = unwrappedTileID.canonical; const scale = this.worldSize / this.zoomScale(canonical.z); const unwrappedX = canonical.x + Math.pow(2, canonical.z) * unwrappedTileID.wrap; const posMatrix = ref_properties.identity(new Float64Array(16)); ref_properties.translate(posMatrix, posMatrix, [ unwrappedX * scale, canonical.y * scale, 0 ]); ref_properties.scale(posMatrix, posMatrix, [ scale / ref_properties.EXTENT, scale / ref_properties.EXTENT, 1 ]); ref_properties.multiply(posMatrix, aligned ? this.alignedProjMatrix : this.projMatrix, posMatrix); cache[posMatrixKey] = new Float32Array(posMatrix); return cache[posMatrixKey]; } customLayerMatrix() { return this.mercatorMatrix.slice(); } recenterOnTerrain() { if (!this._elevation) return; const elevation = this._elevation; this._updateCameraState(); const start = this._camera.position; const dir = this._camera.forward(); if (start.z <= 0 || dir[2] >= 0) return; const metersToMerc = ref_properties.mercatorZfromAltitude(1, this._center.lat); start[2] /= metersToMerc; dir[2] /= metersToMerc; ref_properties.normalize(dir, dir); const t = elevation.raycast(start, dir, elevation.exaggeration()); if (t) { const point = ref_properties.scaleAndAdd([], start, dir, t); const newCenter = new ref_properties.MercatorCoordinate(point[0], point[1], ref_properties.mercatorZfromAltitude(point[2], ref_properties.latFromMercatorY(point[1]))); const pos = this._camera.position; const camToNew = [ newCenter.x - pos[0], newCenter.y - pos[1], newCenter.z - pos[2] ]; const maxAltitude = newCenter.z + ref_properties.length(camToNew); this._cameraZoom = this._zoomFromMercatorZ(maxAltitude); this._centerAltitude = newCenter.toAltitude(); this._center = newCenter.toLngLat(); this._updateZoomFromElevation(); this._constrain(); this._calcMatrices(); } } _constrainCameraAltitude() { if (!this._elevation) return; const elevation = this._elevation; this._updateCameraState(); const elevationAtCamera = elevation.getAtPoint(this._camera.mercatorPosition); const minHeight = this._minimumHeightOverTerrain() * Math.cos(ref_properties.degToRad(this._maxPitch)); const terrainElevation = ref_properties.mercatorZfromAltitude(elevationAtCamera, this._center.lat); const cameraHeight = this._camera.position[2] - terrainElevation; if (cameraHeight < minHeight) { const center = ref_properties.MercatorCoordinate.fromLngLat(this._center, this._centerAltitude); const cameraPos = this._camera.mercatorPosition; const cameraToCenter = [ center.x - cameraPos.x, center.y - cameraPos.y, center.z - cameraPos.z ]; const prevDistToCamera = ref_properties.length(cameraToCenter); cameraToCenter[2] -= minHeight - cameraHeight; const newDistToCamera = ref_properties.length(cameraToCenter); if (newDistToCamera === 0) return; ref_properties.scale$2(cameraToCenter, cameraToCenter, prevDistToCamera / newDistToCamera); this._camera.position = [ center.x - cameraToCenter[0], center.y - cameraToCenter[1], center.z - cameraToCenter[2] ]; this._camera.orientation = orientationFromFrame(cameraToCenter, this._camera.up()); this._updateStateFromCamera(); } } _constrain() { if (!this.center || !this.width || !this.height || this._constraining) return; this._constraining = true; let minY = -90; let maxY = 90; let minX = -180; let maxX = 180; let sy, sx, x2, y2; const size = this.size, unmodified = this._unmodified; if (this.latRange) { const latRange = this.latRange; minY = ref_properties.mercatorYfromLat(latRange[1]) * this.worldSize; maxY = ref_properties.mercatorYfromLat(latRange[0]) * this.worldSize; sy = maxY - minY < size.y ? size.y / (maxY - minY) : 0; } if (this.lngRange) { const lngRange = this.lngRange; minX = ref_properties.mercatorXfromLng(lngRange[0]) * this.worldSize; maxX = ref_properties.mercatorXfromLng(lngRange[1]) * this.worldSize; sx = maxX - minX < size.x ? size.x / (maxX - minX) : 0; } const point = this.point; const s = Math.max(sx || 0, sy || 0); if (s) { this.center = this.unproject(new ref_properties.Point(sx ? (maxX + minX) / 2 : point.x, sy ? (maxY + minY) / 2 : point.y)); this.zoom += this.scaleZoom(s); this._unmodified = unmodified; this._constraining = false; return; } if (this.latRange) { const y = point.y, h2 = size.y / 2; if (y - h2 < minY) y2 = minY + h2; if (y + h2 > maxY) y2 = maxY - h2; } if (this.lngRange) { const x = point.x, w2 = size.x / 2; if (x - w2 < minX) x2 = minX + w2; if (x + w2 > maxX) x2 = maxX - w2; } if (x2 !== undefined || y2 !== undefined) { this.center = this.unproject(new ref_properties.Point(x2 !== undefined ? x2 : point.x, y2 !== undefined ? y2 : point.y)); } this._constrainCameraAltitude(); this._unmodified = unmodified; this._constraining = false; } _minZoomForBounds() { const minZoomForDim = (dim, range) => { return Math.log2(dim / (this.tileSize * Math.abs(range[1] - range[0]))); }; let minLatZoom = DEFAULT_MIN_ZOOM; if (this.latRange) { const latRange = this.latRange; minLatZoom = minZoomForDim(this.height, [ ref_properties.mercatorYfromLat(latRange[0]), ref_properties.mercatorYfromLat(latRange[1]) ]); } let minLngZoom = DEFAULT_MIN_ZOOM; if (this.lngRange) { const lngRange = this.lngRange; minLngZoom = minZoomForDim(this.width, [ ref_properties.mercatorXfromLng(lngRange[0]), ref_properties.mercatorXfromLng(lngRange[1]) ]); } return Math.max(minLatZoom, minLngZoom); } _maxCameraBoundsDistance() { return this._mercatorZfromZoom(this._minZoomForBounds()); } _calcMatrices() { if (!this.height) return; const halfFov = this._fov / 2; const offset = this.centerOffset; this.cameraToCenterDistance = 0.5 / Math.tan(halfFov) * this.height; const pixelsPerMeter = ref_properties.mercatorZfromAltitude(1, this.center.lat) * this.worldSize; this._updateCameraState(); const groundAngle = Math.PI / 2 + this._pitch; const fovAboveCenter = this.fovAboveCenter; const cameraToSeaLevelDistance = this._camera.position[2] * this.worldSize / Math.cos(this._pitch); const topHalfSurfaceDistance = Math.sin(fovAboveCenter) * cameraToSeaLevelDistance / Math.sin(ref_properties.clamp(Math.PI - groundAngle - fovAboveCenter, 0.01, Math.PI - 0.01)); const point = this.point; const x = point.x, y = point.y; const furthestDistance = Math.cos(Math.PI / 2 - this._pitch) * topHalfSurfaceDistance + cameraToSeaLevelDistance; const horizonDistance = cameraToSeaLevelDistance * (1 / this._horizonShift); const farZ = Math.min(furthestDistance * 1.01, horizonDistance); const nearZ = this.height / 50; const worldToCamera = this._camera.getWorldToCamera(this.worldSize, pixelsPerMeter); const cameraToClip = this._camera.getCameraToClipPerspective(this._fov, this.width / this.height, nearZ, farZ); cameraToClip[8] = -offset.x * 2 / this.width; cameraToClip[9] = offset.y * 2 / this.height; let m = ref_properties.mul([], cameraToClip, worldToCamera); this.mercatorMatrix = ref_properties.scale([], m, [ this.worldSize, this.worldSize, this.worldSize / pixelsPerMeter ]); this.projMatrix = m; this.invProjMatrix = ref_properties.invert(new Float64Array(16), this.projMatrix); const view = new Float32Array(16); ref_properties.identity(view); ref_properties.scale(view, view, [ 1, -1, 1 ]); ref_properties.rotateX(view, view, this._pitch); ref_properties.rotateZ(view, view, this.angle); const projection = ref_properties.perspective(new Float32Array(16), this._fov, this.width / this.height, nearZ, farZ); const skyboxHorizonShift = (Math.PI / 2 - this._pitch) * (this.height / this._fov) * this._horizonShift; projection[8] = -offset.x * 2 / this.width; projection[9] = (offset.y + skyboxHorizonShift) * 2 / this.height; this.skyboxMatrix = ref_properties.multiply(view, projection, view); const xShift = this.width % 2 / 2, yShift = this.height % 2 / 2, angleCos = Math.cos(this.angle), angleSin = Math.sin(this.angle), dx = x - Math.round(x) + angleCos * xShift + angleSin * yShift, dy = y - Math.round(y) + angleCos * yShift + angleSin * xShift; const alignedM = new Float64Array(m); ref_properties.translate(alignedM, alignedM, [ dx > 0.5 ? dx - 1 : dx, dy > 0.5 ? dy - 1 : dy, 0 ]); this.alignedProjMatrix = alignedM; m = ref_properties.create(); ref_properties.scale(m, m, [ this.width / 2, -this.height / 2, 1 ]); ref_properties.translate(m, m, [ 1, -1, 0 ]); this.labelPlaneMatrix = m; m = ref_properties.create(); ref_properties.scale(m, m, [ 1, -1, 1 ]); ref_properties.translate(m, m, [ -1, -1, 0 ]); ref_properties.scale(m, m, [ 2 / this.width, 2 / this.height, 1 ]); this.glCoordMatrix = m; this.pixelMatrix = ref_properties.multiply(new Float64Array(16), this.labelPlaneMatrix, this.projMatrix); m = ref_properties.invert(new Float64Array(16), this.pixelMatrix); if (!m) throw new Error('failed to invert matrix'); this.pixelMatrixInverse = m; this._posMatrixCache = {}; this._alignedPosMatrixCache = {}; } _updateCameraState() { if (!this.height) return; this._camera.setPitchBearing(this._pitch, this.angle); const dir = this._camera.forward(); const distance = this.cameraToCenterDistance; const center = this.point; const zoom = this._cameraZoom ? this._cameraZoom : this._zoom; const altitude = this._mercatorZfromZoom(zoom); const height = altitude - ref_properties.mercatorZfromAltitude(this._centerAltitude, this.center.lat); const updatedWorldSize = this.cameraToCenterDistance / height; this._camera.position = [ center.x / this.worldSize - dir[0] * distance / updatedWorldSize, center.y / this.worldSize - dir[1] * distance / updatedWorldSize, ref_properties.mercatorZfromAltitude(this._centerAltitude, this._center.lat) + -dir[2] * distance / updatedWorldSize ]; } _translateCameraConstrained(translation) { const maxDistance = this._maxCameraBoundsDistance(); const maxZ = maxDistance * Math.cos(this._pitch); const z = this._camera.position[2]; const deltaZ = translation[2]; let t = 1; if (deltaZ > 0) { t = Math.min((maxZ - z) / deltaZ, 1); } this._camera.position = ref_properties.scaleAndAdd([], this._camera.position, translation, t); this._updateStateFromCamera(); } _updateStateFromCamera() { const position = this._camera.position; const dir = this._camera.forward(); const {pitch, bearing} = this._camera.getPitchBearing(); const centerAltitude = ref_properties.mercatorZfromAltitude(this._centerAltitude, this.center.lat); const minHeight = this._mercatorZfromZoom(this._maxZoom) * Math.cos(ref_properties.degToRad(this._maxPitch)); const height = Math.max((position[2] - centerAltitude) / Math.cos(pitch), minHeight); const zoom = this._zoomFromMercatorZ(height); ref_properties.scaleAndAdd(position, position, dir, height); this._pitch = ref_properties.clamp(pitch, ref_properties.degToRad(this.minPitch), ref_properties.degToRad(this.maxPitch)); this.angle = ref_properties.wrap(bearing, -Math.PI, Math.PI); this._setZoom(ref_properties.clamp(zoom, this._minZoom, this._maxZoom)); if (this._terrainEnabled()) this._updateCameraOnTerrain(); this._center = new ref_properties.MercatorCoordinate(position[0], position[1], position[2]).toLngLat(); this._unmodified = false; this._constrain(); this._calcMatrices(); } _worldSizeFromZoom(zoom) { return Math.pow(2, zoom) * this.tileSize; } _mercatorZfromZoom(zoom) { return this.cameraToCenterDistance / this._worldSizeFromZoom(zoom); } _minimumHeightOverTerrain() { const MAX_DRAPE_OVERZOOM = 2; const zoom = Math.min((this._cameraZoom != null ? this._cameraZoom : this._zoom) + MAX_DRAPE_OVERZOOM, this._maxZoom); return this._mercatorZfromZoom(zoom); } _zoomFromMercatorZ(z) { return this.scaleZoom(this.cameraToCenterDistance / (z * this.tileSize)); } _terrainEnabled() { return !!this._elevation; } isHorizonVisibleForPoints(p0, p1) { const minX = Math.min(p0.x, p1.x); const maxX = Math.max(p0.x, p1.x); const minY = Math.min(p0.y, p1.y); const maxY = Math.max(p0.y, p1.y); const min = new ref_properties.Point(minX, minY); const max = new ref_properties.Point(maxX, maxY); const corners = [ min, max, new ref_properties.Point(minX, maxY), new ref_properties.Point(maxX, minY) ]; const minWX = this._renderWorldCopies ? -NUM_WORLD_COPIES : 0; const maxWX = this._renderWorldCopies ? 1 + NUM_WORLD_COPIES : 1; const minWY = 0; const maxWY = 1; for (const corner of corners) { const rayIntersection = this.pointRayIntersection(corner); if (rayIntersection.t < 0) { return true; } const coordinate = this.rayIntersectionCoordinate(rayIntersection); if (coordinate.x < minWX || coordinate.y < minWY || coordinate.x > maxWX || coordinate.y > maxWY) { return true; } } return false; } isHorizonVisible() { const horizonAngleEpsilon = 2; if (this.pitch + ref_properties.radToDeg(this.fovAboveCenter) > 90 - horizonAngleEpsilon) { return true; } return this.isHorizonVisibleForPoints(new ref_properties.Point(0, 0), new ref_properties.Point(this.width, this.height)); } zoomDeltaToMovement(center, zoomDelta) { const distance = ref_properties.length(ref_properties.sub([], this._camera.position, center)); const relativeZoom = this._zoomFromMercatorZ(distance) + zoomDelta; return distance - this._mercatorZfromZoom(relativeZoom); } getCameraPoint() { const pitch = this._pitch; const yOffset = Math.tan(pitch) * (this.cameraToCenterDistance || 1); return this.centerPoint.add(new ref_properties.Point(0, yOffset)); } } function throttle(fn, time) { let pending = false; let timerId = null; const later = () => { timerId = null; if (pending) { fn(); timerId = setTimeout(later, time); pending = false; } }; return () => { pending = true; if (!timerId) { later(); } return timerId; }; } class Hash { constructor(hashName) { this._hashName = hashName && encodeURIComponent(hashName); ref_properties.bindAll([ '_getCurrentHash', '_onHashChange', '_updateHash' ], this); this._updateHash = throttle(this._updateHashUnthrottled.bind(this), 30 * 1000 / 100); } addTo(map) { this._map = map; ref_properties.window.addEventListener('hashchange', this._onHashChange, false); this._map.on('moveend', this._updateHash); return this; } remove() { ref_properties.window.removeEventListener('hashchange', this._onHashChange, false); this._map.off('moveend', this._updateHash); clearTimeout(this._updateHash()); delete this._map; return this; } getHashString(mapFeedback) { const center = this._map.getCenter(), zoom = Math.round(this._map.getZoom() * 100) / 100, precision = Math.ceil((zoom * Math.LN2 + Math.log(512 / 360 / 0.5)) / Math.LN10), m = Math.pow(10, precision), lng = Math.round(center.lng * m) / m, lat = Math.round(center.lat * m) / m, bearing = this._map.getBearing(), pitch = this._map.getPitch(); let hash = ''; if (mapFeedback) { hash += `/${ lng }/${ lat }/${ zoom }`; } else { hash += `${ zoom }/${ lat }/${ lng }`; } if (bearing || pitch) hash += `/${ Math.round(bearing * 10) / 10 }`; if (pitch) hash += `/${ Math.round(pitch) }`; if (this._hashName) { const hashName = this._hashName; let found = false; const parts = ref_properties.window.location.hash.slice(1).split('&').map(part => { const key = part.split('=')[0]; if (key === hashName) { found = true; return `${ key }=${ hash }`; } return part; }).filter(a => a); if (!found) { parts.push(`${ hashName }=${ hash }`); } return `#${ parts.join('&') }`; } return `#${ hash }`; } _getCurrentHash() { const hash = ref_properties.window.location.hash.replace('#', ''); if (this._hashName) { let keyval; hash.split('&').map(part => part.split('=')).forEach(part => { if (part[0] === this._hashName) { keyval = part; } }); return (keyval ? keyval[1] || '' : '').split('/'); } return hash.split('/'); } _onHashChange() { const loc = this._getCurrentHash(); if (loc.length >= 3 && !loc.some(v => isNaN(v))) { const bearing = this._map.dragRotate.isEnabled() && this._map.touchZoomRotate.isEnabled() ? +(loc[3] || 0) : this._map.getBearing(); this._map.jumpTo({ center: [ +loc[2], +loc[1] ], zoom: +loc[0], bearing, pitch: +(loc[4] || 0) }); return true; } return false; } _updateHashUnthrottled() { const location = ref_properties.window.location.href.replace(/(#.+)?$/, this.getHashString()); ref_properties.window.history.replaceState(ref_properties.window.history.state, null, location); } } const defaultInertiaOptions = { linearity: 0.3, easing: ref_properties.bezier(0, 0, 0.3, 1) }; const defaultPanInertiaOptions = ref_properties.extend({ deceleration: 2500, maxSpeed: 1400 }, defaultInertiaOptions); const defaultZoomInertiaOptions = ref_properties.extend({ deceleration: 20, maxSpeed: 1400 }, defaultInertiaOptions); const defaultBearingInertiaOptions = ref_properties.extend({ deceleration: 1000, maxSpeed: 360 }, defaultInertiaOptions); const defaultPitchInertiaOptions = ref_properties.extend({ deceleration: 1000, maxSpeed: 90 }, defaultInertiaOptions); class HandlerInertia { constructor(map) { this._map = map; this.clear(); } clear() { this._inertiaBuffer = []; } record(settings) { this._drainInertiaBuffer(); this._inertiaBuffer.push({ time: ref_properties.browser.now(), settings }); } _drainInertiaBuffer() { const inertia = this._inertiaBuffer, now = ref_properties.browser.now(), cutoff = 160; while (inertia.length > 0 && now - inertia[0].time > cutoff) inertia.shift(); } _onMoveEnd(panInertiaOptions) { this._drainInertiaBuffer(); if (this._inertiaBuffer.length < 2) { return; } const deltas = { zoom: 0, bearing: 0, pitch: 0, pan: new ref_properties.Point(0, 0), pinchAround: undefined, around: undefined }; for (const {settings} of this._inertiaBuffer) { deltas.zoom += settings.zoomDelta || 0; deltas.bearing += settings.bearingDelta || 0; deltas.pitch += settings.pitchDelta || 0; if (settings.panDelta) deltas.pan._add(settings.panDelta); if (settings.around) deltas.around = settings.around; if (settings.pinchAround) deltas.pinchAround = settings.pinchAround; } const lastEntry = this._inertiaBuffer[this._inertiaBuffer.length - 1]; const duration = lastEntry.time - this._inertiaBuffer[0].time; const easeOptions = {}; if (deltas.pan.mag()) { const result = calculateEasing(deltas.pan.mag(), duration, ref_properties.extend({}, defaultPanInertiaOptions, panInertiaOptions || {})); easeOptions.offset = deltas.pan.mult(result.amount / deltas.pan.mag()); easeOptions.center = this._map.transform.center; extendDuration(easeOptions, result); } if (deltas.zoom) { const result = calculateEasing(deltas.zoom, duration, defaultZoomInertiaOptions); easeOptions.zoom = this._map.transform.zoom + result.amount; extendDuration(easeOptions, result); } if (deltas.bearing) { const result = calculateEasing(deltas.bearing, duration, defaultBearingInertiaOptions); easeOptions.bearing = this._map.transform.bearing + ref_properties.clamp(result.amount, -179, 179); extendDuration(easeOptions, result); } if (deltas.pitch) { const result = calculateEasing(deltas.pitch, duration, defaultPitchInertiaOptions); easeOptions.pitch = this._map.transform.pitch + result.amount; extendDuration(easeOptions, result); } if (easeOptions.zoom || easeOptions.bearing) { const last = deltas.pinchAround === undefined ? deltas.around : deltas.pinchAround; easeOptions.around = last ? this._map.unproject(last) : this._map.getCenter(); } this.clear(); return ref_properties.extend(easeOptions, { noMoveStart: true }); } } function extendDuration(easeOptions, result) { if (!easeOptions.duration || easeOptions.duration < result.duration) { easeOptions.duration = result.duration; easeOptions.easing = result.easing; } } function calculateEasing(amount, inertiaDuration, inertiaOptions) { const {maxSpeed, linearity, deceleration} = inertiaOptions; const speed = ref_properties.clamp(amount * linearity / (inertiaDuration / 1000), -maxSpeed, maxSpeed); const duration = Math.abs(speed) / (deceleration * linearity); return { easing: inertiaOptions.easing, duration: duration * 1000, amount: speed * (duration / 2) }; } class MapMouseEvent extends ref_properties.Event { preventDefault() { this._defaultPrevented = true; } get defaultPrevented() { return this._defaultPrevented; } constructor(type, map, originalEvent, data = {}) { const point = DOM.mousePos(map.getCanvasContainer(), originalEvent); const lngLat = map.unproject(point); super(type, ref_properties.extend({ point, lngLat, originalEvent }, data)); this._defaultPrevented = false; this.target = map; } } class MapTouchEvent extends ref_properties.Event { preventDefault() { this._defaultPrevented = true; } get defaultPrevented() { return this._defaultPrevented; } constructor(type, map, originalEvent) { const touches = type === 'touchend' ? originalEvent.changedTouches : originalEvent.touches; const points = DOM.touchPos(map.getCanvasContainer(), touches); const lngLats = points.map(t => map.unproject(t)); const point = points.reduce((prev, curr, i, arr) => { return prev.add(curr.div(arr.length)); }, new ref_properties.Point(0, 0)); const lngLat = map.unproject(point); super(type, { points, point, lngLats, lngLat, originalEvent }); this._defaultPrevented = false; } } class MapWheelEvent extends ref_properties.Event { preventDefault() { this._defaultPrevented = true; } get defaultPrevented() { return this._defaultPrevented; } constructor(type, map, originalEvent) { super(type, { originalEvent }); this._defaultPrevented = false; } } class MapEventHandler { constructor(map, options) { this._map = map; this._clickTolerance = options.clickTolerance; } reset() { delete this._mousedownPos; } wheel(e) { return this._firePreventable(new MapWheelEvent(e.type, this._map, e)); } mousedown(e, point) { this._mousedownPos = point; return this._firePreventable(new MapMouseEvent(e.type, this._map, e)); } mouseup(e) { this._map.fire(new MapMouseEvent(e.type, this._map, e)); } click(e, point) { if (this._mousedownPos && this._mousedownPos.dist(point) >= this._clickTolerance) return; this._map.fire(new MapMouseEvent(e.type, this._map, e)); } dblclick(e) { return this._firePreventable(new MapMouseEvent(e.type, this._map, e)); } mouseover(e) { this._map.fire(new MapMouseEvent(e.type, this._map, e)); } mouseout(e) { this._map.fire(new MapMouseEvent(e.type, this._map, e)); } touchstart(e) { return this._firePreventable(new MapTouchEvent(e.type, this._map, e)); } touchmove(e) { this._map.fire(new MapTouchEvent(e.type, this._map, e)); } touchend(e) { this._map.fire(new MapTouchEvent(e.type, this._map, e)); } touchcancel(e) { this._map.fire(new MapTouchEvent(e.type, this._map, e)); } _firePreventable(mapEvent) { this._map.fire(mapEvent); if (mapEvent.defaultPrevented) { return {}; } } isEnabled() { return true; } isActive() { return false; } enable() { } disable() { } } class BlockableMapEventHandler { constructor(map) { this._map = map; } reset() { this._delayContextMenu = false; delete this._contextMenuEvent; } mousemove(e) { this._map.fire(new MapMouseEvent(e.type, this._map, e)); } mousedown() { this._delayContextMenu = true; } mouseup() { this._delayContextMenu = false; if (this._contextMenuEvent) { this._map.fire(new MapMouseEvent('contextmenu', this._map, this._contextMenuEvent)); delete this._contextMenuEvent; } } contextmenu(e) { if (this._delayContextMenu) { this._contextMenuEvent = e; } else { this._map.fire(new MapMouseEvent(e.type, this._map, e)); } if (this._map.listens('contextmenu')) { e.preventDefault(); } } isEnabled() { return true; } isActive() { return false; } enable() { } disable() { } } class BoxZoomHandler { constructor(map, options) { this._map = map; this._el = map.getCanvasContainer(); this._container = map.getContainer(); this._clickTolerance = options.clickTolerance || 1; } isEnabled() { return !!this._enabled; } isActive() { return !!this._active; } enable() { if (this.isEnabled()) return; this._enabled = true; } disable() { if (!this.isEnabled()) return; this._enabled = false; } mousedown(e, point) { if (!this.isEnabled()) return; if (!(e.shiftKey && e.button === 0)) return; DOM.disableDrag(); this._startPos = this._lastPos = point; this._active = true; } mousemoveWindow(e, point) { if (!this._active) return; const pos = point; if (this._lastPos.equals(pos) || !this._box && pos.dist(this._startPos) < this._clickTolerance) { return; } const p0 = this._startPos; this._lastPos = pos; if (!this._box) { this._box = DOM.create('div', 'mapboxgl-boxzoom', this._container); this._container.classList.add('mapboxgl-crosshair'); this._fireEvent('boxzoomstart', e); } const minX = Math.min(p0.x, pos.x), maxX = Math.max(p0.x, pos.x), minY = Math.min(p0.y, pos.y), maxY = Math.max(p0.y, pos.y); DOM.setTransform(this._box, `translate(${ minX }px,${ minY }px)`); this._box.style.width = `${ maxX - minX }px`; this._box.style.height = `${ maxY - minY }px`; } mouseupWindow(e, point) { if (!this._active) return; if (e.button !== 0) return; const p0 = this._startPos, p1 = point; this.reset(); DOM.suppressClick(); if (p0.x === p1.x && p0.y === p1.y) { this._fireEvent('boxzoomcancel', e); } else { this._map.fire(new ref_properties.Event('boxzoomend', { originalEvent: e })); return { cameraAnimation: map => map.fitScreenCoordinates(p0, p1, this._map.getBearing(), { linear: false }) }; } } keydown(e) { if (!this._active) return; if (e.keyCode === 27) { this.reset(); this._fireEvent('boxzoomcancel', e); } } reset() { this._active = false; this._container.classList.remove('mapboxgl-crosshair'); if (this._box) { DOM.remove(this._box); this._box = null; } DOM.enableDrag(); delete this._startPos; delete this._lastPos; } _fireEvent(type, e) { return this._map.fire(new ref_properties.Event(type, { originalEvent: e })); } } function indexTouches(touches, points) { const obj = {}; for (let i = 0; i < touches.length; i++) { obj[touches[i].identifier] = points[i]; } return obj; } function getCentroid(points) { const sum = new ref_properties.Point(0, 0); for (const point of points) { sum._add(point); } return sum.div(points.length); } const MAX_TAP_INTERVAL = 500; const MAX_TOUCH_TIME = 500; const MAX_DIST = 30; class SingleTapRecognizer { constructor(options) { this.reset(); this.numTouches = options.numTouches; } reset() { delete this.centroid; delete this.startTime; delete this.touches; this.aborted = false; } touchstart(e, points, mapTouches) { if (this.centroid || mapTouches.length > this.numTouches) { this.aborted = true; } if (this.aborted) { return; } if (this.startTime === undefined) { this.startTime = e.timeStamp; } if (mapTouches.length === this.numTouches) { this.centroid = getCentroid(points); this.touches = indexTouches(mapTouches, points); } } touchmove(e, points, mapTouches) { if (this.aborted || !this.centroid) return; const newTouches = indexTouches(mapTouches, points); for (const id in this.touches) { const prevPos = this.touches[id]; const pos = newTouches[id]; if (!pos || pos.dist(prevPos) > MAX_DIST) { this.aborted = true; } } } touchend(e, points, mapTouches) { if (!this.centroid || e.timeStamp - this.startTime > MAX_TOUCH_TIME) { this.aborted = true; } if (mapTouches.length === 0) { const centroid = !this.aborted && this.centroid; this.reset(); if (centroid) return centroid; } } } class TapRecognizer { constructor(options) { this.singleTap = new SingleTapRecognizer(options); this.numTaps = options.numTaps; this.reset(); } reset() { this.lastTime = Infinity; delete this.lastTap; this.count = 0; this.singleTap.reset(); } touchstart(e, points, mapTouches) { this.singleTap.touchstart(e, points, mapTouches); } touchmove(e, points, mapTouches) { this.singleTap.touchmove(e, points, mapTouches); } touchend(e, points, mapTouches) { const tap = this.singleTap.touchend(e, points, mapTouches); if (tap) { const soonEnough = e.timeStamp - this.lastTime < MAX_TAP_INTERVAL; const closeEnough = !this.lastTap || this.lastTap.dist(tap) < MAX_DIST; if (!soonEnough || !closeEnough) { this.reset(); } this.count++; this.lastTime = e.timeStamp; this.lastTap = tap; if (this.count === this.numTaps) { this.reset(); return tap; } } } } class TapZoomHandler { constructor() { this._zoomIn = new TapRecognizer({ numTouches: 1, numTaps: 2 }); this._zoomOut = new TapRecognizer({ numTouches: 2, numTaps: 1 }); this.reset(); } reset() { this._active = false; this._zoomIn.reset(); this._zoomOut.reset(); } touchstart(e, points, mapTouches) { this._zoomIn.touchstart(e, points, mapTouches); this._zoomOut.touchstart(e, points, mapTouches); } touchmove(e, points, mapTouches) { this._zoomIn.touchmove(e, points, mapTouches); this._zoomOut.touchmove(e, points, mapTouches); } touchend(e, points, mapTouches) { const zoomInPoint = this._zoomIn.touchend(e, points, mapTouches); const zoomOutPoint = this._zoomOut.touchend(e, points, mapTouches); if (zoomInPoint) { this._active = true; e.preventDefault(); setTimeout(() => this.reset(), 0); return { cameraAnimation: map => map.easeTo({ duration: 300, zoom: map.getZoom() + 1, around: map.unproject(zoomInPoint) }, { originalEvent: e }) }; } else if (zoomOutPoint) { this._active = true; e.preventDefault(); setTimeout(() => this.reset(), 0); return { cameraAnimation: map => map.easeTo({ duration: 300, zoom: map.getZoom() - 1, around: map.unproject(zoomOutPoint) }, { originalEvent: e }) }; } } touchcancel() { this.reset(); } enable() { this._enabled = true; } disable() { this._enabled = false; this.reset(); } isEnabled() { return this._enabled; } isActive() { return this._active; } } const LEFT_BUTTON = 0; const RIGHT_BUTTON = 2; const BUTTONS_FLAGS = { [LEFT_BUTTON]: 1, [RIGHT_BUTTON]: 2 }; function buttonStillPressed(e, button) { const flag = BUTTONS_FLAGS[button]; return e.buttons === undefined || (e.buttons & flag) !== flag; } class MouseHandler { constructor(options) { this.reset(); this._clickTolerance = options.clickTolerance || 1; } reset() { this._active = false; this._moved = false; delete this._lastPoint; delete this._eventButton; } _correctButton(e, button) { return false; } _move(lastPoint, point) { return {}; } mousedown(e, point) { if (this._lastPoint) return; const eventButton = DOM.mouseButton(e); if (!this._correctButton(e, eventButton)) return; this._lastPoint = point; this._eventButton = eventButton; } mousemoveWindow(e, point) { const lastPoint = this._lastPoint; if (!lastPoint) return; e.preventDefault(); if (buttonStillPressed(e, this._eventButton)) { this.reset(); return; } if (!this._moved && point.dist(lastPoint) < this._clickTolerance) return; this._moved = true; this._lastPoint = point; return this._move(lastPoint, point); } mouseupWindow(e) { if (!this._lastPoint) return; const eventButton = DOM.mouseButton(e); if (eventButton !== this._eventButton) return; if (this._moved) DOM.suppressClick(); this.reset(); } enable() { this._enabled = true; } disable() { this._enabled = false; this.reset(); } isEnabled() { return this._enabled; } isActive() { return this._active; } } class MousePanHandler extends MouseHandler { mousedown(e, point) { super.mousedown(e, point); if (this._lastPoint) this._active = true; } _correctButton(e, button) { return button === LEFT_BUTTON && !e.ctrlKey; } _move(lastPoint, point) { return { around: point, panDelta: point.sub(lastPoint) }; } } class MouseRotateHandler extends MouseHandler { _correctButton(e, button) { return button === LEFT_BUTTON && e.ctrlKey || button === RIGHT_BUTTON; } _move(lastPoint, point) { const degreesPerPixelMoved = 0.8; const bearingDelta = (point.x - lastPoint.x) * degreesPerPixelMoved; if (bearingDelta) { this._active = true; return { bearingDelta }; } } contextmenu(e) { e.preventDefault(); } } class MousePitchHandler extends MouseHandler { _correctButton(e, button) { return button === LEFT_BUTTON && e.ctrlKey || button === RIGHT_BUTTON; } _move(lastPoint, point) { const degreesPerPixelMoved = -0.5; const pitchDelta = (point.y - lastPoint.y) * degreesPerPixelMoved; if (pitchDelta) { this._active = true; return { pitchDelta }; } } contextmenu(e) { e.preventDefault(); } } class TouchPanHandler { constructor(options) { this._minTouches = 1; this._clickTolerance = options.clickTolerance || 1; this.reset(); } reset() { this._active = false; this._touches = {}; this._sum = new ref_properties.Point(0, 0); } touchstart(e, points, mapTouches) { return this._calculateTransform(e, points, mapTouches); } touchmove(e, points, mapTouches) { if (!this._active || mapTouches.length < this._minTouches) return; e.preventDefault(); return this._calculateTransform(e, points, mapTouches); } touchend(e, points, mapTouches) { this._calculateTransform(e, points, mapTouches); if (this._active && mapTouches.length < this._minTouches) { this.reset(); } } touchcancel() { this.reset(); } _calculateTransform(e, points, mapTouches) { if (mapTouches.length > 0) this._active = true; const touches = indexTouches(mapTouches, points); const touchPointSum = new ref_properties.Point(0, 0); const touchDeltaSum = new ref_properties.Point(0, 0); let touchDeltaCount = 0; for (const identifier in touches) { const point = touches[identifier]; const prevPoint = this._touches[identifier]; if (prevPoint) { touchPointSum._add(point); touchDeltaSum._add(point.sub(prevPoint)); touchDeltaCount++; touches[identifier] = point; } } this._touches = touches; if (touchDeltaCount < this._minTouches || !touchDeltaSum.mag()) return; const panDelta = touchDeltaSum.div(touchDeltaCount); this._sum._add(panDelta); if (this._sum.mag() < this._clickTolerance) return; const around = touchPointSum.div(touchDeltaCount); return { around, panDelta }; } enable() { this._enabled = true; } disable() { this._enabled = false; this.reset(); } isEnabled() { return this._enabled; } isActive() { return this._active; } } class TwoTouchHandler { constructor() { this.reset(); } reset() { this._active = false; delete this._firstTwoTouches; } _start(points) { } _move(points, pinchAround, e) { return {}; } touchstart(e, points, mapTouches) { if (this._firstTwoTouches || mapTouches.length < 2) return; this._firstTwoTouches = [ mapTouches[0].identifier, mapTouches[1].identifier ]; this._start([ points[0], points[1] ]); } touchmove(e, points, mapTouches) { if (!this._firstTwoTouches) return; e.preventDefault(); const [idA, idB] = this._firstTwoTouches; const a = getTouchById(mapTouches, points, idA); const b = getTouchById(mapTouches, points, idB); if (!a || !b) return; const pinchAround = this._aroundCenter ? null : a.add(b).div(2); return this._move([ a, b ], pinchAround, e); } touchend(e, points, mapTouches) { if (!this._firstTwoTouches) return; const [idA, idB] = this._firstTwoTouches; const a = getTouchById(mapTouches, points, idA); const b = getTouchById(mapTouches, points, idB); if (a && b) return; if (this._active) DOM.suppressClick(); this.reset(); } touchcancel() { this.reset(); } enable(options) { this._enabled = true; this._aroundCenter = !!options && options.around === 'center'; } disable() { this._enabled = false; this.reset(); } isEnabled() { return this._enabled; } isActive() { return this._active; } } function getTouchById(mapTouches, points, identifier) { for (let i = 0; i < mapTouches.length; i++) { if (mapTouches[i].identifier === identifier) return points[i]; } } const ZOOM_THRESHOLD = 0.1; function getZoomDelta(distance, lastDistance) { return Math.log(distance / lastDistance) / Math.LN2; } class TouchZoomHandler extends TwoTouchHandler { reset() { super.reset(); delete this._distance; delete this._startDistance; } _start(points) { this._startDistance = this._distance = points[0].dist(points[1]); } _move(points, pinchAround) { const lastDistance = this._distance; this._distance = points[0].dist(points[1]); if (!this._active && Math.abs(getZoomDelta(this._distance, this._startDistance)) < ZOOM_THRESHOLD) return; this._active = true; return { zoomDelta: getZoomDelta(this._distance, lastDistance), pinchAround }; } } const ROTATION_THRESHOLD = 25; function getBearingDelta(a, b) { return a.angleWith(b) * 180 / Math.PI; } class TouchRotateHandler extends TwoTouchHandler { reset() { super.reset(); delete this._minDiameter; delete this._startVector; delete this._vector; } _start(points) { this._startVector = this._vector = points[0].sub(points[1]); this._minDiameter = points[0].dist(points[1]); } _move(points, pinchAround) { const lastVector = this._vector; this._vector = points[0].sub(points[1]); if (!this._active && this._isBelowThreshold(this._vector)) return; this._active = true; return { bearingDelta: getBearingDelta(this._vector, lastVector), pinchAround }; } _isBelowThreshold(vector) { this._minDiameter = Math.min(this._minDiameter, vector.mag()); const circumference = Math.PI * this._minDiameter; const threshold = ROTATION_THRESHOLD / circumference * 360; const bearingDeltaSinceStart = getBearingDelta(vector, this._startVector); return Math.abs(bearingDeltaSinceStart) < threshold; } } function isVertical(vector) { return Math.abs(vector.y) > Math.abs(vector.x); } const ALLOWED_SINGLE_TOUCH_TIME = 100; class TouchPitchHandler extends TwoTouchHandler { reset() { super.reset(); this._valid = undefined; delete this._firstMove; delete this._lastPoints; } _start(points) { this._lastPoints = points; if (isVertical(points[0].sub(points[1]))) { this._valid = false; } } _move(points, center, e) { const vectorA = points[0].sub(this._lastPoints[0]); const vectorB = points[1].sub(this._lastPoints[1]); this._valid = this.gestureBeginsVertically(vectorA, vectorB, e.timeStamp); if (!this._valid) return; this._lastPoints = points; this._active = true; const yDeltaAverage = (vectorA.y + vectorB.y) / 2; const degreesPerPixelMoved = -0.5; return { pitchDelta: yDeltaAverage * degreesPerPixelMoved }; } gestureBeginsVertically(vectorA, vectorB, timeStamp) { if (this._valid !== undefined) return this._valid; const threshold = 2; const movedA = vectorA.mag() >= threshold; const movedB = vectorB.mag() >= threshold; if (!movedA && !movedB) return; if (!movedA || !movedB) { if (this._firstMove === undefined) { this._firstMove = timeStamp; } if (timeStamp - this._firstMove < ALLOWED_SINGLE_TOUCH_TIME) { return undefined; } else { return false; } } const isSameDirection = vectorA.y > 0 === vectorB.y > 0; return isVertical(vectorA) && isVertical(vectorB) && isSameDirection; } } const defaultOptions = { panStep: 100, bearingStep: 15, pitchStep: 10 }; class KeyboardHandler { constructor() { const stepOptions = defaultOptions; this._panStep = stepOptions.panStep; this._bearingStep = stepOptions.bearingStep; this._pitchStep = stepOptions.pitchStep; this._rotationDisabled = false; } reset() { this._active = false; } keydown(e) { if (e.altKey || e.ctrlKey || e.metaKey) return; let zoomDir = 0; let bearingDir = 0; let pitchDir = 0; let xDir = 0; let yDir = 0; switch (e.keyCode) { case 61: case 107: case 171: case 187: zoomDir = 1; break; case 189: case 109: case 173: zoomDir = -1; break; case 37: if (e.shiftKey) { bearingDir = -1; } else { e.preventDefault(); xDir = -1; } break; case 39: if (e.shiftKey) { bearingDir = 1; } else { e.preventDefault(); xDir = 1; } break; case 38: if (e.shiftKey) { pitchDir = 1; } else { e.preventDefault(); yDir = -1; } break; case 40: if (e.shiftKey) { pitchDir = -1; } else { e.preventDefault(); yDir = 1; } break; default: return; } if (this._rotationDisabled) { bearingDir = 0; pitchDir = 0; } return { cameraAnimation: map => { const zoom = map.getZoom(); map.easeTo({ duration: 300, easeId: 'keyboardHandler', easing: easeOut, zoom: zoomDir ? Math.round(zoom) + zoomDir * (e.shiftKey ? 2 : 1) : zoom, bearing: map.getBearing() + bearingDir * this._bearingStep, pitch: map.getPitch() + pitchDir * this._pitchStep, offset: [ -xDir * this._panStep, -yDir * this._panStep ], center: map.getCenter() }, { originalEvent: e }); } }; } enable() { this._enabled = true; } disable() { this._enabled = false; this.reset(); } isEnabled() { return this._enabled; } isActive() { return this._active; } disableRotation() { this._rotationDisabled = true; } enableRotation() { this._rotationDisabled = false; } } function easeOut(t) { return t * (2 - t); } const wheelZoomDelta = 4.000244140625; const defaultZoomRate = 1 / 100; const wheelZoomRate = 1 / 450; const maxScalePerFrame = 2; class ScrollZoomHandler { constructor(map, handler) { this._map = map; this._el = map.getCanvasContainer(); this._handler = handler; this._delta = 0; this._defaultZoomRate = defaultZoomRate; this._wheelZoomRate = wheelZoomRate; ref_properties.bindAll(['_onTimeout'], this); } setZoomRate(zoomRate) { this._defaultZoomRate = zoomRate; } setWheelZoomRate(wheelZoomRate) { this._wheelZoomRate = wheelZoomRate; } isEnabled() { return !!this._enabled; } isActive() { return !!this._active || this._finishTimeout !== undefined; } isZooming() { return !!this._zooming; } enable(options) { if (this.isEnabled()) return; this._enabled = true; this._aroundCenter = options && options.around === 'center'; } disable() { if (!this.isEnabled()) return; this._enabled = false; } wheel(e) { if (!this.isEnabled()) return; let value = e.deltaMode === ref_properties.window.WheelEvent.DOM_DELTA_LINE ? e.deltaY * 40 : e.deltaY; const now = ref_properties.browser.now(), timeDelta = now - (this._lastWheelEventTime || 0); this._lastWheelEventTime = now; if (value !== 0 && value % wheelZoomDelta === 0) { this._type = 'wheel'; } else if (value !== 0 && Math.abs(value) < 4) { this._type = 'trackpad'; } else if (timeDelta > 400) { this._type = null; this._lastValue = value; this._timeout = setTimeout(this._onTimeout, 40, e); } else if (!this._type) { this._type = Math.abs(timeDelta * value) < 200 ? 'trackpad' : 'wheel'; if (this._timeout) { clearTimeout(this._timeout); this._timeout = null; value += this._lastValue; } } if (e.shiftKey && value) value = value / 4; if (this._type) { this._lastWheelEvent = e; this._delta -= value; if (!this._active) { this._start(e); } } e.preventDefault(); } _onTimeout(initialEvent) { this._type = 'wheel'; this._delta -= this._lastValue; if (!this._active) { this._start(initialEvent); } } _start(e) { if (!this._delta) return; if (this._frameId) { this._frameId = null; } this._active = true; if (!this.isZooming()) { this._zooming = true; } if (this._finishTimeout) { clearTimeout(this._finishTimeout); delete this._finishTimeout; } const pos = DOM.mousePos(this._el, e); this._aroundPoint = this._aroundCenter ? this._map.transform.centerPoint : pos; this._aroundCoord = this._map.transform.pointCoordinate3D(this._aroundPoint); this._targetZoom = undefined; if (!this._frameId) { this._frameId = true; this._handler._triggerRenderFrame(); } } renderFrame() { if (!this._frameId) return; this._frameId = null; if (!this.isActive()) return; const tr = this._map.transform; const startingZoom = () => { return tr._terrainEnabled() ? tr.computeZoomRelativeTo(this._aroundCoord) : tr.zoom; }; if (this._delta !== 0) { const zoomRate = this._type === 'wheel' && Math.abs(this._delta) > wheelZoomDelta ? this._wheelZoomRate : this._defaultZoomRate; let scale = maxScalePerFrame / (1 + Math.exp(-Math.abs(this._delta * zoomRate))); if (this._delta < 0 && scale !== 0) { scale = 1 / scale; } const startZoom = startingZoom(); const startScale = Math.pow(2, startZoom); const fromScale = typeof this._targetZoom === 'number' ? tr.zoomScale(this._targetZoom) : startScale; this._targetZoom = Math.min(tr.maxZoom, Math.max(tr.minZoom, tr.scaleZoom(fromScale * scale))); if (this._type === 'wheel') { this._startZoom = startingZoom(); this._easing = this._smoothOutEasing(200); } this._delta = 0; } const targetZoom = typeof this._targetZoom === 'number' ? this._targetZoom : startingZoom(); const startZoom = this._startZoom; const easing = this._easing; let finished = false; let zoom; if (this._type === 'wheel' && startZoom && easing) { const t = Math.min((ref_properties.browser.now() - this._lastWheelEventTime) / 200, 1); const k = easing(t); zoom = ref_properties.number(startZoom, targetZoom, k); if (t < 1) { if (!this._frameId) { this._frameId = true; } } else { finished = true; } } else { zoom = targetZoom; finished = true; } this._active = true; if (finished) { this._active = false; this._finishTimeout = setTimeout(() => { this._zooming = false; this._handler._triggerRenderFrame(); delete this._targetZoom; delete this._finishTimeout; }, 200); } return { noInertia: true, needsRenderFrame: !finished, zoomDelta: zoom - startingZoom(), around: this._aroundPoint, aroundCoord: this._aroundCoord, originalEvent: this._lastWheelEvent }; } _smoothOutEasing(duration) { let easing = ref_properties.ease; if (this._prevEase) { const ease = this._prevEase, t = (ref_properties.browser.now() - ease.start) / ease.duration, speed = ease.easing(t + 0.01) - ease.easing(t), x = 0.27 / Math.sqrt(speed * speed + 0.0001) * 0.01, y = Math.sqrt(0.27 * 0.27 - x * x); easing = ref_properties.bezier(x, y, 0.25, 1); } this._prevEase = { start: ref_properties.browser.now(), duration, easing }; return easing; } reset() { this._active = false; } } class DoubleClickZoomHandler { constructor(clickZoom, TapZoom) { this._clickZoom = clickZoom; this._tapZoom = TapZoom; } enable() { this._clickZoom.enable(); this._tapZoom.enable(); } disable() { this._clickZoom.disable(); this._tapZoom.disable(); } isEnabled() { return this._clickZoom.isEnabled() && this._tapZoom.isEnabled(); } isActive() { return this._clickZoom.isActive() || this._tapZoom.isActive(); } } class ClickZoomHandler { constructor() { this.reset(); } reset() { this._active = false; } dblclick(e, point) { e.preventDefault(); return { cameraAnimation: map => { map.easeTo({ duration: 300, zoom: map.getZoom() + (e.shiftKey ? -1 : 1), around: map.unproject(point) }, { originalEvent: e }); } }; } enable() { this._enabled = true; } disable() { this._enabled = false; this.reset(); } isEnabled() { return this._enabled; } isActive() { return this._active; } } class TapDragZoomHandler { constructor() { this._tap = new TapRecognizer({ numTouches: 1, numTaps: 1 }); this.reset(); } reset() { this._active = false; delete this._swipePoint; delete this._swipeTouch; delete this._tapTime; this._tap.reset(); } touchstart(e, points, mapTouches) { if (this._swipePoint) return; if (this._tapTime && e.timeStamp - this._tapTime > MAX_TAP_INTERVAL) { this.reset(); } if (!this._tapTime) { this._tap.touchstart(e, points, mapTouches); } else if (mapTouches.length > 0) { this._swipePoint = points[0]; this._swipeTouch = mapTouches[0].identifier; } } touchmove(e, points, mapTouches) { if (!this._tapTime) { this._tap.touchmove(e, points, mapTouches); } else if (this._swipePoint) { if (mapTouches[0].identifier !== this._swipeTouch) { return; } const newSwipePoint = points[0]; const dist = newSwipePoint.y - this._swipePoint.y; this._swipePoint = newSwipePoint; e.preventDefault(); this._active = true; return { zoomDelta: dist / 128 }; } } touchend(e, points, mapTouches) { if (!this._tapTime) { const point = this._tap.touchend(e, points, mapTouches); if (point) { this._tapTime = e.timeStamp; } } else if (this._swipePoint) { if (mapTouches.length === 0) { this.reset(); } } } touchcancel() { this.reset(); } enable() { this._enabled = true; } disable() { this._enabled = false; this.reset(); } isEnabled() { return this._enabled; } isActive() { return this._active; } } class DragPanHandler { constructor(el, mousePan, touchPan) { this._el = el; this._mousePan = mousePan; this._touchPan = touchPan; } enable(options) { this._inertiaOptions = options || {}; this._mousePan.enable(); this._touchPan.enable(); this._el.classList.add('mapboxgl-touch-drag-pan'); } disable() { this._mousePan.disable(); this._touchPan.disable(); this._el.classList.remove('mapboxgl-touch-drag-pan'); } isEnabled() { return this._mousePan.isEnabled() && this._touchPan.isEnabled(); } isActive() { return this._mousePan.isActive() || this._touchPan.isActive(); } } class DragRotateHandler { constructor(options, mouseRotate, mousePitch) { this._pitchWithRotate = options.pitchWithRotate; this._mouseRotate = mouseRotate; this._mousePitch = mousePitch; } enable() { this._mouseRotate.enable(); if (this._pitchWithRotate) this._mousePitch.enable(); } disable() { this._mouseRotate.disable(); this._mousePitch.disable(); } isEnabled() { return this._mouseRotate.isEnabled() && (!this._pitchWithRotate || this._mousePitch.isEnabled()); } isActive() { return this._mouseRotate.isActive() || this._mousePitch.isActive(); } } class TouchZoomRotateHandler { constructor(el, touchZoom, touchRotate, tapDragZoom) { this._el = el; this._touchZoom = touchZoom; this._touchRotate = touchRotate; this._tapDragZoom = tapDragZoom; this._rotationDisabled = false; this._enabled = true; } enable(options) { this._touchZoom.enable(options); if (!this._rotationDisabled) this._touchRotate.enable(options); this._tapDragZoom.enable(); this._el.classList.add('mapboxgl-touch-zoom-rotate'); } disable() { this._touchZoom.disable(); this._touchRotate.disable(); this._tapDragZoom.disable(); this._el.classList.remove('mapboxgl-touch-zoom-rotate'); } isEnabled() { return this._touchZoom.isEnabled() && (this._rotationDisabled || this._touchRotate.isEnabled()) && this._tapDragZoom.isEnabled(); } isActive() { return this._touchZoom.isActive() || this._touchRotate.isActive() || this._tapDragZoom.isActive(); } disableRotation() { this._rotationDisabled = true; this._touchRotate.disable(); } enableRotation() { this._rotationDisabled = false; if (this._touchZoom.isEnabled()) this._touchRotate.enable(); } } const isMoving = p => p.zoom || p.drag || p.pitch || p.rotate; class RenderFrameEvent extends ref_properties.Event { } class TrackingEllipsoid { constructor() { this.constants = [ 1, 1, 0.01 ]; this.radius = 0; } setup(center, pointOnSurface) { const centerToSurface = ref_properties.sub([], pointOnSurface, center); if (centerToSurface[2] < 0) { this.radius = ref_properties.length(ref_properties.div([], centerToSurface, this.constants)); } else { this.radius = ref_properties.length([ centerToSurface[0], centerToSurface[1], 0 ]); } } projectRay(dir) { ref_properties.div(dir, dir, this.constants); ref_properties.normalize(dir, dir); ref_properties.mul$1(dir, dir, this.constants); const intersection = ref_properties.scale$2([], dir, this.radius); if (intersection[2] > 0) { const h = ref_properties.scale$2([], [ 0, 0, 1 ], ref_properties.dot(intersection, [ 0, 0, 1 ])); const r = ref_properties.scale$2([], ref_properties.normalize([], [ intersection[0], intersection[1], 0 ]), this.radius); const p = ref_properties.add([], intersection, ref_properties.scale$2([], ref_properties.sub([], ref_properties.add([], r, h), intersection), 2)); intersection[0] = p[0]; intersection[1] = p[1]; } return intersection; } } function hasChange(result) { return result.panDelta && result.panDelta.mag() || result.zoomDelta || result.bearingDelta || result.pitchDelta; } class HandlerManager { constructor(map, options) { this._map = map; this._el = this._map.getCanvasContainer(); this._handlers = []; this._handlersById = {}; this._changes = []; this._inertia = new HandlerInertia(map); this._bearingSnap = options.bearingSnap; this._previousActiveHandlers = {}; this._trackingEllipsoid = new TrackingEllipsoid(); this._dragOrigin = null; this._eventsInProgress = {}; this._addDefaultHandlers(options); ref_properties.bindAll([ 'handleEvent', 'handleWindowEvent' ], this); const el = this._el; this._listeners = [ [ el, 'touchstart', { passive: true } ], [ el, 'touchmove', { passive: false } ], [ el, 'touchend', undefined ], [ el, 'touchcancel', undefined ], [ el, 'mousedown', undefined ], [ el, 'mousemove', undefined ], [ el, 'mouseup', undefined ], [ ref_properties.window.document, 'mousemove', { capture: true } ], [ ref_properties.window.document, 'mouseup', undefined ], [ el, 'mouseover', undefined ], [ el, 'mouseout', undefined ], [ el, 'dblclick', undefined ], [ el, 'click', undefined ], [ el, 'keydown', { capture: false } ], [ el, 'keyup', undefined ], [ el, 'wheel', { passive: false } ], [ el, 'contextmenu', undefined ], [ ref_properties.window, 'blur', undefined ] ]; for (const [target, type, listenerOptions] of this._listeners) { DOM.addEventListener(target, type, target === ref_properties.window.document ? this.handleWindowEvent : this.handleEvent, listenerOptions); } } destroy() { for (const [target, type, listenerOptions] of this._listeners) { DOM.removeEventListener(target, type, target === ref_properties.window.document ? this.handleWindowEvent : this.handleEvent, listenerOptions); } } _addDefaultHandlers(options) { const map = this._map; const el = map.getCanvasContainer(); this._add('mapEvent', new MapEventHandler(map, options)); const boxZoom = map.boxZoom = new BoxZoomHandler(map, options); this._add('boxZoom', boxZoom); const tapZoom = new TapZoomHandler(); const clickZoom = new ClickZoomHandler(); map.doubleClickZoom = new DoubleClickZoomHandler(clickZoom, tapZoom); this._add('tapZoom', tapZoom); this._add('clickZoom', clickZoom); const tapDragZoom = new TapDragZoomHandler(); this._add('tapDragZoom', tapDragZoom); const touchPitch = map.touchPitch = new TouchPitchHandler(); this._add('touchPitch', touchPitch); const mouseRotate = new MouseRotateHandler(options); const mousePitch = new MousePitchHandler(options); map.dragRotate = new DragRotateHandler(options, mouseRotate, mousePitch); this._add('mouseRotate', mouseRotate, ['mousePitch']); this._add('mousePitch', mousePitch, ['mouseRotate']); const mousePan = new MousePanHandler(options); const touchPan = new TouchPanHandler(options); map.dragPan = new DragPanHandler(el, mousePan, touchPan); this._add('mousePan', mousePan); this._add('touchPan', touchPan, [ 'touchZoom', 'touchRotate' ]); const touchRotate = new TouchRotateHandler(); const touchZoom = new TouchZoomHandler(); map.touchZoomRotate = new TouchZoomRotateHandler(el, touchZoom, touchRotate, tapDragZoom); this._add('touchRotate', touchRotate, [ 'touchPan', 'touchZoom' ]); this._add('touchZoom', touchZoom, [ 'touchPan', 'touchRotate' ]); this._add('blockableMapEvent', new BlockableMapEventHandler(map)); const scrollZoom = map.scrollZoom = new ScrollZoomHandler(map, this); this._add('scrollZoom', scrollZoom, ['mousePan']); const keyboard = map.keyboard = new KeyboardHandler(); this._add('keyboard', keyboard); for (const name of [ 'boxZoom', 'doubleClickZoom', 'tapDragZoom', 'touchPitch', 'dragRotate', 'dragPan', 'touchZoomRotate', 'scrollZoom', 'keyboard' ]) { if (options.interactive && options[name]) { map[name].enable(options[name]); } } } _add(handlerName, handler, allowed) { this._handlers.push({ handlerName, handler, allowed }); this._handlersById[handlerName] = handler; } stop(allowEndAnimation) { if (this._updatingCamera) return; for (const {handler} of this._handlers) { handler.reset(); } this._inertia.clear(); this._fireEvents({}, {}, allowEndAnimation); this._changes = []; } isActive() { for (const {handler} of this._handlers) { if (handler.isActive()) return true; } return false; } isZooming() { return !!this._eventsInProgress.zoom || this._map.scrollZoom.isZooming(); } isRotating() { return !!this._eventsInProgress.rotate; } isMoving() { return Boolean(isMoving(this._eventsInProgress)) || this.isZooming(); } _blockedByActive(activeHandlers, allowed, myName) { for (const name in activeHandlers) { if (name === myName) continue; if (!allowed || allowed.indexOf(name) < 0) { return true; } } return false; } handleWindowEvent(e) { this.handleEvent(e, `${ e.type }Window`); } _getMapTouches(touches) { const mapTouches = []; for (const t of touches) { const target = t.target; if (this._el.contains(target)) { mapTouches.push(t); } } return mapTouches; } handleEvent(e, eventName) { if (e.type === 'blur') { this.stop(true); return; } this._updatingCamera = true; const inputEvent = e.type === 'renderFrame' ? undefined : e; const mergedHandlerResult = { needsRenderFrame: false }; const eventsInProgress = {}; const activeHandlers = {}; const mapTouches = e.touches ? this._getMapTouches(e.touches) : undefined; const points = mapTouches ? DOM.touchPos(this._el, mapTouches) : DOM.mousePos(this._el, e); for (const {handlerName, handler, allowed} of this._handlers) { if (!handler.isEnabled()) continue; let data; if (this._blockedByActive(activeHandlers, allowed, handlerName)) { handler.reset(); } else { if (handler[eventName || e.type]) { data = handler[eventName || e.type](e, points, mapTouches); this.mergeHandlerResult(mergedHandlerResult, eventsInProgress, data, handlerName, inputEvent); if (data && data.needsRenderFrame) { this._triggerRenderFrame(); } } } if (data || handler.isActive()) { activeHandlers[handlerName] = handler; } } const deactivatedHandlers = {}; for (const name in this._previousActiveHandlers) { if (!activeHandlers[name]) { deactivatedHandlers[name] = inputEvent; } } this._previousActiveHandlers = activeHandlers; if (Object.keys(deactivatedHandlers).length || hasChange(mergedHandlerResult)) { this._changes.push([ mergedHandlerResult, eventsInProgress, deactivatedHandlers ]); this._triggerRenderFrame(); } if (Object.keys(activeHandlers).length || hasChange(mergedHandlerResult)) { this._map._stop(true); } this._updatingCamera = false; const {cameraAnimation} = mergedHandlerResult; if (cameraAnimation) { this._inertia.clear(); this._fireEvents({}, {}, true); this._changes = []; cameraAnimation(this._map); } } mergeHandlerResult(mergedHandlerResult, eventsInProgress, handlerResult, name, e) { if (!handlerResult) return; ref_properties.extend(mergedHandlerResult, handlerResult); const eventData = { handlerName: name, originalEvent: handlerResult.originalEvent || e }; if (handlerResult.zoomDelta !== undefined) { eventsInProgress.zoom = eventData; } if (handlerResult.panDelta !== undefined) { eventsInProgress.drag = eventData; } if (handlerResult.pitchDelta !== undefined) { eventsInProgress.pitch = eventData; } if (handlerResult.bearingDelta !== undefined) { eventsInProgress.rotate = eventData; } } _applyChanges() { const combined = {}; const combinedEventsInProgress = {}; const combinedDeactivatedHandlers = {}; for (const [change, eventsInProgress, deactivatedHandlers] of this._changes) { if (change.panDelta) combined.panDelta = (combined.panDelta || new ref_properties.Point(0, 0))._add(change.panDelta); if (change.zoomDelta) combined.zoomDelta = (combined.zoomDelta || 0) + change.zoomDelta; if (change.bearingDelta) combined.bearingDelta = (combined.bearingDelta || 0) + change.bearingDelta; if (change.pitchDelta) combined.pitchDelta = (combined.pitchDelta || 0) + change.pitchDelta; if (change.around !== undefined) combined.around = change.around; if (change.aroundCoord !== undefined) combined.aroundCoord = change.aroundCoord; if (change.pinchAround !== undefined) combined.pinchAround = change.pinchAround; if (change.noInertia) combined.noInertia = change.noInertia; ref_properties.extend(combinedEventsInProgress, eventsInProgress); ref_properties.extend(combinedDeactivatedHandlers, deactivatedHandlers); } this._updateMapTransform(combined, combinedEventsInProgress, combinedDeactivatedHandlers); this._changes = []; } _updateMapTransform(combinedResult, combinedEventsInProgress, deactivatedHandlers) { const map = this._map; const tr = map.transform; const eventStarted = type => { const newEvent = combinedEventsInProgress[type]; return newEvent && !this._eventsInProgress[type]; }; const eventEnded = type => { const event = this._eventsInProgress[type]; return event && !this._handlersById[event.handlerName].isActive(); }; const toVec3 = p => [ p.x, p.y, p.z ]; if (eventEnded('drag') && !hasChange(combinedResult)) { const preZoom = tr.zoom; tr.cameraElevationReference = 'sea'; tr.recenterOnTerrain(); tr.cameraElevationReference = 'ground'; if (preZoom !== tr.zoom) this._map._update(true); } if (!hasChange(combinedResult)) { return this._fireEvents(combinedEventsInProgress, deactivatedHandlers, true); } let {panDelta, zoomDelta, bearingDelta, pitchDelta, around, aroundCoord, pinchAround} = combinedResult; if (pinchAround !== undefined) { around = pinchAround; } if (eventStarted('drag') && around) { this._dragOrigin = toVec3(tr.pointCoordinate3D(around)); this._trackingEllipsoid.setup(tr._camera.position, this._dragOrigin); } tr.cameraElevationReference = 'sea'; map._stop(true); around = around || map.transform.centerPoint; if (bearingDelta) tr.bearing += bearingDelta; if (pitchDelta) tr.pitch += pitchDelta; tr._updateCameraState(); const panVec = [ 0, 0, 0 ]; if (panDelta) { const startRay = tr.screenPointToMercatorRay(around); const endRay = tr.screenPointToMercatorRay(around.sub(panDelta)); const startPoint = this._trackingEllipsoid.projectRay(startRay.dir); const endPoint = this._trackingEllipsoid.projectRay(endRay.dir); panVec[0] = endPoint[0] - startPoint[0]; panVec[1] = endPoint[1] - startPoint[1]; } const originalZoom = tr.zoom; const zoomVec = [ 0, 0, 0 ]; if (zoomDelta) { const pickedPosition = aroundCoord ? toVec3(aroundCoord) : toVec3(tr.pointCoordinate3D(around)); const aroundRay = { dir: ref_properties.normalize([], ref_properties.sub([], pickedPosition, tr._camera.position)) }; const centerRay = tr.screenPointToMercatorRay(tr.centerPoint); if (aroundRay.dir[2] < 0) { const pickedAltitude = ref_properties.altitudeFromMercatorZ(pickedPosition[2], pickedPosition[1]); const centerOnTargetPlane = tr.rayIntersectionCoordinate(tr.pointRayIntersection(tr.centerPoint, pickedAltitude)); const movement = tr.zoomDeltaToMovement(toVec3(centerOnTargetPlane), zoomDelta) * (centerRay.dir[2] / aroundRay.dir[2]); ref_properties.scale$2(zoomVec, aroundRay.dir, movement); } else if (tr._terrainEnabled()) { const movement = tr.zoomDeltaToMovement(pickedPosition, zoomDelta); ref_properties.scale$2(zoomVec, aroundRay.dir, movement); } } const translation = ref_properties.add(panVec, panVec, zoomVec); tr._translateCameraConstrained(translation); if (zoomDelta && Math.abs(tr.zoom - originalZoom) > 0.0001) { tr.recenterOnTerrain(); } tr.cameraElevationReference = 'ground'; this._map._update(); if (!combinedResult.noInertia) this._inertia.record(combinedResult); this._fireEvents(combinedEventsInProgress, deactivatedHandlers, true); } _fireEvents(newEventsInProgress, deactivatedHandlers, allowEndAnimation) { const wasMoving = isMoving(this._eventsInProgress); const nowMoving = isMoving(newEventsInProgress); const startEvents = {}; for (const eventName in newEventsInProgress) { const {originalEvent} = newEventsInProgress[eventName]; if (!this._eventsInProgress[eventName]) { startEvents[`${ eventName }start`] = originalEvent; } this._eventsInProgress[eventName] = newEventsInProgress[eventName]; } if (!wasMoving && nowMoving) { this._fireEvent('movestart', nowMoving.originalEvent); } for (const name in startEvents) { this._fireEvent(name, startEvents[name]); } if (nowMoving) { this._fireEvent('move', nowMoving.originalEvent); } for (const eventName in newEventsInProgress) { const {originalEvent} = newEventsInProgress[eventName]; this._fireEvent(eventName, originalEvent); } const endEvents = {}; let originalEndEvent; for (const eventName in this._eventsInProgress) { const {handlerName, originalEvent} = this._eventsInProgress[eventName]; if (!this._handlersById[handlerName].isActive()) { delete this._eventsInProgress[eventName]; originalEndEvent = deactivatedHandlers[handlerName] || originalEvent; endEvents[`${ eventName }end`] = originalEndEvent; } } for (const name in endEvents) { this._fireEvent(name, endEvents[name]); } const stillMoving = isMoving(this._eventsInProgress); if (allowEndAnimation && (wasMoving || nowMoving) && !stillMoving) { this._updatingCamera = true; const inertialEase = this._inertia._onMoveEnd(this._map.dragPan._inertiaOptions); const shouldSnapToNorth = bearing => bearing !== 0 && -this._bearingSnap < bearing && bearing < this._bearingSnap; if (inertialEase) { if (shouldSnapToNorth(inertialEase.bearing || this._map.getBearing())) { inertialEase.bearing = 0; } this._map.easeTo(inertialEase, { originalEvent: originalEndEvent }); } else { this._map.fire(new ref_properties.Event('moveend', { originalEvent: originalEndEvent })); if (shouldSnapToNorth(this._map.getBearing())) { this._map.resetNorth(); } } this._updatingCamera = false; } } _fireEvent(type, e) { this._map.fire(new ref_properties.Event(type, e ? { originalEvent: e } : {})); } _requestFrame() { this._map.triggerRepaint(); return this._map._renderTaskQueue.add(timeStamp => { delete this._frameId; this.handleEvent(new RenderFrameEvent('renderFrame', { timeStamp })); this._applyChanges(); }); } _triggerRenderFrame() { if (this._frameId === undefined) { this._frameId = this._requestFrame(); } } } class Camera extends ref_properties.Evented { constructor(transform, options) { super(); this._moving = false; this._zooming = false; this.transform = transform; this._bearingSnap = options.bearingSnap; ref_properties.bindAll(['_renderFrameCallback'], this); } getCenter() { return new ref_properties.LngLat(this.transform.center.lng, this.transform.center.lat); } setCenter(center, eventData) { return this.jumpTo({ center }, eventData); } panBy(offset, options, eventData) { offset = ref_properties.Point.convert(offset).mult(-1); return this.panTo(this.transform.center, ref_properties.extend({ offset }, options), eventData); } panTo(lnglat, options, eventData) { return this.easeTo(ref_properties.extend({ center: lnglat }, options), eventData); } getZoom() { return this.transform.zoom; } setZoom(zoom, eventData) { this.jumpTo({ zoom }, eventData); return this; } zoomTo(zoom, options, eventData) { return this.easeTo(ref_properties.extend({ zoom }, options), eventData); } zoomIn(options, eventData) { this.zoomTo(this.getZoom() + 1, options, eventData); return this; } zoomOut(options, eventData) { this.zoomTo(this.getZoom() - 1, options, eventData); return this; } getBearing() { return this.transform.bearing; } setBearing(bearing, eventData) { this.jumpTo({ bearing }, eventData); return this; } getPadding() { return this.transform.padding; } setPadding(padding, eventData) { this.jumpTo({ padding }, eventData); return this; } rotateTo(bearing, options, eventData) { return this.easeTo(ref_properties.extend({ bearing }, options), eventData); } resetNorth(options, eventData) { this.rotateTo(0, ref_properties.extend({ duration: 1000 }, options), eventData); return this; } resetNorthPitch(options, eventData) { this.easeTo(ref_properties.extend({ bearing: 0, pitch: 0, duration: 1000 }, options), eventData); return this; } snapToNorth(options, eventData) { if (Math.abs(this.getBearing()) < this._bearingSnap) { return this.resetNorth(options, eventData); } return this; } getPitch() { return this.transform.pitch; } setPitch(pitch, eventData) { this.jumpTo({ pitch }, eventData); return this; } cameraForBounds(bounds, options) { bounds = ref_properties.LngLatBounds.convert(bounds); const bearing = options && options.bearing || 0; return this._cameraForBoxAndBearing(bounds.getNorthWest(), bounds.getSouthEast(), bearing, options); } _extendCameraOptions(options) { const defaultPadding = { top: 0, bottom: 0, right: 0, left: 0 }; options = ref_properties.extend({ padding: defaultPadding, offset: [ 0, 0 ], maxZoom: this.transform.maxZoom }, options); if (typeof options.padding === 'number') { const p = options.padding; options.padding = { top: p, bottom: p, right: p, left: p }; } options.padding = ref_properties.extend(defaultPadding, options.padding); return options; } _cameraForBoxAndBearing(p0, p1, bearing, options) { const eOptions = this._extendCameraOptions(options); const tr = this.transform; const edgePadding = tr.padding; const p0world = tr.project(ref_properties.LngLat.convert(p0)); const p1world = tr.project(ref_properties.LngLat.convert(p1)); const p0rotated = p0world.rotate(-ref_properties.degToRad(bearing)); const p1rotated = p1world.rotate(-ref_properties.degToRad(bearing)); const upperRight = new ref_properties.Point(Math.max(p0rotated.x, p1rotated.x), Math.max(p0rotated.y, p1rotated.y)); const lowerLeft = new ref_properties.Point(Math.min(p0rotated.x, p1rotated.x), Math.min(p0rotated.y, p1rotated.y)); const size = upperRight.sub(lowerLeft); const scaleX = (tr.width - (edgePadding.left + edgePadding.right + eOptions.padding.left + eOptions.padding.right)) / size.x; const scaleY = (tr.height - (edgePadding.top + edgePadding.bottom + eOptions.padding.top + eOptions.padding.bottom)) / size.y; if (scaleY < 0 || scaleX < 0) { ref_properties.warnOnce('Map cannot fit within canvas with the given bounds, padding, and/or offset.'); return; } const zoom = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), eOptions.maxZoom); const offset = typeof eOptions.offset.x === 'number' ? new ref_properties.Point(eOptions.offset.x, eOptions.offset.y) : ref_properties.Point.convert(eOptions.offset); const paddingOffsetX = (eOptions.padding.left - eOptions.padding.right) / 2; const paddingOffsetY = (eOptions.padding.top - eOptions.padding.bottom) / 2; const paddingOffset = new ref_properties.Point(paddingOffsetX, paddingOffsetY); const rotatedPaddingOffset = paddingOffset.rotate(bearing * Math.PI / 180); const offsetAtInitialZoom = offset.add(rotatedPaddingOffset); const offsetAtFinalZoom = offsetAtInitialZoom.mult(tr.scale / tr.zoomScale(zoom)); const center = tr.unproject(p0world.add(p1world).div(2).sub(offsetAtFinalZoom)); return { center, zoom, bearing }; } _cameraForBox(p0, p1, minAltitude, maxAltitude, options) { const eOptions = this._extendCameraOptions(options); minAltitude = minAltitude || 0; maxAltitude = maxAltitude || 0; p0 = ref_properties.LngLat.convert(p0); p1 = ref_properties.LngLat.convert(p1); const tr = this.transform.clone(); tr.padding = eOptions.padding; const camera = this.getFreeCameraOptions(); const focus = new ref_properties.LngLat((p0.lng + p1.lng) * 0.5, (p0.lat + p1.lat) * 0.5); const focusAltitude = (minAltitude + maxAltitude) * 0.5; if (tr._camera.position[2] < ref_properties.mercatorZfromAltitude(focusAltitude, focus.lat)) { ref_properties.warnOnce('Map cannot fit within canvas with the given bounds, padding, and/or offset.'); return; } camera.lookAtPoint(focus); tr.setFreeCameraOptions(camera); const coord0 = ref_properties.MercatorCoordinate.fromLngLat(p0); const coord1 = ref_properties.MercatorCoordinate.fromLngLat(p1); const toVec3 = p => [ p.x, p.y, p.z ]; const centerIntersectionPoint = tr.pointRayIntersection(tr.centerPoint, focusAltitude); const centerIntersectionCoord = toVec3(tr.rayIntersectionCoordinate(centerIntersectionPoint)); const centerMercatorRay = tr.screenPointToMercatorRay(tr.centerPoint); const maxMarchingSteps = 10; let steps = 0; let halfDistanceToGround; do { const z = Math.floor(tr.zoom); const z2 = 1 << z; const minX = Math.min(z2 * coord0.x, z2 * coord1.x); const minY = Math.min(z2 * coord0.y, z2 * coord1.y); const maxX = Math.max(z2 * coord0.x, z2 * coord1.x); const maxY = Math.max(z2 * coord0.y, z2 * coord1.y); const aabb = new ref_properties.Aabb([ minX, minY, minAltitude ], [ maxX, maxY, maxAltitude ]); const frustum = ref_properties.Frustum.fromInvProjectionMatrix(tr.invProjMatrix, tr.worldSize, z); if (aabb.intersects(frustum) !== 2) { if (halfDistanceToGround) { tr._camera.position = ref_properties.scaleAndAdd([], tr._camera.position, centerMercatorRay.dir, -halfDistanceToGround); tr._updateStateFromCamera(); } break; } const cameraPositionToGround = ref_properties.sub([], tr._camera.position, centerIntersectionCoord); halfDistanceToGround = 0.5 * ref_properties.length(cameraPositionToGround); tr._camera.position = ref_properties.scaleAndAdd([], tr._camera.position, centerMercatorRay.dir, halfDistanceToGround); try { tr._updateStateFromCamera(); } catch (e) { ref_properties.warnOnce('Map cannot fit within canvas with the given bounds, padding, and/or offset.'); return; } } while (++steps < maxMarchingSteps); return { center: tr.center, zoom: tr.zoom, bearing: tr.bearing, pitch: tr.pitch }; } fitBounds(bounds, options, eventData) { return this._fitInternal(this.cameraForBounds(bounds, options), options, eventData); } _raycastElevationBox(point0, point1) { const elevation = this.transform.elevation; if (!elevation) return; const point2 = new ref_properties.Point(point0.x, point1.y); const point3 = new ref_properties.Point(point1.x, point0.y); const r0 = elevation.pointCoordinate(point0); if (!r0) return; const r1 = elevation.pointCoordinate(point1); if (!r1) return; const r2 = elevation.pointCoordinate(point2); if (!r2) return; const r3 = elevation.pointCoordinate(point3); if (!r3) return; const m0 = new ref_properties.MercatorCoordinate(r0[0], r0[1]).toLngLat(); const m1 = new ref_properties.MercatorCoordinate(r1[0], r1[1]).toLngLat(); const m2 = new ref_properties.MercatorCoordinate(r2[0], r2[1]).toLngLat(); const m3 = new ref_properties.MercatorCoordinate(r3[0], r3[1]).toLngLat(); const minLng = Math.min(m0.lng, Math.min(m1.lng, Math.min(m2.lng, m3.lng))); const minLat = Math.min(m0.lat, Math.min(m1.lat, Math.min(m2.lat, m3.lat))); const maxLng = Math.max(m0.lng, Math.max(m1.lng, Math.max(m2.lng, m3.lng))); const maxLat = Math.max(m0.lat, Math.max(m1.lat, Math.max(m2.lat, m3.lat))); const minAltitude = Math.min(r0[3], Math.min(r1[3], Math.min(r2[3], r3[3]))); const maxAltitude = Math.max(r0[3], Math.max(r1[3], Math.max(r2[3], r3[3]))); const minLngLat = new ref_properties.LngLat(minLng, minLat); const maxLngLat = new ref_properties.LngLat(maxLng, maxLat); return { minLngLat, maxLngLat, minAltitude, maxAltitude }; } fitScreenCoordinates(p0, p1, bearing, options, eventData) { let lngLat0, lngLat1, minAltitude, maxAltitude; const point0 = ref_properties.Point.convert(p0); const point1 = ref_properties.Point.convert(p1); const raycast = this._raycastElevationBox(point0, point1); if (!raycast) { if (this.transform.isHorizonVisibleForPoints(point0, point1)) { return this; } lngLat0 = this.transform.pointLocation(point0); lngLat1 = this.transform.pointLocation(point1); } else { lngLat0 = raycast.minLngLat; lngLat1 = raycast.maxLngLat; minAltitude = raycast.minAltitude; maxAltitude = raycast.maxAltitude; } if (this.transform.pitch === 0) { return this._fitInternal(this._cameraForBoxAndBearing(this.transform.pointLocation(ref_properties.Point.convert(p0)), this.transform.pointLocation(ref_properties.Point.convert(p1)), bearing, options), options, eventData); } return this._fitInternal(this._cameraForBox(lngLat0, lngLat1, minAltitude, maxAltitude, options), options, eventData); } _fitInternal(calculatedOptions, options, eventData) { if (!calculatedOptions) return this; options = ref_properties.extend(calculatedOptions, options); delete options.padding; return options.linear ? this.easeTo(options, eventData) : this.flyTo(options, eventData); } jumpTo(options, eventData) { this.stop(); const tr = this.transform; let zoomChanged = false, bearingChanged = false, pitchChanged = false; if ('zoom' in options && tr.zoom !== +options.zoom) { zoomChanged = true; tr.zoom = +options.zoom; } if (options.center !== undefined) { tr.center = ref_properties.LngLat.convert(options.center); } if ('bearing' in options && tr.bearing !== +options.bearing) { bearingChanged = true; tr.bearing = +options.bearing; } if ('pitch' in options && tr.pitch !== +options.pitch) { pitchChanged = true; tr.pitch = +options.pitch; } if (options.padding != null && !tr.isPaddingEqual(options.padding)) { tr.padding = options.padding; } this.fire(new ref_properties.Event('movestart', eventData)).fire(new ref_properties.Event('move', eventData)); if (zoomChanged) { this.fire(new ref_properties.Event('zoomstart', eventData)).fire(new ref_properties.Event('zoom', eventData)).fire(new ref_properties.Event('zoomend', eventData)); } if (bearingChanged) { this.fire(new ref_properties.Event('rotatestart', eventData)).fire(new ref_properties.Event('rotate', eventData)).fire(new ref_properties.Event('rotateend', eventData)); } if (pitchChanged) { this.fire(new ref_properties.Event('pitchstart', eventData)).fire(new ref_properties.Event('pitch', eventData)).fire(new ref_properties.Event('pitchend', eventData)); } return this.fire(new ref_properties.Event('moveend', eventData)); } getFreeCameraOptions() { return this.transform.getFreeCameraOptions(); } setFreeCameraOptions(options, eventData) { this.stop(); const tr = this.transform; const prevZoom = tr.zoom; const prevPitch = tr.pitch; const prevBearing = tr.bearing; tr.setFreeCameraOptions(options); const zoomChanged = prevZoom !== tr.zoom; const pitchChanged = prevPitch !== tr.pitch; const bearingChanged = prevBearing !== tr.bearing; this.fire(new ref_properties.Event('movestart', eventData)).fire(new ref_properties.Event('move', eventData)); if (zoomChanged) { this.fire(new ref_properties.Event('zoomstart', eventData)).fire(new ref_properties.Event('zoom', eventData)).fire(new ref_properties.Event('zoomend', eventData)); } if (bearingChanged) { this.fire(new ref_properties.Event('rotatestart', eventData)).fire(new ref_properties.Event('rotate', eventData)).fire(new ref_properties.Event('rotateend', eventData)); } if (pitchChanged) { this.fire(new ref_properties.Event('pitchstart', eventData)).fire(new ref_properties.Event('pitch', eventData)).fire(new ref_properties.Event('pitchend', eventData)); } this.fire(new ref_properties.Event('moveend', eventData)); return this; } easeTo(options, eventData) { this._stop(false, options.easeId); options = ref_properties.extend({ offset: [ 0, 0 ], duration: 500, easing: ref_properties.ease }, options); if (options.animate === false || !options.essential && ref_properties.browser.prefersReducedMotion) options.duration = 0; const tr = this.transform, startZoom = this.getZoom(), startBearing = this.getBearing(), startPitch = this.getPitch(), startPadding = this.getPadding(), zoom = 'zoom' in options ? +options.zoom : startZoom, bearing = 'bearing' in options ? this._normalizeBearing(options.bearing, startBearing) : startBearing, pitch = 'pitch' in options ? +options.pitch : startPitch, padding = 'padding' in options ? options.padding : tr.padding; const offsetAsPoint = ref_properties.Point.convert(options.offset); let pointAtOffset = tr.centerPoint.add(offsetAsPoint); const locationAtOffset = tr.pointLocation(pointAtOffset); const center = ref_properties.LngLat.convert(options.center || locationAtOffset); this._normalizeCenter(center); const from = tr.project(locationAtOffset); const delta = tr.project(center).sub(from); const finalScale = tr.zoomScale(zoom - startZoom); let around, aroundPoint; if (options.around) { around = ref_properties.LngLat.convert(options.around); aroundPoint = tr.locationPoint(around); } const currently = { moving: this._moving, zooming: this._zooming, rotating: this._rotating, pitching: this._pitching }; this._zooming = this._zooming || zoom !== startZoom; this._rotating = this._rotating || startBearing !== bearing; this._pitching = this._pitching || pitch !== startPitch; this._padding = !tr.isPaddingEqual(padding); this._easeId = options.easeId; this._prepareEase(eventData, options.noMoveStart, currently); this._ease(k => { if (this._zooming) { tr.zoom = ref_properties.number(startZoom, zoom, k); } if (this._rotating) { tr.bearing = ref_properties.number(startBearing, bearing, k); } if (this._pitching) { tr.pitch = ref_properties.number(startPitch, pitch, k); } if (this._padding) { tr.interpolatePadding(startPadding, padding, k); pointAtOffset = tr.centerPoint.add(offsetAsPoint); } if (around) { tr.setLocationAtPoint(around, aroundPoint); } else { const scale = tr.zoomScale(tr.zoom - startZoom); const base = zoom > startZoom ? Math.min(2, finalScale) : Math.max(0.5, finalScale); const speedup = Math.pow(base, 1 - k); const newCenter = tr.unproject(from.add(delta.mult(k * speedup)).mult(scale)); tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, pointAtOffset); } this._fireMoveEvents(eventData); }, interruptingEaseId => { tr.recenterOnTerrain(); this._afterEase(eventData, interruptingEaseId); }, options); return this; } _prepareEase(eventData, noMoveStart, currently = {}) { this._moving = true; this.transform.cameraElevationReference = 'sea'; if (!noMoveStart && !currently.moving) { this.fire(new ref_properties.Event('movestart', eventData)); } if (this._zooming && !currently.zooming) { this.fire(new ref_properties.Event('zoomstart', eventData)); } if (this._rotating && !currently.rotating) { this.fire(new ref_properties.Event('rotatestart', eventData)); } if (this._pitching && !currently.pitching) { this.fire(new ref_properties.Event('pitchstart', eventData)); } } _fireMoveEvents(eventData) { this.fire(new ref_properties.Event('move', eventData)); if (this._zooming) { this.fire(new ref_properties.Event('zoom', eventData)); } if (this._rotating) { this.fire(new ref_properties.Event('rotate', eventData)); } if (this._pitching) { this.fire(new ref_properties.Event('pitch', eventData)); } } _afterEase(eventData, easeId) { if (this._easeId && easeId && this._easeId === easeId) { return; } delete this._easeId; this.transform.cameraElevationReference = 'ground'; const wasZooming = this._zooming; const wasRotating = this._rotating; const wasPitching = this._pitching; this._moving = false; this._zooming = false; this._rotating = false; this._pitching = false; this._padding = false; if (wasZooming) { this.fire(new ref_properties.Event('zoomend', eventData)); } if (wasRotating) { this.fire(new ref_properties.Event('rotateend', eventData)); } if (wasPitching) { this.fire(new ref_properties.Event('pitchend', eventData)); } this.fire(new ref_properties.Event('moveend', eventData)); } flyTo(options, eventData) { if (!options.essential && ref_properties.browser.prefersReducedMotion) { const coercedOptions = ref_properties.pick(options, [ 'center', 'zoom', 'bearing', 'pitch', 'around' ]); return this.jumpTo(coercedOptions, eventData); } this.stop(); options = ref_properties.extend({ offset: [ 0, 0 ], speed: 1.2, curve: 1.42, easing: ref_properties.ease }, options); const tr = this.transform, startZoom = this.getZoom(), startBearing = this.getBearing(), startPitch = this.getPitch(), startPadding = this.getPadding(); const zoom = 'zoom' in options ? ref_properties.clamp(+options.zoom, tr.minZoom, tr.maxZoom) : startZoom; const bearing = 'bearing' in options ? this._normalizeBearing(options.bearing, startBearing) : startBearing; const pitch = 'pitch' in options ? +options.pitch : startPitch; const padding = 'padding' in options ? options.padding : tr.padding; const scale = tr.zoomScale(zoom - startZoom); const offsetAsPoint = ref_properties.Point.convert(options.offset); let pointAtOffset = tr.centerPoint.add(offsetAsPoint); const locationAtOffset = tr.pointLocation(pointAtOffset); const center = ref_properties.LngLat.convert(options.center || locationAtOffset); this._normalizeCenter(center); const from = tr.project(locationAtOffset); const delta = tr.project(center).sub(from); let rho = options.curve; const w0 = Math.max(tr.width, tr.height), w1 = w0 / scale, u1 = delta.mag(); if ('minZoom' in options) { const minZoom = ref_properties.clamp(Math.min(options.minZoom, startZoom, zoom), tr.minZoom, tr.maxZoom); const wMax = w0 / tr.zoomScale(minZoom - startZoom); rho = Math.sqrt(wMax / u1 * 2); } const rho2 = rho * rho; function r(i) { const b = (w1 * w1 - w0 * w0 + (i ? -1 : 1) * rho2 * rho2 * u1 * u1) / (2 * (i ? w1 : w0) * rho2 * u1); return Math.log(Math.sqrt(b * b + 1) - b); } function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } function tanh(n) { return sinh(n) / cosh(n); } const r0 = r(0); let w = function (s) { return cosh(r0) / cosh(r0 + rho * s); }; let u = function (s) { return w0 * ((cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2) / u1; }; let S = (r(1) - r0) / rho; if (Math.abs(u1) < 0.000001 || !isFinite(S)) { if (Math.abs(w0 - w1) < 0.000001) return this.easeTo(options, eventData); const k = w1 < w0 ? -1 : 1; S = Math.abs(Math.log(w1 / w0)) / rho; u = function () { return 0; }; w = function (s) { return Math.exp(k * rho * s); }; } if ('duration' in options) { options.duration = +options.duration; } else { const V = 'screenSpeed' in options ? +options.screenSpeed / rho : +options.speed; options.duration = 1000 * S / V; } if (options.maxDuration && options.duration > options.maxDuration) { options.duration = 0; } this._zooming = true; this._rotating = startBearing !== bearing; this._pitching = pitch !== startPitch; this._padding = !tr.isPaddingEqual(padding); this._prepareEase(eventData, false); this._ease(k => { const s = k * S; const scale = 1 / w(s); tr.zoom = k === 1 ? zoom : startZoom + tr.scaleZoom(scale); if (this._rotating) { tr.bearing = ref_properties.number(startBearing, bearing, k); } if (this._pitching) { tr.pitch = ref_properties.number(startPitch, pitch, k); } if (this._padding) { tr.interpolatePadding(startPadding, padding, k); pointAtOffset = tr.centerPoint.add(offsetAsPoint); } const newCenter = k === 1 ? center : tr.unproject(from.add(delta.mult(u(s))).mult(scale)); tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, pointAtOffset); tr._updateCenterElevation(); this._fireMoveEvents(eventData); }, () => this._afterEase(eventData), options); return this; } isEasing() { return !!this._easeFrameId; } stop() { return this._stop(); } _stop(allowGestures, easeId) { if (this._easeFrameId) { this._cancelRenderFrame(this._easeFrameId); delete this._easeFrameId; delete this._onEaseFrame; } if (this._onEaseEnd) { const onEaseEnd = this._onEaseEnd; delete this._onEaseEnd; onEaseEnd.call(this, easeId); } if (!allowGestures) { const handlers = this.handlers; if (handlers) handlers.stop(false); } return this; } _ease(frame, finish, options) { if (options.animate === false || options.duration === 0) { frame(1); finish(); } else { this._easeStart = ref_properties.browser.now(); this._easeOptions = options; this._onEaseFrame = frame; this._onEaseEnd = finish; this._easeFrameId = this._requestRenderFrame(this._renderFrameCallback); } } _renderFrameCallback() { const t = Math.min((ref_properties.browser.now() - this._easeStart) / this._easeOptions.duration, 1); this._onEaseFrame(this._easeOptions.easing(t)); if (t < 1) { this._easeFrameId = this._requestRenderFrame(this._renderFrameCallback); } else { this.stop(); } } _normalizeBearing(bearing, currentBearing) { bearing = ref_properties.wrap(bearing, -180, 180); const diff = Math.abs(bearing - currentBearing); if (Math.abs(bearing - 360 - currentBearing) < diff) bearing -= 360; if (Math.abs(bearing + 360 - currentBearing) < diff) bearing += 360; return bearing; } _normalizeCenter(center) { const tr = this.transform; if (!tr.renderWorldCopies || tr.lngRange) return; const delta = center.lng - tr.center.lng; center.lng += delta > 180 ? -360 : delta < -180 ? 360 : 0; } } class AttributionControl { constructor(options = {}) { this.options = options; ref_properties.bindAll([ '_toggleAttribution', '_updateEditLink', '_updateData', '_updateCompact' ], this); } getDefaultPosition() { return 'bottom-right'; } onAdd(map) { const compact = this.options && this.options.compact; this._map = map; this._container = DOM.create('div', 'mapboxgl-ctrl mapboxgl-ctrl-attrib'); this._compactButton = DOM.create('button', 'mapboxgl-ctrl-attrib-button', this._container); this._compactButton.addEventListener('click', this._toggleAttribution); this._setElementTitle(this._compactButton, 'ToggleAttribution'); this._innerContainer = DOM.create('div', 'mapboxgl-ctrl-attrib-inner', this._container); this._innerContainer.setAttribute('role', 'list'); if (compact) { this._container.classList.add('mapboxgl-compact'); } this._updateAttributions(); this._updateEditLink(); this._map.on('styledata', this._updateData); this._map.on('sourcedata', this._updateData); this._map.on('moveend', this._updateEditLink); if (compact === undefined) { this._map.on('resize', this._updateCompact); this._updateCompact(); } return this._container; } onRemove() { DOM.remove(this._container); this._map.off('styledata', this._updateData); this._map.off('sourcedata', this._updateData); this._map.off('moveend', this._updateEditLink); this._map.off('resize', this._updateCompact); this._map = undefined; this._attribHTML = undefined; } _setElementTitle(element, title) { const str = this._map._getUIString(`AttributionControl.${ title }`); element.title = str; element.setAttribute('aria-label', str); } _toggleAttribution() { if (this._container.classList.contains('mapboxgl-compact-show')) { this._container.classList.remove('mapboxgl-compact-show'); this._compactButton.setAttribute('aria-pressed', 'false'); } else { this._container.classList.add('mapboxgl-compact-show'); this._compactButton.setAttribute('aria-pressed', 'true'); } } _updateEditLink() { let editLink = this._editLink; if (!editLink) { editLink = this._editLink = this._container.querySelector('.mapbox-improve-map'); } const params = [ { key: 'owner', value: this.styleOwner }, { key: 'id', value: this.styleId }, { key: 'access_token', value: this._map._requestManager._customAccessToken || ref_properties.config.ACCESS_TOKEN } ]; if (editLink) { const paramString = params.reduce((acc, next, i) => { if (next.value) { acc += `${ next.key }=${ next.value }${ i < params.length - 1 ? '&' : '' }`; } return acc; }, `?`); editLink.href = `${ ref_properties.config.FEEDBACK_URL }/${ paramString }${ this._map._hash ? this._map._hash.getHashString(true) : '' }`; editLink.rel = 'noopener nofollow'; this._setElementTitle(editLink, 'MapFeedback'); } } _updateData(e) { if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' || e.dataType === 'style')) { this._updateAttributions(); this._updateEditLink(); } } _updateAttributions() { if (!this._map.style) return; let attributions = []; if (this.options.customAttribution) { if (Array.isArray(this.options.customAttribution)) { attributions = attributions.concat(this.options.customAttribution.map(attribution => { if (typeof attribution !== 'string') return ''; return attribution; })); } else if (typeof this.options.customAttribution === 'string') { attributions.push(this.options.customAttribution); } } if (this._map.style.stylesheet) { const stylesheet = this._map.style.stylesheet; this.styleOwner = stylesheet.owner; this.styleId = stylesheet.id; } const sourceCaches = this._map.style._sourceCaches; for (const id in sourceCaches) { const sourceCache = sourceCaches[id]; if (sourceCache.used) { const source = sourceCache.getSource(); if (source.attribution && attributions.indexOf(source.attribution) < 0) { attributions.push(source.attribution); } } } attributions.sort((a, b) => a.length - b.length); attributions = attributions.filter((attrib, i) => { for (let j = i + 1; j < attributions.length; j++) { if (attributions[j].indexOf(attrib) >= 0) { return false; } } return true; }); const attribHTML = attributions.join(' | '); if (attribHTML === this._attribHTML) return; this._attribHTML = attribHTML; if (attributions.length) { this._innerContainer.innerHTML = attribHTML; this._container.classList.remove('mapboxgl-attrib-empty'); } else { this._container.classList.add('mapboxgl-attrib-empty'); } this._editLink = null; } _updateCompact() { if (this._map.getCanvasContainer().offsetWidth <= 640) { this._container.classList.add('mapboxgl-compact'); } else { this._container.classList.remove('mapboxgl-compact', 'mapboxgl-compact-show'); } } } class LogoControl { constructor() { ref_properties.bindAll(['_updateLogo'], this); ref_properties.bindAll(['_updateCompact'], this); } onAdd(map) { this._map = map; this._container = DOM.create('div', 'mapboxgl-ctrl'); const anchor = DOM.create('a', 'mapboxgl-ctrl-logo'); anchor.target = '_blank'; anchor.rel = 'noopener nofollow'; anchor.href = 'https://www.mapbox.com/'; anchor.setAttribute('aria-label', this._map._getUIString('LogoControl.Title')); anchor.setAttribute('rel', 'noopener nofollow'); this._container.appendChild(anchor); this._container.style.display = 'none'; this._map.on('sourcedata', this._updateLogo); this._updateLogo(); this._map.on('resize', this._updateCompact); this._updateCompact(); return this._container; } onRemove() { DOM.remove(this._container); this._map.off('sourcedata', this._updateLogo); this._map.off('resize', this._updateCompact); } getDefaultPosition() { return 'bottom-left'; } _updateLogo(e) { if (!e || e.sourceDataType === 'metadata') { this._container.style.display = this._logoRequired() ? 'block' : 'none'; } } _logoRequired() { if (!this._map.style) return; const sourceCaches = this._map.style._sourceCaches; if (Object.entries(sourceCaches).length === 0) return true; for (const id in sourceCaches) { const source = sourceCaches[id].getSource(); if (source.hasOwnProperty('mapbox_logo') && !source.mapbox_logo) { return false; } } return true; } _updateCompact() { const containerChildren = this._container.children; if (containerChildren.length) { const anchor = containerChildren[0]; if (this._map.getCanvasContainer().offsetWidth < 250) { anchor.classList.add('mapboxgl-compact'); } else { anchor.classList.remove('mapboxgl-compact'); } } } } class TaskQueue { constructor() { this._queue = []; this._id = 0; this._cleared = false; this._currentlyRunning = false; } add(callback) { const id = ++this._id; const queue = this._queue; queue.push({ callback, id, cancelled: false }); return id; } remove(id) { const running = this._currentlyRunning; const queue = running ? this._queue.concat(running) : this._queue; for (const task of queue) { if (task.id === id) { task.cancelled = true; return; } } } run(timeStamp = 0) { const queue = this._currentlyRunning = this._queue; this._queue = []; for (const task of queue) { if (task.cancelled) continue; task.callback(timeStamp); if (this._cleared) break; } this._cleared = false; this._currentlyRunning = false; } clear() { if (this._currentlyRunning) { this._cleared = true; } this._queue = []; } } const defaultLocale = { 'AttributionControl.ToggleAttribution': 'Toggle attribution', 'AttributionControl.MapFeedback': 'Map feedback', 'FullscreenControl.Enter': 'Enter fullscreen', 'FullscreenControl.Exit': 'Exit fullscreen', 'GeolocateControl.FindMyLocation': 'Find my location', 'GeolocateControl.LocationNotAvailable': 'Location not available', 'LogoControl.Title': 'Mapbox logo', 'NavigationControl.ResetBearing': 'Reset bearing to north', 'NavigationControl.ZoomIn': 'Zoom in', 'NavigationControl.ZoomOut': 'Zoom out', 'ScaleControl.Feet': 'ft', 'ScaleControl.Meters': 'm', 'ScaleControl.Kilometers': 'km', 'ScaleControl.Miles': 'mi', 'ScaleControl.NauticalMiles': 'nm' }; const {HTMLImageElement, HTMLElement, ImageBitmap} = ref_properties.window; const defaultMinZoom = -2; const defaultMaxZoom = 22; const defaultMinPitch = 0; const defaultMaxPitch = 85; const defaultOptions$1 = { center: [ 0, 0 ], zoom: 0, bearing: 0, pitch: 0, minZoom: defaultMinZoom, maxZoom: defaultMaxZoom, minPitch: defaultMinPitch, maxPitch: defaultMaxPitch, interactive: true, scrollZoom: true, boxZoom: true, dragRotate: true, dragPan: true, keyboard: true, doubleClickZoom: true, touchZoomRotate: true, touchPitch: true, bearingSnap: 7, clickTolerance: 3, pitchWithRotate: true, hash: false, attributionControl: true, failIfMajorPerformanceCaveat: false, preserveDrawingBuffer: false, trackResize: true, optimizeForTerrain: true, renderWorldCopies: true, refreshExpiredTiles: true, maxTileCacheSize: null, localIdeographFontFamily: 'sans-serif', localFontFamily: null, transformRequest: null, accessToken: null, fadeDuration: 300, crossSourceCollisions: true }; class Map extends Camera { constructor(options) { options = ref_properties.extend({}, defaultOptions$1, options); if (options.minZoom != null && options.maxZoom != null && options.minZoom > options.maxZoom) { throw new Error(`maxZoom must be greater than or equal to minZoom`); } if (options.minPitch != null && options.maxPitch != null && options.minPitch > options.maxPitch) { throw new Error(`maxPitch must be greater than or equal to minPitch`); } if (options.minPitch != null && options.minPitch < defaultMinPitch) { throw new Error(`minPitch must be greater than or equal to ${ defaultMinPitch }`); } if (options.maxPitch != null && options.maxPitch > defaultMaxPitch) { throw new Error(`maxPitch must be less than or equal to ${ defaultMaxPitch }`); } const transform = new Transform(options.minZoom, options.maxZoom, options.minPitch, options.maxPitch, options.renderWorldCopies); super(transform, options); this._interactive = options.interactive; this._maxTileCacheSize = options.maxTileCacheSize; this._failIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat; this._preserveDrawingBuffer = options.preserveDrawingBuffer; this._antialias = options.antialias; this._trackResize = options.trackResize; this._bearingSnap = options.bearingSnap; this._refreshExpiredTiles = options.refreshExpiredTiles; this._fadeDuration = options.fadeDuration; this._isInitialLoad = true; this._crossSourceCollisions = options.crossSourceCollisions; this._crossFadingFactor = 1; this._collectResourceTiming = options.collectResourceTiming; this._optimizeForTerrain = options.optimizeForTerrain; this._renderTaskQueue = new TaskQueue(); this._controls = []; this._mapId = ref_properties.uniqueId(); this._locale = ref_properties.extend({}, defaultLocale, options.locale); this._clickTolerance = options.clickTolerance; this._requestManager = new ref_properties.RequestManager(options.transformRequest, options.accessToken); if (typeof options.container === 'string') { this._container = ref_properties.window.document.getElementById(options.container); if (!this._container) { throw new Error(`Container '${ options.container }' not found.`); } } else if (options.container instanceof HTMLElement) { this._container = options.container; } else { throw new Error(`Invalid type: 'container' must be a String or HTMLElement.`); } if (options.maxBounds) { this.setMaxBounds(options.maxBounds); } ref_properties.bindAll([ '_onWindowOnline', '_onWindowResize', '_onMapScroll', '_contextLost', '_contextRestored' ], this); this._setupContainer(); this._setupPainter(); if (this.painter === undefined) { throw new Error(`Failed to initialize WebGL.`); } this.on('move', () => this._update(false)); this.on('moveend', () => this._update(false)); this.on('zoom', () => this._update(true)); if (typeof ref_properties.window !== 'undefined') { ref_properties.window.addEventListener('online', this._onWindowOnline, false); ref_properties.window.addEventListener('resize', this._onWindowResize, false); ref_properties.window.addEventListener('orientationchange', this._onWindowResize, false); } this.handlers = new HandlerManager(this, options); const hashName = typeof options.hash === 'string' && options.hash || undefined; this._hash = options.hash && new Hash(hashName).addTo(this); if (!this._hash || !this._hash._onHashChange()) { this.jumpTo({ center: options.center, zoom: options.zoom, bearing: options.bearing, pitch: options.pitch }); if (options.bounds) { this.resize(); this.fitBounds(options.bounds, ref_properties.extend({}, options.fitBoundsOptions, { duration: 0 })); } } this.resize(); this._localFontFamily = options.localFontFamily; this._localIdeographFontFamily = options.localIdeographFontFamily; if (options.style) this.setStyle(options.style, { localFontFamily: this._localFontFamily, localIdeographFontFamily: this._localIdeographFontFamily }); if (options.attributionControl) this.addControl(new AttributionControl({ customAttribution: options.customAttribution })); this._logoControl = new LogoControl(); this.addControl(this._logoControl, options.logoPosition); this.on('style.load', () => { if (this.transform.unmodified) { this.jumpTo(this.style.stylesheet); } }); this.on('data', event => { this._update(event.dataType === 'style'); this.fire(new ref_properties.Event(`${ event.dataType }data`, event)); }); this.on('dataloading', event => { this.fire(new ref_properties.Event(`${ event.dataType }dataloading`, event)); }); } _getMapId() { return this._mapId; } addControl(control, position) { if (position === undefined) { if (control.getDefaultPosition) { position = control.getDefaultPosition(); } else { position = 'top-right'; } } if (!control || !control.onAdd) { return this.fire(new ref_properties.ErrorEvent(new Error('Invalid argument to map.addControl(). Argument must be a control with onAdd and onRemove methods.'))); } const controlElement = control.onAdd(this); this._controls.push(control); const positionContainer = this._controlPositions[position]; if (position.indexOf('bottom') !== -1) { positionContainer.insertBefore(controlElement, positionContainer.firstChild); } else { positionContainer.appendChild(controlElement); } return this; } removeControl(control) { if (!control || !control.onRemove) { return this.fire(new ref_properties.ErrorEvent(new Error('Invalid argument to map.removeControl(). Argument must be a control with onAdd and onRemove methods.'))); } const ci = this._controls.indexOf(control); if (ci > -1) this._controls.splice(ci, 1); control.onRemove(this); return this; } hasControl(control) { return this._controls.indexOf(control) > -1; } resize(eventData) { const dimensions = this._containerDimensions(); const width = dimensions[0]; const height = dimensions[1]; this._resizeCanvas(width, height); this.transform.resize(width, height); this.painter.resize(width, height); const fireMoving = !this._moving; if (fireMoving) { this.stop(); this.fire(new ref_properties.Event('movestart', eventData)).fire(new ref_properties.Event('move', eventData)); } this.fire(new ref_properties.Event('resize', eventData)); if (fireMoving) this.fire(new ref_properties.Event('moveend', eventData)); return this; } getBounds() { return this.transform.getBounds(); } getMaxBounds() { return this.transform.getMaxBounds(); } setMaxBounds(bounds) { this.transform.setMaxBounds(ref_properties.LngLatBounds.convert(bounds)); return this._update(); } setMinZoom(minZoom) { minZoom = minZoom === null || minZoom === undefined ? defaultMinZoom : minZoom; if (minZoom >= defaultMinZoom && minZoom <= this.transform.maxZoom) { this.transform.minZoom = minZoom; this._update(); if (this.getZoom() < minZoom) this.setZoom(minZoom); return this; } else throw new Error(`minZoom must be between ${ defaultMinZoom } and the current maxZoom, inclusive`); } getMinZoom() { return this.transform.minZoom; } setMaxZoom(maxZoom) { maxZoom = maxZoom === null || maxZoom === undefined ? defaultMaxZoom : maxZoom; if (maxZoom >= this.transform.minZoom) { this.transform.maxZoom = maxZoom; this._update(); if (this.getZoom() > maxZoom) this.setZoom(maxZoom); return this; } else throw new Error(`maxZoom must be greater than the current minZoom`); } getMaxZoom() { return this.transform.maxZoom; } setMinPitch(minPitch) { minPitch = minPitch === null || minPitch === undefined ? defaultMinPitch : minPitch; if (minPitch < defaultMinPitch) { throw new Error(`minPitch must be greater than or equal to ${ defaultMinPitch }`); } if (minPitch >= defaultMinPitch && minPitch <= this.transform.maxPitch) { this.transform.minPitch = minPitch; this._update(); if (this.getPitch() < minPitch) this.setPitch(minPitch); return this; } else throw new Error(`minPitch must be between ${ defaultMinPitch } and the current maxPitch, inclusive`); } getMinPitch() { return this.transform.minPitch; } setMaxPitch(maxPitch) { maxPitch = maxPitch === null || maxPitch === undefined ? defaultMaxPitch : maxPitch; if (maxPitch > defaultMaxPitch) { throw new Error(`maxPitch must be less than or equal to ${ defaultMaxPitch }`); } if (maxPitch >= this.transform.minPitch) { this.transform.maxPitch = maxPitch; this._update(); if (this.getPitch() > maxPitch) this.setPitch(maxPitch); return this; } else throw new Error(`maxPitch must be greater than the current minPitch`); } getMaxPitch() { return this.transform.maxPitch; } getRenderWorldCopies() { return this.transform.renderWorldCopies; } setRenderWorldCopies(renderWorldCopies) { this.transform.renderWorldCopies = renderWorldCopies; return this._update(); } project(lnglat) { return this.transform.locationPoint3D(ref_properties.LngLat.convert(lnglat)); } unproject(point) { return this.transform.pointLocation3D(ref_properties.Point.convert(point)); } isMoving() { return this._moving || this.handlers && this.handlers.isMoving(); } isZooming() { return this._zooming || this.handlers && this.handlers.isZooming(); } isRotating() { return this._rotating || this.handlers && this.handlers.isRotating(); } _createDelegatedListener(type, layerId, listener) { if (type === 'mouseenter' || type === 'mouseover') { let mousein = false; const mousemove = e => { const features = this.getLayer(layerId) ? this.queryRenderedFeatures(e.point, { layers: [layerId] }) : []; if (!features.length) { mousein = false; } else if (!mousein) { mousein = true; listener.call(this, new MapMouseEvent(type, this, e.originalEvent, { features })); } }; const mouseout = () => { mousein = false; }; return { layer: layerId, listener, delegates: { mousemove, mouseout } }; } else if (type === 'mouseleave' || type === 'mouseout') { let mousein = false; const mousemove = e => { const features = this.getLayer(layerId) ? this.queryRenderedFeatures(e.point, { layers: [layerId] }) : []; if (features.length) { mousein = true; } else if (mousein) { mousein = false; listener.call(this, new MapMouseEvent(type, this, e.originalEvent)); } }; const mouseout = e => { if (mousein) { mousein = false; listener.call(this, new MapMouseEvent(type, this, e.originalEvent)); } }; return { layer: layerId, listener, delegates: { mousemove, mouseout } }; } else { const delegate = e => { const features = this.getLayer(layerId) ? this.queryRenderedFeatures(e.point, { layers: [layerId] }) : []; if (features.length) { e.features = features; listener.call(this, e); delete e.features; } }; return { layer: layerId, listener, delegates: { [type]: delegate } }; } } on(type, layerId, listener) { if (listener === undefined) { return super.on(type, layerId); } const delegatedListener = this._createDelegatedListener(type, layerId, listener); this._delegatedListeners = this._delegatedListeners || {}; this._delegatedListeners[type] = this._delegatedListeners[type] || []; this._delegatedListeners[type].push(delegatedListener); for (const event in delegatedListener.delegates) { this.on(event, delegatedListener.delegates[event]); } return this; } once(type, layerId, listener) { if (listener === undefined) { return super.once(type, layerId); } const delegatedListener = this._createDelegatedListener(type, layerId, listener); for (const event in delegatedListener.delegates) { this.once(event, delegatedListener.delegates[event]); } return this; } off(type, layerId, listener) { if (listener === undefined) { return super.off(type, layerId); } const removeDelegatedListener = delegatedListeners => { const listeners = delegatedListeners[type]; for (let i = 0; i < listeners.length; i++) { const delegatedListener = listeners[i]; if (delegatedListener.layer === layerId && delegatedListener.listener === listener) { for (const event in delegatedListener.delegates) { this.off(event, delegatedListener.delegates[event]); } listeners.splice(i, 1); return this; } } }; if (this._delegatedListeners && this._delegatedListeners[type]) { removeDelegatedListener(this._delegatedListeners); } return this; } queryRenderedFeatures(geometry, options) { if (!this.style) { return []; } if (options === undefined && geometry !== undefined && !(geometry instanceof ref_properties.Point) && !Array.isArray(geometry)) { options = geometry; geometry = undefined; } options = options || {}; geometry = geometry || [ [ 0, 0 ], [ this.transform.width, this.transform.height ] ]; return this.style.queryRenderedFeatures(geometry, options, this.transform); } querySourceFeatures(sourceId, parameters) { return this.style.querySourceFeatures(sourceId, parameters); } setStyle(style, options) { options = ref_properties.extend({}, { localIdeographFontFamily: this._localIdeographFontFamily, localFontFamily: this._localFontFamily }, options); if (options.diff !== false && options.localIdeographFontFamily === this._localIdeographFontFamily && options.localFontFamily === this._localFontFamily && this.style && style) { this._diffStyle(style, options); return this; } else { this._localIdeographFontFamily = options.localIdeographFontFamily; this._localFontFamily = options.localFontFamily; return this._updateStyle(style, options); } } _getUIString(key) { const str = this._locale[key]; if (str == null) { throw new Error(`Missing UI string '${ key }'`); } return str; } _updateStyle(style, options) { if (this.style) { this.style.setEventedParent(null); this.style._remove(); delete this.style; } if (style) { this.style = new Style(this, options || {}); this.style.setEventedParent(this, { style: this.style }); if (typeof style === 'string') { this.style.loadURL(style); } else { this.style.loadJSON(style); } } this._updateTerrain(); return this; } _lazyInitEmptyStyle() { if (!this.style) { this.style = new Style(this, {}); this.style.setEventedParent(this, { style: this.style }); this.style.loadEmpty(); } } _diffStyle(style, options) { if (typeof style === 'string') { const url = this._requestManager.normalizeStyleURL(style); const request = this._requestManager.transformRequest(url, ref_properties.ResourceType.Style); ref_properties.getJSON(request, (error, json) => { if (error) { this.fire(new ref_properties.ErrorEvent(error)); } else if (json) { this._updateDiff(json, options); } }); } else if (typeof style === 'object') { this._updateDiff(style, options); } } _updateDiff(style, options) { try { if (this.style.setState(style)) { this._update(true); } } catch (e) { ref_properties.warnOnce(`Unable to perform style diff: ${ e.message || e.error || e }. Rebuilding the style from scratch.`); this._updateStyle(style, options); } } getStyle() { if (this.style) { return this.style.serialize(); } } isStyleLoaded() { if (!this.style) return ref_properties.warnOnce('There is no style added to the map.'); return this.style.loaded(); } addSource(id, source) { this._lazyInitEmptyStyle(); this.style.addSource(id, source); return this._update(true); } isSourceLoaded(id) { const sourceCaches = this.style && this.style._getSourceCaches(id); if (sourceCaches.length === 0) { this.fire(new ref_properties.ErrorEvent(new Error(`There is no source with ID '${ id }'`))); return; } return sourceCaches.every(sc => sc.loaded()); } areTilesLoaded() { const sources = this.style && this.style._sourceCaches; for (const id in sources) { const source = sources[id]; const tiles = source._tiles; for (const t in tiles) { const tile = tiles[t]; if (!(tile.state === 'loaded' || tile.state === 'errored')) return false; } } return true; } addSourceType(name, SourceType, callback) { this._lazyInitEmptyStyle(); return this.style.addSourceType(name, SourceType, callback); } removeSource(id) { this.style.removeSource(id); this._updateTerrain(); return this._update(true); } getSource(id) { return this.style.getSource(id); } addImage(id, image, {pixelRatio = 1, sdf = false, stretchX, stretchY, content} = {}) { this._lazyInitEmptyStyle(); const version = 0; if (image instanceof HTMLImageElement || ImageBitmap && image instanceof ImageBitmap) { const {width, height, data} = ref_properties.browser.getImageData(image); this.style.addImage(id, { data: new ref_properties.RGBAImage({ width, height }, data), pixelRatio, stretchX, stretchY, content, sdf, version }); } else if (image.width === undefined || image.height === undefined) { return this.fire(new ref_properties.ErrorEvent(new Error('Invalid arguments to map.addImage(). The second argument must be an `HTMLImageElement`, `ImageData`, `ImageBitmap`, ' + 'or object with `width`, `height`, and `data` properties with the same format as `ImageData`'))); } else { const {width, height, data} = image; const userImage = image; this.style.addImage(id, { data: new ref_properties.RGBAImage({ width, height }, new Uint8Array(data)), pixelRatio, stretchX, stretchY, content, sdf, version, userImage }); if (userImage.onAdd) { userImage.onAdd(this, id); } } } updateImage(id, image) { const existingImage = this.style.getImage(id); if (!existingImage) { return this.fire(new ref_properties.ErrorEvent(new Error('The map has no image with that id. If you are adding a new image use `map.addImage(...)` instead.'))); } const imageData = image instanceof HTMLImageElement || ImageBitmap && image instanceof ImageBitmap ? ref_properties.browser.getImageData(image) : image; const {width, height, data} = imageData; if (width === undefined || height === undefined) { return this.fire(new ref_properties.ErrorEvent(new Error('Invalid arguments to map.updateImage(). The second argument must be an `HTMLImageElement`, `ImageData`, `ImageBitmap`, ' + 'or object with `width`, `height`, and `data` properties with the same format as `ImageData`'))); } if (width !== existingImage.data.width || height !== existingImage.data.height) { return this.fire(new ref_properties.ErrorEvent(new Error('The width and height of the updated image must be that same as the previous version of the image'))); } const copy = !(image instanceof HTMLImageElement || ImageBitmap && image instanceof ImageBitmap); existingImage.data.replace(data, copy); this.style.updateImage(id, existingImage); } hasImage(id) { if (!id) { this.fire(new ref_properties.ErrorEvent(new Error('Missing required image id'))); return false; } return !!this.style.getImage(id); } removeImage(id) { this.style.removeImage(id); } loadImage(url, callback) { ref_properties.getImage(this._requestManager.transformRequest(url, ref_properties.ResourceType.Image), callback); } listImages() { return this.style.listImages(); } addLayer(layer, beforeId) { this._lazyInitEmptyStyle(); this.style.addLayer(layer, beforeId); return this._update(true); } moveLayer(id, beforeId) { this.style.moveLayer(id, beforeId); return this._update(true); } removeLayer(id) { this.style.removeLayer(id); return this._update(true); } getLayer(id) { return this.style.getLayer(id); } setLayerZoomRange(layerId, minzoom, maxzoom) { this.style.setLayerZoomRange(layerId, minzoom, maxzoom); return this._update(true); } setFilter(layerId, filter, options = {}) { this.style.setFilter(layerId, filter, options); return this._update(true); } getFilter(layerId) { return this.style.getFilter(layerId); } setPaintProperty(layerId, name, value, options = {}) { this.style.setPaintProperty(layerId, name, value, options); return this._update(true); } getPaintProperty(layerId, name) { return this.style.getPaintProperty(layerId, name); } setLayoutProperty(layerId, name, value, options = {}) { this.style.setLayoutProperty(layerId, name, value, options); return this._update(true); } getLayoutProperty(layerId, name) { return this.style.getLayoutProperty(layerId, name); } setLight(light, options = {}) { this._lazyInitEmptyStyle(); this.style.setLight(light, options); return this._update(true); } getLight() { return this.style.getLight(); } setTerrain(terrain) { this._lazyInitEmptyStyle(); this.style.setTerrain(terrain); return this._update(true); } setFeatureState(feature, state) { this.style.setFeatureState(feature, state); return this._update(); } removeFeatureState(target, key) { this.style.removeFeatureState(target, key); return this._update(); } getFeatureState(feature) { return this.style.getFeatureState(feature); } getContainer() { return this._container; } getCanvasContainer() { return this._canvasContainer; } getCanvas() { return this._canvas; } _containerDimensions() { let width = 0; let height = 0; if (this._container) { width = this._container.clientWidth || 400; height = this._container.clientHeight || 300; } return [ width, height ]; } _detectMissingCSS() { const computedColor = ref_properties.window.getComputedStyle(this._missingCSSCanary).getPropertyValue('background-color'); if (computedColor !== 'rgb(250, 128, 114)') { ref_properties.warnOnce('This page appears to be missing CSS declarations for ' + 'Mapbox GL JS, which may cause the map to display incorrectly. ' + 'Please ensure your page includes mapbox-gl.css, as described ' + 'in https://www.mapbox.com/mapbox-gl-js/api/.'); } } _setupContainer() { const container = this._container; container.classList.add('mapboxgl-map'); const missingCSSCanary = this._missingCSSCanary = DOM.create('div', 'mapboxgl-canary', container); missingCSSCanary.style.visibility = 'hidden'; this._detectMissingCSS(); const canvasContainer = this._canvasContainer = DOM.create('div', 'mapboxgl-canvas-container', container); if (this._interactive) { canvasContainer.classList.add('mapboxgl-interactive'); } this._canvas = DOM.create('canvas', 'mapboxgl-canvas', canvasContainer); this._canvas.addEventListener('webglcontextlost', this._contextLost, false); this._canvas.addEventListener('webglcontextrestored', this._contextRestored, false); this._canvas.setAttribute('tabindex', '0'); this._canvas.setAttribute('aria-label', 'Map'); this._canvas.setAttribute('role', 'region'); const dimensions = this._containerDimensions(); this._resizeCanvas(dimensions[0], dimensions[1]); const controlContainer = this._controlContainer = DOM.create('div', 'mapboxgl-control-container', container); const positions = this._controlPositions = {}; [ 'top-left', 'top-right', 'bottom-left', 'bottom-right' ].forEach(positionName => { positions[positionName] = DOM.create('div', `mapboxgl-ctrl-${ positionName }`, controlContainer); }); this._container.addEventListener('scroll', this._onMapScroll, false); } _resizeCanvas(width, height) { const pixelRatio = ref_properties.browser.devicePixelRatio || 1; this._canvas.width = pixelRatio * width; this._canvas.height = pixelRatio * height; this._canvas.style.width = `${ width }px`; this._canvas.style.height = `${ height }px`; } _setupPainter() { const attributes = ref_properties.extend({}, supported.webGLContextAttributes, { failIfMajorPerformanceCaveat: this._failIfMajorPerformanceCaveat, preserveDrawingBuffer: this._preserveDrawingBuffer, antialias: this._antialias || false }); const gl = this._canvas.getContext('webgl', attributes) || this._canvas.getContext('experimental-webgl', attributes); if (!gl) { this.fire(new ref_properties.ErrorEvent(new Error('Failed to initialize WebGL'))); return; } this.painter = new Painter(gl, this.transform); this.on('data', event => { if (event.dataType === 'source') { this.painter.setTileLoadedFlag(true); } }); ref_properties.webpSupported.testSupport(gl); } _contextLost(event) { event.preventDefault(); if (this._frame) { this._frame.cancel(); this._frame = null; } this.fire(new ref_properties.Event('webglcontextlost', { originalEvent: event })); } _contextRestored(event) { this._setupPainter(); this.resize(); this._update(); this.fire(new ref_properties.Event('webglcontextrestored', { originalEvent: event })); } _onMapScroll(event) { if (event.target !== this._container) return; this._container.scrollTop = 0; this._container.scrollLeft = 0; return false; } loaded() { return !this._styleDirty && !this._sourcesDirty && !!this.style && this.style.loaded(); } _update(updateStyle) { if (!this.style) return this; this._styleDirty = this._styleDirty || updateStyle; this._sourcesDirty = true; this.triggerRepaint(); return this; } _requestRenderFrame(callback) { this._update(); return this._renderTaskQueue.add(callback); } _cancelRenderFrame(id) { this._renderTaskQueue.remove(id); } _render(paintStartTimeStamp) { let gpuTimer, frameStartTime = 0; const extTimerQuery = this.painter.context.extTimerQuery; if (this.listens('gpu-timing-frame')) { gpuTimer = extTimerQuery.createQueryEXT(); extTimerQuery.beginQueryEXT(extTimerQuery.TIME_ELAPSED_EXT, gpuTimer); frameStartTime = ref_properties.browser.now(); } this.painter.context.setDirty(); this.painter.setBaseState(); this._renderTaskQueue.run(paintStartTimeStamp); if (this._removed) return; let crossFading = false; const fadeDuration = this._isInitialLoad ? 0 : this._fadeDuration; if (this.style && this._styleDirty) { this._styleDirty = false; const zoom = this.transform.zoom; const now = ref_properties.browser.now(); this.style.zoomHistory.update(zoom, now); const parameters = new ref_properties.EvaluationParameters(zoom, { now, fadeDuration, zoomHistory: this.style.zoomHistory, transition: this.style.getTransition() }); const factor = parameters.crossFadingFactor(); if (factor !== 1 || factor !== this._crossFadingFactor) { crossFading = true; this._crossFadingFactor = factor; } this.style.update(parameters); } if (this.style && this._sourcesDirty) { this._sourcesDirty = false; this._updateTerrain(); this.style._updateSources(this.transform); } this._placementDirty = this.style && this.style._updatePlacement(this.painter.transform, this.showCollisionBoxes, fadeDuration, this._crossSourceCollisions); this.painter.render(this.style, { showTileBoundaries: this.showTileBoundaries, showOverdrawInspector: this._showOverdrawInspector, showQueryGeometry: !!this._showQueryGeometry, rotating: this.isRotating(), zooming: this.isZooming(), moving: this.isMoving(), fadeDuration, isInitialLoad: this._isInitialLoad, showPadding: this.showPadding, gpuTiming: !!this.listens('gpu-timing-layer'), speedIndexTiming: this.speedIndexTiming }); this.fire(new ref_properties.Event('render')); if (this.loaded() && !this._loaded) { this._loaded = true; this.fire(new ref_properties.Event('load')); } if (this.style && (this.style.hasTransitions() || crossFading)) { this._styleDirty = true; } if (this.style && !this._placementDirty) { this.style._releaseSymbolFadeTiles(); } if (this.listens('gpu-timing-frame')) { const renderCPUTime = ref_properties.browser.now() - frameStartTime; extTimerQuery.endQueryEXT(extTimerQuery.TIME_ELAPSED_EXT, gpuTimer); setTimeout(() => { const renderGPUTime = extTimerQuery.getQueryObjectEXT(gpuTimer, extTimerQuery.QUERY_RESULT_EXT) / (1000 * 1000); extTimerQuery.deleteQueryEXT(gpuTimer); this.fire(new ref_properties.Event('gpu-timing-frame', { cpuTime: renderCPUTime, gpuTime: renderGPUTime })); }, 50); } if (this.listens('gpu-timing-layer')) { const frameLayerQueries = this.painter.collectGpuTimers(); setTimeout(() => { const renderedLayerTimes = this.painter.queryGpuTimers(frameLayerQueries); this.fire(new ref_properties.Event('gpu-timing-layer', { layerTimes: renderedLayerTimes })); }, 50); } const somethingDirty = this._sourcesDirty || this._styleDirty || this._placementDirty; if (somethingDirty || this._repaint) { this.triggerRepaint(); } else { this._triggerFrame(false); if (!this.isMoving() && this.loaded()) { this.fire(new ref_properties.Event('idle')); if (this._isInitialLoad) { this._authenticate(); } this._isInitialLoad = false; if (this.speedIndexTiming) { const speedIndexNumber = this._calculateSpeedIndex(); this.fire(new ref_properties.Event('speedindexcompleted', { speedIndex: speedIndexNumber })); this.speedIndexTiming = false; } } } if (this._loaded && !this._fullyLoaded && !somethingDirty) { this._fullyLoaded = true; } return this; } _authenticate() { ref_properties.getMapSessionAPI(this._getMapId(), this._requestManager._skuToken, this._requestManager._customAccessToken, err => { if (err) { if (err.message === ref_properties.AUTH_ERR_MSG || err.status === 401) { console.error('Error: A valid Mapbox access token is required to use Mapbox GL JS. To create an account or a new access token, visit https://account.mapbox.com/'); ref_properties.browser.setErrorState(); const gl = this.painter.context.gl; if (this._logoControl instanceof LogoControl) { this._logoControl._updateLogo(); } if (gl) gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); } } }); ref_properties.postMapLoadEvent(this._getMapId(), this._requestManager._skuToken, this._requestManager._customAccessToken, () => { }); } _updateTerrain() { this.painter.updateTerrain(this.style, this.isMoving() || this.isRotating() || this.isZooming()); } _calculateSpeedIndex() { const finalFrame = this.painter.canvasCopy(); const canvasCopyInstances = this.painter.getCanvasCopiesAndTimestamps(); canvasCopyInstances.timeStamps.push(performance.now()); const gl = this.painter.context.gl; const framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); function read(texture) { gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); const pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4); gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels); return pixels; } return this._canvasPixelComparison(read(finalFrame), canvasCopyInstances.canvasCopies.map(read), canvasCopyInstances.timeStamps); } _canvasPixelComparison(finalFrame, allFrames, timeStamps) { let finalScore = timeStamps[1] - timeStamps[0]; const numPixels = finalFrame.length / 4; for (let i = 0; i < allFrames.length; i++) { const frame = allFrames[i]; let cnt = 0; for (let j = 0; j < frame.length; j += 4) { if (frame[j] === finalFrame[j] && frame[j + 1] === finalFrame[j + 1] && frame[j + 2] === finalFrame[j + 2] && frame[j + 3] === finalFrame[j + 3]) { cnt = cnt + 1; } } const interval = timeStamps[i + 2] - timeStamps[i + 1]; const visualCompletness = cnt / numPixels; finalScore += interval * (1 - visualCompletness); } return finalScore; } remove() { if (this._hash) this._hash.remove(); for (const control of this._controls) control.onRemove(this); this._controls = []; if (this._frame) { this._frame.cancel(); this._frame = null; } this._renderTaskQueue.clear(); this.painter.destroy(); this.handlers.destroy(); delete this.handlers; this.setStyle(null); if (typeof ref_properties.window !== 'undefined') { ref_properties.window.removeEventListener('resize', this._onWindowResize, false); ref_properties.window.removeEventListener('orientationchange', this._onWindowResize, false); ref_properties.window.removeEventListener('online', this._onWindowOnline, false); } const extension = this.painter.context.gl.getExtension('WEBGL_lose_context'); if (extension) extension.loseContext(); removeNode(this._canvasContainer); removeNode(this._controlContainer); removeNode(this._missingCSSCanary); this._container.classList.remove('mapboxgl-map'); this._removed = true; this.fire(new ref_properties.Event('remove')); } triggerRepaint() { this._triggerFrame(true); } _triggerFrame(render) { this._renderNextFrame = this._renderNextFrame || render; if (this.style && !this._frame) { this._frame = ref_properties.browser.frame(paintStartTimeStamp => { const isRenderFrame = !!this._renderNextFrame; this._frame = null; this._renderNextFrame = null; if (isRenderFrame) { this._render(paintStartTimeStamp); } }); } } _onWindowOnline() { this._update(); } _onWindowResize(event) { if (this._trackResize) { this.resize({ originalEvent: event })._update(); } } get showTileBoundaries() { return !!this._showTileBoundaries; } set showTileBoundaries(value) { if (this._showTileBoundaries === value) return; this._showTileBoundaries = value; this._update(); } get speedIndexTiming() { return !!this._speedIndexTiming; } set speedIndexTiming(value) { if (this._speedIndexTiming === value) return; this._speedIndexTiming = value; this._update(); } get showPadding() { return !!this._showPadding; } set showPadding(value) { if (this._showPadding === value) return; this._showPadding = value; this._update(); } get showCollisionBoxes() { return !!this._showCollisionBoxes; } set showCollisionBoxes(value) { if (this._showCollisionBoxes === value) return; this._showCollisionBoxes = value; if (value) { this.style._generateCollisionBoxes(); } else { this._update(); } } get showOverdrawInspector() { return !!this._showOverdrawInspector; } set showOverdrawInspector(value) { if (this._showOverdrawInspector === value) return; this._showOverdrawInspector = value; this._update(); } get repaint() { return !!this._repaint; } set repaint(value) { if (this._repaint !== value) { this._repaint = value; this.triggerRepaint(); } } get vertices() { return !!this._vertices; } set vertices(value) { this._vertices = value; this._update(); } _setCacheLimits(limit, checkThreshold) { ref_properties.setCacheLimits(limit, checkThreshold); } get version() { return ref_properties.version; } } function removeNode(node) { if (node.parentNode) { node.parentNode.removeChild(node); } } const defaultOptions$2 = { showCompass: true, showZoom: true, visualizePitch: false }; class NavigationControl { constructor(options) { this.options = ref_properties.extend({}, defaultOptions$2, options); this._container = DOM.create('div', 'mapboxgl-ctrl mapboxgl-ctrl-group'); this._container.addEventListener('contextmenu', e => e.preventDefault()); if (this.options.showZoom) { ref_properties.bindAll([ '_setButtonTitle', '_updateZoomButtons' ], this); this._zoomInButton = this._createButton('mapboxgl-ctrl-zoom-in', e => this._map.zoomIn({}, { originalEvent: e })); DOM.create('span', `mapboxgl-ctrl-icon`, this._zoomInButton).setAttribute('aria-hidden', true); this._zoomOutButton = this._createButton('mapboxgl-ctrl-zoom-out', e => this._map.zoomOut({}, { originalEvent: e })); DOM.create('span', `mapboxgl-ctrl-icon`, this._zoomOutButton).setAttribute('aria-hidden', true); } if (this.options.showCompass) { ref_properties.bindAll(['_rotateCompassArrow'], this); this._compass = this._createButton('mapboxgl-ctrl-compass', e => { if (this.options.visualizePitch) { this._map.resetNorthPitch({}, { originalEvent: e }); } else { this._map.resetNorth({}, { originalEvent: e }); } }); this._compassIcon = DOM.create('span', 'mapboxgl-ctrl-icon', this._compass); this._compassIcon.setAttribute('aria-hidden', true); } } _updateZoomButtons() { const zoom = this._map.getZoom(); const isMax = zoom === this._map.getMaxZoom(); const isMin = zoom === this._map.getMinZoom(); this._zoomInButton.disabled = isMax; this._zoomOutButton.disabled = isMin; this._zoomInButton.setAttribute('aria-disabled', isMax.toString()); this._zoomOutButton.setAttribute('aria-disabled', isMin.toString()); } _rotateCompassArrow() { const rotate = this.options.visualizePitch ? `scale(${ 1 / Math.pow(Math.cos(this._map.transform.pitch * (Math.PI / 180)), 0.5) }) rotateX(${ this._map.transform.pitch }deg) rotateZ(${ this._map.transform.angle * (180 / Math.PI) }deg)` : `rotate(${ this._map.transform.angle * (180 / Math.PI) }deg)`; this._compassIcon.style.transform = rotate; } onAdd(map) { this._map = map; if (this.options.showZoom) { this._setButtonTitle(this._zoomInButton, 'ZoomIn'); this._setButtonTitle(this._zoomOutButton, 'ZoomOut'); this._map.on('zoom', this._updateZoomButtons); this._updateZoomButtons(); } if (this.options.showCompass) { this._setButtonTitle(this._compass, 'ResetBearing'); if (this.options.visualizePitch) { this._map.on('pitch', this._rotateCompassArrow); } this._map.on('rotate', this._rotateCompassArrow); this._rotateCompassArrow(); this._handler = new MouseRotateWrapper(this._map, this._compass, this.options.visualizePitch); } return this._container; } onRemove() { DOM.remove(this._container); if (this.options.showZoom) { this._map.off('zoom', this._updateZoomButtons); } if (this.options.showCompass) { if (this.options.visualizePitch) { this._map.off('pitch', this._rotateCompassArrow); } this._map.off('rotate', this._rotateCompassArrow); this._handler.off(); delete this._handler; } delete this._map; } _createButton(className, fn) { const a = DOM.create('button', className, this._container); a.type = 'button'; a.addEventListener('click', fn); return a; } _setButtonTitle(button, title) { const str = this._map._getUIString(`NavigationControl.${ title }`); button.title = str; button.setAttribute('aria-label', str); } } class MouseRotateWrapper { constructor(map, element, pitch = false) { this._clickTolerance = 10; this.element = element; this.mouseRotate = new MouseRotateHandler({ clickTolerance: map.dragRotate._mouseRotate._clickTolerance }); this.map = map; if (pitch) this.mousePitch = new MousePitchHandler({ clickTolerance: map.dragRotate._mousePitch._clickTolerance }); ref_properties.bindAll([ 'mousedown', 'mousemove', 'mouseup', 'touchstart', 'touchmove', 'touchend', 'reset' ], this); DOM.addEventListener(element, 'mousedown', this.mousedown); DOM.addEventListener(element, 'touchstart', this.touchstart, { passive: false }); DOM.addEventListener(element, 'touchmove', this.touchmove); DOM.addEventListener(element, 'touchend', this.touchend); DOM.addEventListener(element, 'touchcancel', this.reset); } down(e, point) { this.mouseRotate.mousedown(e, point); if (this.mousePitch) this.mousePitch.mousedown(e, point); DOM.disableDrag(); } move(e, point) { const map = this.map; const r = this.mouseRotate.mousemoveWindow(e, point); if (r && r.bearingDelta) map.setBearing(map.getBearing() + r.bearingDelta); if (this.mousePitch) { const p = this.mousePitch.mousemoveWindow(e, point); if (p && p.pitchDelta) map.setPitch(map.getPitch() + p.pitchDelta); } } off() { const element = this.element; DOM.removeEventListener(element, 'mousedown', this.mousedown); DOM.removeEventListener(element, 'touchstart', this.touchstart, { passive: false }); DOM.removeEventListener(element, 'touchmove', this.touchmove); DOM.removeEventListener(element, 'touchend', this.touchend); DOM.removeEventListener(element, 'touchcancel', this.reset); this.offTemp(); } offTemp() { DOM.enableDrag(); DOM.removeEventListener(ref_properties.window, 'mousemove', this.mousemove); DOM.removeEventListener(ref_properties.window, 'mouseup', this.mouseup); } mousedown(e) { this.down(ref_properties.extend({}, e, { ctrlKey: true, preventDefault: () => e.preventDefault() }), DOM.mousePos(this.element, e)); DOM.addEventListener(ref_properties.window, 'mousemove', this.mousemove); DOM.addEventListener(ref_properties.window, 'mouseup', this.mouseup); } mousemove(e) { this.move(e, DOM.mousePos(this.element, e)); } mouseup(e) { this.mouseRotate.mouseupWindow(e); if (this.mousePitch) this.mousePitch.mouseupWindow(e); this.offTemp(); } touchstart(e) { if (e.targetTouches.length !== 1) { this.reset(); } else { this._startPos = this._lastPos = DOM.touchPos(this.element, e.targetTouches)[0]; this.down({ type: 'mousedown', button: 0, ctrlKey: true, preventDefault: () => e.preventDefault() }, this._startPos); } } touchmove(e) { if (e.targetTouches.length !== 1) { this.reset(); } else { this._lastPos = DOM.touchPos(this.element, e.targetTouches)[0]; this.move({ preventDefault: () => e.preventDefault() }, this._lastPos); } } touchend(e) { if (e.targetTouches.length === 0 && this._startPos && this._lastPos && this._startPos.dist(this._lastPos) < this._clickTolerance) { this.element.click(); } this.reset(); } reset() { this.mouseRotate.reset(); if (this.mousePitch) this.mousePitch.reset(); delete this._startPos; delete this._lastPos; this.offTemp(); } } function smartWrap (lngLat, priorPos, transform) { lngLat = new ref_properties.LngLat(lngLat.lng, lngLat.lat); if (priorPos) { const left = new ref_properties.LngLat(lngLat.lng - 360, lngLat.lat); const right = new ref_properties.LngLat(lngLat.lng + 360, lngLat.lat); const withinWrap = Math.ceil(Math.abs(lngLat.lng - transform.center.lng) / 360) * 360; const delta = transform.locationPoint(lngLat).distSqr(priorPos); const offscreen = priorPos.x < 0 || priorPos.y < 0 || priorPos.x > transform.width || priorPos.y > transform.height; if (transform.locationPoint(left).distSqr(priorPos) < delta && (offscreen || Math.abs(left.lng - transform.center.lng) < withinWrap)) { lngLat = left; } else if (transform.locationPoint(right).distSqr(priorPos) < delta && (offscreen || Math.abs(right.lng - transform.center.lng) < withinWrap)) { lngLat = right; } } while (Math.abs(lngLat.lng - transform.center.lng) > 180) { const pos = transform.locationPoint(lngLat); if (pos.x >= 0 && pos.y >= 0 && pos.x <= transform.width && pos.y <= transform.height) { break; } if (lngLat.lng > transform.center.lng) { lngLat.lng -= 360; } else { lngLat.lng += 360; } } return lngLat; } const anchorTranslate = { 'center': 'translate(-50%,-50%)', 'top': 'translate(-50%,0)', 'top-left': 'translate(0,0)', 'top-right': 'translate(-100%,0)', 'bottom': 'translate(-50%,-100%)', 'bottom-left': 'translate(0,-100%)', 'bottom-right': 'translate(-100%,-100%)', 'left': 'translate(0,-50%)', 'right': 'translate(-100%,-50%)' }; function applyAnchorClass(element, anchor, prefix) { const classList = element.classList; for (const key in anchorTranslate) { classList.remove(`mapboxgl-${ prefix }-anchor-${ key }`); } classList.add(`mapboxgl-${ prefix }-anchor-${ anchor }`); } class Marker extends ref_properties.Evented { constructor(options, legacyOptions) { super(); if (options instanceof ref_properties.window.HTMLElement || legacyOptions) { options = ref_properties.extend({ element: options }, legacyOptions); } ref_properties.bindAll([ '_update', '_onMove', '_onUp', '_addDragHandler', '_onMapClick', '_onKeyPress' ], this); this._anchor = options && options.anchor || 'center'; this._color = options && options.color || '#3FB1CE'; this._scale = options && options.scale || 1; this._draggable = options && options.draggable || false; this._clickTolerance = options && options.clickTolerance || 0; this._isDragging = false; this._state = 'inactive'; this._rotation = options && options.rotation || 0; this._rotationAlignment = options && options.rotationAlignment || 'auto'; this._pitchAlignment = options && options.pitchAlignment && options.pitchAlignment !== 'auto' ? options.pitchAlignment : this._rotationAlignment; if (!options || !options.element) { this._defaultMarker = true; this._element = DOM.create('div'); this._element.setAttribute('aria-label', 'Map marker'); const svg = DOM.createNS('http://www.w3.org/2000/svg', 'svg'); const defaultHeight = 41; const defaultWidth = 27; svg.setAttributeNS(null, 'display', 'block'); svg.setAttributeNS(null, 'height', `${ defaultHeight }px`); svg.setAttributeNS(null, 'width', `${ defaultWidth }px`); svg.setAttributeNS(null, 'viewBox', `0 0 ${ defaultWidth } ${ defaultHeight }`); const markerLarge = DOM.createNS('http://www.w3.org/2000/svg', 'g'); markerLarge.setAttributeNS(null, 'stroke', 'none'); markerLarge.setAttributeNS(null, 'stroke-width', '1'); markerLarge.setAttributeNS(null, 'fill', 'none'); markerLarge.setAttributeNS(null, 'fill-rule', 'evenodd'); const page1 = DOM.createNS('http://www.w3.org/2000/svg', 'g'); page1.setAttributeNS(null, 'fill-rule', 'nonzero'); const shadow = DOM.createNS('http://www.w3.org/2000/svg', 'g'); shadow.setAttributeNS(null, 'transform', 'translate(3.0, 29.0)'); shadow.setAttributeNS(null, 'fill', '#000000'); const ellipses = [ { 'rx': '10.5', 'ry': '5.25002273' }, { 'rx': '10.5', 'ry': '5.25002273' }, { 'rx': '9.5', 'ry': '4.77275007' }, { 'rx': '8.5', 'ry': '4.29549936' }, { 'rx': '7.5', 'ry': '3.81822308' }, { 'rx': '6.5', 'ry': '3.34094679' }, { 'rx': '5.5', 'ry': '2.86367051' }, { 'rx': '4.5', 'ry': '2.38636864' } ]; for (const data of ellipses) { const ellipse = DOM.createNS('http://www.w3.org/2000/svg', 'ellipse'); ellipse.setAttributeNS(null, 'opacity', '0.04'); ellipse.setAttributeNS(null, 'cx', '10.5'); ellipse.setAttributeNS(null, 'cy', '5.80029008'); ellipse.setAttributeNS(null, 'rx', data['rx']); ellipse.setAttributeNS(null, 'ry', data['ry']); shadow.appendChild(ellipse); } const background = DOM.createNS('http://www.w3.org/2000/svg', 'g'); background.setAttributeNS(null, 'fill', this._color); const bgPath = DOM.createNS('http://www.w3.org/2000/svg', 'path'); bgPath.setAttributeNS(null, 'd', 'M27,13.5 C27,19.074644 20.250001,27.000002 14.75,34.500002 C14.016665,35.500004 12.983335,35.500004 12.25,34.500002 C6.7499993,27.000002 0,19.222562 0,13.5 C0,6.0441559 6.0441559,0 13.5,0 C20.955844,0 27,6.0441559 27,13.5 Z'); background.appendChild(bgPath); const border = DOM.createNS('http://www.w3.org/2000/svg', 'g'); border.setAttributeNS(null, 'opacity', '0.25'); border.setAttributeNS(null, 'fill', '#000000'); const borderPath = DOM.createNS('http://www.w3.org/2000/svg', 'path'); borderPath.setAttributeNS(null, 'd', 'M13.5,0 C6.0441559,0 0,6.0441559 0,13.5 C0,19.222562 6.7499993,27 12.25,34.5 C13,35.522727 14.016664,35.500004 14.75,34.5 C20.250001,27 27,19.074644 27,13.5 C27,6.0441559 20.955844,0 13.5,0 Z M13.5,1 C20.415404,1 26,6.584596 26,13.5 C26,15.898657 24.495584,19.181431 22.220703,22.738281 C19.945823,26.295132 16.705119,30.142167 13.943359,33.908203 C13.743445,34.180814 13.612715,34.322738 13.5,34.441406 C13.387285,34.322738 13.256555,34.180814 13.056641,33.908203 C10.284481,30.127985 7.4148684,26.314159 5.015625,22.773438 C2.6163816,19.232715 1,15.953538 1,13.5 C1,6.584596 6.584596,1 13.5,1 Z'); border.appendChild(borderPath); const maki = DOM.createNS('http://www.w3.org/2000/svg', 'g'); maki.setAttributeNS(null, 'transform', 'translate(6.0, 7.0)'); maki.setAttributeNS(null, 'fill', '#FFFFFF'); const circleContainer = DOM.createNS('http://www.w3.org/2000/svg', 'g'); circleContainer.setAttributeNS(null, 'transform', 'translate(8.0, 8.0)'); const circle1 = DOM.createNS('http://www.w3.org/2000/svg', 'circle'); circle1.setAttributeNS(null, 'fill', '#000000'); circle1.setAttributeNS(null, 'opacity', '0.25'); circle1.setAttributeNS(null, 'cx', '5.5'); circle1.setAttributeNS(null, 'cy', '5.5'); circle1.setAttributeNS(null, 'r', '5.4999962'); const circle2 = DOM.createNS('http://www.w3.org/2000/svg', 'circle'); circle2.setAttributeNS(null, 'fill', '#FFFFFF'); circle2.setAttributeNS(null, 'cx', '5.5'); circle2.setAttributeNS(null, 'cy', '5.5'); circle2.setAttributeNS(null, 'r', '5.4999962'); circleContainer.appendChild(circle1); circleContainer.appendChild(circle2); page1.appendChild(shadow); page1.appendChild(background); page1.appendChild(border); page1.appendChild(maki); page1.appendChild(circleContainer); svg.appendChild(page1); svg.setAttributeNS(null, 'height', `${ defaultHeight * this._scale }px`); svg.setAttributeNS(null, 'width', `${ defaultWidth * this._scale }px`); this._element.appendChild(svg); this._offset = ref_properties.Point.convert(options && options.offset || [ 0, -14 ]); } else { this._element = options.element; this._offset = ref_properties.Point.convert(options && options.offset || [ 0, 0 ]); } this._element.classList.add('mapboxgl-marker'); this._element.addEventListener('dragstart', e => { e.preventDefault(); }); this._element.addEventListener('mousedown', e => { e.preventDefault(); }); applyAnchorClass(this._element, this._anchor, 'marker'); this._popup = null; } addTo(map) { this.remove(); this._map = map; map.getCanvasContainer().appendChild(this._element); map.on('move', this._update); map.on('moveend', this._update); this.setDraggable(this._draggable); this._update(); this._map.on('click', this._onMapClick); return this; } remove() { if (this._map) { this._map.off('click', this._onMapClick); this._map.off('move', this._update); this._map.off('moveend', this._update); this._map.off('mousedown', this._addDragHandler); this._map.off('touchstart', this._addDragHandler); this._map.off('mouseup', this._onUp); this._map.off('touchend', this._onUp); this._map.off('mousemove', this._onMove); this._map.off('touchmove', this._onMove); delete this._map; } DOM.remove(this._element); if (this._popup) this._popup.remove(); return this; } getLngLat() { return this._lngLat; } setLngLat(lnglat) { this._lngLat = ref_properties.LngLat.convert(lnglat); this._pos = null; if (this._popup) this._popup.setLngLat(this._lngLat); this._update(); return this; } getElement() { return this._element; } setPopup(popup) { if (this._popup) { this._popup.remove(); this._popup = null; this._element.removeEventListener('keypress', this._onKeyPress); if (!this._originalTabIndex) { this._element.removeAttribute('tabindex'); } } if (popup) { if (!('offset' in popup.options)) { const markerHeight = 41 - 5.8 / 2; const markerRadius = 13.5; const linearOffset = Math.sqrt(Math.pow(markerRadius, 2) / 2); popup.options.offset = this._defaultMarker ? { 'top': [ 0, 0 ], 'top-left': [ 0, 0 ], 'top-right': [ 0, 0 ], 'bottom': [ 0, -markerHeight ], 'bottom-left': [ linearOffset, (markerHeight - markerRadius + linearOffset) * -1 ], 'bottom-right': [ -linearOffset, (markerHeight - markerRadius + linearOffset) * -1 ], 'left': [ markerRadius, (markerHeight - markerRadius) * -1 ], 'right': [ -markerRadius, (markerHeight - markerRadius) * -1 ] } : this._offset; } this._popup = popup; if (this._lngLat) this._popup.setLngLat(this._lngLat); this._originalTabIndex = this._element.getAttribute('tabindex'); if (!this._originalTabIndex) { this._element.setAttribute('tabindex', '0'); } this._element.addEventListener('keypress', this._onKeyPress); } return this; } _onKeyPress(e) { const code = e.code; const legacyCode = e.charCode || e.keyCode; if (code === 'Space' || code === 'Enter' || legacyCode === 32 || legacyCode === 13) { this.togglePopup(); } } _onMapClick(e) { const targetElement = e.originalEvent.target; const element = this._element; if (this._popup && (targetElement === element || element.contains(targetElement))) { this.togglePopup(); } } getPopup() { return this._popup; } togglePopup() { const popup = this._popup; if (!popup) return this; else if (popup.isOpen()) popup.remove(); else popup.addTo(this._map); return this; } _updateOcclusion() { if (!this._occlusionTimer) { this._occlusionTimer = setTimeout(this._onOcclusionTimer.bind(this), 60); } } _onOcclusionTimer() { const tr = this._map.transform; const pos = this._pos ? this._pos.sub(this._transformedOffset()) : null; if (pos && pos.x >= 0 && pos.x < tr.width && pos.y >= 0 && pos.y < tr.height) { const raycastLoc = this._map.unproject(pos); const camera = this._map.getFreeCameraOptions(); if (camera.position) { const cameraPos = camera.position.toLngLat(); const raycastDistance = cameraPos.distanceTo(raycastLoc); const posDistance = cameraPos.distanceTo(this._lngLat); const occluded = raycastDistance < posDistance * 0.9; this._element.classList.toggle('mapboxgl-marker-occluded', occluded); } } this._occlusionTimer = null; } _update(e) { if (!this._map) return; if (this._map.transform.renderWorldCopies) { this._lngLat = smartWrap(this._lngLat, this._pos, this._map.transform); } this._pos = this._map.project(this._lngLat)._add(this._transformedOffset()); if (this._map.transform.elevation) this._updateOcclusion(); let rotation = ''; if (this._rotationAlignment === 'viewport' || this._rotationAlignment === 'auto') { rotation = `rotateZ(${ this._rotation }deg)`; } else if (this._rotationAlignment === 'map') { rotation = `rotateZ(${ this._rotation - this._map.getBearing() }deg)`; } let pitch = ''; if (this._pitchAlignment === 'viewport' || this._pitchAlignment === 'auto') { pitch = 'rotateX(0deg)'; } else if (this._pitchAlignment === 'map') { pitch = `rotateX(${ this._map.getPitch() }deg)`; } if (!e || e.type === 'moveend') { this._pos = this._pos.round(); } DOM.setTransform(this._element, `${ anchorTranslate[this._anchor] } translate(${ this._pos.x }px, ${ this._pos.y }px) ${ pitch } ${ rotation }`); } _transformedOffset() { if (!this._defaultMarker) return this._offset; const tr = this._map.transform; const offset = this._offset.mult(this._scale); if (this._rotationAlignment === 'map') offset._rotate(tr.angle); if (this._pitchAlignment === 'map') offset.y *= Math.cos(tr._pitch); return offset; } getOffset() { return this._offset; } setOffset(offset) { this._offset = ref_properties.Point.convert(offset); this._update(); return this; } _onMove(e) { if (!this._isDragging) { const clickTolerance = this._clickTolerance || this._map._clickTolerance; this._isDragging = e.point.dist(this._pointerdownPos) >= clickTolerance; } if (!this._isDragging) return; this._pos = e.point.sub(this._positionDelta); this._lngLat = this._map.unproject(this._pos); this.setLngLat(this._lngLat); this._element.style.pointerEvents = 'none'; if (this._state === 'pending') { this._state = 'active'; this.fire(new ref_properties.Event('dragstart')); } this.fire(new ref_properties.Event('drag')); } _onUp() { this._element.style.pointerEvents = 'auto'; this._positionDelta = null; this._pointerdownPos = null; this._isDragging = false; this._map.off('mousemove', this._onMove); this._map.off('touchmove', this._onMove); if (this._state === 'active') { this.fire(new ref_properties.Event('dragend')); } this._state = 'inactive'; } _addDragHandler(e) { if (this._element.contains(e.originalEvent.target)) { e.preventDefault(); this._positionDelta = e.point.sub(this._pos).add(this._transformedOffset()); this._pointerdownPos = e.point; this._state = 'pending'; this._map.on('mousemove', this._onMove); this._map.on('touchmove', this._onMove); this._map.once('mouseup', this._onUp); this._map.once('touchend', this._onUp); } } setDraggable(shouldBeDraggable) { this._draggable = !!shouldBeDraggable; if (this._map) { if (shouldBeDraggable) { this._map.on('mousedown', this._addDragHandler); this._map.on('touchstart', this._addDragHandler); } else { this._map.off('mousedown', this._addDragHandler); this._map.off('touchstart', this._addDragHandler); } } return this; } isDraggable() { return this._draggable; } setRotation(rotation) { this._rotation = rotation || 0; this._update(); return this; } getRotation() { return this._rotation; } setRotationAlignment(alignment) { this._rotationAlignment = alignment || 'auto'; this._update(); return this; } getRotationAlignment() { return this._rotationAlignment; } setPitchAlignment(alignment) { this._pitchAlignment = alignment && alignment !== 'auto' ? alignment : this._rotationAlignment; this._update(); return this; } getPitchAlignment() { return this._pitchAlignment; } } const defaultOptions$3 = { positionOptions: { enableHighAccuracy: false, maximumAge: 0, timeout: 6000 }, fitBoundsOptions: { maxZoom: 15 }, trackUserLocation: false, showAccuracyCircle: true, showUserLocation: true }; let supportsGeolocation; function checkGeolocationSupport(callback) { if (supportsGeolocation !== undefined) { callback(supportsGeolocation); } else if (ref_properties.window.navigator.permissions !== undefined) { ref_properties.window.navigator.permissions.query({ name: 'geolocation' }).then(p => { supportsGeolocation = p.state !== 'denied'; callback(supportsGeolocation); }); } else { supportsGeolocation = !!ref_properties.window.navigator.geolocation; callback(supportsGeolocation); } } let numberOfWatches = 0; let noTimeout = false; class GeolocateControl extends ref_properties.Evented { constructor(options) { super(); this.options = ref_properties.extend({}, defaultOptions$3, options); ref_properties.bindAll([ '_onSuccess', '_onError', '_onZoom', '_finish', '_setupUI', '_updateCamera', '_updateMarker' ], this); } onAdd(map) { this._map = map; this._container = DOM.create('div', `mapboxgl-ctrl mapboxgl-ctrl-group`); checkGeolocationSupport(this._setupUI); return this._container; } onRemove() { if (this._geolocationWatchID !== undefined) { ref_properties.window.navigator.geolocation.clearWatch(this._geolocationWatchID); this._geolocationWatchID = undefined; } if (this.options.showUserLocation && this._userLocationDotMarker) { this._userLocationDotMarker.remove(); } if (this.options.showAccuracyCircle && this._accuracyCircleMarker) { this._accuracyCircleMarker.remove(); } DOM.remove(this._container); this._map.off('zoom', this._onZoom); this._map = undefined; numberOfWatches = 0; noTimeout = false; } _isOutOfMapMaxBounds(position) { const bounds = this._map.getMaxBounds(); const coordinates = position.coords; return bounds && (coordinates.longitude < bounds.getWest() || coordinates.longitude > bounds.getEast() || coordinates.latitude < bounds.getSouth() || coordinates.latitude > bounds.getNorth()); } _setErrorState() { switch (this._watchState) { case 'WAITING_ACTIVE': this._watchState = 'ACTIVE_ERROR'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-active-error'); break; case 'ACTIVE_LOCK': this._watchState = 'ACTIVE_ERROR'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-active-error'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-waiting'); break; case 'BACKGROUND': this._watchState = 'BACKGROUND_ERROR'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-background'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-background-error'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-waiting'); break; } } _onSuccess(position) { if (!this._map) { return; } if (this._isOutOfMapMaxBounds(position)) { this._setErrorState(); this.fire(new ref_properties.Event('outofmaxbounds', position)); this._updateMarker(); this._finish(); return; } if (this.options.trackUserLocation) { this._lastKnownPosition = position; switch (this._watchState) { case 'WAITING_ACTIVE': case 'ACTIVE_LOCK': case 'ACTIVE_ERROR': this._watchState = 'ACTIVE_LOCK'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active-error'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-active'); break; case 'BACKGROUND': case 'BACKGROUND_ERROR': this._watchState = 'BACKGROUND'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-background-error'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-background'); break; } } if (this.options.showUserLocation && this._watchState !== 'OFF') { this._updateMarker(position); } if (!this.options.trackUserLocation || this._watchState === 'ACTIVE_LOCK') { this._updateCamera(position); } if (this.options.showUserLocation) { this._dotElement.classList.remove('mapboxgl-user-location-dot-stale'); } this.fire(new ref_properties.Event('geolocate', position)); this._finish(); } _updateCamera(position) { const center = new ref_properties.LngLat(position.coords.longitude, position.coords.latitude); const radius = position.coords.accuracy; const bearing = this._map.getBearing(); const options = ref_properties.extend({ bearing }, this.options.fitBoundsOptions); this._map.fitBounds(center.toBounds(radius), options, { geolocateSource: true }); } _updateMarker(position) { if (position) { const center = new ref_properties.LngLat(position.coords.longitude, position.coords.latitude); this._accuracyCircleMarker.setLngLat(center).addTo(this._map); this._userLocationDotMarker.setLngLat(center).addTo(this._map); this._accuracy = position.coords.accuracy; if (this.options.showUserLocation && this.options.showAccuracyCircle) { this._updateCircleRadius(); } } else { this._userLocationDotMarker.remove(); this._accuracyCircleMarker.remove(); } } _updateCircleRadius() { const y = this._map._container.clientHeight / 2; const a = this._map.unproject([ 0, y ]); const b = this._map.unproject([ 1, y ]); const metersPerPixel = a.distanceTo(b); const circleDiameter = Math.ceil(2 * this._accuracy / metersPerPixel); this._circleElement.style.width = `${ circleDiameter }px`; this._circleElement.style.height = `${ circleDiameter }px`; } _onZoom() { if (this.options.showUserLocation && this.options.showAccuracyCircle) { this._updateCircleRadius(); } } _onError(error) { if (!this._map) { return; } if (this.options.trackUserLocation) { if (error.code === 1) { this._watchState = 'OFF'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active-error'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-background'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-background-error'); this._geolocateButton.disabled = true; const title = this._map._getUIString('GeolocateControl.LocationNotAvailable'); this._geolocateButton.title = title; this._geolocateButton.setAttribute('aria-label', title); if (this._geolocationWatchID !== undefined) { this._clearWatch(); } } else if (error.code === 3 && noTimeout) { return; } else { this._setErrorState(); } } if (this._watchState !== 'OFF' && this.options.showUserLocation) { this._dotElement.classList.add('mapboxgl-user-location-dot-stale'); } this.fire(new ref_properties.Event('error', error)); this._finish(); } _finish() { if (this._timeoutId) { clearTimeout(this._timeoutId); } this._timeoutId = undefined; } _setupUI(supported) { this._container.addEventListener('contextmenu', e => e.preventDefault()); this._geolocateButton = DOM.create('button', `mapboxgl-ctrl-geolocate`, this._container); DOM.create('span', `mapboxgl-ctrl-icon`, this._geolocateButton).setAttribute('aria-hidden', true); this._geolocateButton.type = 'button'; if (supported === false) { ref_properties.warnOnce('Geolocation support is not available so the GeolocateControl will be disabled.'); const title = this._map._getUIString('GeolocateControl.LocationNotAvailable'); this._geolocateButton.disabled = true; this._geolocateButton.title = title; this._geolocateButton.setAttribute('aria-label', title); } else { const title = this._map._getUIString('GeolocateControl.FindMyLocation'); this._geolocateButton.title = title; this._geolocateButton.setAttribute('aria-label', title); } if (this.options.trackUserLocation) { this._geolocateButton.setAttribute('aria-pressed', 'false'); this._watchState = 'OFF'; } if (this.options.showUserLocation) { this._dotElement = DOM.create('div', 'mapboxgl-user-location-dot'); this._userLocationDotMarker = new Marker(this._dotElement); this._circleElement = DOM.create('div', 'mapboxgl-user-location-accuracy-circle'); this._accuracyCircleMarker = new Marker({ element: this._circleElement, pitchAlignment: 'map' }); if (this.options.trackUserLocation) this._watchState = 'OFF'; this._map.on('zoom', this._onZoom); } this._geolocateButton.addEventListener('click', this.trigger.bind(this)); this._setup = true; if (this.options.trackUserLocation) { this._map.on('movestart', event => { const fromResize = event.originalEvent && event.originalEvent.type === 'resize'; if (!event.geolocateSource && this._watchState === 'ACTIVE_LOCK' && !fromResize) { this._watchState = 'BACKGROUND'; this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-background'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active'); this.fire(new ref_properties.Event('trackuserlocationend')); } }); } } trigger() { if (!this._setup) { ref_properties.warnOnce('Geolocate control triggered before added to a map'); return false; } if (this.options.trackUserLocation) { switch (this._watchState) { case 'OFF': this._watchState = 'WAITING_ACTIVE'; this.fire(new ref_properties.Event('trackuserlocationstart')); break; case 'WAITING_ACTIVE': case 'ACTIVE_LOCK': case 'ACTIVE_ERROR': case 'BACKGROUND_ERROR': numberOfWatches--; noTimeout = false; this._watchState = 'OFF'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active-error'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-background'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-background-error'); this.fire(new ref_properties.Event('trackuserlocationend')); break; case 'BACKGROUND': this._watchState = 'ACTIVE_LOCK'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-background'); if (this._lastKnownPosition) this._updateCamera(this._lastKnownPosition); this.fire(new ref_properties.Event('trackuserlocationstart')); break; } switch (this._watchState) { case 'WAITING_ACTIVE': this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-active'); break; case 'ACTIVE_LOCK': this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-active'); break; case 'ACTIVE_ERROR': this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-active-error'); break; case 'BACKGROUND': this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-background'); break; case 'BACKGROUND_ERROR': this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-background-error'); break; } if (this._watchState === 'OFF' && this._geolocationWatchID !== undefined) { this._clearWatch(); } else if (this._geolocationWatchID === undefined) { this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.setAttribute('aria-pressed', 'true'); numberOfWatches++; let positionOptions; if (numberOfWatches > 1) { positionOptions = { maximumAge: 600000, timeout: 0 }; noTimeout = true; } else { positionOptions = this.options.positionOptions; noTimeout = false; } this._geolocationWatchID = ref_properties.window.navigator.geolocation.watchPosition(this._onSuccess, this._onError, positionOptions); } } else { ref_properties.window.navigator.geolocation.getCurrentPosition(this._onSuccess, this._onError, this.options.positionOptions); this._timeoutId = setTimeout(this._finish, 10000); } return true; } _clearWatch() { ref_properties.window.navigator.geolocation.clearWatch(this._geolocationWatchID); this._geolocationWatchID = undefined; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.setAttribute('aria-pressed', 'false'); if (this.options.showUserLocation) { this._updateMarker(null); } } } const defaultOptions$4 = { maxWidth: 100, unit: 'metric' }; class ScaleControl { constructor(options) { this.options = ref_properties.extend({}, defaultOptions$4, options); ref_properties.bindAll([ '_onMove', 'setUnit' ], this); } getDefaultPosition() { return 'bottom-left'; } _onMove() { updateScale(this._map, this._container, this.options); } onAdd(map) { this._map = map; this._container = DOM.create('div', 'mapboxgl-ctrl mapboxgl-ctrl-scale', map.getContainer()); this._map.on('move', this._onMove); this._onMove(); return this._container; } onRemove() { DOM.remove(this._container); this._map.off('move', this._onMove); this._map = undefined; } setUnit(unit) { this.options.unit = unit; updateScale(this._map, this._container, this.options); } } function updateScale(map, container, options) { const maxWidth = options && options.maxWidth || 100; const y = map._container.clientHeight / 2; const left = map.unproject([ 0, y ]); const right = map.unproject([ maxWidth, y ]); const maxMeters = left.distanceTo(right); if (options && options.unit === 'imperial') { const maxFeet = 3.2808 * maxMeters; if (maxFeet > 5280) { const maxMiles = maxFeet / 5280; setScale(container, maxWidth, maxMiles, map._getUIString('ScaleControl.Miles')); } else { setScale(container, maxWidth, maxFeet, map._getUIString('ScaleControl.Feet')); } } else if (options && options.unit === 'nautical') { const maxNauticals = maxMeters / 1852; setScale(container, maxWidth, maxNauticals, map._getUIString('ScaleControl.NauticalMiles')); } else if (maxMeters >= 1000) { setScale(container, maxWidth, maxMeters / 1000, map._getUIString('ScaleControl.Kilometers')); } else { setScale(container, maxWidth, maxMeters, map._getUIString('ScaleControl.Meters')); } } function setScale(container, maxWidth, maxDistance, unit) { const distance = getRoundNum(maxDistance); const ratio = distance / maxDistance; container.style.width = `${ maxWidth * ratio }px`; container.innerHTML = `${ distance } ${ unit }`; } function getDecimalRoundNum(d) { const multiplier = Math.pow(10, Math.ceil(-Math.log(d) / Math.LN10)); return Math.round(d * multiplier) / multiplier; } function getRoundNum(num) { const pow10 = Math.pow(10, `${ Math.floor(num) }`.length - 1); let d = num / pow10; d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : d >= 1 ? 1 : getDecimalRoundNum(d); return pow10 * d; } class FullscreenControl { constructor(options) { this._fullscreen = false; if (options && options.container) { if (options.container instanceof ref_properties.window.HTMLElement) { this._container = options.container; } else { ref_properties.warnOnce('Full screen control \'container\' must be a DOM element.'); } } ref_properties.bindAll([ '_onClickFullscreen', '_changeIcon' ], this); if ('onfullscreenchange' in ref_properties.window.document) { this._fullscreenchange = 'fullscreenchange'; } else if ('onwebkitfullscreenchange' in ref_properties.window.document) { this._fullscreenchange = 'webkitfullscreenchange'; } } onAdd(map) { this._map = map; if (!this._container) this._container = this._map.getContainer(); this._controlContainer = DOM.create('div', `mapboxgl-ctrl mapboxgl-ctrl-group`); if (this._checkFullscreenSupport()) { this._setupUI(); } else { this._controlContainer.style.display = 'none'; ref_properties.warnOnce('This device does not support fullscreen mode.'); } return this._controlContainer; } onRemove() { DOM.remove(this._controlContainer); this._map = null; ref_properties.window.document.removeEventListener(this._fullscreenchange, this._changeIcon); } _checkFullscreenSupport() { return !!(ref_properties.window.document.fullscreenEnabled || ref_properties.window.document.webkitFullscreenEnabled); } _setupUI() { const button = this._fullscreenButton = DOM.create('button', `mapboxgl-ctrl-fullscreen`, this._controlContainer); DOM.create('span', `mapboxgl-ctrl-icon`, button).setAttribute('aria-hidden', true); button.type = 'button'; this._updateTitle(); this._fullscreenButton.addEventListener('click', this._onClickFullscreen); ref_properties.window.document.addEventListener(this._fullscreenchange, this._changeIcon); } _updateTitle() { const title = this._getTitle(); this._fullscreenButton.setAttribute('aria-label', title); this._fullscreenButton.title = title; } _getTitle() { return this._map._getUIString(this._isFullscreen() ? 'FullscreenControl.Exit' : 'FullscreenControl.Enter'); } _isFullscreen() { return this._fullscreen; } _changeIcon() { const fullscreenElement = ref_properties.window.document.fullscreenElement || ref_properties.window.document.webkitFullscreenElement; if (fullscreenElement === this._container !== this._fullscreen) { this._fullscreen = !this._fullscreen; this._fullscreenButton.classList.toggle(`mapboxgl-ctrl-shrink`); this._fullscreenButton.classList.toggle(`mapboxgl-ctrl-fullscreen`); this._updateTitle(); } } _onClickFullscreen() { if (this._isFullscreen()) { if (ref_properties.window.document.exitFullscreen) { ref_properties.window.document.exitFullscreen(); } else if (ref_properties.window.document.webkitCancelFullScreen) { ref_properties.window.document.webkitCancelFullScreen(); } } else if (this._container.requestFullscreen) { this._container.requestFullscreen(); } else if (this._container.webkitRequestFullscreen) { this._container.webkitRequestFullscreen(); } } } const defaultOptions$5 = { closeButton: true, closeOnClick: true, focusAfterOpen: true, className: '', maxWidth: '240px' }; const focusQuerySelector = [ 'a[href]', '[tabindex]:not([tabindex=\'-1\'])', '[contenteditable]:not([contenteditable=\'false\'])', 'button:not([disabled])', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])' ].join(', '); class Popup extends ref_properties.Evented { constructor(options) { super(); this.options = ref_properties.extend(Object.create(defaultOptions$5), options); ref_properties.bindAll([ '_update', '_onClose', 'remove', '_onMouseMove', '_onMouseUp', '_onDrag' ], this); } addTo(map) { if (this._map) this.remove(); this._map = map; if (this.options.closeOnClick) { this._map.on('click', this._onClose); } if (this.options.closeOnMove) { this._map.on('move', this._onClose); } this._map.on('remove', this.remove); this._update(); this._focusFirstElement(); if (this._trackPointer) { this._map.on('mousemove', this._onMouseMove); this._map.on('mouseup', this._onMouseUp); if (this._container) { this._container.classList.add('mapboxgl-popup-track-pointer'); } this._map._canvasContainer.classList.add('mapboxgl-track-pointer'); } else { this._map.on('move', this._update); } this.fire(new ref_properties.Event('open')); return this; } isOpen() { return !!this._map; } remove() { if (this._content) { DOM.remove(this._content); } if (this._container) { DOM.remove(this._container); delete this._container; } if (this._map) { this._map.off('move', this._update); this._map.off('move', this._onClose); this._map.off('click', this._onClose); this._map.off('remove', this.remove); this._map.off('mousemove', this._onMouseMove); this._map.off('mouseup', this._onMouseUp); this._map.off('drag', this._onDrag); delete this._map; } this.fire(new ref_properties.Event('close')); return this; } getLngLat() { return this._lngLat; } setLngLat(lnglat) { this._lngLat = ref_properties.LngLat.convert(lnglat); this._pos = null; this._trackPointer = false; this._update(); if (this._map) { this._map.on('move', this._update); this._map.off('mousemove', this._onMouseMove); if (this._container) { this._container.classList.remove('mapboxgl-popup-track-pointer'); } this._map._canvasContainer.classList.remove('mapboxgl-track-pointer'); } return this; } trackPointer() { this._trackPointer = true; this._pos = null; this._update(); if (this._map) { this._map.off('move', this._update); this._map.on('mousemove', this._onMouseMove); this._map.on('drag', this._onDrag); if (this._container) { this._container.classList.add('mapboxgl-popup-track-pointer'); } this._map._canvasContainer.classList.add('mapboxgl-track-pointer'); } return this; } getElement() { return this._container; } setText(text) { return this.setDOMContent(ref_properties.window.document.createTextNode(text)); } setHTML(html) { const frag = ref_properties.window.document.createDocumentFragment(); const temp = ref_properties.window.document.createElement('body'); let child; temp.innerHTML = html; while (true) { child = temp.firstChild; if (!child) break; frag.appendChild(child); } return this.setDOMContent(frag); } getMaxWidth() { return this._container && this._container.style.maxWidth; } setMaxWidth(maxWidth) { this.options.maxWidth = maxWidth; this._update(); return this; } setDOMContent(htmlNode) { if (this._content) { while (this._content.hasChildNodes()) { if (this._content.firstChild) { this._content.removeChild(this._content.firstChild); } } } else { this._content = DOM.create('div', 'mapboxgl-popup-content', this._container); } this._content.appendChild(htmlNode); this._createCloseButton(); this._update(); this._focusFirstElement(); return this; } addClassName(className) { if (this._container) { this._container.classList.add(className); } } removeClassName(className) { if (this._container) { this._container.classList.remove(className); } } setOffset(offset) { this.options.offset = offset; this._update(); return this; } toggleClassName(className) { if (this._container) { return this._container.classList.toggle(className); } } _createCloseButton() { if (this.options.closeButton) { this._closeButton = DOM.create('button', 'mapboxgl-popup-close-button', this._content); this._closeButton.type = 'button'; this._closeButton.setAttribute('aria-label', 'Close popup'); this._closeButton.innerHTML = '×'; this._closeButton.addEventListener('click', this._onClose); } } _onMouseUp(event) { this._update(event.point); } _onMouseMove(event) { this._update(event.point); } _onDrag(event) { this._update(event.point); } _update(cursor) { const hasPosition = this._lngLat || this._trackPointer; if (!this._map || !hasPosition || !this._content) { return; } if (!this._container) { this._container = DOM.create('div', 'mapboxgl-popup', this._map.getContainer()); this._tip = DOM.create('div', 'mapboxgl-popup-tip', this._container); this._container.appendChild(this._content); if (this.options.className) { this.options.className.split(' ').forEach(name => this._container.classList.add(name)); } if (this._trackPointer) { this._container.classList.add('mapboxgl-popup-track-pointer'); } } if (this.options.maxWidth && this._container.style.maxWidth !== this.options.maxWidth) { this._container.style.maxWidth = this.options.maxWidth; } if (this._map.transform.renderWorldCopies && !this._trackPointer) { this._lngLat = smartWrap(this._lngLat, this._pos, this._map.transform); } if (this._trackPointer && !cursor) return; const pos = this._pos = this._trackPointer && cursor ? cursor : this._map.project(this._lngLat); let anchor = this.options.anchor; const offset = normalizeOffset(this.options.offset); if (!anchor) { const width = this._container.offsetWidth; const height = this._container.offsetHeight; let anchorComponents; if (pos.y + offset.bottom.y < height) { anchorComponents = ['top']; } else if (pos.y > this._map.transform.height - height) { anchorComponents = ['bottom']; } else { anchorComponents = []; } if (pos.x < width / 2) { anchorComponents.push('left'); } else if (pos.x > this._map.transform.width - width / 2) { anchorComponents.push('right'); } if (anchorComponents.length === 0) { anchor = 'bottom'; } else { anchor = anchorComponents.join('-'); } } const offsetedPos = pos.add(offset[anchor]).round(); DOM.setTransform(this._container, `${ anchorTranslate[anchor] } translate(${ offsetedPos.x }px,${ offsetedPos.y }px)`); applyAnchorClass(this._container, anchor, 'popup'); } _focusFirstElement() { if (!this.options.focusAfterOpen || !this._container) return; const firstFocusable = this._container.querySelector(focusQuerySelector); if (firstFocusable) firstFocusable.focus(); } _onClose() { this.remove(); } } function normalizeOffset(offset) { if (!offset) { return normalizeOffset(new ref_properties.Point(0, 0)); } else if (typeof offset === 'number') { const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2))); return { 'center': new ref_properties.Point(0, 0), 'top': new ref_properties.Point(0, offset), 'top-left': new ref_properties.Point(cornerOffset, cornerOffset), 'top-right': new ref_properties.Point(-cornerOffset, cornerOffset), 'bottom': new ref_properties.Point(0, -offset), 'bottom-left': new ref_properties.Point(cornerOffset, -cornerOffset), 'bottom-right': new ref_properties.Point(-cornerOffset, -cornerOffset), 'left': new ref_properties.Point(offset, 0), 'right': new ref_properties.Point(-offset, 0) }; } else if (offset instanceof ref_properties.Point || Array.isArray(offset)) { const convertedOffset = ref_properties.Point.convert(offset); return { 'center': convertedOffset, 'top': convertedOffset, 'top-left': convertedOffset, 'top-right': convertedOffset, 'bottom': convertedOffset, 'bottom-left': convertedOffset, 'bottom-right': convertedOffset, 'left': convertedOffset, 'right': convertedOffset }; } else { return { 'center': ref_properties.Point.convert(offset['center'] || [ 0, 0 ]), 'top': ref_properties.Point.convert(offset['top'] || [ 0, 0 ]), 'top-left': ref_properties.Point.convert(offset['top-left'] || [ 0, 0 ]), 'top-right': ref_properties.Point.convert(offset['top-right'] || [ 0, 0 ]), 'bottom': ref_properties.Point.convert(offset['bottom'] || [ 0, 0 ]), 'bottom-left': ref_properties.Point.convert(offset['bottom-left'] || [ 0, 0 ]), 'bottom-right': ref_properties.Point.convert(offset['bottom-right'] || [ 0, 0 ]), 'left': ref_properties.Point.convert(offset['left'] || [ 0, 0 ]), 'right': ref_properties.Point.convert(offset['right'] || [ 0, 0 ]) }; } } const exported = { version: ref_properties.version, supported, setRTLTextPlugin: ref_properties.setRTLTextPlugin, getRTLTextPluginStatus: ref_properties.getRTLTextPluginStatus, Map, NavigationControl, GeolocateControl, AttributionControl, ScaleControl, FullscreenControl, Popup, Marker, Style, LngLat: ref_properties.LngLat, LngLatBounds: ref_properties.LngLatBounds, Point: ref_properties.Point, MercatorCoordinate: ref_properties.MercatorCoordinate, FreeCameraOptions, Evented: ref_properties.Evented, config: ref_properties.config, prewarm, clearPrewarmedResources, get accessToken() { return ref_properties.config.ACCESS_TOKEN; }, set accessToken(token) { ref_properties.config.ACCESS_TOKEN = token; }, get baseApiUrl() { return ref_properties.config.API_URL; }, set baseApiUrl(url) { ref_properties.config.API_URL = url; }, get workerCount() { return WorkerPool.workerCount; }, set workerCount(count) { WorkerPool.workerCount = count; }, get maxParallelImageRequests() { return ref_properties.config.MAX_PARALLEL_IMAGE_REQUESTS; }, set maxParallelImageRequests(numRequests) { ref_properties.config.MAX_PARALLEL_IMAGE_REQUESTS = numRequests; }, clearStorage(callback) { ref_properties.clearTileCache(callback); }, workerUrl: '', workerClass: null, setNow: ref_properties.browser.setNow, restoreNow: ref_properties.browser.restoreNow }; return exported; }); // return mapboxgl; }))); //# sourceMappingURL=mapbox-gl-unminified.js.map