1/** 2* MapLibre GL JS 3* @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v6.11.0/LICENSE.txt 4*/ 5import { $ as projectToWorldCoordinates, $n as differenceOfAnglesDegrees, $r as add, $t as Transitionable, A as evaluateSizeForZoom, Ai as rotateX, An as addProtocol, Ar as scaleZoom, At as toEvaluationFeature, B as isCluster, Bi as rotate, Bn as arrayBufferToImage, Br as EXTENT, Bt as SegmentVector, C as addDynamicAttributes, Ci as exactEquals, Cn as GLOBAL_DISPATCHER_ID, Cr as pointPlaneSignedDistance, Ct as HEATMAP_FULL_RENDER_FBO_KEY, Di as multiply, Dn as getVideo, Dr as remapSaturate, Dt as RGBAImage, E as TextAnchorEnum, Ei as invert$1, En as getReferrer, Er as readImageUsingVideoFrame, Et as AlphaImage, F as potpack, Fi as create$4, Fn as isAbortError, Fr as uniqueId, Ft as Uniform4f, Gn as createIdentityMat4f32, Gr as zero, Gt as PosArray, Hi as offscreenCanvasSupported, Hn as bezier, Hr as length$1, Ht as CollisionCircleLayoutArray, I as isStyleImageWebGLData, Ii as fromRotation, In as throwIfAborted, Ir as warnOnce, It as UniformColor, J as cameraDirectionFromPitchBearing, Jn as createVec3f64, Jr as multiply$1, Jt as TriangleIndexArray, K as isFillExtrusionStyleLayer, Kn as createIdentityMat4f64, Kr as fromEuler, Kt as QuadTriangleArray, L as renderStyleImage, Li as create$2, Lr as wrap, Lt as UniformColorArray, M as getAnchorAlignment, Mi as rotateZ, Mn as removeProtocol, Mr as subscribe, Mt as Uniform1i, Ni as scale, Nn as config, Nr as threePlaneIntersection, Nt as Uniform2f, O as clipLine, Oi as ortho, On as makeRequest, Or as rollPitchBearingEqual, Ot as isCircleStyleLayer, P as ImagePosition, Pi as translate, Pn as AbortError, Pr as translatePosition, Pt as Uniform3f, Q as maxMercatorHorizonAngle, Qn as degreesToRadians, Qr as transformMat4, Qt as Properties, R as parseGlyphPbf, Ri as determinant, Rn as MAX_VALID_LATITUDE, Rr as zoomScale, Rt as UniformFloatArray, S as SymbolBucket, Si as equals, Sn as AJAXError, Sr as pick, St as isHillshadeStyleLayer, T as getAnchorJustification, Ti as identity, Tn as getJSON, Tr as rayPlaneIntersection, Tt as renderColorRamp, U as collisionCircleLayout, Ui as Point, Un as clamp, Ur as scale$3, Ut as LineStripIndexArray, V as codePointUsesLocalIdeographFontFamily, Vi as isOffscreenCanvasDistorted, Vn as arrayBufferToImageBitmap, Vr as create$3, Vt as CollisionBoxArray, W as isLineStyleLayer, Wn as clone$2, Wr as sqrLen, Wt as Pos3dArray, X as cameraMercatorCoordinateFromCenterAndRotation, Xn as deepEqual, Xr as mul, Xt as isRasterStyleLayer, Y as cameraMercatorCoordinate, Yn as createVec4f64, Yr as slerp, Yt as createLayout, Z as getMercatorHorizon, Zn as defaultEasing, Zr as scale$2, Zt as DataConstantProperty, _ as createStyleLayer, _i as transformQuat, _n as derefLayers, _r as lerp, _t as SubdivisionGranularityExpression, a as GeoJSONFeature, ai as length, an as validateAndEmit, ar as findLineIntersection, at as lngFromMercatorX, b as isBackgroundStyleLayer, bi as copy, bn as Event, br as nextPowerOfTwo, ci as rotateX$1, cn as emptyStyle, cr as getEdgeTiles, ct as mercatorZfromAltitude, d as OverscaledTileID, di as scale$1, dr as isImageBitmap, ei as clone, en as EvaluationParameters, er as distanceOfAnglesRadians, et as tileCoordinatesToMercatorCoordinates, f as UnwrappedTileID, fi as scaleAndAdd, fn as interpolateFactory, fr as isPointableEvent, ft as EXTENT_BOUNDS, g as Actor, gi as transformMat4$1, gn as diff, gr as isWorker, gt as SOUTH_POLE_Y, h as isInBoundsForZoomLngLat, hi as transformMat3, hn as ValidationError, hr as isTouchableOrPointableType, ht as NORTH_POLE_Y, ii as len, in as emitValidationErrors, ir as filterObject, it as latFromMercatorY, ji as rotateY$1, jn as getProtocol, jr as sphericalToCartesian, jt as Uniform1f, k as evaluateSizeForFeature, ki as perspective, kn as sameOrigin, kr as rollPitchBearingToQuat, kt as polygonIntersectsPolygon, li as rotateY, lt as LngLat, m as compareTileId, mi as subtract, mn as Color, mr as isTouchableEvent, mt as isFillStyleLayer, n as TileCache, ni as distance, nr as evaluateZoomSnap, nt as MercatorCoordinate, oi as negate, on as validateStyle, or as getAABB, ot as mercatorXfromLng, p as calculateTileKey, pi as sub, pn as ProjectionDefinition, pr as isSafari, pt as Bounds, q as calculateTileMatrix, qn as createMat4f64, qr as fromValues, qt as RasterBoundsArray, ri as dot, rn as SPEC_SOURCE_TYPES, rr as extend, rt as altitudeFromMercatorZ, s as GEOJSON_TILE_LAYER_NAME, si as normalize, sn as validateStyleAndEmit, sr as getAngleDelta, st as mercatorYfromLat, ti as cross, tn as ZoomHistory, tr as ensureError, tt as unprojectFromWorldCoordinates, u as CanonicalTileID, ui as rotateZ$1, un as featureFilter, ur as getRollPitchBearing, ut as earthRadius, v as isCustomStyleLayer, vi as zero$1, vn as latest, vr as mapObject, vt as SubdivisionGranularitySetting, w as getOverlapMode, wi as fromScaling, wn as getArrayBuffer, wr as radiansToDegrees, wt as isHeatmapStyleLayer, x as isSymbolStyleLayer, xi as create$1, xn as Evented, xr as parseCacheControl, xt as Texture, y as validateCustomStyleLayer, yi as clone$1, yn as ErrorEvent, yr as mod, yt as isColorReliefStyleLayer, zi as invert, zn as angleToRotateBetweenVectors2D, zr as pixelsToTileUnits, zt as UniformMatrix4f } from "./maplibre-gl-shared-dev.mjs"; 6//#region package.json 7var version$2 = "6.11.0
vendor: 5,834 bytes, lines 7-200
7"; 8//#endregion 9//#region src/util/browser.ts 10let linkEl; 11let reducedMotionQuery; 12let reducedMotionOverride; 13/** */ 14const browser = { 15 /** 16 * Schedules a callback to be invoked on the next animation frame. 17 * @param abortController - Controller to abort the scheduled frame. 18 * @param fn - Callback to invoke with the paint start timestamp. 19 * @param reject - Callback to invoke if the frame is aborted. 20 * @param targetWindow - Optional window to use for requestAnimationFrame. 21 * When the map is rendered in a popup window or iframe, pass the owning 22 * window to ensure animation frames continue even when the main window 23 * is not focused. 24 */ 25 frame(abortController, fn, reject, targetWindow) { 26 const win = targetWindow || window; 27 const frameId = win.requestAnimationFrame((paintStartTimestamp) => { 28 unsubscribe(); 29 fn(paintStartTimestamp); 30 }); 31 const { unsubscribe } = subscribe(abortController.signal, "abort", () => { 32 unsubscribe(); 33 win.cancelAnimationFrame(frameId); 34 reject(new AbortError(abortController.signal.reason)); 35 }, false); 36 }, 37 /** 38 * Returns a promise that resolves on the next animation frame. 39 * @param abortController - Controller to abort the scheduled frame. 40 * @param targetWindow - Optional window to use for requestAnimationFrame. 41 * @see {@link browser.frame} 42 */ 43 frameAsync(abortController, targetWindow) { 44 return new Promise((resolve, reject) => { 45 this.frame(abortController, resolve, reject, targetWindow); 46 }); 47 }, 48 getImageData(img, padding = 0) { 49 return this.getImageCanvasContext(img).getImageData(-padding, -padding, img.width + 2 * padding, img.height + 2 * padding); 50 }, 51 /** 52 * Draws an image into a 2D canvas whose pixels can be read back cheaply. An `OffscreenCanvas` 53 * is used where the browser has one that reads back faithfully: drawing a GPU-backed 54 * `ImageBitmap` into a document canvas opened with `willReadFrequently` stalls the main thread 55 * for tens of milliseconds on GPU-accelerated browsers, an `OffscreenCanvas` does not. 56 */ 57 getImageCanvasContext(img) { 58 let context; 59 if (offscreenCanvasSupported() && !isOffscreenCanvasDistorted()) context = new OffscreenCanvas(img.width, img.height).getContext("2d", { willReadFrequently: true }); 60 else { 61 const canvas = window.document.createElement("canvas"); 62 canvas.width = img.width; 63 canvas.height = img.height; 64 context = canvas.getContext("2d", { willReadFrequently: true }); 65 } 66 if (!context) throw new Error("failed to create canvas 2d context"); 67 context.drawImage(img, 0, 0, img.width, img.height); 68 return context; 69 }, 70 resolveURL(path) { 71 linkEl ||= document.createElement("a"); 72 linkEl.href = path; 73 return linkEl.href; 74 }, 75 get hardwareConcurrency() { 76 return typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4; 77 }, 78 get prefersReducedMotion() { 79 if (reducedMotionOverride !== void 0) return reducedMotionOverride; 80 if (!matchMedia) return false; 81 reducedMotionQuery ??= matchMedia("(prefers-reduced-motion: reduce)"); 82 return reducedMotionQuery.matches; 83 }, 84 set prefersReducedMotion(value) { 85 reducedMotionOverride = value; 86 } 87}; 88//#endregion 89//#region src/util/time_control.ts 90/** 91* Manages time flow with optional freezing capability for deterministic rendering. 92*/ 93var TimeManager = class { 94 constructor() { 95 this._frozenAt = null; 96 } 97 /** 98 * Gets the current time, either real or frozen. 99 * @returns Current time in milliseconds 100 */ 101 getCurrentTime() { 102 return this._frozenAt !== null ? this._frozenAt : performance.now(); 103 } 104 /** 105 * Sets time at a specific timestamp. 106 * @param timestamp - Time in milliseconds to set 107 */ 108 setNow(timestamp) { 109 this._frozenAt = timestamp; 110 } 111 /** 112 * Restores normal time flow. 113 */ 114 restoreNow() { 115 this._frozenAt = null; 116 } 117 /** 118 * Returns whether time is currently frozen. 119 * @returns True if time is frozen, false otherwise 120 */ 121 isFrozen() { 122 return this._frozenAt !== null; 123 } 124}; 125const timeManager = new TimeManager(); 126/** 127* Returns the current time in milliseconds. 128* When time is frozen via setNow(), returns the frozen timestamp. 129* Otherwise returns real browser time via performance.now(). 130* 131* @returns Current time in milliseconds 132* @example 133* ```ts 134* // Measure elapsed time 135* const start = maplibregl.now(); 136* // ... later ... 137* const elapsed = maplibregl.now() - start; 138* 139* // During frozen time 140* maplibregl.setNow(16.67); 141* console.log(maplibregl.now()); // 16.67 142* maplibregl.restoreNow(); 143* console.log(maplibregl.now()); // real time 144* ``` 145*/ 146function now() { 147 return timeManager.getCurrentTime(); 148} 149/** 150* Freezes time at a specific timestamp for deterministic rendering. 151* Useful for frame-by-frame video capture where each frame needs 152* a consistent time value. 153* 154* @param timestamp - Time in milliseconds to freeze at 155* @example 156* ```ts 157* // Freeze time for video export at 60fps 158* setNow(0); // First frame 159* // ... render frame ... 160* setNow(16.67); // Second frame 161* // ... render frame ... 162* setNow(33.34); // Third frame 163* // ... done ... 164* restoreNow(); // Resume normal time 165* ``` 166*/ 167function setNow(timestamp) { 168 timeManager.setNow(timestamp); 169} 170/** 171* Restores normal time flow after freezing with setNow(). 172* Call this after finishing deterministic rendering operations. 173* 174* @example 175* ```ts 176* // After video export, resume normal time 177* setNow(0); 178* // ... export frames ... 179* restoreNow(); // Map animations resume normally 180* ``` 181*/ 182function restoreNow() { 183 timeManager.restoreNow(); 184} 185/** 186* Returns whether time is currently frozen. 187* @returns True if time is frozen via setNow(), false otherwise 188* @example 189* ```ts 190* setNow(1000); 191* console.log(isTimeFrozen()); // true 192* restoreNow(); 193* console.log(isTimeFrozen()); // false 194* ``` 195*/ 196function isTimeFrozen() { 197 return timeManager.isFrozen(); 198} 199//#endregion 200//#region src/util/dom.ts
201var DOM = class DOM { 202 static { 203 this.docStyle = typeof window !== "undefined" && window.document?.documentElement.style; 204 } 205 static { 206 this.selectProp = !DOM.docStyle || "userSelect" in DOM.docStyle ? "userSelect" : "webkitUserSelect"; 207 } 208 static create(tagName, className, container) { 209 const el = window.document.createElement(tagName); 210 if (className !== void 0) el.className = className; 211 if (container) container.appendChild(el); 212 return el; 213 } 214 static createNS(namespaceURI, tagName) { 215 return window.document.createElementNS(namespaceURI, tagName); 216 } 217 static disableDrag() { 218 if (DOM.docStyle && DOM.selectProp) { 219 DOM.userSelect = DOM.docStyle[DOM.selectProp]; 220 DOM.docStyle[DOM.selectProp] = "none"; 221 } 222 } 223 static enableDrag() { 224 if (DOM.docStyle && DOM.selectProp) DOM.docStyle[DOM.selectProp] = DOM.userSelect; 225 } 226 static suppressClickInternal(e) { 227 e.preventDefault(); 228 e.stopPropagation(); 229 window.removeEventListener("click", DOM.suppressClickInternal, true); 230 } 231 static suppressClick() { 232 window.addEventListener("click", DOM.suppressClickInternal, true); 233 window.setTimeout(() => { 234 window.removeEventListener("click", DOM.suppressClickInternal, true); 235 }, 0); 236 } 237 static getScale(element) { 238 const rect = element.getBoundingClientRect(); 239 return { 240 x: rect.width / element.offsetWidth || 1, 241 y: rect.height / element.offsetHeight || 1, 242 boundingClientRect: rect 243 }; 244 } 245 static getPoint(el, scale, e) { 246 const rect = scale.boundingClientRect; 247 return new Point((e.clientX - rect.left) / scale.x - el.clientLeft, (e.clientY - rect.top) / scale.y - el.clientTop); 248 } 249 static mousePos(el, e) { 250 const scale = DOM.getScale(el); 251 return DOM.getPoint(el, scale, e); 252 } 253 static touchPos(el, touches) { 254 const points = []; 255 const scale = DOM.getScale(el); 256 for (const touch of touches) points.push(DOM.getPoint(el, scale, touch)); 257 return points; 258 } 259 /** 260 * Sanitize an HTML string - this might not be enough to prevent all XSS attacks 261 * Base on https://javascriptsource.com/sanitize-an-html-string-to-reduce-the-risk-of-xss-attacks/ 262 * (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com 263 * 264 * Returns the sanitized nodes rather than a string, since re-parsing serialized HTML can produce a different tree. 265 */ 266 static sanitize(str) { 267 const html = new DOMParser().parseFromString(str, "text/html").body || document.createElement("body"); 268 const dangerousElements = html.querySelectorAll("script, iframe"); 269 for (const element of dangerousElements) element.remove(); 270 DOM.clean(html); 271 const fragment = document.createDocumentFragment(); 272 fragment.append(...html.childNodes); 273 return fragment; 274 } 275 /** 276 * Check if the attribute is potentially dangerous 277 */ 278 static isPossiblyDangerous(name, value) { 279 const val = value.replace(/\s+/g, "").toLowerCase(); 280 if ([ 281 "src", 282 "href", 283 "xlink:href" 284 ].includes(name)) { 285 if (val.includes("javascript:") || val.includes("data:")) return true; 286 } 287 if (name === "srcdoc") return true; 288 if (name.startsWith("on")) return true; 289 } 290 /** 291 * Remove dangerous stuff from the HTML document's nodes 292 * @param html - The HTML document 293 */ 294 static clean(html) { 295 const nodes = html.children; 296 for (const node of nodes) { 297 DOM.removeAttributes(node); 298 DOM.clean(node); 299 } 300 } 301 /** 302 * Remove potentially dangerous attributes from an element 303 * @param elem - The element 304 */ 305 static removeAttributes(elem) { 306 for (const { name, value } of Array.from(elem.attributes)) { 307 if (!DOM.isPossiblyDangerous(name, value)) continue; 308 elem.removeAttribute(name)
vendor: 40,366 bytes, lines 308-1526
308; 309 } 310 } 311}; 312//#endregion 313//#region src/util/image_request.ts 314let ImageRequest; 315(function(_ImageRequest) { 316 let imageRequestQueue; 317 let currentParallelImageRequests; 318 let throttleControlCallbackHandleCounter; 319 let throttleControlCallbacks; 320 _ImageRequest.resetRequestQueue = () => { 321 imageRequestQueue = []; 322 currentParallelImageRequests = 0; 323 throttleControlCallbackHandleCounter = 0; 324 throttleControlCallbacks = {}; 325 }; 326 _ImageRequest.addThrottleControl = (callback) => { 327 const handle = throttleControlCallbackHandleCounter++; 328 throttleControlCallbacks[handle] = callback; 329 return handle; 330 }; 331 _ImageRequest.removeThrottleControl = (callbackHandle) => { 332 delete throttleControlCallbacks[callbackHandle]; 333 processQueue(); 334 }; 335 /** 336 * Check to see if any of the installed callbacks are requesting the queue 337 * to be throttled. 338 * @returns `true` if any callback is causing the queue to be throttled. 339 */ 340 const isThrottled = () => { 341 for (const key of Object.keys(throttleControlCallbacks)) if (throttleControlCallbacks[key]()) return true; 342 return false; 343 }; 344 async function transformAndGetImage(requestManager, url, resourceType, abortController, supportImageRefresh = true, imageBitmapOptions) { 345 const requestParameters = await requestManager.transformRequest(url, resourceType); 346 throwIfAborted(abortController.signal); 347 return ImageRequest.getImage(requestParameters, abortController, supportImageRefresh, imageBitmapOptions); 348 } 349 _ImageRequest.transformAndGetImage = transformAndGetImage; 350 _ImageRequest.getImage = (requestParameters, abortController, supportImageRefresh = true, imageBitmapOptions) => { 351 return new Promise((resolve, reject) => { 352 requestParameters.headers ||= {}; 353 requestParameters.headers.accept = "image/webp,*/*"; 354 extend(requestParameters, { type: "image" }); 355 const request = { 356 abortController, 357 requestParameters, 358 supportImageRefresh, 359 imageBitmapOptions, 360 state: "queued", 361 onError: (error) => { 362 reject(error); 363 }, 364 onSuccess: (response) => { 365 resolve(response); 366 } 367 }; 368 imageRequestQueue.push(request); 369 processQueue(); 370 }); 371 }; 372 const arrayBufferToCanvasImageSource = (data, imageBitmapOptions) => { 373 if (typeof createImageBitmap === "function") return arrayBufferToImageBitmap(data, imageBitmapOptions); 374 else return arrayBufferToImage(data); 375 }; 376 /** 377 * Runs one queued image request. 378 * 379 * An empty response body (e.g. HTTP 204 for a tile without content) resolves with 380 * `data: null`, keeping the expiry headers, so callers can handle the absence 381 * explicitly and re-request when the headers say so. 382 */ 383 const doImageRequest = async (itemInQueue) => { 384 itemInQueue.state = "running"; 385 const { requestParameters, supportImageRefresh, imageBitmapOptions, onError, onSuccess, abortController } = itemInQueue; 386 const canUseHTMLImageElement = supportImageRefresh === false && !imageBitmapOptions && !isWorker(self) && !getProtocol(requestParameters.url) && (!requestParameters.headers || Object.keys(requestParameters.headers).reduce((acc, item) => acc && item === "accept", true)); 387 currentParallelImageRequests++; 388 const getImagePromise = canUseHTMLImageElement ? getImageUsingHtmlImage(requestParameters, abortController) : makeRequest(requestParameters, abortController); 389 try { 390 const response = await getImagePromise; 391 delete itemInQueue.abortController; 392 itemInQueue.state = "completed"; 393 if (response.data instanceof HTMLImageElement || isImageBitmap(response.data)) onSuccess(response); 394 else if (!response.data || response.data.byteLength === 0) onSuccess({ 395 data: null, 396 cacheControl: response.cacheControl, 397 expires: response.expires 398 }); 399 else onSuccess({ 400 data: await arrayBufferToCanvasImageSource(response.data, imageBitmapOptions), 401 cacheControl: response.cacheControl, 402 expires: response.expires 403 }); 404 } catch (err) { 405 delete itemInQueue.abortController; 406 onError(ensureError(err)); 407 } finally { 408 currentParallelImageRequests--; 409 processQueue(); 410 } 411 }; 412 /** 413 * Process some number of items in the image request queue. 414 */ 415 const processQueue = () => { 416 const maxImageRequests = isThrottled() ? config.MAX_PARALLEL_IMAGE_REQUESTS_PER_FRAME : config.MAX_PARALLEL_IMAGE_REQUESTS; 417 for (let numImageRequests = currentParallelImageRequests; numImageRequests < maxImageRequests && imageRequestQueue.length > 0; numImageRequests++) { 418 const topItemInQueue = imageRequestQueue.shift(); 419 if (topItemInQueue.abortController.signal.aborted) { 420 numImageRequests--; 421 continue; 422 } 423 doImageRequest(topItemInQueue); 424 } 425 }; 426 const getImageUsingHtmlImage = (requestParameters, abortController) => { 427 return new Promise((resolve, reject) => { 428 const image = new Image(); 429 const url = requestParameters.url; 430 const credentials = requestParameters.credentials; 431 if (credentials && credentials === "include") image.crossOrigin = "use-credentials"; 432 else if (credentials && credentials === "same-origin" || !sameOrigin(url)) image.crossOrigin = "anonymous"; 433 abortController.signal.addEventListener("abort", () => { 434 image.src = ""; 435 reject(new AbortError(abortController.signal.reason)); 436 }); 437 image.fetchPriority = "high"; 438 image.onload = () => { 439 image.onerror = image.onload = null; 440 resolve({ data: image }); 441 }; 442 image.onerror = () => { 443 image.onerror = image.onload = null; 444 if (abortController.signal.aborted) return; 445 reject(/* @__PURE__ */ 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.")); 446 }; 447 image.src = url; 448 }); 449 }; 450})(ImageRequest || (ImageRequest = {})); 451ImageRequest.resetRequestQueue(); 452var RequestManager = class { 453 constructor(transformRequestFn) { 454 this._transformRequestFn = transformRequestFn ?? null; 455 } 456 transformRequest(url, type) { 457 if (this._transformRequestFn) return this._transformRequestFn(url, type) || { url }; 458 return { url }; 459 } 460 setTransformRequest(transformRequest) { 461 this._transformRequestFn = transformRequest; 462 } 463}; 464//#endregion 465//#region src/ui/events.ts 466/** 467* The base event for MapLibre 468* 469* @group Event Related 470*/ 471var MapLibreEvent = class extends Event {}; 472/** 473* `MapMovementEvent` is the event type for the camera-transition map events: 474* `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, `zoomend`, `rotatestart`, `rotate`, `rotateend`, 475* `dragstart`, `drag`, `dragend`, `pitchstart`, `pitch`, `pitchend`, `rollstart`, `roll` and `rollend`. 476* These are fired as the map's view changes, as a result of either user interaction or methods such 477* as {@link Map.jumpTo} / {@link Map.flyTo}. 478* 479* @group Event Related 480*/ 481var MapMovementEvent = class extends MapLibreEvent {}; 482/** 483* The `style.load` event, fired once the map's style has fully loaded or changed. 484* 485* @group Event Related 486*/ 487var MapStyleLoadEvent = class extends MapLibreEvent { 488 constructor(data = {}) { 489 super("style.load", data); 490 } 491}; 492/** 493* The style data event 494* 495* @group Event Related 496*/ 497var MapStyleDataEvent = class extends MapLibreEvent { 498 constructor(type, data = {}) { 499 super(type, data); 500 this.dataType = "style"; 501 } 502}; 503/** 504* A `MapSourceDataEvent` is emitted with the source-related `data`, `dataloading`, `dataabort`, 505* `sourcedata`, `sourcedataloading` and `sourcedataabort` events. Its `dataType` is always `'source'`. 506* 507* Possible values for `sourceDataType`s are: 508* 509* - `'metadata'`: indicates that any necessary source metadata has been loaded (such as TileJSON) and it is ok to start loading tiles 510* - `'content'`: indicates the source data has changed (such as when source.setData() has been called on GeoJSONSource) 511* - `'visibility'`: send when the source becomes used when at least one of its layers becomes visible in style sense (inside the layer's zoom range and with layout.visibility set to 'visible') 512* - `'idle'`: indicates that no new source data has been fetched (but the source has done loading) 513* 514* @group Event Related 515* 516* @example 517* ```ts 518* // The sourcedata event is an example of a MapSourceDataEvent. 519* // Set up an event listener on the map. 520* map.on('sourcedata', (e) => { 521* if (e.isSourceLoaded) { 522* // Do something when the source has finished loading 523* } 524* }); 525* ``` 526*/ 527var MapSourceDataEvent = class extends MapLibreEvent { 528 constructor(type, data = {}) { 529 super(type, data); 530 this.dataType = "source"; 531 } 532}; 533/** 534* `MapMouseEvent` is the event type for mouse-related map events. 535* 536* @group Event Related 537* 538* @example 539* ```ts 540* // The `click` event is an example of a `MapMouseEvent`. 541* // Set up an event listener on the map. 542* map.on('click', (e) => { 543* // The event object (e) contains information like the 544* // coordinates of the point on the map that was clicked. 545* console.log('A click event has occurred at ' + e.lngLat); 546* }); 547* ``` 548*/ 549var MapMouseEvent = class extends MapLibreEvent { 550 /** 551 * Prevents subsequent default processing of the event by the map. 552 * 553 * Calling this method will prevent the following default map behaviors: 554 * 555 * * On `mousedown` events, the behavior of {@link DragPanHandler} 556 * * On `mousedown` events, the behavior of {@link DragRotateHandler} 557 * * On `mousedown` events, the behavior of {@link BoxZoomHandler} 558 * * On `dblclick` events, the behavior of {@link DoubleClickZoomHandler} 559 * 560 */ 561 preventDefault() { 562 this._defaultPrevented = true; 563 } 564 /** 565 * `true` if `preventDefault` has been called. 566 */ 567 get defaultPrevented() { 568 return this._defaultPrevented; 569 } 570 constructor(type, map, originalEvent, data = {}) { 571 originalEvent = originalEvent instanceof MouseEvent ? originalEvent : new MouseEvent(type, originalEvent); 572 const point = DOM.mousePos(map.getCanvas(), originalEvent); 573 const lngLat = map.unproject(point); 574 super(type, extend({ 575 point, 576 lngLat, 577 originalEvent 578 }, data)); 579 this._defaultPrevented = false; 580 this.target = map; 581 } 582}; 583/** 584* `MapTouchEvent` is the event type for touch-related map events. 585* 586* @group Event Related 587*/ 588var MapTouchEvent = class extends MapLibreEvent { 589 /** 590 * Prevents subsequent default processing of the event by the map. 591 * 592 * Calling this method will prevent the following default map behaviors: 593 * 594 * * On `touchstart` events, the behavior of {@link DragPanHandler} 595 * * On `touchstart` events, the behavior of {@link TwoFingersTouchZoomRotateHandler} 596 * 597 */ 598 preventDefault() { 599 this._defaultPrevented = true; 600 } 601 /** 602 * `true` if `preventDefault` has been called. 603 */ 604 get defaultPrevented() { 605 return this._defaultPrevented; 606 } 607 constructor(type, map, originalEvent) { 608 const touches = type === "touchend" ? originalEvent.changedTouches : originalEvent.touches; 609 const points = DOM.touchPos(map.getCanvasContainer(), touches); 610 const lngLats = points.map((t) => map.unproject(t)); 611 const point = points.reduce((prev, curr, i, arr) => { 612 return prev.add(curr.div(arr.length)); 613 }, new Point(0, 0)); 614 const lngLat = map.unproject(point); 615 super(type, { 616 points, 617 point, 618 lngLats, 619 lngLat, 620 originalEvent 621 }); 622 this._defaultPrevented = false; 623 } 624}; 625/** 626* `MapWheelEvent` is the event type for the `wheel` map event. 627* 628* @group Event Related 629*/ 630var MapWheelEvent = class extends MapLibreEvent { 631 /** 632 * Prevents subsequent default processing of the event by the map. 633 * 634 * Calling this method will prevent the behavior of {@link ScrollZoomHandler}. 635 */ 636 preventDefault() { 637 this._defaultPrevented = true; 638 } 639 /** 640 * `true` if `preventDefault` has been called. 641 */ 642 get defaultPrevented() { 643 return this._defaultPrevented; 644 } 645 /** */ 646 constructor(map, originalEvent) { 647 super("wheel", { originalEvent }); 648 this._defaultPrevented = false; 649 } 650}; 651/** 652* A `MapBoxZoomEvent` is the event type for the boxzoom-related map events emitted by the {@link BoxZoomHandler}. 653* 654* @group Event Related 655*/ 656var MapBoxZoomEvent = class extends MapLibreEvent {}; 657/** 658* The terrain event 659* 660* @group Event Related 661*/ 662var MapTerrainEvent = class extends MapLibreEvent { 663 constructor(data = {}) { 664 super("terrain", data); 665 } 666}; 667/** 668* The map projection event 669* 670* @group Event Related 671*/ 672var MapProjectionEvent = class extends MapLibreEvent { 673 constructor(data = {}) { 674 super("projectiontransition", data); 675 } 676}; 677/** 678* An event related to the web gl context 679* 680* @group Event Related 681*/ 682var MapContextEvent = class extends MapLibreEvent {}; 683/** 684* The style image missing event, fired when an image is still missing after the missing style image 685* resolver has been given a chance to supply it. To load or generate images on demand, 686* use {@link Map.setMissingStyleImageResolver}. 687* Event listeners cannot resolve the missing image for the current request. 688* 689* @group Event Related 690* 691* @see [Generate and add a missing icon to the map](https://maplibre.org/maplibre-gl-js/docs/examples/generate-and-add-a-missing-icon-to-the-map/) 692*/ 693var MapStyleImageMissingEvent = class extends MapLibreEvent { 694 constructor(data = {}) { 695 super("styleimagemissing", data); 696 } 697}; 698//#endregion 699//#region src/util/style.ts 700/** 701* Takes a SpriteSpecification value and returns it in its array form. If `undefined` is passed as an input value, an 702* empty array is returned. 703* duplicated entries with identical id/url will be removed in returned array 704* @param sprite - optional sprite to coerce 705* @returns an empty array in case `undefined` is passed; id-url pairs otherwise 706*/ 707function coerceSpriteToArray(sprite) { 708 const resultArray = []; 709 if (typeof sprite === "string") resultArray.push({ 710 id: "default", 711 url: sprite 712 }); 713 else if (sprite && sprite.length > 0) { 714 const dedupArray = []; 715 for (const { id, url } of sprite) { 716 const key = `${id}${url}`; 717 if (!dedupArray.includes(key)) { 718 dedupArray.push(key); 719 resultArray.push({ 720 id, 721 url 722 }); 723 } 724 } 725 } 726 return resultArray; 727} 728//#endregion 729//#region src/style/load_sprite.ts 730function normalizeSpriteURL(url, format, extension) { 731 try { 732 const parsed = new URL(url); 733 parsed.pathname += `${format}${extension}`; 734 return parsed.toString(); 735 } catch { 736 throw new Error(`Invalid sprite URL "${url}", must be absolute. Modify style specification directly or use TransformStyleFunction to correct the issue dynamically`); 737 } 738} 739async function loadSprite(originalSprite, requestManager, pixelRatio, abortController) { 740 const spriteArray = coerceSpriteToArray(originalSprite); 741 const format = pixelRatio > 1 ? "@2x" : ""; 742 const jsonsMap = {}; 743 const imagesMap = {}; 744 for (const { id, url } of spriteArray) { 745 const jsonRequestParameters = await requestManager.transformRequest(normalizeSpriteURL(url, format, ".json"), "SpriteJSON"); 746 jsonsMap[id] = getJSON(jsonRequestParameters, abortController); 747 const imageRequestParameters = await requestManager.transformRequest(normalizeSpriteURL(url, format, ".png"), "SpriteImage"); 748 imagesMap[id] = ImageRequest.getImage(imageRequestParameters, abortController); 749 } 750 await Promise.all([...Object.values(jsonsMap), ...Object.values(imagesMap)]); 751 return doOnceCompleted(jsonsMap, imagesMap); 752} 753/** 754* @param jsonsMap - JSON data map 755* @param imagesMap - image data map 756*/ 757async function doOnceCompleted(jsonsMap, imagesMap) { 758 const result = {}; 759 for (const spriteName in jsonsMap) { 760 result[spriteName] = {}; 761 const image = (await imagesMap[spriteName]).data; 762 if (!image) throw new Error(`Could not load sprite image for ${spriteName}: the response is empty`); 763 const context = browser.getImageCanvasContext(image); 764 const json = (await jsonsMap[spriteName]).data; 765 for (const id in json) { 766 const { width, height, x, y, sdf, pixelRatio, stretchX, stretchY, content, textFitWidth, textFitHeight } = json[id]; 767 const spriteData = { 768 width, 769 height, 770 x, 771 y, 772 context 773 }; 774 result[spriteName][id] = { 775 data: null, 776 pixelRatio, 777 sdf, 778 stretchX, 779 stretchY, 780 content, 781 textFitWidth, 782 textFitHeight, 783 spriteData 784 }; 785 } 786 } 787 return result; 788} 789//#endregion 790//#region src/render/image_manager.ts 791/** 792* Owns every image of the style - the ones the sprites bring in as well as the ones added at 793* runtime - and tracks requests for them from the tile workers, sending responses when the 794* requests are fulfilled. 795*/ 796var ImageManager = class extends Evented { 797 constructor() { 798 super(); 799 this.images = {}; 800 this.updateVersion = 0; 801 this.loaded = false; 802 this.requestors = []; 803 this.missingImageResolver = null; 804 this._spriteImagesIds = {}; 805 this._imagesIds = null; 806 this._renderCallbacksDispatchedThisFrame = {}; 807 } 808 destroy() { 809 for (const id of Object.keys(this.images)) this.removeImage(id); 810 this._spriteImagesIds = {}; 811 } 812 isLoaded() { 813 return this.loaded; 814 } 815 setLoaded(loaded) { 816 if (this.loaded === loaded) return; 817 this.loaded = loaded; 818 if (loaded) { 819 for (const { ids, promiseResolve } of this.requestors) promiseResolve(this._getImagesForIds(ids)); 820 this.requestors = []; 821 } 822 } 823 getImage(id) { 824 const image = this.images[id]; 825 if (image && !image.data && image.spriteData) { 826 const spriteData = image.spriteData; 827 image.data = new RGBAImage({ 828 width: spriteData.width, 829 height: spriteData.height 830 }, spriteData.context.getImageData(spriteData.x, spriteData.y, spriteData.width, spriteData.height).data); 831 image.spriteData = null; 832 } 833 return image; 834 } 835 addImage(id, image) { 836 if (this.images[id]) throw new Error(`Image id ${id} already exist, use updateImage instead`); 837 if (this._validate(id, image)) { 838 this.images[id] = image; 839 this._imagesIds = null; 840 if (image.isWebGLImage) this.updateImage(id, image, false); 841 } 842 } 843 _validate(id, image) { 844 let valid = true; 845 const data = image.data || image.spriteData; 846 if (!this._validateStretch(image.stretchX, data?.width)) { 847 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Image "${id}" has invalid "stretchX" value`))); 848 valid = false; 849 } 850 if (!this._validateStretch(image.stretchY, data?.height)) { 851 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Image "${id}" has invalid "stretchY" value`))); 852 valid = false; 853 } 854 if (!this._validateContent(image.content, image)) { 855 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Image "${id}" has invalid "content" value`))); 856 valid = false; 857 } 858 return valid; 859 } 860 _validateStretch(stretch, size) { 861 if (!stretch) return true; 862 let last = 0; 863 for (const part of stretch) { 864 if (part[0] < last || part[1] < part[0] || size < part[1]) return false; 865 last = part[1]; 866 } 867 return true; 868 } 869 _validateContent(content, image) { 870 if (!content) return true; 871 if (content.length !== 4) return false; 872 const spriteData = image.spriteData; 873 const width = spriteData?.width || image.data.width; 874 const height = spriteData?.height || image.data.height; 875 if (content[0] < 0 || width < content[0]) return false; 876 if (content[1] < 0 || height < content[1]) return false; 877 if (content[2] < 0 || width < content[2]) return false; 878 if (content[3] < 0 || height < content[3]) return false; 879 if (content[2] < content[0]) return false; 880 return content[3] >= content[1]; 881 } 882 /** 883 * Replaces an image that is already known under this id, bumping its version so that the 884 * atlases holding it notice. 885 * 886 * The old record is read directly rather than through {@link ImageManager.getImage}, which 887 * would force a synchronous decode of the sprite data of the very image being replaced. Its 888 * size therefore comes from whichever of the two the pixels are still in, since an image that 889 * came from a sprite and was never rendered has not been decoded yet, and its version is 890 * defaulted, since images start out without one. 891 * 892 * @param validate - whether to reject an image of a different size than the one it replaces 893 */ 894 updateImage(id, image, validate = true) { 895 const oldImage = this.images[id]; 896 if (validate) { 897 const oldData = oldImage.data || oldImage.spriteData; 898 if (oldData.width !== image.data.width || oldData.height !== image.data.height) throw new Error(`size mismatch between old image (${oldData.width}x${oldData.height}) and new image (${image.data.width}x${image.data.height}).`); 899 } 900 image.version = (oldImage.version ?? 0) + 1; 901 this.images[id] = image; 902 this.updateVersion++; 903 } 904 removeImage(id) { 905 const image = this.images[id]; 906 if (!image) return; 907 delete this.images[id]; 908 this._imagesIds = null; 909 if (image.userImage?.onRemove) image.userImage.onRemove(); 910 } 911 /** 912 * @returns the ids of every image currently held, sprite images and runtime ones alike. The 913 * returned array is shared between callers and must not be modified. 914 */ 915 listImages() { 916 this._imagesIds ??= Object.keys(this.images); 917 return this._imagesIds; 918 } 919 /** 920 * Images of the `default` sprite keep their plain id, the ones of any other sprite are 921 * namespaced by their sprite's id. 922 */ 923 _getSpriteImageId(spriteId, imageId) { 924 return spriteId === "default" ? imageId : `${spriteId}:${imageId}`; 925 } 926 /** 927 * Takes over the images of a single sprite: adds the new ones, updates the ones that are 928 * already known, and removes the ones that a previous load of this same sprite had brought in 929 * but that are not part of it anymore. 930 * 931 * @param spriteId - the id of the sprite the images belong to 932 * @param images - the sprite's images, keyed by their id within the sprite 933 * @returns the ids of the images that this sprite now provides, and the ids of the ones that 934 * were removed because they are no longer part of it 935 */ 936 setSpriteImages(spriteId, images) { 937 const previousIds = this._spriteImagesIds[spriteId] ?? []; 938 const loaded = []; 939 for (const id in images) { 940 const imageId = this._getSpriteImageId(spriteId, id); 941 loaded.push(imageId); 942 if (imageId in this.images) this.updateImage(imageId, images[id], false); 943 else this.addImage(imageId, images[id]); 944 } 945 const loadedIds = new Set(loaded); 946 const removed = previousIds.filter((imageId) => !loadedIds.has(imageId)); 947 for (const imageId of removed) this.removeImage(imageId); 948 this._spriteImagesIds[spriteId] = loaded; 949 return { 950 loaded, 951 removed 952 }; 953 } 954 /** 955 * Removes all the images a single sprite has brought in. 956 * @returns the ids of the removed images 957 */ 958 removeSpriteImages(spriteId) { 959 const removed = this._spriteImagesIds[spriteId] ?? []; 960 for (const imageId of removed) this.removeImage(imageId); 961 delete this._spriteImagesIds[spriteId]; 962 return removed; 963 } 964 /** 965 * Removes the images of every sprite, leaving the images added at runtime alone. 966 * @returns the ids of the removed images 967 */ 968 removeAllSpriteImages() { 969 const removed = Object.values(this._spriteImagesIds).flat(); 970 for (const imageId of removed) this.removeImage(imageId); 971 this._spriteImagesIds = {}; 972 return removed; 973 } 974 setMissingImageResolver(resolver) { 975 this.missingImageResolver = resolver; 976 } 977 getImages(ids) { 978 return new Promise((resolve, _reject) => { 979 let hasAllDependencies = true; 980 if (!this.isLoaded()) { 981 for (const id of ids) if (!this.images[id]) hasAllDependencies = false; 982 } 983 if (this.isLoaded() || hasAllDependencies) resolve(this._getImagesForIds(ids)); 984 else this.requestors.push({ 985 ids, 986 promiseResolve: resolve 987 }); 988 }); 989 } 990 async _getImagesForIds(ids) { 991 const unresolvedIds = new Set(ids.filter((id) => !this.getImage(id))); 992 const resolver = this.missingImageResolver; 993 if (resolver) await Promise.allSettled(Array.from(unresolvedIds, (id) => resolver(id))); 994 const response = {}; 995 for (const id of ids) { 996 const image = this.getImage(id); 997 if (image) { 998 unresolvedIds.delete(id); 999 response[id] = { 1000 data: image.data.clone(), 1001 pixelRatio: image.pixelRatio, 1002 sdf: image.sdf, 1003 version: image.version, 1004 stretchX: image.stretchX, 1005 stretchY: image.stretchY, 1006 content: image.content, 1007 textFitWidth: image.textFitWidth, 1008 textFitHeight: image.textFitHeight, 1009 hasRenderCallback: Boolean(image.userImage?.render), 1010 isWebGLImage: image.isWebGLImage 1011 }; 1012 } 1013 } 1014 for (const id of unresolvedIds) { 1015 this.fire(new MapStyleImageMissingEvent({ id })); 1016 warnOnce(`Image "${id}" could not be loaded. Please make sure you have added the image before it is needed with map.addImage(), resolved it with map.setMissingStyleImageResolver(), or included it in a "sprite" property in your style.`); 1017 } 1018 return response; 1019 } 1020 beginFrame() { 1021 this._renderCallbacksDispatchedThisFrame = {}; 1022 } 1023 /** 1024 * Re-renders the images among `ids` that were added with a `render` callback (see 1025 * `StyleImageInterface`), at most once per frame each. 1026 */ 1027 dispatchRenderCallbacks(ids) { 1028 for (const id of ids) { 1029 if (this._renderCallbacksDispatchedThisFrame[id]) continue; 1030 this._renderCallbacksDispatchedThisFrame[id] = true; 1031 const image = this.getImage(id); 1032 if (!image) warnOnce(`Image with ID: "${id}" was not found`); 1033 if (renderStyleImage(image)) this.updateImage(id, image); 1034 } 1035 } 1036 cloneImages() { 1037 const clonedImages = {}; 1038 for (const id in this.images) { 1039 const image = this.images[id]; 1040 clonedImages[id] = { 1041 ...image, 1042 data: image.data ? image.data.clone() : null 1043 }; 1044 } 1045 return clonedImages; 1046 } 1047}; 1048//#endregion 1049//#region src/render/pattern_atlas.ts 1050/** 1051* When copied into the atlas texture, pattern images are padded by one pixel on each side with a 1052* copy of the image data wrapped from the opposite side. This ensures the correct behavior of 1053* GL_LINEAR texture sampling mode. 1054*/ 1055const PADDING = 1; 1056/** 1057* A texture atlas of the pattern images that are drawn without a tile's own {@link ImageAtlas}, 1058* i.e. `background-pattern`. Entries are packed on demand and re-packed whenever one of them is 1059* added or changes, which is rare enough for the repacking cost not to matter. 1060*/ 1061var PatternAtlas = class { 1062 constructor(imageManager) { 1063 this._imageManager = imageManager; 1064 this._entries = {}; 1065 this._image = new RGBAImage({ 1066 width: 1, 1067 height: 1 1068 }); 1069 this._dirty = true; 1070 } 1071 destroy() { 1072 if (this._texture) { 1073 this._texture.destroy(); 1074 this._texture = null; 1075 } 1076 this._entries = {}; 1077 this._image = new RGBAImage({ 1078 width: 1, 1079 height: 1 1080 }); 1081 this._dirty = true; 1082 } 1083 getPixelSize() { 1084 const { width, height } = this._image; 1085 return { 1086 width, 1087 height 1088 }; 1089 } 1090 /** 1091 * @returns the position of the pattern within the atlas, or `null` if there is no such image 1092 */ 1093 getPattern(id) { 1094 const image = this._imageManager.getImage(id); 1095 if (!image) return null; 1096 const entry = this._entries[id]; 1097 if (entry?.image !== image) { 1098 const bin = { 1099 w: image.data.width + 2, 1100 h: image.data.height + 2, 1101 x: 0, 1102 y: 0 1103 }; 1104 this._entries[id] = { 1105 bin, 1106 position: new ImagePosition(bin, image), 1107 image 1108 }; 1109 } else if (entry.position.version !== image.version) entry.position.version = image.version; 1110 else return entry.position; 1111 this._update(); 1112 return this._entries[id].position; 1113 } 1114 bind(context) { 1115 const gl = context.gl; 1116 if (!this._texture) { 1117 this._texture = new Texture(context, this._image, gl.RGBA); 1118 this._dirty = false; 1119 } else if (this._dirty) { 1120 this._texture.update(this._image); 1121 this._dirty = false; 1122 } 1123 this._texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 1124 } 1125 _update() { 1126 for (const id in this._entries) if (!this._imageManager.getImage(id)) delete this._entries[id]; 1127 const bins = []; 1128 for (const id in this._entries) bins.push(this._entries[id].bin); 1129 const { w, h } = potpack(bins); 1130 const dst = this._image; 1131 dst.resize({ 1132 width: w || 1, 1133 height: h || 1 1134 }); 1135 for (const id in this._entries) { 1136 const { bin } = this._entries[id]; 1137 const x = bin.x + PADDING; 1138 const y = bin.y + PADDING; 1139 const src = this._entries[id].image.data; 1140 const w = src.width; 1141 const h = src.height; 1142 RGBAImage.copy(src, dst, { 1143 x: 0, 1144 y: 0 1145 }, { 1146 x, 1147 y 1148 }, { 1149 width: w, 1150 height: h 1151 }); 1152 RGBAImage.copy(src, dst, { 1153 x: 0, 1154 y: h - 1 1155 }, { 1156 x, 1157 y: y - 1 1158 }, { 1159 width: w, 1160 height: 1 1161 }); 1162 RGBAImage.copy(src, dst, { 1163 x: 0, 1164 y: 0 1165 }, { 1166 x, 1167 y: y + h 1168 }, { 1169 width: w, 1170 height: 1 1171 }); 1172 RGBAImage.copy(src, dst, { 1173 x: w - 1, 1174 y: 0 1175 }, { 1176 x: x - 1, 1177 y 1178 }, { 1179 width: 1, 1180 height: h 1181 }); 1182 RGBAImage.copy(src, dst, { 1183 x: 0, 1184 y: 0 1185 }, { 1186 x: x + w, 1187 y 1188 }, { 1189 width: 1, 1190 height: h 1191 }); 1192 } 1193 this._dirty = true; 1194 } 1195}; 1196//#endregion 1197//#region src/render/font_face_manager.ts 1198const MAX_CODE_POINT = 1114111; 1199/** 1200* What a font file covers when the style does not narrow it down with `unicode-range`. 1201*/ 1202const DEFAULT_UNICODE_RANGE = { 1203 start: 0, 1204 end: MAX_CODE_POINT 1205}; 1206/** 1207* Makes the family names unique across every map on the page, since `document.fonts` is shared. 1208*/ 1209let nextFamilyId = 0; 1210/** 1211* Parses one `unicode-range` entry, in the CSS grammar the style specification borrows: `U+A5`, 1212* `U+0-10FFFF` or `U+4??`. 1213* @param value - a single `unicode-range` entry 1214* @returns the codepoints it covers, or `null` if it is not a range this can make sense of 1215*/ 1216function parseUnicodeRange(value) { 1217 const wildcard = /^u\+([0-9a-f]*)(\?+)$/i.exec(value); 1218 if (wildcard) { 1219 const [, prefix, questionMarks] = wildcard; 1220 if (prefix.length + questionMarks.length > 6) return null; 1221 return clampRange(parseInt(`${prefix}${"0".repeat(questionMarks.length)}`, 16), parseInt(`${prefix}${"f".repeat(questionMarks.length)}`, 16)); 1222 } 1223 const explicit = /^u\+([0-9a-f]{1,6})(?:-([0-9a-f]{1,6}))?$/i.exec(value); 1224 if (!explicit) return null; 1225 const start = parseInt(explicit[1], 16); 1226 return clampRange(start, explicit[2] === void 0 ? start : parseInt(explicit[2], 16)); 1227} 1228function clampRange(start, end) { 1229 if (start > end || start > MAX_CODE_POINT) return null; 1230 return { 1231 start, 1232 end: Math.min(end, MAX_CODE_POINT) 1233 }; 1234} 1235/** 1236* Whether the style lets this file cover the given codepoint. 1237*/ 1238function covers(face, codePoint) { 1239 return face.ranges.some(({ start, end }) => codePoint >= start && codePoint <= end); 1240} 1241/** 1242* Keeps the font files a style declares in its 1243* [`font-faces`](https://maplibre.org/maplibre-style-spec/root/#font-faces) property, and hands the 1244* {@link GlyphManager} the CSS family to draw a given codepoint with. 1245* 1246* The files go to the browser's CSS Font Loading API, so any format it can render text with works. 1247* Each is registered under a family name of our own making, so that a style cannot restyle the 1248* surrounding page and a codepoint can be pinned to one file rather than to whatever font matching 1249* picks. 1250* 1251* Rasterizing happens a grapheme cluster at a time, so a letter reaches the browser's text engine 1252* with its marks. Shaping across cluster boundaries still needs the RTL text plugin. 1253*/ 1254var FontFaceManager = class { 1255 constructor(requestManager) { 1256 this.requestManager = requestManager; 1257 this._faces = {}; 1258 this._registered = /* @__PURE__ */ new Set(); 1259 } 1260 /** 1261 * Replaces the declared font faces. Nothing is downloaded here: each file waits until a 1262 * codepoint it covers is actually drawn. 1263 */ 1264 setFontFaces(fontFaces) { 1265 this._unregisterAll(); 1266 this._faces = {}; 1267 for (const [fontName, declaration] of Object.entries(fontFaces ?? {})) { 1268 const declarations = Array.isArray(declaration) ? declaration : [declaration]; 1269 this._faces[fontName] = declarations.map((entry) => this._declareFontFace(fontName, entry)).filter((face) => face !== null); 1270 } 1271 } 1272 /** 1273 * Whether the style declared any font face at all, so that the common case of a style without 1274 * `font-faces` costs nothing. 1275 */ 1276 hasFontFaces() { 1277 return Object.keys(this._faces).length > 0; 1278 } 1279 /** 1280 * Finds the font file to draw a codepoint with: each name of the `text-font` stack in turn, and 1281 * within a name each declared file, until one covers it. A file that fails to load is skipped, 1282 * the specification asking for unsupported fonts to be ignored. 1283 * 1284 * @returns the CSS family to draw with, or `null` to leave the codepoint to the `glyphs` URL 1285 */ 1286 async getFontFamily(fontStack, codePoint) { 1287 for (const fontName of fontStack.split(",")) for (const face of this._faces[fontName.trim()] ?? []) { 1288 if (!covers(face, codePoint)) continue; 1289 face.loaded ??= this._loadFontFace(face); 1290 if (await face.loaded) return face.family; 1291 } 1292 return null; 1293 } 1294 /** 1295 * Turns one declaration into a face to draw with, or `null` if there is nothing usable in it. 1296 * 1297 * A bare URL is the same as a face that names no `unicode-range`: it covers every codepoint. 1298 */ 1299 _declareFontFace(fontName, declaration) { 1300 const face = typeof declaration === "string" ? { url: declaration } : declaration; 1301 if (typeof face?.url !== "string") { 1302 warnOnce(`Ignoring the font face declared for "${fontName}": it has no URL.`); 1303 return null; 1304 } 1305 const family = `maplibre-gl-font-face-${nextFamilyId++}`; 1306 const unicodeRange = face["unicode-range"]; 1307 if (!unicodeRange?.length) return { 1308 url: face.url, 1309 ranges: [DEFAULT_UNICODE_RANGE], 1310 family 1311 }; 1312 const ranges = []; 1313 for (const entry of unicodeRange) { 1314 const range = parseUnicodeRange(entry); 1315 if (!range) { 1316 warnOnce(`Ignoring the unicode range "${entry}" of the font face at ${face.url}: it is not a valid range.`); 1317 continue; 1318 } 1319 ranges.push(range); 1320 } 1321 if (!ranges.length) return null; 1322 return { 1323 url: face.url, 1324 ranges, 1325 family 1326 }; 1327 } 1328 /** 1329 * Downloads a declared file and hands it to the browser, reporting whether it can be drawn with. 1330 * A failure is not an error: its codepoints fall through to the next file, then to `glyphs`. 1331 */ 1332 async _loadFontFace(face) { 1333 if (typeof FontFace === "undefined" || typeof document === "undefined" || !document.fonts) { 1334 warnOnce(`Ignoring the font face at ${face.url}: this environment has no CSS Font Loading API.`); 1335 return false; 1336 } 1337 let fontFace; 1338 try { 1339 fontFace = new FontFace(face.family, await this._downloadFontFile(face.url)); 1340 if (!Object.values(this._faces).some((faces) => faces.includes(face))) return false; 1341 document.fonts.add(fontFace); 1342 this._registered.add(fontFace); 1343 await fontFace.load(); 1344 return true; 1345 } catch (e) { 1346 if (fontFace) this._unregister(fontFace); 1347 warnOnce(`Ignoring the font face at ${face.url}: ${ensureError(e).message}`); 1348 return false; 1349 } 1350 } 1351 /** 1352 * Downloads a font file, giving the map's `transformRequest` a chance to rewrite the request the 1353 * same way it gets one for a glyph range. 1354 */ 1355 async _downloadFontFile(url) { 1356 const request = await this.requestManager.transformRequest(url, "Glyphs"); 1357 const response = await getArrayBuffer(request, new AbortController()); 1358 if (!response?.data) throw new Error(`the response was empty for the font file at ${url}`); 1359 return response.data; 1360 } 1361 _unregister(fontFace) { 1362 document.fonts?.delete(fontFace); 1363 this._registered.delete(fontFace); 1364 } 1365 _unregisterAll() { 1366 for (const fontFace of this._registered) document.fonts?.delete(fontFace); 1367 this._registered.clear(); 1368 } 1369 destroy() { 1370 this._unregisterAll(); 1371 this._faces = {}; 1372 } 1373}; 1374//#endregion 1375//#region node_modules/@mapbox/tiny-sdf/index.js 1376const INF = 0x56bc75e2d63100000; 1377const alphaTable = /* @__PURE__ */ new Float64Array(256); 1378for (let i = 0; i < 256; i++) { 1379 const d = .5 - Math.pow(i / 255, 1 / 2.2); 1380 alphaTable[i] = d * Math.abs(d); 1381} 1382alphaTable[255] = -0x56bc75e2d63100000; 1383var TinySDF = class { 1384 constructor({ fontSize = 24, buffer = 3, radius = 8, cutoff = .25, fontFamily = "sans-serif", fontWeight = "normal", fontStyle = "normal", lang = null } = {}) { 1385 this.buffer = buffer; 1386 this.radius = radius; 1387 this.cutoff = cutoff; 1388 this.lang = lang; 1389 const size = this.size = fontSize + buffer * 4; 1390 const canvas = this._createCanvas(size); 1391 const ctx = this.ctx = canvas.getContext("2d", { willReadFrequently: true }); 1392 ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`; 1393 ctx.textBaseline = "alphabetic"; 1394 ctx.textAlign = "left"; 1395 ctx.fillStyle = "black"; 1396 this.gridOuter = new Float64Array(size * size); 1397 this.gridInner = new Float64Array(size * size); 1398 this.f = new Float64Array(size); 1399 this.z = new Float64Array(size + 1); 1400 this.v = new Uint16Array(size); 1401 } 1402 _createCanvas(size) { 1403 if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(size, size); 1404 const canvas = document.createElement("canvas"); 1405 canvas.width = canvas.height = size; 1406 return canvas; 1407 } 1408 draw(char) { 1409 const { width: glyphAdvance, actualBoundingBoxAscent, actualBoundingBoxDescent, actualBoundingBoxLeft, actualBoundingBoxRight } = this.ctx.measureText(char); 1410 const glyphTop = Math.ceil(actualBoundingBoxAscent); 1411 const glyphLeft = Math.floor(-actualBoundingBoxLeft); 1412 const glyphWidth = Math.max(0, Math.min(this.size - this.buffer, Math.ceil(actualBoundingBoxRight) - glyphLeft)); 1413 const glyphHeight = Math.max(0, Math.min(this.size - this.buffer, glyphTop + Math.ceil(actualBoundingBoxDescent))); 1414 const width = glyphWidth + 2 * this.buffer; 1415 const height = glyphHeight + 2 * this.buffer; 1416 const len = Math.max(width * height, 0); 1417 const data = new Uint8ClampedArray(len); 1418 const glyph = { 1419 data, 1420 width, 1421 height, 1422 glyphWidth, 1423 glyphHeight, 1424 glyphTop, 1425 glyphLeft, 1426 glyphAdvance 1427 }; 1428 if (glyphWidth === 0 || glyphHeight === 0) return glyph; 1429 const { ctx, buffer, gridInner, gridOuter } = this; 1430 if (this.lang) ctx.lang = this.lang; 1431 ctx.clearRect(buffer, buffer, glyphWidth, glyphHeight); 1432 ctx.fillText(char, buffer - glyphLeft, buffer + glyphTop); 1433 const imgData = ctx.getImageData(buffer, buffer, glyphWidth, glyphHeight); 1434 gridOuter.fill(INF, 0, len); 1435 gridInner.fill(0, 0, len); 1436 let imgIdx = 3; 1437 for (let y = 0; y < glyphHeight; y++) { 1438 let j = (y + buffer) * width + buffer; 1439 for (let x = 0; x < glyphWidth; x++, imgIdx += 4, j++) { 1440 const a = imgData.data[imgIdx]; 1441 if (a === 0) continue; 1442 const t = alphaTable[a]; 1443 gridOuter[j] = Math.max(0, t); 1444 gridInner[j] = Math.max(0, -t); 1445 } 1446 } 1447 edt(gridOuter, 0, 0, width, height, width, this.f, this.v, this.z); 1448 const pad = Math.min(buffer, 1); 1449 edt(gridInner, buffer - pad, buffer - pad, glyphWidth + 2 * pad, glyphHeight + 2 * pad, width, this.f, this.v, this.z); 1450 const scale = 255 / this.radius; 1451 const base = 255 * (1 - this.cutoff); 1452 for (let i = 0; i < len; i++) { 1453 const d = Math.sqrt(gridOuter[i]) - Math.sqrt(gridInner[i]); 1454 data[i] = Math.round(base - scale * d); 1455 } 1456 return glyph; 1457 } 1458}; 1459function edt(data, x0, y0, width, height, gridSize, f, v, z) { 1460 for (let x = x0; x < x0 + width; x++) edt1d(data, y0 * gridSize + x, gridSize, height, f, v, z); 1461 for (let y = y0; y < y0 + height; y++) edt1d(data, y * gridSize + x0, 1, width, f, v, z); 1462} 1463function edt1d(grid, offset, stride, length, f, v, z) { 1464 v[0] = 0; 1465 z[0] = -0x56bc75e2d63100000; 1466 z[1] = INF; 1467 f[0] = grid[offset]; 1468 for (let q = 1, k = 0, s = 0; q < length; q++) { 1469 f[q] = grid[offset + q * stride]; 1470 const q2 = q * q; 1471 do { 1472 const r = v[k]; 1473 s = (f[q] - f[r] + q2 - r * r) / (q - r) / 2; 1474 } while (s <= z[k] && --k > -1); 1475 k++; 1476 v[k] = q; 1477 z[k] = s; 1478 z[k + 1] = INF; 1479 } 1480 for (let q = 0, k = 0; q < length; q++) { 1481 while (z[k + 1] < q) k++; 1482 const r = v[k]; 1483 const qr = q - r; 1484 grid[offset + q * stride] = f[r] + qr * qr; 1485 } 1486} 1487//#endregion 1488//#region src/render/glyph_manager.ts 1489/** 1490* The style specification hard-codes some last resort fonts as a default fontstack. 1491*/ 1492const defaultStack = latest.layout_symbol["text-font"].default.join(","); 1493/** 1494* The CSS generic font family closest to `defaultStack`. 1495*/ 1496const defaultGenericFontFamily = "sans-serif"; 1497/** 1498* Scale factor for client-generated glyphs. 1499* 1500* Client-generated glyphs are rendered at 2à because CJK glyphs are more detailed than others. 1501*/ 1502const textureScale = 2; 1503const defaultCreateRasterizer = (options, padding) => { 1504 const tinySDF = new TinySDF(options); 1505 tinySDF.buffer = padding; 1506 return tinySDF; 1507}; 1508/** 1509* How wide a grapheme cluster may be drawn, in multiples of the font size. 1510* 1511* TinySDF sizes its canvas for a single character and cuts off the rest, but a cluster is a whole 1512* syllable: Burmese `áá¬á¸` is nearly twice the font size. Three ems fits the ones that occur. 1513*/ 1514const clusterEmsWide = 3; 1515var GlyphManager = class { 1516 constructor(requestManager, localIdeographFontFamily, lang, createRasterizer = defaultCreateRasterizer) { 1517 this.requestManager = requestManager; 1518 this.localIdeographFontFamily = localIdeographFontFamily; 1519 this.entries = {}; 1520 this.lang = lang; 1521 this.fontFaceManager = new FontFaceManager(requestManager); 1522 this.createRasterizer = createRasterizer; 1523 } 1524 setURL(url) { 1525 this.url = url; 1526 }
vendor: 19,389 bytes, lines 1527-2066
1527 /** 1528 * Replaces the font files the style declares in its `font-faces` property, dropping every glyph 1529 * drawn with the previous ones. 1530 */ 1531 setFontFaces(fontFaces) { 1532 this.fontFaceManager.setFontFaces(fontFaces); 1533 this.entries = {}; 1534 } 1535 async getGlyphs(glyphs) { 1536 const glyphsPromises = []; 1537 for (const stack in glyphs) for (const id of glyphs[stack]) glyphsPromises.push(this._getAndCacheGlyphsPromise(stack, id)); 1538 const updatedGlyphs = await Promise.all(glyphsPromises); 1539 const result = {}; 1540 for (const { stack, id, glyph } of updatedGlyphs) { 1541 result[stack] ||= {}; 1542 result[stack][id] = glyph && { 1543 id: glyph.id, 1544 bitmap: glyph.bitmap.clone(), 1545 metrics: glyph.metrics 1546 }; 1547 } 1548 return result; 1549 } 1550 /** 1551 * Gets one glyph, asked for by grapheme cluster so that a letter and its marks are drawn as the 1552 * one shape they are written as. 1553 * 1554 * A cluster is drawn from a font rather than fetched, because a glyphs URL serves codepoints and 1555 * has no way to serve the one shape they are written as. A file the style pinned with 1556 * `font-faces` draws it where one covers it, and the local fonts otherwise. For a single 1557 * codepoint a declared file still wins over the glyphs URL and the local fallbacks. 1558 */ 1559 async _getAndCacheGlyphsPromise(stack, id) { 1560 this.entries[stack] ??= { 1561 glyphs: {}, 1562 requests: {}, 1563 ranges: {} 1564 }; 1565 const entry = this.entries[stack]; 1566 let glyph = entry.glyphs[id]; 1567 if (glyph !== void 0) return { 1568 stack, 1569 id, 1570 glyph 1571 }; 1572 const codePoint = id.codePointAt(0); 1573 const fontFaceFamily = this.fontFaceManager.hasFontFaces() ? await this.fontFaceManager.getFontFamily(stack, codePoint) : null; 1574 if (fontFaceFamily) { 1575 glyph = entry.glyphs[id] = await this._drawGlyph(entry, stack, id, fontFaceFamily); 1576 return { 1577 stack, 1578 id, 1579 glyph 1580 }; 1581 } 1582 if (!this.url || isCluster(id) || this._charUsesLocalIdeographFontFamily(codePoint)) { 1583 glyph = entry.glyphs[id] = await this._drawGlyph(entry, stack, id); 1584 return { 1585 stack, 1586 id, 1587 glyph 1588 }; 1589 } 1590 return await this._downloadAndCacheRangePromise(stack, id); 1591 } 1592 /** 1593 * Gets a glyph from the server-side cache, downloading the PBF range it falls in if need be. 1594 * 1595 * Only reached for a single codepoint. What comes back is keyed by codepoint, as the file is, 1596 * and is cached by cluster -- which for one codepoint is the character itself. 1597 */ 1598 async _downloadAndCacheRangePromise(stack, id) { 1599 const codePoint = id.codePointAt(0); 1600 const entry = this.entries[stack]; 1601 const range = Math.floor(codePoint / 256); 1602 if (entry.ranges[range]) return { 1603 stack, 1604 id, 1605 glyph: null 1606 }; 1607 entry.requests[range] ||= this._loadGlyphRange(stack, range); 1608 try { 1609 const response = await entry.requests[range]; 1610 for (const responseId in response) entry.glyphs[String.fromCodePoint(+responseId)] = response[+responseId]; 1611 entry.ranges[range] = true; 1612 return { 1613 stack, 1614 id, 1615 glyph: response[codePoint] || null 1616 }; 1617 } catch (e) { 1618 const glyph = entry.glyphs[id] = await this._drawGlyph(entry, stack, id); 1619 this._warnOnMissingGlyphRange(glyph, range, codePoint, ensureError(e)); 1620 return { 1621 stack, 1622 id, 1623 glyph 1624 }; 1625 } 1626 } 1627 /** 1628 * Downloads one range of 256 codepoints from the glyphs URL and parses the glyphs out of it. 1629 */ 1630 async _loadGlyphRange(fontstack, range) { 1631 const begin = range * 256; 1632 const end = begin + 255; 1633 const request = await this.requestManager.transformRequest(this.url.replace("{fontstack}", fontstack).replace("{range}", `${begin}-${end}`), "Glyphs"); 1634 const response = await getArrayBuffer(request, new AbortController()); 1635 if (!response?.data) throw new Error(`Could not load glyph range. range: ${range}, ${begin}-${end}`); 1636 const glyphs = {}; 1637 for (const glyph of parseGlyphPbf(response.data)) glyphs[glyph.id] = glyph; 1638 return glyphs; 1639 } 1640 _warnOnMissingGlyphRange(glyph, range, id, err) { 1641 const begin = range * 256; 1642 const end = begin + 255; 1643 const codePoint = id.toString(16).padStart(4, "0").toUpperCase(); 1644 warnOnce(`Unable to load glyph range ${range}, ${begin}-${end}. Rendering codepoint U+${codePoint} locally instead. ${err}`); 1645 } 1646 /** 1647 * Returns whether the given codepoint should be rendered locally. 1648 */ 1649 _charUsesLocalIdeographFontFamily(id) { 1650 return !!this.localIdeographFontFamily && codePointUsesLocalIdeographFontFamily(id); 1651 } 1652 /** 1653 * Draws a glyph offscreen using TinySDF, created lazily. The whole cluster goes to TinySDF, which 1654 * is what lets the browser's text engine place a letter's marks on it. 1655 * 1656 * @param fontFaceFamily - the CSS family of the `font-faces` file covering this codepoint, if any 1657 */ 1658 async _drawGlyph(entry, stack, id, fontFaceFamily) { 1659 const char = (await this._getTinySDF(entry, stack, id, fontFaceFamily)).draw(id); 1660 /** 1661 * TinySDF's "top" is the distance from the alphabetic baseline to the top of the glyph. 1662 * Server-generated fonts specify "top" relative to an origin above the em box (the origin 1663 * comes from FreeType, but I'm unclear on exactly how it's derived) 1664 * ref: https://github.com/mapbox/sdf-glyph-foundry 1665 * 1666 * Server fonts don't yet include baseline information, so we can't line up exactly with them 1667 * (and they don't line up with each other) 1668 * ref: https://github.com/mapbox/node-fontnik/pull/160 1669 * 1670 * To approximately align TinySDF glyphs with server-provided glyphs, we use this baseline adjustment 1671 * factor calibrated to be in between DIN Pro and Arial Unicode (but closer to Arial Unicode) 1672 */ 1673 const topAdjustment = 27.5; 1674 const leftAdjustment = .5; 1675 const isControl = /^\p{gc=Cf}+$/u.test(id); 1676 return { 1677 id: id.codePointAt(0), 1678 bitmap: new AlphaImage({ 1679 width: char.width || 60, 1680 height: char.height || 60 1681 }, char.data), 1682 metrics: { 1683 width: isControl ? 0 : char.glyphWidth / textureScale || 24, 1684 height: char.glyphHeight / textureScale || 24, 1685 left: char.glyphLeft / textureScale + leftAdjustment || 0, 1686 top: char.glyphTop / textureScale - topAdjustment || -8, 1687 advance: isControl ? 0 : char.glyphAdvance / textureScale || 24, 1688 isDoubleResolution: true 1689 } 1690 }; 1691 } 1692 /** 1693 * Returns the TinySDF that draws this grapheme, created lazily. A stack keeps one per font 1694 * selection, so a fallback cannot bleed into the rest of the text, and one more per font file 1695 * for the clusters drawn from it, which need a wider canvas. 1696 * 1697 * A font file carries its own weight and style, so neither is sniffed out of the family name. 1698 * Where no file covers the grapheme, `localIdeographFontFamily` beats the last resort fontstack. 1699 */ 1700 _getTinySDF(entry, stack, id, fontFaceFamily) { 1701 const cluster = isCluster(id); 1702 if (fontFaceFamily) { 1703 const cache = cluster ? "clusterTinySDFs" : "fontFaceTinySDFs"; 1704 entry[cache] ??= {}; 1705 entry[cache][fontFaceFamily] ||= this._createTinySDF(fontFaceFamily, false, cluster ? clusterEmsWide : 1); 1706 return entry[cache][fontFaceFamily]; 1707 } 1708 const usesLocalIdeographFontFamily = stack === defaultStack && this.localIdeographFontFamily !== "" && this._charUsesLocalIdeographFontFamily(id.codePointAt(0)); 1709 const family = usesLocalIdeographFontFamily ? this.localIdeographFontFamily : stack; 1710 if (cluster) { 1711 entry.clusterTinySDFs ??= {}; 1712 entry.clusterTinySDFs[family] ||= this._createTinySDF(family, true, clusterEmsWide); 1713 return entry.clusterTinySDFs[family]; 1714 } 1715 const cache = usesLocalIdeographFontFamily ? "ideographTinySDF" : "tinySDF"; 1716 entry[cache] ||= this._createTinySDF(family); 1717 return entry[cache]; 1718 } 1719 /** 1720 * Builds the TinySDF that draws with a given font selection. 1721 * 1722 * TinySDF derives its canvas from `fontSize + buffer * 4`, so the buffer is the only way in to a 1723 * wider one. It also stands for the padding the atlas expects to be `GLYPH_PBF_BORDER`, so the 1724 * two are passed separately. 1725 * 1726 * @param emsWide - how wide the glyphs may be, in font sizes, before TinySDF cuts them off 1727 */ 1728 async _createTinySDF(stack, sniffFontStyles = true, emsWide = 1) { 1729 const fontFamilies = stack ? stack.split(",") : []; 1730 fontFamilies.push(defaultGenericFontFamily); 1731 const fontFamily = fontFamilies.map((fontName) => /[-\w]+/.test(fontName) ? fontName : `'${CSS.escape(fontName)}'`).join(","); 1732 const fontSize = 48; 1733 const fontWeight = sniffFontStyles ? this._fontWeight(fontFamilies[0]) : void 0; 1734 const fontStyle = sniffFontStyles ? this._fontStyle(fontFamilies[0]) : "normal"; 1735 if (typeof document !== "undefined" && document.fonts?.load) try { 1736 await document.fonts.load(`${fontStyle} ${fontWeight || "normal"} ${fontSize}px ${fontFamily}`); 1737 } catch (e) { 1738 warnOnce(`Failed to load font "${fontFamily}": ${ensureError(e).message}`); 1739 } 1740 const padding = 6; 1741 return this.createRasterizer({ 1742 fontSize, 1743 buffer: Math.max(padding, Math.ceil(fontSize * (emsWide - 1) / 4)), 1744 radius: 16, 1745 cutoff: .25, 1746 fontFamily, 1747 fontWeight, 1748 fontStyle, 1749 lang: this.lang 1750 }, padding); 1751 } 1752 /** 1753 * Sniffs the font style out of a font family name. 1754 */ 1755 _fontStyle(fontFamily) { 1756 if (/italic/i.test(fontFamily)) return "italic"; 1757 else if (/oblique/i.test(fontFamily)) return "oblique"; 1758 return "normal"; 1759 } 1760 /** 1761 * Sniffs the font weight out of a font family name. 1762 */ 1763 _fontWeight(fontFamily) { 1764 const weightsByName = { 1765 thin: 100, 1766 hairline: 100, 1767 "extra light": 200, 1768 "ultra light": 200, 1769 light: 300, 1770 normal: 400, 1771 regular: 400, 1772 medium: 500, 1773 semibold: 600, 1774 demibold: 600, 1775 bold: 700, 1776 "extra bold": 800, 1777 "ultra bold": 800, 1778 black: 900, 1779 heavy: 900, 1780 "extra black": 950, 1781 "ultra black": 950 1782 }; 1783 let match; 1784 for (const [name, weight] of Object.entries(weightsByName)) if (new RegExp(`\\b${name}\\b`, "i").test(fontFamily)) match = `${weight}`; 1785 return match; 1786 } 1787 destroy() { 1788 for (const stack in this.entries) { 1789 const entry = this.entries[stack]; 1790 entry.tinySDF = null; 1791 entry.ideographTinySDF = null; 1792 entry.fontFaceTinySDFs = {}; 1793 entry.glyphs = {}; 1794 entry.requests = {}; 1795 entry.ranges = {}; 1796 } 1797 this.entries = {}; 1798 this.fontFaceManager.destroy(); 1799 } 1800}; 1801//#endregion 1802//#region src/style/light_properties.g.ts 1803let properties$2; 1804const getProperties$2 = () => properties$2 = properties$2 || new Properties({ 1805 "anchor": new DataConstantProperty(latest["light"]["anchor"], "anchor"), 1806 "position": new DataConstantProperty(latest["light"]["position"], "position"), 1807 "color": new DataConstantProperty(latest["light"]["color"], "color"), 1808 "intensity": new DataConstantProperty(latest["light"]["intensity"], "intensity") 1809}); 1810//#endregion 1811//#region src/style/light.ts 1812var Light = class extends Evented { 1813 constructor(lightOptions, globalState) { 1814 super(); 1815 this._transitionable = new Transitionable(getProperties$2(), "light", globalState); 1816 this.setLight(lightOptions); 1817 this._transitioning = this._transitionable.untransitioned(); 1818 } 1819 getLight() { 1820 return this._transitionable.serialize(); 1821 } 1822 /** 1823 * Gets the light position in cartesian coordinates. 1824 */ 1825 getCartesianPosition() { 1826 return sphericalToCartesian(this.properties.get("position")); 1827 } 1828 setLight(light, options = {}) { 1829 if (this._validate(validateStyle.light, light, options)) return; 1830 this._transitionable.setValues(light); 1831 } 1832 updateTransitions(parameters) { 1833 this._transitioning = this._transitionable.transitioned(parameters, this._transitioning); 1834 } 1835 hasTransition() { 1836 return this._transitioning.hasTransition(); 1837 } 1838 applyGlobalStateChange(refs, priorGlobalState, parameters) { 1839 this._transitioning = this._transitionable.applyGlobalStateChange(refs, priorGlobalState, this._transitioning, parameters); 1840 } 1841 recalculate(parameters) { 1842 this.properties = this._transitioning.possiblyEvaluate(parameters); 1843 } 1844 _validate(validate, value, options) { 1845 return validateAndEmit(this, validate, { value }, options); 1846 } 1847}; 1848//#endregion 1849//#region src/style/sky_properties.g.ts 1850let properties$1; 1851const getProperties$1 = () => properties$1 = properties$1 || new Properties({ 1852 "sky-color": new DataConstantProperty(latest["sky"]["sky-color"], "sky-color"), 1853 "horizon-color": new DataConstantProperty(latest["sky"]["horizon-color"], "horizon-color"), 1854 "fog-color": new DataConstantProperty(latest["sky"]["fog-color"], "fog-color"), 1855 "fog-ground-blend": new DataConstantProperty(latest["sky"]["fog-ground-blend"], "fog-ground-blend"), 1856 "horizon-fog-blend": new DataConstantProperty(latest["sky"]["horizon-fog-blend"], "horizon-fog-blend"), 1857 "sky-horizon-blend": new DataConstantProperty(latest["sky"]["sky-horizon-blend"], "sky-horizon-blend"), 1858 "atmosphere-blend": new DataConstantProperty(latest["sky"]["atmosphere-blend"], "atmosphere-blend") 1859}); 1860//#endregion 1861//#region src/style/sky.ts 1862var Sky = class extends Evented { 1863 constructor(sky, globalState) { 1864 super(); 1865 this._transitionable = new Transitionable(getProperties$1(), "sky", globalState); 1866 this.setSky(sky); 1867 this._transitioning = this._transitionable.untransitioned(); 1868 this.recalculate(new EvaluationParameters(0)); 1869 } 1870 /** 1871 * Validates and applies the sky. Returns false, after firing `error`, when 1872 * the value was rejected and nothing changed. 1873 */ 1874 setSky(sky, options = {}) { 1875 if (this._validate(validateStyle.sky, sky, options)) return false; 1876 sky ||= { 1877 "sky-color": "transparent", 1878 "horizon-color": "transparent", 1879 "fog-color": "transparent", 1880 "fog-ground-blend": 1, 1881 "atmosphere-blend": 0 1882 }; 1883 this._transitionable.setValues(sky); 1884 return true; 1885 } 1886 getSky() { 1887 return this._transitionable.serialize(); 1888 } 1889 updateTransitions(parameters) { 1890 this._transitioning = this._transitionable.transitioned(parameters, this._transitioning); 1891 } 1892 hasTransition() { 1893 return this._transitioning.hasTransition(); 1894 } 1895 applyGlobalStateChange(refs, priorGlobalState, parameters) { 1896 this._transitioning = this._transitionable.applyGlobalStateChange(refs, priorGlobalState, this._transitioning, parameters); 1897 } 1898 recalculate(parameters) { 1899 this.properties = this._transitioning.possiblyEvaluate(parameters); 1900 } 1901 _validate(validate, value, options = {}) { 1902 return validateAndEmit(this, validate, { value }, options); 1903 } 1904 /** 1905 * Currently fog is a very simple implementation, and should only used 1906 * to create an atmosphere near the horizon. 1907 * But because the fog is drawn from the far-clipping-plane to 1908 * map-center, and because the fog does nothing know about the horizon, 1909 * this method does a fadeout in respect of pitch. So, when the horizon 1910 * gets out of view, which is at about pitch 70, this methods calculates 1911 * the corresponding opacity values. Below pitch 60 the fog is completely 1912 * invisible. 1913 */ 1914 calculateFogBlendOpacity(pitch) { 1915 if (pitch < 60) return 0; 1916 if (pitch < 70) return (pitch - 60) / 10; 1917 return 1; 1918 } 1919}; 1920//#endregion 1921//#region src/render/line_atlas.ts 1922/** 1923* @internal 1924* A LineAtlas lets us reuse rendered dashed lines 1925* by writing many of them to a texture and then fetching their positions 1926* using {@link LineAtlas.getDash}. 1927* 1928* @param width - the width 1929* @param height - the height 1930*/ 1931var LineAtlas = class { 1932 constructor(width, height) { 1933 this.width = width; 1934 this.height = height; 1935 this.nextRow = 0; 1936 this.data = new Uint8Array(this.width * this.height); 1937 this.dashEntry = {}; 1938 } 1939 /** 1940 * Get or create a dash line pattern. 1941 * 1942 * @param dasharray - the key (represented by numbers) to get the dash texture 1943 * @param round - whether to add circle caps in between dash segments 1944 * @returns position of dash texture in {@link DashEntry} 1945 */ 1946 getDash(dasharray, round) { 1947 const key = dasharray.join(",") + String(round); 1948 this.dashEntry[key] ||= this.addDash(dasharray, round); 1949 return this.dashEntry[key]; 1950 } 1951 getDashRanges(dasharray, lineAtlasWidth, stretch) { 1952 const oddDashArray = dasharray.length % 2 === 1; 1953 const ranges = []; 1954 let left = oddDashArray ? -dasharray[dasharray.length - 1] * stretch : 0; 1955 let right = dasharray[0] * stretch; 1956 let isDash = true; 1957 ranges.push({ 1958 left, 1959 right, 1960 isDash, 1961 zeroLength: dasharray[0] === 0 1962 }); 1963 let currentDashLength = dasharray[0]; 1964 for (let i = 1; i < dasharray.length; i++) { 1965 isDash = !isDash; 1966 const dashLength = dasharray[i]; 1967 left = currentDashLength * stretch; 1968 currentDashLength += dashLength; 1969 right = currentDashLength * stretch; 1970 ranges.push({ 1971 left, 1972 right, 1973 isDash, 1974 zeroLength: dashLength === 0 1975 }); 1976 } 1977 return ranges; 1978 } 1979 addRoundDash(ranges, stretch, n) { 1980 const halfStretch = stretch / 2; 1981 for (let y = -n; y <= n; y++) { 1982 const row = this.nextRow + n + y; 1983 const index = this.width * row; 1984 let currIndex = 0; 1985 let range = ranges[currIndex]; 1986 for (let x = 0; x < this.width; x++) { 1987 if (x / range.right > 1) range = ranges[++currIndex]; 1988 const distLeft = Math.abs(x - range.left); 1989 const distRight = Math.abs(x - range.right); 1990 const minDist = Math.min(distLeft, distRight); 1991 let signedDistance; 1992 const distMiddle = y / n * (halfStretch + 1); 1993 if (range.isDash) { 1994 const distEdge = halfStretch - Math.abs(distMiddle); 1995 signedDistance = Math.sqrt(minDist * minDist + distEdge * distEdge); 1996 } else signedDistance = halfStretch - Math.sqrt(minDist * minDist + distMiddle * distMiddle); 1997 this.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128)); 1998 } 1999 } 2000 } 2001 addRegularDash(ranges) { 2002 for (let i = ranges.length - 1; i >= 0; --i) { 2003 const part = ranges[i]; 2004 const next = ranges[i + 1]; 2005 if (part.zeroLength) ranges.splice(i, 1); 2006 else if (next?.isDash === part.isDash) { 2007 next.left = part.left; 2008 ranges.splice(i, 1); 2009 } 2010 } 2011 const first = ranges[0]; 2012 const last = ranges[ranges.length - 1]; 2013 if (first.isDash === last.isDash) { 2014 first.left = last.left - this.width; 2015 last.right = first.right + this.width; 2016 } 2017 const index = this.width * this.nextRow; 2018 let currIndex = 0; 2019 let range = ranges[currIndex]; 2020 for (let x = 0; x < this.width; x++) { 2021 if (x / range.right > 1) range = ranges[++currIndex]; 2022 const distLeft = Math.abs(x - range.left); 2023 const distRight = Math.abs(x - range.right); 2024 const minDist = Math.min(distLeft, distRight); 2025 const signedDistance = range.isDash ? minDist : -minDist; 2026 this.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128)); 2027 } 2028 } 2029 addDash(dasharray, round) { 2030 const n = round ? 7 : 0; 2031 const height = 2 * n + 1; 2032 if (this.nextRow + height > this.height) { 2033 warnOnce("LineAtlas out of space"); 2034 return null; 2035 } 2036 let length = 0; 2037 for (const dash of dasharray) length += dash; 2038 if (length !== 0) { 2039 const stretch = this.width / length; 2040 const ranges = this.getDashRanges(dasharray, this.width, stretch); 2041 if (round) this.addRoundDash(ranges, stretch, n); 2042 else this.addRegularDash(ranges); 2043 } 2044 const dashEntry = { 2045 y: this.nextRow + n, 2046 height: 2 * n, 2047 width: length 2048 }; 2049 this.nextRow += height; 2050 this.dirty = true; 2051 return dashEntry; 2052 } 2053 bind(context) { 2054 const gl = context.gl; 2055 if (!this.texture) { 2056 this.texture = gl.createTexture(); 2057 gl.bindTexture(gl.TEXTURE_2D, this.texture); 2058 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); 2059 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 2060 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 2061 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 2062 gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, this.width, this.height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, this.data); 2063 } else { 2064 gl.bindTexture(gl.TEXTURE_2D, this.texture); 2065 if (this.dirty) { 2066 this.dirty = false;
vendor: 48,123 bytes, lines 2067-3477
2067 gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, this.height, gl.ALPHA, gl.UNSIGNED_BYTE, this.data); 2068 } 2069 } 2070 } 2071}; 2072//#endregion 2073//#region src/util/web_worker.ts 2074function isCrossOrigin(url) { 2075 if (!url) return false; 2076 const loc = globalThis.location; 2077 if (!loc) return false; 2078 try { 2079 return new URL(url, loc.href).origin !== loc.origin; 2080 } catch { 2081 return false; 2082 } 2083} 2084function defaultWorkerUrl() { 2085 const moduleUrl = import.meta.url; 2086 if (!/^https?:/.test(moduleUrl)) return ""; 2087 const workerName = moduleUrl.endsWith("-dev.mjs") ? "maplibre-gl-worker-dev.mjs" : "maplibre-gl-worker.mjs"; 2088 return new URL(`./${workerName}`, moduleUrl).href; 2089} 2090function createWorker(url, asModule) { 2091 if (asModule) try { 2092 return new Worker(url, { type: "module" }); 2093 } catch (e) { 2094 console.warn("Module worker not supported, falling back to classic worker", e); 2095 } 2096 return new Worker(url); 2097} 2098async function fetchAsBlobUrl(url) { 2099 const response = await fetch(url); 2100 if (!response.ok) throw new Error(`Failed to fetch worker script (${response.status}): ${url}`); 2101 const code = await response.text(); 2102 const blob = new Blob([code], { type: "text/javascript" }); 2103 return URL.createObjectURL(blob); 2104} 2105function importAsBlobUrl(url) { 2106 const blob = new Blob([`import ${JSON.stringify(new URL(url, import.meta.url).href)}`], { type: "text/javascript" }); 2107 return URL.createObjectURL(blob); 2108} 2109async function workerFactory() { 2110 const url = config.WORKER_URL || defaultWorkerUrl(); 2111 const asModule = url?.endsWith(".cjs") ? false : true; 2112 if (!isCrossOrigin(url)) return createWorker(url, asModule); 2113 if (asModule) { 2114 const blobUrl = importAsBlobUrl(url); 2115 try { 2116 return createWorker(blobUrl, asModule); 2117 } finally { 2118 URL.revokeObjectURL(blobUrl); 2119 } 2120 } 2121 const blobUrl = await fetchAsBlobUrl(url); 2122 try { 2123 return createWorker(blobUrl, asModule); 2124 } finally { 2125 URL.revokeObjectURL(blobUrl); 2126 } 2127} 2128//#endregion 2129//#region src/util/worker_pool.ts 2130const PRELOAD_POOL_ID = "maplibre_preloaded_worker_pool"; 2131/** 2132* Constructs a worker pool. 2133*/ 2134var WorkerPool = class WorkerPool { 2135 constructor() { 2136 this.active = {}; 2137 this.workersPromise = null; 2138 } 2139 async acquire(mapId) { 2140 this.active[mapId] = true; 2141 if (!this.workersPromise) { 2142 const promises = []; 2143 while (promises.length < WorkerPool.workerCount) promises.push(workerFactory()); 2144 this.workersPromise = Promise.all(promises); 2145 } 2146 return (await this.workersPromise).slice(); 2147 } 2148 release(mapId) { 2149 delete this.active[mapId]; 2150 if (this.numActive() === 0 && this.workersPromise) { 2151 const promise = this.workersPromise; 2152 this.workersPromise = null; 2153 promise.then((workers) => { 2154 for (const w of workers) w.terminate(); 2155 }); 2156 } 2157 } 2158 isPreloaded() { 2159 return !!this.active[PRELOAD_POOL_ID]; 2160 } 2161 numActive() { 2162 return Object.keys(this.active).length; 2163 } 2164}; 2165const availableLogicalProcessors = Math.floor(browser.hardwareConcurrency / 2); 2166WorkerPool.workerCount = isSafari(globalThis) ? Math.max(Math.min(availableLogicalProcessors, 3), 1) : 1; 2167//#endregion 2168//#region src/util/global_worker_pool.ts 2169let globalWorkerPool; 2170/** 2171* Creates (if necessary) and returns the single, global WorkerPool instance 2172* to be shared across each Map 2173*/ 2174function getGlobalWorkerPool() { 2175 globalWorkerPool ||= new WorkerPool(); 2176 return globalWorkerPool; 2177} 2178/** 2179* Initializes resources like WebWorkers that can be shared across maps to lower load 2180* times in some situations. `setWorkerUrl()` and `setWorkerCount()`, if being 2181* used, must be set before `prewarm()` is called to have an effect. 2182* 2183* By default, the lifecycle of these resources is managed automatically, and they are 2184* lazily initialized when a Map is first created. By invoking `prewarm()`, these 2185* resources will be created ahead of time, and will not be cleared when the last Map 2186* is removed from the page. This allows them to be re-used by new Map instances that 2187* are created later. They can be manually cleared by calling 2188* `clearPrewarmedResources()`. This is only necessary if your web page remains 2189* active but stops using maps altogether. 2190* 2191* This is primarily useful when using GL-JS maps in a single page app, wherein a user 2192* would navigate between various views that can cause Map instances to constantly be 2193* created and destroyed. 2194* 2195* @example 2196* ```ts 2197* prewarm() 2198* ``` 2199*/ 2200function prewarm() { 2201 getGlobalWorkerPool().acquire(PRELOAD_POOL_ID); 2202} 2203/** 2204* Clears up resources that have previously been created by `prewarm()`. 2205* Note that this is typically not necessary. You should only call this function 2206* if you expect the user of your app to not return to a Map view at any point 2207* in your application. 2208* 2209* @example 2210* ```ts 2211* clearPrewarmedResources() 2212* ``` 2213*/ 2214function clearPrewarmedResources() { 2215 const pool = globalWorkerPool; 2216 if (pool) { 2217 if (pool.isPreloaded() && pool.numActive() === 1) { 2218 pool.release(PRELOAD_POOL_ID); 2219 globalWorkerPool = null; 2220 } 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()"); 2221 } 2222} 2223//#endregion 2224//#region src/util/dispatcher.ts 2225/** 2226* Responsible for sending messages from a {@link Source} to an associated worker source (usually with the same name). 2227*/ 2228var Dispatcher = class extends Evented { 2229 constructor(workerPool, mapId) { 2230 super(); 2231 this.workerPool = workerPool; 2232 this.actors = []; 2233 this.currentActor = 0; 2234 this.id = mapId; 2235 this.removed = false; 2236 this.workerErrorSubscriptions = []; 2237 this.actorsPromise = this.initActors(mapId); 2238 } 2239 async initActors(mapId) { 2240 const workers = await this.workerPool.acquire(mapId); 2241 if (this.removed) return []; 2242 this.actors = workers.map((worker, i) => { 2243 this.workerErrorSubscriptions.push(subscribe(worker, "error", () => { 2244 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("Worker failed to load. Check that the worker URL is correct."))); 2245 }, false)); 2246 const actor = new Actor(worker, mapId); 2247 actor.name = `Worker ${i}`; 2248 return actor; 2249 }); 2250 if (!this.actors.length) throw new Error("No actors found"); 2251 return this.actors; 2252 } 2253 /** 2254 * Broadcast a message to all Workers. 2255 */ 2256 async broadcast(type, data) { 2257 const actors = await this.actorsPromise; 2258 return Promise.all(actors.map((actor) => actor.sendAsync({ 2259 type, 2260 data 2261 }))); 2262 } 2263 /** 2264 * Acquires an actor to dispatch messages to. The actors are distributed in round-robin fashion. 2265 * @returns An actor object backed by a web worker for processing messages. 2266 */ 2267 async getActor() { 2268 const actors = await this.actorsPromise; 2269 this.currentActor = (this.currentActor + 1) % actors.length; 2270 return actors[this.currentActor]; 2271 } 2272 async waitForInitComplete() { 2273 if (this.actors.length === 0) await this.actorsPromise; 2274 } 2275 getReadyActor() { 2276 this.currentActor = (this.currentActor + 1) % this.actors.length; 2277 return this.actors[this.currentActor]; 2278 } 2279 remove(mapRemoved = true) { 2280 this.removed = true; 2281 for (const actor of this.actors) actor.remove(); 2282 for (const subscription of this.workerErrorSubscriptions) subscription.unsubscribe(); 2283 this.actors = []; 2284 this.workerErrorSubscriptions = []; 2285 this.setEventedParent(null); 2286 if (mapRemoved) this.workerPool.release(this.id); 2287 } 2288 async registerMessageHandler(type, handler) { 2289 const actors = await this.actorsPromise; 2290 for (const actor of actors) actor.registerMessageHandler(type, handler); 2291 } 2292 async unregisterMessageHandler(type) { 2293 const actors = await this.actorsPromise; 2294 for (const actor of actors) actor.unregisterMessageHandler(type); 2295 } 2296}; 2297let globalDispatcher; 2298/** 2299* This function is used to get the global dispatcher that is shared across all maps instances. 2300* It is used by the main thread to send messages to the workers, and by the workers to send messages back to the main thread. 2301* If you import a script into the worker and need to send a message to the workers to pass some parameters for example, 2302* you can use this function to get the global dispatcher and send a message to the workers. 2303* @returns The global dispatcher instance. 2304*/ 2305function getGlobalDispatcher() { 2306 if (!globalDispatcher) { 2307 globalDispatcher = new Dispatcher(getGlobalWorkerPool(), GLOBAL_DISPATCHER_ID); 2308 globalDispatcher.registerMessageHandler("GR", (_mapId, params, abortController) => { 2309 return makeRequest(params, abortController); 2310 }); 2311 } 2312 return globalDispatcher; 2313} 2314//#endregion 2315//#region src/source/query_features.ts 2316function getPixelPosMatrix(transform, tileID) { 2317 const t = create$1(); 2318 translate(t, t, [ 2319 1, 2320 1, 2321 0 2322 ]); 2323 scale(t, t, [ 2324 transform.width * .5, 2325 transform.height * .5, 2326 1 2327 ]); 2328 if (transform.calculatePosMatrix) return multiply(t, t, transform.calculatePosMatrix(tileID.toUnwrapped())); 2329 else return t; 2330} 2331function queryIncludes3DLayer(layers, styleLayers, sourceID) { 2332 if (layers) for (const layerID of layers) { 2333 const layer = styleLayers[layerID]; 2334 if (layer?.source === sourceID && layer.type === "fill-extrusion") return true; 2335 } 2336 else for (const key in styleLayers) { 2337 const layer = styleLayers[key]; 2338 if (layer.source === sourceID && layer.type === "fill-extrusion") return true; 2339 } 2340 return false; 2341} 2342function queryRenderedFeatures(tileManager, styleLayers, serializedLayers, queryGeometry, params, transform, getElevation) { 2343 const has3DLayer = queryIncludes3DLayer(params?.layers ?? null, styleLayers, tileManager.id); 2344 const maxPitchScaleFactor = transform.maxPitchScaleFactor(); 2345 const tilesIn = tileManager.tilesIn(queryGeometry, maxPitchScaleFactor, has3DLayer); 2346 tilesIn.sort(sortTilesIn); 2347 const renderedFeatureLayers = []; 2348 for (const tileIn of tilesIn) renderedFeatureLayers.push({ 2349 wrappedTileID: tileIn.tileID.wrapped().key, 2350 queryResults: tileIn.tile.queryRenderedFeatures(styleLayers, serializedLayers, tileManager.getState(), tileIn.queryGeometry, tileIn.cameraQueryGeometry, tileIn.scale, params, transform, maxPitchScaleFactor, getPixelPosMatrix(transform, tileIn.tileID), getElevation ? (x, y) => getElevation(tileIn.tileID, x, y) : void 0) 2351 }); 2352 return convertFeaturesToMapFeatures(mergeRenderedFeatureLayers(renderedFeatureLayers), tileManager); 2353} 2354function queryRenderedSymbols(styleLayers, serializedLayers, tileManagers, queryGeometry, params, collisionIndex, retainedQueryData) { 2355 const result = {}; 2356 const renderedSymbols = collisionIndex.queryRenderedSymbols(queryGeometry); 2357 const bucketQueryData = []; 2358 for (const bucketInstanceId of Object.keys(renderedSymbols).map(Number)) bucketQueryData.push(retainedQueryData[bucketInstanceId]); 2359 bucketQueryData.sort(sortTilesIn); 2360 for (const queryData of bucketQueryData) { 2361 const bucketSymbols = queryData.featureIndex.lookupSymbolFeatures(renderedSymbols[queryData.bucketInstanceId], serializedLayers, queryData.bucketIndex, queryData.sourceLayerIndex, { 2362 filterSpec: params.filter, 2363 globalState: params.globalState 2364 }, params.layers, params.availableImages, styleLayers); 2365 for (const layerID in bucketSymbols) { 2366 result[layerID] ||= []; 2367 const layerSymbols = bucketSymbols[layerID]; 2368 layerSymbols.sort((a, b) => { 2369 const featureSortOrder = queryData.featureSortOrder; 2370 if (featureSortOrder) { 2371 const sortedA = featureSortOrder.indexOf(a.featureIndex); 2372 return featureSortOrder.indexOf(b.featureIndex) - sortedA; 2373 } else return b.featureIndex - a.featureIndex; 2374 }); 2375 for (const symbolFeature of layerSymbols) result[layerID].push(symbolFeature); 2376 } 2377 } 2378 return convertFeaturesToMapFeaturesMultiple(result, styleLayers, tileManagers); 2379} 2380function querySourceFeatures(tileManager, params) { 2381 const tiles = tileManager.getRenderableIds().map((id) => { 2382 return tileManager.getTileByID(id); 2383 }); 2384 const result = []; 2385 const dataTiles = {}; 2386 for (const tile of tiles) { 2387 const dataID = tile.tileID.canonical.key; 2388 if (!dataTiles[dataID]) { 2389 dataTiles[dataID] = true; 2390 tile.querySourceFeatures(result, params); 2391 } 2392 } 2393 return result; 2394} 2395function sortTilesIn(a, b) { 2396 const idA = a.tileID; 2397 const idB = b.tileID; 2398 return idA.overscaledZ - idB.overscaledZ || idA.canonical.y - idB.canonical.y || idA.wrap - idB.wrap || idA.canonical.x - idB.canonical.x; 2399} 2400function mergeRenderedFeatureLayers(tiles) { 2401 const result = {}; 2402 const wrappedIDLayerMap = {}; 2403 for (const { queryResults, wrappedTileID } of tiles) { 2404 wrappedIDLayerMap[wrappedTileID] ||= {}; 2405 const wrappedIDLayers = wrappedIDLayerMap[wrappedTileID]; 2406 for (const layerID in queryResults) { 2407 const tileFeatures = queryResults[layerID]; 2408 wrappedIDLayers[layerID] ||= {}; 2409 const wrappedIDFeatures = wrappedIDLayers[layerID]; 2410 result[layerID] ||= []; 2411 for (const tileFeature of tileFeatures) if (!wrappedIDFeatures[tileFeature.featureIndex]) { 2412 wrappedIDFeatures[tileFeature.featureIndex] = true; 2413 result[layerID].push(tileFeature); 2414 } 2415 } 2416 } 2417 return result; 2418} 2419function convertFeaturesToMapFeatures(result, tileManager) { 2420 for (const layerID in result) for (const featureWrapper of result[layerID]) convertFeatureToMapFeature(featureWrapper, tileManager); 2421 return result; 2422} 2423function convertFeaturesToMapFeaturesMultiple(result, styleLayers, tileManagers) { 2424 for (const layerName in result) for (const featureWrapper of result[layerName]) { 2425 const tileManager = tileManagers[styleLayers[layerName].source]; 2426 convertFeatureToMapFeature(featureWrapper, tileManager); 2427 } 2428 return result; 2429} 2430function convertFeatureToMapFeature(featureWrapper, tileManager) { 2431 const feature = featureWrapper.feature; 2432 const state = tileManager.getFeatureState(feature.layer["source-layer"], feature.id); 2433 feature.source = feature.layer.source; 2434 if (feature.layer["source-layer"]) feature.sourceLayer = feature.layer["source-layer"]; 2435 feature.state = state; 2436} 2437//#endregion 2438//#region src/source/load_tilejson.ts 2439async function loadTileJson(options, requestManager, abortController, targetWindow) { 2440 let tileJSON = options; 2441 if (options.url) tileJSON = (await getJSON(await requestManager.transformRequest(options.url, "Source"), abortController)).data; 2442 else await browser.frameAsync(abortController, targetWindow); 2443 if (!tileJSON) return null; 2444 const result = pick(extend(tileJSON, options), [ 2445 "tiles", 2446 "minzoom", 2447 "maxzoom", 2448 "attribution", 2449 "bounds", 2450 "scheme", 2451 "tileSize", 2452 "encoding" 2453 ]); 2454 if ("vector_layers" in tileJSON && tileJSON.vector_layers) result.vectorLayerIds = tileJSON.vector_layers.map((layer) => layer.id); 2455 return result; 2456} 2457//#endregion 2458//#region src/geo/lng_lat_bounds.ts 2459/** 2460* A `LngLatBounds` object represents a geographical bounding box, 2461* defined by its southwest and northeast points in longitude and latitude. 2462* 2463* If no arguments are provided to the constructor, a `null` bounding box is created. 2464* 2465* Note that any MapLibre GL method that accepts a `LngLatBounds` object as an argument or option 2466* can also accept an `Array` of two {@link LngLatLike} constructs and will perform an implicit conversion. 2467* This flexible type is documented as {@link LngLatBoundsLike}. 2468* 2469* @group Geography and Geometry 2470* 2471* @example 2472* ```ts 2473* let sw = new LngLat(-73.9876, 40.7661); 2474* let ne = new LngLat(-73.9397, 40.8002); 2475* let llb = new LngLatBounds(sw, ne); 2476* ``` 2477* @see [Fit to the bounds of a LineString](https://maplibre.org/maplibre-gl-js/docs/examples/fit-to-the-bounds-of-a-linestring/) 2478*/ 2479var LngLatBounds = class LngLatBounds { 2480 /** 2481 * @param sw - The southwest corner of the bounding box. 2482 * OR array of 4 numbers in the order of west, south, east, north 2483 * OR array of 2 LngLatLike: `[sw, ne]` 2484 * @param ne - The northeast corner of the bounding box. 2485 * @example 2486 * ```ts 2487 * let sw = new LngLat(-73.9876, 40.7661); 2488 * let ne = new LngLat(-73.9397, 40.8002); 2489 * let llb = new LngLatBounds(sw, ne); 2490 * ``` 2491 * OR 2492 * ```ts 2493 * let llb = new LngLatBounds([-73.9876, 40.7661, -73.9397, 40.8002]); 2494 * ``` 2495 * OR 2496 * ```ts 2497 * let llb = new LngLatBounds([sw, ne]); 2498 * ``` 2499 */ 2500 constructor(sw, ne) { 2501 if (!sw) {} else if (ne) this.setSouthWest(sw).setNorthEast(ne); 2502 else if (Array.isArray(sw)) { 2503 if (sw.length === 4) this.setSouthWest([sw[0], sw[1]]).setNorthEast([sw[2], sw[3]]); 2504 else this.setSouthWest(sw[0]).setNorthEast(sw[1]); 2505 } 2506 } 2507 /** 2508 * Set the northeast corner of the bounding box 2509 * 2510 * @param ne - a {@link LngLatLike} object describing the northeast corner of the bounding box. 2511 */ 2512 setNorthEast(ne) { 2513 this._ne = ne instanceof LngLat ? new LngLat(ne.lng, ne.lat) : LngLat.convert(ne); 2514 return this; 2515 } 2516 /** 2517 * Set the southwest corner of the bounding box 2518 * 2519 * @param sw - a {@link LngLatLike} object describing the southwest corner of the bounding box. 2520 */ 2521 setSouthWest(sw) { 2522 this._sw = sw instanceof LngLat ? new LngLat(sw.lng, sw.lat) : LngLat.convert(sw); 2523 return this; 2524 } 2525 /** 2526 * Extend the bounds to include a given LngLatLike or LngLatBoundsLike. 2527 * 2528 * @param obj - object to extend to 2529 */ 2530 extend(obj) { 2531 const sw = this._sw, ne = this._ne; 2532 let sw2, ne2; 2533 if (obj instanceof LngLat) { 2534 sw2 = obj; 2535 ne2 = obj; 2536 } else if (obj instanceof LngLatBounds) { 2537 sw2 = obj._sw; 2538 ne2 = obj._ne; 2539 if (!sw2 || !ne2) return this; 2540 } else { 2541 if (Array.isArray(obj)) { 2542 if (obj.length === 4 || obj.every(Array.isArray)) { 2543 const lngLatBoundsObj = obj; 2544 return this.extend(LngLatBounds.convert(lngLatBoundsObj)); 2545 } else { 2546 const lngLatObj = obj; 2547 return this.extend(LngLat.convert(lngLatObj)); 2548 } 2549 } else if (obj && ("lng" in obj || "lon" in obj) && "lat" in obj) return this.extend(LngLat.convert(obj)); 2550 return this; 2551 } 2552 if (!sw && !ne) { 2553 this._sw = new LngLat(sw2.lng, sw2.lat); 2554 this._ne = new LngLat(ne2.lng, ne2.lat); 2555 } else { 2556 sw.lng = Math.min(sw2.lng, sw.lng); 2557 sw.lat = Math.min(sw2.lat, sw.lat); 2558 ne.lng = Math.max(ne2.lng, ne.lng); 2559 ne.lat = Math.max(ne2.lat, ne.lat); 2560 } 2561 return this; 2562 } 2563 /** 2564 * Returns the geographical coordinate equidistant from the bounding box's corners. 2565 * 2566 * @returns The bounding box's center. 2567 * @example 2568 * ```ts 2569 * let llb = new LngLatBounds([-73.9876, 40.7661], [-73.9397, 40.8002]); 2570 * llb.getCenter(); // = LngLat {lng: -73.96365, lat: 40.78315} 2571 * ``` 2572 */ 2573 getCenter() { 2574 return new LngLat((this._sw.lng + this._ne.lng) / 2, (this._sw.lat + this._ne.lat) / 2); 2575 } 2576 /** 2577 * Returns the southwest corner of the bounding box. 2578 * 2579 * @returns The southwest corner of the bounding box. 2580 */ 2581 getSouthWest() { 2582 return this._sw; 2583 } 2584 /** 2585 * Returns the northeast corner of the bounding box. 2586 * 2587 * @returns The northeast corner of the bounding box. 2588 */ 2589 getNorthEast() { 2590 return this._ne; 2591 } 2592 /** 2593 * Returns the northwest corner of the bounding box. 2594 * 2595 * @returns The northwest corner of the bounding box. 2596 */ 2597 getNorthWest() { 2598 return new LngLat(this.getWest(), this.getNorth()); 2599 } 2600 /** 2601 * Returns the southeast corner of the bounding box. 2602 * 2603 * @returns The southeast corner of the bounding box. 2604 */ 2605 getSouthEast() { 2606 return new LngLat(this.getEast(), this.getSouth()); 2607 } 2608 /** 2609 * Returns the west edge of the bounding box. 2610 * 2611 * @returns The west edge of the bounding box. 2612 */ 2613 getWest() { 2614 return this._sw.lng; 2615 } 2616 /** 2617 * Returns the south edge of the bounding box. 2618 * 2619 * @returns The south edge of the bounding box. 2620 */ 2621 getSouth() { 2622 return this._sw.lat; 2623 } 2624 /** 2625 * Returns the east edge of the bounding box. 2626 * 2627 * @returns The east edge of the bounding box. 2628 */ 2629 getEast() { 2630 return this._ne.lng; 2631 } 2632 /** 2633 * Returns the north edge of the bounding box. 2634 * 2635 * @returns The north edge of the bounding box. 2636 */ 2637 getNorth() { 2638 return this._ne.lat; 2639 } 2640 /** 2641 * Returns the bounding box represented as an array. 2642 * 2643 * @returns The bounding box represented as an array, consisting of the 2644 * southwest and northeast coordinates of the bounding represented as arrays of numbers. 2645 * @example 2646 * ```ts 2647 * let llb = new LngLatBounds([-73.9876, 40.7661], [-73.9397, 40.8002]); 2648 * llb.toArray(); // = [[-73.9876, 40.7661], [-73.9397, 40.8002]] 2649 * ``` 2650 */ 2651 toArray() { 2652 return [this._sw.toArray(), this._ne.toArray()]; 2653 } 2654 /** 2655 * Return the bounding box represented as a string. 2656 * 2657 * @returns The bounding box represents as a string of the format 2658 * `'LngLatBounds(LngLat(lng, lat), LngLat(lng, lat))'`. 2659 * @example 2660 * ```ts 2661 * let llb = new LngLatBounds([-73.9876, 40.7661], [-73.9397, 40.8002]); 2662 * llb.toString(); // = "LngLatBounds(LngLat(-73.9876, 40.7661), LngLat(-73.9397, 40.8002))" 2663 * ``` 2664 */ 2665 toString() { 2666 return `LngLatBounds(${this._sw.toString()}, ${this._ne.toString()})`; 2667 } 2668 /** 2669 * Check if the bounding box is an empty/`null`-type box. 2670 * 2671 * @returns True if bounds have been defined, otherwise false. 2672 */ 2673 isEmpty() { 2674 return !(this._sw && this._ne); 2675 } 2676 /** 2677 * Check if the point is within the bounding box. 2678 * 2679 * @param lnglat - geographic point to check against. 2680 * @returns `true` if the point is within the bounding box. 2681 * @example 2682 * ```ts 2683 * let llb = new LngLatBounds( 2684 * new LngLat(-73.9876, 40.7661), 2685 * new LngLat(-73.9397, 40.8002) 2686 * ); 2687 * 2688 * let ll = new LngLat(-73.9567, 40.7789); 2689 * 2690 * console.log(llb.contains(ll)); // = true 2691 * ``` 2692 */ 2693 contains(lnglat) { 2694 const { lng, lat } = LngLat.convert(lnglat); 2695 const containsLatitude = this._sw.lat <= lat && lat <= this._ne.lat; 2696 let containsLongitude = this._sw.lng <= lng && lng <= this._ne.lng; 2697 if (this._sw.lng > this._ne.lng) containsLongitude = this._sw.lng >= lng && lng >= this._ne.lng; 2698 return containsLatitude && containsLongitude; 2699 } 2700 /** 2701 * Checks if this bounding box intersects with another bounding box. 2702 * 2703 * Returns true if the bounding boxes share any area, including cases where 2704 * they only touch along an edge or at a corner. 2705 * 2706 * This method properly handles cases where either or both bounding boxes cross 2707 * the antimeridian (date line). 2708 */ 2709 intersects(other) { 2710 other = LngLatBounds.convert(other); 2711 if (!(other.getNorth() >= this.getSouth() && other.getSouth() <= this.getNorth())) return false; 2712 const thisSpan = Math.abs(this.getEast() - this.getWest()); 2713 const otherSpan = Math.abs(other.getEast() - other.getWest()); 2714 if (thisSpan >= 360 || otherSpan >= 360) return true; 2715 const thisWest = wrap(this.getWest(), -180, 180); 2716 const thisEast = wrap(this.getEast(), -180, 180); 2717 const otherWest = wrap(other.getWest(), -180, 180); 2718 const otherEast = wrap(other.getEast(), -180, 180); 2719 const thisWraps = thisWest > thisEast; 2720 const otherWraps = otherWest > otherEast; 2721 if (thisWraps && otherWraps) return true; 2722 if (thisWraps) return otherEast >= thisWest || otherWest <= thisEast; 2723 if (otherWraps) return thisEast >= otherWest || thisWest <= otherEast; 2724 return otherWest <= thisEast && otherEast >= thisWest; 2725 } 2726 /** 2727 * Converts an array to a `LngLatBounds` object. 2728 * 2729 * If a `LngLatBounds` object is passed in, the function returns it unchanged. 2730 * 2731 * Internally, the function calls {@link LngLat.convert} to convert arrays to `LngLat` values. 2732 * 2733 * @param input - An array of two coordinates to convert, or a `LngLatBounds` object to return. 2734 * @returns A new `LngLatBounds` object, if a conversion occurred, or the original `LngLatBounds` object. 2735 * @example 2736 * ```ts 2737 * let arr = [[-73.9876, 40.7661], [-73.9397, 40.8002]]; 2738 * let llb = LngLatBounds.convert(arr); // = LngLatBounds {_sw: LngLat {lng: -73.9876, lat: 40.7661}, _ne: LngLat {lng: -73.9397, lat: 40.8002}} 2739 * ``` 2740 */ 2741 static convert(input) { 2742 if (input instanceof LngLatBounds) return input; 2743 if (!input) return input; 2744 return new LngLatBounds(input); 2745 } 2746 /** 2747 * Returns a `LngLatBounds` from the coordinates extended by a given `radius`. The returned `LngLatBounds` completely contains the `radius`. 2748 * 2749 * @param center - center coordinates of the new bounds. 2750 * @param radius - Distance in meters from the coordinates to extend the bounds. 2751 * @returns A new `LngLatBounds` object representing the coordinates extended by the `radius`. 2752 * @example 2753 * ```ts 2754 * let center = new LngLat(-73.9749, 40.7736); 2755 * LngLatBounds.fromLngLat(100).toArray(); // = [[-73.97501862141328, 40.77351016847229], [-73.97478137858673, 40.77368983152771]] 2756 * ``` 2757 */ 2758 static fromLngLat(center, radius = 0) { 2759 const latAccuracy = 360 * radius / 40075017, lngAccuracy = latAccuracy / Math.cos(Math.PI / 180 * center.lat); 2760 return new LngLatBounds(new LngLat(center.lng - lngAccuracy, center.lat - latAccuracy), new LngLat(center.lng + lngAccuracy, center.lat + latAccuracy)); 2761 } 2762 /** 2763 * Adjusts the given bounds to handle the case where the bounds cross the 180th meridian (antimeridian). 2764 * 2765 * @returns The adjusted LngLatBounds 2766 * @example 2767 * ```ts 2768 * let bounds = new LngLatBounds([175.813127, -20.157768], [-178. 340903, -15.449124]); 2769 * let adjustedBounds = bounds.adjustAntiMeridian(); 2770 * // adjustedBounds will be: [[175.813127, -20.157768], [181.659097, -15.449124]] 2771 * ``` 2772 */ 2773 adjustAntiMeridian() { 2774 const sw = new LngLat(this._sw.lng, this._sw.lat); 2775 const ne = new LngLat(this._ne.lng, this._ne.lat); 2776 if (sw.lng > ne.lng) return new LngLatBounds(sw, new LngLat(ne.lng + 360, ne.lat)); 2777 return new LngLatBounds(sw, ne); 2778 } 2779}; 2780//#endregion 2781//#region src/tile/tile_bounds.ts 2782var TileBounds = class { 2783 constructor(bounds, minzoom, maxzoom) { 2784 this.bounds = LngLatBounds.convert(this.validateBounds(bounds)); 2785 this.minzoom = minzoom || 0; 2786 this.maxzoom = maxzoom || 24; 2787 } 2788 validateBounds(bounds) { 2789 if (!Array.isArray(bounds) || bounds.length !== 4) return [ 2790 -180, 2791 -90, 2792 180, 2793 90 2794 ]; 2795 return [ 2796 Math.max(-180, bounds[0]), 2797 Math.max(-90, bounds[1]), 2798 Math.min(180, bounds[2]), 2799 Math.min(90, bounds[3]) 2800 ]; 2801 } 2802 contains(tileID) { 2803 const worldSize = Math.pow(2, tileID.z); 2804 const level = { 2805 minX: Math.floor(mercatorXfromLng(this.bounds.getWest()) * worldSize), 2806 minY: Math.floor(mercatorYfromLat(this.bounds.getNorth()) * worldSize), 2807 maxX: Math.ceil(mercatorXfromLng(this.bounds.getEast()) * worldSize), 2808 maxY: Math.ceil(mercatorYfromLat(this.bounds.getSouth()) * worldSize) 2809 }; 2810 return tileID.x >= level.minX && tileID.x < level.maxX && tileID.y >= level.minY && tileID.y < level.maxY; 2811 } 2812}; 2813//#endregion 2814//#region src/source/vector_tile_source.ts 2815/** 2816* A source containing vector tiles in [Maplibre Vector Tile format](https://maplibre.org/maplibre-tile-spec/) or [Mapbox Vector Tile format](https://docs.mapbox.com/vector-tiles/reference/). 2817* (See the [Style Specification](https://maplibre.org/maplibre-style-spec/) for detailed documentation of options.) 2818* 2819* @group Sources 2820* 2821* @example 2822* ```ts 2823* map.addSource('some id', { 2824* type: 'vector', 2825* url: 'https://demotiles.maplibre.org/tiles/tiles.json' 2826* }); 2827* ``` 2828* 2829* @example 2830* ```ts 2831* map.addSource('some id', { 2832* type: 'vector', 2833* tiles: ['https://d25uarhxywzl1j.cloudfront.net/v0.1/{z}/{x}/{y}.mvt'], 2834* minzoom: 6, 2835* maxzoom: 14 2836* }); 2837* ``` 2838* 2839* @example 2840* ```ts 2841* map.getSource('some id').setUrl("https://demotiles.maplibre.org/tiles/tiles.json"); 2842* ``` 2843* 2844* @example 2845* ```ts 2846* map.getSource('some id').setTiles(['https://d25uarhxywzl1j.cloudfront.net/v0.1/{z}/{x}/{y}.mvt']); 2847* ``` 2848* @see [Add a vector tile source](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-vector-tile-source/) 2849*/ 2850var VectorTileSource = class extends Evented { 2851 constructor(id, options, dispatcher, eventedParent) { 2852 super(); 2853 this.id = id; 2854 this.dispatcher = dispatcher; 2855 this.type = "vector"; 2856 this.minzoom = 0; 2857 this.maxzoom = 22; 2858 this.scheme = "xyz"; 2859 this.tileSize = 512; 2860 this.reparseOverscaled = true; 2861 this.isTileClipped = true; 2862 this._loaded = false; 2863 extend(this, pick(options, [ 2864 "url", 2865 "scheme", 2866 "tileSize", 2867 "promoteId", 2868 "encoding" 2869 ])); 2870 this._options = extend({ type: "vector" }, options); 2871 this._collectResourceTiming = options.collectResourceTiming; 2872 if (this.tileSize !== 512) throw new Error("vector tile sources must have a tileSize of 512"); 2873 this.setEventedParent(eventedParent); 2874 } 2875 async load(sourceDataChanged = false) { 2876 this._loaded = false; 2877 this.fire(new MapSourceDataEvent("dataloading")); 2878 this._tileJSONRequest = new AbortController(); 2879 try { 2880 const tileJSON = await loadTileJson(this._options, this.map._requestManager, this._tileJSONRequest, this.map._ownerWindow); 2881 this._tileJSONRequest = null; 2882 this._loaded = true; 2883 if (tileJSON) { 2884 extend(this, tileJSON); 2885 if (tileJSON.bounds) this.tileBounds = new TileBounds(tileJSON.bounds, this.minzoom, this.maxzoom); 2886 this.fire(new MapSourceDataEvent("data", { sourceDataType: "metadata" })); 2887 this.fire(new MapSourceDataEvent("data", { 2888 sourceDataType: "content", 2889 sourceDataChanged 2890 })); 2891 } 2892 } catch (err) { 2893 this._tileJSONRequest = null; 2894 this._loaded = true; 2895 if (!isAbortError(err)) this.fire(new ErrorEvent(ensureError(err))); 2896 } 2897 } 2898 loaded() { 2899 return this._loaded; 2900 } 2901 hasTile(tileID) { 2902 return !this.tileBounds || this.tileBounds.contains(tileID.canonical); 2903 } 2904 onAdd(map) { 2905 this.map = map; 2906 this.load(); 2907 } 2908 setSourceProperty(callback) { 2909 if (this._tileJSONRequest) this._tileJSONRequest.abort(); 2910 callback(); 2911 this.load(true); 2912 } 2913 /** 2914 * Sets the source `tiles` property and re-renders the map. 2915 * 2916 * @param tiles - An array of one or more tile source URLs, as in the TileJSON spec. 2917 */ 2918 setTiles(tiles) { 2919 this.setSourceProperty(() => { 2920 this.tiles = tiles; 2921 this._options.tiles = tiles; 2922 }); 2923 return this; 2924 } 2925 /** 2926 * Sets the source `url` property and re-renders the map. 2927 * 2928 * @param url - A URL to a TileJSON resource. Supported protocols are `http:` and `https:`. 2929 */ 2930 setUrl(url) { 2931 this.setSourceProperty(() => { 2932 this.url = url; 2933 this._options.url = url; 2934 }); 2935 return this; 2936 } 2937 onRemove() { 2938 if (this._tileJSONRequest) { 2939 this._tileJSONRequest.abort(); 2940 this._tileJSONRequest = null; 2941 } 2942 } 2943 serialize() { 2944 return extend({}, this._options); 2945 } 2946 async loadTile(tile) { 2947 const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme); 2948 const params = { 2949 request: await this.map._requestManager.transformRequest(url, "Tile"), 2950 uid: tile.uid, 2951 tileID: tile.tileID, 2952 zoom: tile.tileID.overscaledZ, 2953 tileSize: this.tileSize * tile.tileID.overscaleFactor(), 2954 type: this.type, 2955 source: this.id, 2956 pixelRatio: this.map.getPixelRatio(), 2957 showCollisionBoxes: this.map.showCollisionBoxes, 2958 promoteId: this.promoteId, 2959 subdivisionGranularity: this.map.style.projection.subdivisionGranularity, 2960 encoding: this.encoding, 2961 overzoomParameters: await this._getOverzoomParameters(tile), 2962 etag: tile.etag 2963 }; 2964 params.request.collectResourceTiming = this._collectResourceTiming; 2965 await this.dispatcher.waitForInitComplete(); 2966 let messageType = "RT"; 2967 if (!tile.actor || tile.state === "expired") { 2968 tile.actor = this.dispatcher.getReadyActor(); 2969 messageType = "LT"; 2970 } else if (tile.state === "loading") return new Promise((resolve, reject) => { 2971 tile.reloadPromise = { 2972 resolve, 2973 reject 2974 }; 2975 }); 2976 tile.abortController = new AbortController(); 2977 try { 2978 const data = await tile.actor.sendAsync({ 2979 type: messageType, 2980 data: params 2981 }, tile.abortController); 2982 delete tile.abortController; 2983 if (tile.aborted) return; 2984 this._afterTileLoadWorkerResponse(tile, data); 2985 const result = {}; 2986 if (data?.etagUnmodified) result.unmodified = true; 2987 return result; 2988 } catch (err) { 2989 delete tile.abortController; 2990 if (tile.aborted || isAbortError(err)) return; 2991 if (err && err.status !== 404) throw err; 2992 this._afterTileLoadWorkerResponse(tile, null); 2993 } 2994 } 2995 /** 2996 * When the requested tile has a higher canonical Z than source maxzoom, pass overzoom parameters so worker can load the 2997 * deepest tile at source max zoom to generate sub tiles using geojsonvt for highest performance on vector overscaling 2998 */ 2999 async _getOverzoomParameters(tile) { 3000 if (tile.tileID.canonical.z <= this.maxzoom) return; 3001 if (this.map._zoomLevelsToOverscale === void 0) return; 3002 const maxZoomTileID = tile.tileID.scaledTo(this.maxzoom).canonical; 3003 const maxZoomTileUrl = maxZoomTileID.url(this.tiles, this.map.getPixelRatio(), this.scheme); 3004 return { 3005 maxZoomTileID, 3006 overzoomRequest: await this.map._requestManager.transformRequest(maxZoomTileUrl, "Tile") 3007 }; 3008 } 3009 _afterTileLoadWorkerResponse(tile, data) { 3010 if (data?.resourceTiming) tile.resourceTiming = data.resourceTiming; 3011 if (data && this.map._refreshExpiredTiles) tile.setExpiryData(data); 3012 tile.etag = data?.etag; 3013 tile.loadVectorData(data, this.map.painter); 3014 if (tile.reloadPromise) { 3015 const reloadPromise = tile.reloadPromise; 3016 tile.reloadPromise = null; 3017 this.loadTile(tile).then(reloadPromise.resolve).catch(reloadPromise.reject); 3018 } 3019 } 3020 async abortTile(tile) { 3021 if (tile.abortController) { 3022 tile.abortController.abort(); 3023 delete tile.abortController; 3024 } 3025 if (tile.actor) await tile.actor.sendAsync({ 3026 type: "AT", 3027 data: { 3028 uid: tile.uid, 3029 type: this.type, 3030 source: this.id 3031 } 3032 }); 3033 } 3034 async unloadTile(tile) { 3035 tile.unloadVectorData(); 3036 if (tile.actor) await tile.actor.sendAsync({ 3037 type: "RMT", 3038 data: { 3039 uid: tile.uid, 3040 type: this.type, 3041 source: this.id 3042 } 3043 }); 3044 } 3045 hasTransition() { 3046 return false; 3047 } 3048}; 3049//#endregion 3050//#region src/source/raster_tile_source.ts 3051/** 3052* A source containing raster tiles (See the [raster source documentation](https://maplibre.org/maplibre-style-spec/sources/#raster) for detailed documentation of options.) 3053* 3054* @group Sources 3055* 3056* \> â¹ï¸ **Note:** The default `tileSize` is `512`. If your tile provider (such as OpenStreetMap or Stadia Maps) serves 256px tiles, set `tileSize: 256` manually to avoid blurry rendering due to upscaling. 3057* 3058* @example 3059* ```ts 3060* map.addSource('raster-source', { 3061* 'type': 'raster', 3062* 'tiles': ['https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg'], 3063* 'tileSize': 256, // Set this to match tile server output to avoid blurry rendering 3064* }); 3065* ``` 3066* 3067* @example 3068* ```ts 3069* map.addSource('wms-test-source', { 3070* 'type': 'raster', 3071* // use the tiles option to specify a WMS tile source URL 3072* 'tiles': [ 3073* 'https://img.nj.gov/imagerywms/Natural2015?bbox={bbox-epsg-3857}&format=image/png&service=WMS&version=1.1.1&request=GetMap&srs=EPSG:3857&transparent=true&width=256&height=256&layers=Natural2015' 3074* ], 3075* 'tileSize': 256 // Important for WMS if tiles are 256px 3076* }); 3077* ``` 3078* @see [Add a raster tile source](https://maplibre.org/maplibre-gl-js/docs/examples/map-tiles/) 3079* @see [Add a WMS source](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-wms-source/) 3080* @see [Display a satellite map](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-satellite-map/) 3081*/ 3082var RasterTileSource = class extends Evented { 3083 constructor(id, options, dispatcher, eventedParent) { 3084 super(); 3085 this.id = id; 3086 this.dispatcher = dispatcher; 3087 this.setEventedParent(eventedParent); 3088 this.type = "raster"; 3089 this.minzoom = 0; 3090 this.maxzoom = 22; 3091 this.roundZoom = true; 3092 this.scheme = "xyz"; 3093 this.tileSize = 512; 3094 this._loaded = false; 3095 this._premultiplyAlpha = true; 3096 this._options = extend({ type: "raster" }, options); 3097 extend(this, pick(options, [ 3098 "url", 3099 "scheme", 3100 "tileSize" 3101 ])); 3102 } 3103 async load(sourceDataChanged = false) { 3104 this._loaded = false; 3105 this.fire(new MapSourceDataEvent("dataloading")); 3106 this._tileJSONRequest = new AbortController(); 3107 try { 3108 const tileJSON = await loadTileJson(this._options, this.map._requestManager, this._tileJSONRequest, this.map._ownerWindow); 3109 this._tileJSONRequest = null; 3110 this._loaded = true; 3111 if (tileJSON) { 3112 extend(this, tileJSON); 3113 if (tileJSON.bounds) this.tileBounds = new TileBounds(tileJSON.bounds, this.minzoom, this.maxzoom); 3114 this.fire(new MapSourceDataEvent("data", { sourceDataType: "metadata" })); 3115 this.fire(new MapSourceDataEvent("data", { 3116 sourceDataType: "content", 3117 sourceDataChanged 3118 })); 3119 } 3120 } catch (err) { 3121 this._tileJSONRequest = null; 3122 this._loaded = true; 3123 if (!isAbortError(err)) this.fire(new ErrorEvent(ensureError(err))); 3124 } 3125 } 3126 loaded() { 3127 return this._loaded; 3128 } 3129 onAdd(map) { 3130 this.map = map; 3131 this.load(); 3132 } 3133 onRemove() { 3134 if (this._tileJSONRequest) { 3135 this._tileJSONRequest.abort(); 3136 this._tileJSONRequest = null; 3137 } 3138 } 3139 setSourceProperty(callback) { 3140 if (this._tileJSONRequest) { 3141 this._tileJSONRequest.abort(); 3142 this._tileJSONRequest = null; 3143 } 3144 callback(); 3145 this.load(true); 3146 } 3147 /** 3148 * Sets the source `tiles` property and re-renders the map. 3149 * 3150 * @param tiles - An array of one or more tile source URLs, as in the raster tiles spec (See the [Style Specification](https://maplibre.org/maplibre-style-spec/) 3151 */ 3152 setTiles(tiles) { 3153 this.setSourceProperty(() => { 3154 this.tiles = tiles; 3155 this._options.tiles = tiles; 3156 }); 3157 return this; 3158 } 3159 /** 3160 * Sets the source `url` property and re-renders the map. 3161 * 3162 * @param url - A URL to a TileJSON resource. Supported protocols are `http:` and `https:`. 3163 */ 3164 setUrl(url) { 3165 this.setSourceProperty(() => { 3166 this.url = url; 3167 this._options.url = url; 3168 }); 3169 return this; 3170 } 3171 serialize() { 3172 return extend({}, this._options); 3173 } 3174 /** 3175 * Sets whether alpha premultiplication is applied to raster tile images. 3176 * Set to `false` to preserve exact RGBA byte values when alpha carries data instead of opacity. 3177 * 3178 * @param premultiplyAlpha - If `false`, disables alpha premultiplication for raster tile image decode and texture upload. 3179 * @example 3180 * ```ts 3181 * map.getSource<RasterTileSource>('raster-source').setPremultiplyAlpha(false); 3182 * ``` 3183 */ 3184 setPremultiplyAlpha(premultiplyAlpha) { 3185 if (this._premultiplyAlpha === premultiplyAlpha) return this; 3186 this.setSourceProperty(() => { 3187 this._premultiplyAlpha = premultiplyAlpha; 3188 }); 3189 return this; 3190 } 3191 hasTile(tileID) { 3192 return !this.tileBounds || this.tileBounds.contains(tileID.canonical); 3193 } 3194 async loadTile(tile) { 3195 const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme); 3196 const premultiply = this._premultiplyAlpha; 3197 const imageBitmapOptions = premultiply ? void 0 : { premultiplyAlpha: "none" }; 3198 tile.abortController = new AbortController(); 3199 try { 3200 const response = await ImageRequest.transformAndGetImage(this.map._requestManager, url, "Tile", tile.abortController, this.map._refreshExpiredTiles, imageBitmapOptions); 3201 delete tile.abortController; 3202 if (tile.aborted) { 3203 tile.state = "unloaded"; 3204 return; 3205 } 3206 if (response) { 3207 if (this.map._refreshExpiredTiles && (response.cacheControl || response.expires)) tile.setExpiryData({ 3208 cacheControl: response.cacheControl, 3209 expires: response.expires 3210 }); 3211 const context = this.map.painter.context; 3212 const gl = context.gl; 3213 const img = response.data ?? new RGBAImage({ 3214 width: 1, 3215 height: 1 3216 }, /* @__PURE__ */ new Uint8Array(4)); 3217 tile.texture = this.map.painter.getTileTexture(img.width); 3218 if (tile.texture) tile.texture.update(img, { 3219 useMipmap: true, 3220 premultiply 3221 }); 3222 else { 3223 tile.texture = new Texture(context, img, gl.RGBA, { 3224 useMipmap: true, 3225 premultiply 3226 }); 3227 tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); 3228 } 3229 tile.state = "loaded"; 3230 } 3231 } catch (err) { 3232 delete tile.abortController; 3233 if (tile.aborted) tile.state = "unloaded"; 3234 else if (err) { 3235 tile.state = "errored"; 3236 throw err; 3237 } 3238 } 3239 } 3240 async abortTile(tile) { 3241 if (tile.abortController) { 3242 tile.abortController.abort(); 3243 delete tile.abortController; 3244 } 3245 } 3246 async unloadTile(tile) { 3247 if (tile.texture) this.map.painter.saveTileTexture(tile.texture); 3248 } 3249 hasTransition() { 3250 return false; 3251 } 3252}; 3253//#endregion 3254//#region src/source/raster_dem_tile_source.ts 3255/** 3256* A source containing raster DEM tiles (See the [Style Specification](https://maplibre.org/maplibre-style-spec/) for detailed documentation of options.) 3257* This source can be used to show hillshading and 3D terrain 3258* 3259* @group Sources 3260* 3261* @example 3262* ```ts 3263* map.addSource('raster-dem-source', { 3264* type: 'raster-dem', 3265* url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json', 3266* tileSize: 256 3267* }); 3268* ``` 3269* @see [3D Terrain](https://maplibre.org/maplibre-gl-js/docs/examples/3d-terrain/) 3270*/ 3271var RasterDEMTileSource = class extends RasterTileSource { 3272 constructor(id, options, dispatcher, eventedParent) { 3273 super(id, options, dispatcher, eventedParent); 3274 this.type = "raster-dem"; 3275 this.maxzoom = 22; 3276 this._options = extend({ type: "raster-dem" }, options); 3277 this.encoding = options.encoding || "mapbox"; 3278 this.redFactor = options.redFactor; 3279 this.greenFactor = options.greenFactor; 3280 this.blueFactor = options.blueFactor; 3281 this.baseShift = options.baseShift; 3282 } 3283 async loadTile(tile) { 3284 const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme); 3285 tile.neighboringTiles = this._getNeighboringTiles(tile.tileID); 3286 tile.abortController = new AbortController(); 3287 try { 3288 const response = await ImageRequest.transformAndGetImage(this.map._requestManager, url, "Tile", tile.abortController, this.map._refreshExpiredTiles, { colorSpaceConversion: "none" }); 3289 delete tile.abortController; 3290 if (tile.aborted) { 3291 tile.state = "unloaded"; 3292 return; 3293 } 3294 if (response) { 3295 if (this.map._refreshExpiredTiles && (response.cacheControl || response.expires)) tile.setExpiryData({ 3296 cacheControl: response.cacheControl, 3297 expires: response.expires 3298 }); 3299 if (!response.data) { 3300 tile.state = "loaded"; 3301 return; 3302 } 3303 const img = response.data; 3304 const rawImageData = isImageBitmap(img) && offscreenCanvasSupported() ? img : await this.readImageNow(img); 3305 const params = { 3306 type: this.type, 3307 uid: tile.uid, 3308 source: this.id, 3309 rawImageData, 3310 encoding: this.encoding, 3311 redFactor: this.redFactor, 3312 greenFactor: this.greenFactor, 3313 blueFactor: this.blueFactor, 3314 baseShift: this.baseShift 3315 }; 3316 if (tile.actor && tile.state !== "expired" && tile.state !== "reloading") return; 3317 await this.dispatcher.waitForInitComplete(); 3318 if (!tile.actor || tile.state === "expired") tile.actor = this.dispatcher.getReadyActor(); 3319 tile.dem = await tile.actor.sendAsync({ 3320 type: "LDT", 3321 data: params 3322 }); 3323 tile.needsHillshadePrepare = true; 3324 tile.needsTerrainPrepare = true; 3325 tile.needsColorReliefPrepare = true; 3326 tile.state = "loaded"; 3327 } 3328 } catch (err) { 3329 delete tile.abortController; 3330 if (tile.aborted) tile.state = "unloaded"; 3331 else if (err) { 3332 tile.state = "errored"; 3333 throw err; 3334 } 3335 } 3336 } 3337 async readImageNow(img) { 3338 if (typeof VideoFrame !== "undefined" && isOffscreenCanvasDistorted()) { 3339 const width = img.width + 4; 3340 const height = img.height + 4; 3341 try { 3342 return new RGBAImage({ 3343 width, 3344 height 3345 }, await readImageUsingVideoFrame(img, -2, -2, width, height)); 3346 } catch {} 3347 } 3348 return browser.getImageData(img, 2); 3349 } 3350 _getNeighboringTiles(tileID) { 3351 const canonical = tileID.canonical; 3352 const dim = Math.pow(2, canonical.z); 3353 const px = (canonical.x - 1 + dim) % dim; 3354 const pxw = canonical.x === 0 ? tileID.wrap - 1 : tileID.wrap; 3355 const nx = (canonical.x + 1 + dim) % dim; 3356 const nxw = canonical.x + 1 === dim ? tileID.wrap + 1 : tileID.wrap; 3357 const neighboringTiles = {}; 3358 neighboringTiles[new OverscaledTileID(tileID.overscaledZ, pxw, canonical.z, px, canonical.y).key] = { backfilled: false }; 3359 neighboringTiles[new OverscaledTileID(tileID.overscaledZ, nxw, canonical.z, nx, canonical.y).key] = { backfilled: false }; 3360 if (canonical.y > 0) { 3361 neighboringTiles[new OverscaledTileID(tileID.overscaledZ, pxw, canonical.z, px, canonical.y - 1).key] = { backfilled: false }; 3362 neighboringTiles[new OverscaledTileID(tileID.overscaledZ, tileID.wrap, canonical.z, canonical.x, canonical.y - 1).key] = { backfilled: false }; 3363 neighboringTiles[new OverscaledTileID(tileID.overscaledZ, nxw, canonical.z, nx, canonical.y - 1).key] = { backfilled: false }; 3364 } 3365 if (canonical.y + 1 < dim) { 3366 neighboringTiles[new OverscaledTileID(tileID.overscaledZ, pxw, canonical.z, px, canonical.y + 1).key] = { backfilled: false }; 3367 neighboringTiles[new OverscaledTileID(tileID.overscaledZ, tileID.wrap, canonical.z, canonical.x, canonical.y + 1).key] = { backfilled: false }; 3368 neighboringTiles[new OverscaledTileID(tileID.overscaledZ, nxw, canonical.z, nx, canonical.y + 1).key] = { backfilled: false }; 3369 } 3370 return neighboringTiles; 3371 } 3372 async unloadTile(tile) { 3373 if (tile.demTexture) this.map.painter.saveTileTexture(tile.demTexture); 3374 if (tile.fbo) { 3375 tile.fbo.destroy(); 3376 delete tile.fbo; 3377 } 3378 if (tile.dem) delete tile.dem; 3379 delete tile.neighboringTiles; 3380 tile.state = "unloaded"; 3381 if (tile.actor) await tile.actor.sendAsync({ 3382 type: "RDT", 3383 data: { 3384 type: this.type, 3385 uid: tile.uid, 3386 source: this.id 3387 } 3388 }); 3389 } 3390}; 3391//#endregion 3392//#region src/source/geojson_source_diff.ts 3393function getFeatureId(feature, promoteId) { 3394 return promoteId ? feature.properties[promoteId] : feature.id; 3395} 3396/** 3397* Converts a GeoJSON object into a map of feature IDs to GeoJSON features. 3398* @param data - The GeoJSON object to convert. 3399* @param promoteId - If set, the feature id will be set to the promoteId property value. 3400* @returns A map of feature IDs to GeoJSON features, or `undefined` if the GeoJSON object is not a valid updateable object. 3401* 3402* Features must have unique identifiers to be updateable. IDs can come from: 3403* - The feature's `id` property (standard GeoJSON) 3404* - A promoted property specified by `promoteId` (e.g., a "name" property) 3405*/ 3406function toUpdateable(data, promoteId) { 3407 const updateable = /* @__PURE__ */ new Map(); 3408 if (data == null) return updateable; 3409 if (data.type == null) return updateable; 3410 if (data.type === "Feature") { 3411 const id = getFeatureId(data, promoteId); 3412 if (id == null) return void 0; 3413 updateable.set(id, data); 3414 return updateable; 3415 } 3416 if (data.type === "FeatureCollection") { 3417 const seenIds = /* @__PURE__ */ new Set(); 3418 for (const feature of data.features) { 3419 const id = getFeatureId(feature, promoteId); 3420 if (id == null) return void 0; 3421 if (seenIds.has(id)) return void 0; 3422 seenIds.add(id); 3423 updateable.set(id, feature); 3424 } 3425 return updateable; 3426 } 3427} 3428/** 3429* Mutates updateable and applies a {@link GeoJSONSourceDiff}. Operations are processed in a specific order to ensure predictable behavior: 3430* 1. Remove operations (removeAll, remove) 3431* 2. Add operations (add) 3432* 3. Update operations (update) 3433* @returns an array of geometries that were affected by the diff - with the exception of removeAll which does not track any affected geometries. 3434*/ 3435function applySourceDiff(updateable, diff, promoteId) { 3436 const affectedGeometries = []; 3437 if (diff.removeAll) updateable.clear(); 3438 else if (diff.remove) for (const id of diff.remove) { 3439 const existing = updateable.get(id); 3440 if (!existing) continue; 3441 affectedGeometries.push(existing.geometry); 3442 updateable.delete(id); 3443 } 3444 if (diff.add) for (const feature of diff.add) { 3445 const id = getFeatureId(feature, promoteId); 3446 if (id == null) continue; 3447 const existing = updateable.get(id); 3448 if (existing) affectedGeometries.push(existing.geometry); 3449 affectedGeometries.push(feature.geometry); 3450 updateable.set(id, feature); 3451 } 3452 if (diff.update) for (const update of diff.update) { 3453 const existing = updateable.get(update.id); 3454 if (!existing) continue; 3455 const changeGeometry = !!update.newGeometry; 3456 const changeProps = update.removeAllProperties || update.removeProperties?.length > 0 || update.addOrUpdateProperties?.length > 0; 3457 if (!changeGeometry && !changeProps) continue; 3458 affectedGeometries.push(existing.geometry); 3459 const feature = { ...existing }; 3460 updateable.set(update.id, feature); 3461 if (changeGeometry) { 3462 affectedGeometries.push(update.newGeometry); 3463 feature.geometry = update.newGeometry; 3464 } 3465 if (changeProps) { 3466 if (update.removeAllProperties) feature.properties = {}; 3467 else feature.properties = { ...feature.properties || {} }; 3468 if (update.removeProperties) for (const key of update.removeProperties) delete feature.properties[key]; 3469 if (update.addOrUpdateProperties) for (const { key, value } of update.addOrUpdateProperties) feature.properties[key] = value; 3470 } 3471 } 3472 return affectedGeometries; 3473} 3474/** 3475* Merge two GeoJSONSourceDiffs, considering the order of operations as specified above (remove, add, update). 3476* 3477*
3477For `add` features that use promoteId, the feature id will be set to the promoteId value temporarily so that 3478* the merge can be completed, then reverted to the original promoteId state after the merge. 3479*/ 3480function mergeSourceDiffs(prevDiff, nextDiff, promoteId) { 3481 if (!prevDiff) return nextDiff || {}; 3482 if (!nextDiff) return prevDiff || {}; 3483 if (promoteId) { 3484 promoteFeatureIds(prevDiff.add, promoteId); 3485 promoteFeatureIds(nextDiff.add, promoteId); 3486 } 3487 const prev = diffToHashed(prevDiff); 3488 const next = diffToHashed(nextDiff); 3489 resolveMergeConflicts(prev, next); 3490 const merged = {}; 3491 if (prev.removeAll || next.removeAll) merged.removeAll = true; 3492 merged.remove = /* @__PURE__ */ new Set([...prev.remove, ...next.remove]); 3493 merged.add = new Map([...prev.add, ...next.add]); 3494 merged.update = new Map([...prev.update, ...next.update]); 3495 if (merged.remove.size && merged.add.size) for (const id of merged.add.keys()) merged.remove.delete(id); 3496 const mergedDiff = hashedToDiff(merged); 3497 if (promoteId) demoteFeatureIds(mergedDiff.add, promoteId); 3498 return mergedDiff; 3499} 3500/** 3501* Resolve merge conflicts between two GeoJSONSourceDiffs considering the ordering above (remove/add/update). 3502* 3503* - If you `removeAll` and then `add` features in the same diff, the added features will be kept. 3504* - Updates only apply to features that exist after removes and adds have been processed. 3505*/ 3506function resolveMergeConflicts(prev, next) { 3507 if (next.removeAll) { 3508 prev.add.clear(); 3509 prev.update.clear(); 3510 prev.remove.clear(); 3511 next.remove.clear(); 3512 } 3513 for (const id of next.remove) { 3514 prev.add.delete(id); 3515 prev.update.delete(id); 3516 } 3517 for (const [id, nextUpdate] of next.update) { 3518 const prevUpdate = prev.update.get(id); 3519 if (!prevUpdate) continue; 3520 next.update.set(id, mergeFeatureDiffs(prevUpdate, nextUpdate)); 3521 prev.update.delete(id); 3522 } 3523} 3524/** 3525* Merge two feature diffs for the same feature id, considering the order of operations as specified above (remove, add/update). 3526*/ 3527function mergeFeatureDiffs(prev, next) { 3528 const merged = { id: prev.id }; 3529 if (next.removeAllProperties) { 3530 delete prev.removeProperties; 3531 delete prev.addOrUpdateProperties; 3532 delete next.removeProperties; 3533 } 3534 if (next.removeProperties && prev.addOrUpdateProperties) { 3535 const removedProperties = new Set(next.removeProperties); 3536 prev.addOrUpdateProperties = prev.addOrUpdateProperties.filter((prop) => !removedProperties.has(prop.key)); 3537 } 3538 if (prev.removeAllProperties || next.removeAllProperties) merged.removeAllProperties = true; 3539 if (prev.removeProperties || next.removeProperties) merged.removeProperties = [...prev.removeProperties || [], ...next.removeProperties || []]; 3540 if (prev.addOrUpdateProperties || next.addOrUpdateProperties) merged.addOrUpdateProperties = [...prev.addOrUpdateProperties || [], ...next.addOrUpdateProperties || []]; 3541 if (prev.newGeometry || next.newGeometry) merged.newGeometry = next.newGeometry || prev.newGeometry; 3542 return merged; 3543} 3544/** 3545* Mutates diff.add and applies a feature id using the promoteId property 3546*/ 3547function promoteFeatureIds(add, promoteId) { 3548 if (!add) return; 3549 for (const feature of add) { 3550 const id = getFeatureId(feature, promoteId); 3551 if (id != null) feature.id = id; 3552 } 3553} 3554/** 3555* Mutates diff.add and removes the feature id if using the promoteId property 3556*/ 3557function demoteFeatureIds(add, promoteId) { 3558 if (!add) return; 3559 for (const feature of add) if (getFeatureId(feature, promoteId) != null) delete feature.id; 3560} 3561/** 3562* @internal 3563* Convert a GeoJSONSourceDiff to an idempotent hashed representation using Sets and Maps 3564*/ 3565function diffToHashed(diff) { 3566 if (!diff) return {}; 3567 const hashed = {}; 3568 hashed.removeAll = diff.removeAll; 3569 hashed.remove = new Set(diff.remove || []); 3570 hashed.add = new Map(diff.add?.map((feature) => [feature.id
vendor: 5,623 bytes, lines 3570-3721
3570, feature])); 3571 hashed.update = new Map(diff.update?.map((update) => [update.id, update])); 3572 return hashed; 3573} 3574/** 3575* @internal 3576* Convert a hashed GeoJSONSourceDiff back to the array-based representation 3577*/ 3578function hashedToDiff(hashed) { 3579 const diff = {}; 3580 if (hashed.removeAll) diff.removeAll = hashed.removeAll; 3581 if (hashed.remove) diff.remove = Array.from(hashed.remove); 3582 if (hashed.add) diff.add = Array.from(hashed.add.values()); 3583 if (hashed.update) diff.update = Array.from(hashed.update.values()); 3584 return diff; 3585} 3586//#endregion 3587//#region src/util/geojson_bounds.ts 3588function extractCoordinates(coords) { 3589 if (!coords || coords.length === 0) return []; 3590 if (typeof coords[0] === "number") return [coords]; 3591 return coords.flatMap((c) => extractCoordinates(c)); 3592} 3593function getCoordinatesFromGeometry(geometry) { 3594 if (geometry.type === "GeometryCollection") return geometry.geometries.flatMap((g) => getCoordinatesFromGeometry(g)); 3595 return extractCoordinates(geometry.coordinates); 3596} 3597/** 3598* Calculates the bounding box of GeoJSON data. 3599* @param data - The GeoJSON data to calculate bounds for 3600* @returns The bounding box of the GeoJSON data 3601*/ 3602function getGeoJSONBounds(data) { 3603 const bounds = new LngLatBounds(); 3604 let coordinates; 3605 switch (data.type) { 3606 case "FeatureCollection": 3607 coordinates = data.features.flatMap((f) => getCoordinatesFromGeometry(f.geometry)); 3608 break; 3609 case "Feature": 3610 coordinates = getCoordinatesFromGeometry(data.geometry); 3611 break; 3612 default: coordinates = getCoordinatesFromGeometry(data); 3613 } 3614 if (coordinates.length === 0) return bounds; 3615 for (const coordinate of coordinates) { 3616 const [lng, lat] = coordinate; 3617 bounds.extend([lng, lat]); 3618 } 3619 return bounds; 3620} 3621//#endregion 3622//#region src/tile/tile_id_to_lng_lat_bounds.ts 3623function tileIdToLngLatBounds({ x, y, z }, buffer = 0) { 3624 const lngMin = lngFromMercatorX((x - buffer) / Math.pow(2, z)); 3625 const latMin = latFromMercatorY((y + 1 + buffer) / Math.pow(2, z)); 3626 const lngMax = lngFromMercatorX((x + 1 + buffer) / Math.pow(2, z)); 3627 const latMax = latFromMercatorY((y - buffer) / Math.pow(2, z)); 3628 return new LngLatBounds([lngMin, latMin], [lngMax, latMax]); 3629} 3630//#endregion 3631//#region src/source/geojson_source.ts 3632/** 3633* A source containing GeoJSON. 3634* (See the [Style Specification](https://maplibre.org/maplibre-style-spec/#sources-geojson) for detailed documentation of options.) 3635* 3636* GeoJSON is tiled internally for rendering. Features exposed from rendered tiles and related events come from vector-tile data, so GeoJSON foreign members that cannot be represented by the vector-tile format are not preserved there. Keep that data separately, or map it to supported feature properties, if you need it after tiling. 3637* 3638* @group Sources 3639* 3640* @example 3641* ```ts 3642* map.addSource('some id', { 3643* type: 'geojson', 3644* data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_ports.geojson' 3645* }); 3646* ``` 3647* 3648* @example 3649* ```ts 3650* map.addSource('some id', { 3651* type: 'geojson', 3652* data: { 3653* "type": "FeatureCollection", 3654* "features": [{ 3655* "type": "Feature", 3656* "properties": {}, 3657* "geometry": { 3658* "type": "Point", 3659* "coordinates": [ 3660* -76.53063297271729, 3661* 39.18174077994108 3662* ] 3663* } 3664* }] 3665* } 3666* }); 3667* ``` 3668* 3669* @example 3670* ```ts 3671* map.getSource('some id').setData({ 3672* "type": "FeatureCollection", 3673* "features": [{ 3674* "type": "Feature", 3675* "properties": { "name": "Null Island" }, 3676* "geometry": { 3677* "type": "Point", 3678* "coordinates": [ 0, 0 ] 3679* } 3680* }] 3681* }); 3682* ``` 3683* @see [Draw GeoJSON points](https://maplibre.org/maplibre-gl-js/docs/examples/draw-geojson-points/) 3684* @see [Add a GeoJSON line](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-geojson-line/) 3685* @see [Create a heatmap from points](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-heatmap-layer/) 3686* @see [Create and style clusters](https://maplibre.org/maplibre-gl-js/docs/examples/create-and-style-clusters/) 3687*/ 3688var GeoJSONSource = class extends Evented { 3689 /** @internal */ 3690 constructor(id, options, dispatcher, eventedParent) { 3691 super(); 3692 this.id = id; 3693 this.type = "geojson"; 3694 this.minzoom = 0; 3695 this.maxzoom = 18; 3696 this.tileSize = 512; 3697 this.isTileClipped = true; 3698 this.reparseOverscaled = true; 3699 this._removed = false; 3700 this._isUpdatingWorker = false; 3701 this._pendingWorkerUpdate = { data: options.data }; 3702 this.actorPromise = dispatcher.getActor(); 3703 this.setEventedParent(eventedParent); 3704 this._data = typeof options.data === "string" ? { url: options.data } : { geojson: options.data }; 3705 this._options = extend({}, options); 3706 this._collectResourceTiming = options.collectResourceTiming; 3707 if (options.maxzoom !== void 0) this.maxzoom = options.maxzoom; 3708 if (options.type) this.type = options.type; 3709 if (options.attribution) this.attribution = options.attribution; 3710 this.promoteId = options.promoteId; 3711 if (options.clusterMaxZoom !== void 0 && this.maxzoom <= options.clusterMaxZoom) warnOnce(`The maxzoom value "${this.maxzoom}" is expected to be greater than the clusterMaxZoom value "${options.clusterMaxZoom}".`); 3712 this.workerOptions = extend({ 3713 source: this.id, 3714 geojsonVtOptions: { 3715 buffer: this._pixelsToTileUnits(options.buffer !== void 0 ? options.buffer : 128), 3716 tolerance: this._pixelsToTileUnits(options.tolerance !== void 0 ? options.tolerance : .375), 3717 extent: EXTENT, 3718 maxZoom: this.maxzoom, 3719 lineMetrics: options.lineMetrics || false, 3720 generateId: options.generateId || false, 3721 promoteId: t
3721ypeof options.promoteId === "string" ? options.promoteId : void 0, 3722 cluster: options.cluster || false, 3723 clusterOptions: { 3724 maxZoom: this._getClusterMaxZoom(options.clusterMaxZoom), 3725 minPoints: Math.max(2, options.clusterMinPoints || 2), 3726 extent: EXTENT, 3727 radius: this._pixelsToTileUnits(options.clusterRadius || 50), 3728 log: false, 3729 generateId: options.generateId || false 3730 } 3731 }, 3732 clusterProperties: options.clusterProperties, 3733 filter: options.filter 3734 }, options.workerOptions); 3735 } 3736 _hasPendingWorkerUpdate() { 3737 return this._pendingWorkerUpdate.data !== void 0 || this._pendingWorkerUpdate.diff !== void 0 || this._pendingWorkerUpdate.updateCluster; 3738 } 3739 _pixelsToTileUnits(pixelValue) { 3740 return pixelValue * (EXTENT / this.tileSize); 3741 } 3742 _tileUnitsToPixels(tileUnitValue) { 3743 return tileUnitValue / (EXTENT / this.tileSize); 3744 } 3745 _getClusterMaxZoom(clusterMaxZoom) { 3746 const effectiveClusterMaxZoom = clusterMaxZoom ? Math.round(clusterMaxZoom) : this.maxzoom - 1; 3747 if (!(Number.isInteger(clusterMaxZoom) || clusterMaxZoom === void 0)) warnOnce(`Integer expected for option 'clusterMaxZoom': provided value "${clusterMaxZoom}" rounded to "${effectiveClusterMaxZoom}"`); 3748 return effectiveClusterMaxZoom; 3749 } 3750 async load() { 3751 await this._updateWorkerData(); 3752 } 3753 onAdd(map) { 3754 this.map = map; 3755 this.load(); 3756 } 3757 /** 3758 * Sets the GeoJSON data and re-renders the map. 3759 * 3760 * @param data - A GeoJSON data object or a URL to one. The latter is preferable in the case of large GeoJSON files. 3761 */ 3762 setData(data) { 3763 this._data = typeof data === "string" ? { url: data } : { geojson: data }; 3764 this._pendingWorkerUpdate = { data }; 3765 return this._updateWorkerData(); 3766 } 3767 /** 3768 * Updates the source's GeoJSON, and re-renders the map. 3769 * 3770 * For sources with lots of features, this method can be used to make updates more quickly. 3771 * 3772 * This approach requires unique IDs for every feature in the source. The IDs can either be specified on the feature, 3773 * or by using the promoteId option to specify which property should be used as the ID. 3774 * 3775 * It is an error to call updateData on a source that did not have unique IDs for each of its features already. 3776 * 3777 * Updates are applied on a best-effort basis, updating an ID that does not exist will not result in an error. 3778 * 3779 * @param diff - The changes that need to be applied. 3780 */ 3781 updateData(diff) { 3782 this._pendingWorkerUpdate.diff = mergeSourceDiffs(this._pendingWorkerUpdate.diff, diff
vendor: 7,643 bytes, lines 3782-4014
3782); 3783 return this._updateWorkerData(); 3784 } 3785 /** 3786 * Allows to get the source's actual GeoJSON data. 3787 * 3788 * @returns a promise which resolves to the source's actual GeoJSON data 3789 */ 3790 async getData() { 3791 if (this._data.url) await this.once("data"); 3792 if (this._data.geojson) return this._data.geojson; 3793 return { 3794 type: "FeatureCollection", 3795 features: Array.from(this._data.updateable.values()) 3796 }; 3797 } 3798 /** 3799 * Allows getting the source's boundaries. 3800 * If there's a problem with the source's data, it will return an empty {@link LngLatBounds}. 3801 * @returns a promise which resolves to the source's boundaries 3802 */ 3803 async getBounds() { 3804 return getGeoJSONBounds(await this.getData()); 3805 } 3806 /** 3807 * To disable/enable clustering on the source options 3808 * @param options - The options to set 3809 * @example 3810 * ```ts 3811 * map.getSource('some id').setClusterOptions({cluster: false}); 3812 * map.getSource('some id').setClusterOptions({cluster: false, clusterRadius: 50, clusterMaxZoom: 14}); 3813 * ``` 3814 */ 3815 setClusterOptions(options) { 3816 this.workerOptions.geojsonVtOptions.cluster = options.cluster; 3817 if (options.clusterRadius !== void 0) this.workerOptions.geojsonVtOptions.clusterOptions.radius = this._pixelsToTileUnits(options.clusterRadius); 3818 if (options.clusterMaxZoom !== void 0) this.workerOptions.geojsonVtOptions.clusterOptions.maxZoom = this._getClusterMaxZoom(options.clusterMaxZoom); 3819 this._pendingWorkerUpdate.updateCluster = true; 3820 return this._updateWorkerData(); 3821 } 3822 /** 3823 * Gets the cluster options currently configured on the source. 3824 * The returned values mirror the options accepted by `setClusterOptions`. 3825 * 3826 * @returns the source's current cluster options 3827 * @example 3828 * ```ts 3829 * const {cluster, clusterMaxZoom, clusterRadius} = map.getSource('some id').getClusterOptions(); 3830 * ``` 3831 */ 3832 getClusterOptions() { 3833 const { cluster, clusterOptions } = this.workerOptions.geojsonVtOptions; 3834 return { 3835 cluster, 3836 clusterMaxZoom: clusterOptions.maxZoom, 3837 clusterRadius: this._tileUnitsToPixels(clusterOptions.radius) 3838 }; 3839 } 3840 /** 3841 * For clustered sources, fetches the zoom at which the given cluster expands. 3842 * 3843 * @param clusterId - The value of the cluster's `cluster_id` property. 3844 * @returns a promise that is resolved with the zoom number 3845 */ 3846 async getClusterExpansionZoom(clusterId) { 3847 return (await this.actorPromise).sendAsync({ 3848 type: "GCEZ", 3849 data: { 3850 type: this.type, 3851 clusterId, 3852 source: this.id 3853 } 3854 }); 3855 } 3856 /** 3857 * For clustered sources, fetches the children of the given cluster on the next zoom level (as an array of GeoJSON features). 3858 * 3859 * @param clusterId - The value of the cluster's `cluster_id` property. 3860 * @returns a promise that is resolved when the features are retrieved 3861 */ 3862 async getClusterChildren(clusterId) { 3863 return (await this.actorPromise).sendAsync({ 3864 type: "GCC", 3865 data: { 3866 type: this.type, 3867 clusterId, 3868 source: this.id 3869 } 3870 }); 3871 } 3872 /** 3873 * For clustered sources, fetches the original points that belong to the cluster (as an array of GeoJSON features). 3874 * 3875 * @param clusterId - The value of the cluster's `cluster_id` property. 3876 * @param limit - The maximum number of features to return. 3877 * @param offset - The number of features to skip (e.g. for pagination). 3878 * @returns a promise that is resolved when the features are retrieved 3879 * @example 3880 * Retrieve cluster leaves on click 3881 * ```ts 3882 * map.on('click', 'clusters', (e) => { 3883 * let features = map.queryRenderedFeatures(e.point, { 3884 * layers: ['clusters'] 3885 * }); 3886 * 3887 * let clusterId = features[0].properties.cluster_id; 3888 * let pointCount = features[0].properties.point_count; 3889 * let clusterSource = map.getSource('clusters'); 3890 * 3891 * const features = await clusterSource.getClusterLeaves(clusterId, pointCount); 3892 * // Print cluster leaves in the console 3893 * console.log('Cluster leaves:', features); 3894 * }); 3895 * ``` 3896 */ 3897 async getClusterLeaves(clusterId, limit, offset) { 3898 return (await this.actorPromise).sendAsync({ 3899 type: "GCL", 3900 data: { 3901 type: this.type, 3902 source: this.id, 3903 clusterId, 3904 limit, 3905 offset 3906 } 3907 }); 3908 } 3909 /** 3910 * Responsible for invoking WorkerSource's geojson.loadData target, which 3911 * handles loading the geojson data and preparing to serve it up as tiles, 3912 * using geojson-vt or supercluster as appropriate. 3913 */ 3914 async _updateWorkerData() { 3915 if (this._isUpdatingWorker) return this._updatePromise; 3916 if (!this._hasPendingWorkerUpdate()) { 3917 warnOnce(`No pending worker updates for GeoJSONSource ${this.id}.`); 3918 return; 3919 } 3920 const { data, diff, updateCluster } = this._pendingWorkerUpdate; 3921 const params = this._getLoadGeoJSONParameters(data, diff, updateCluster); 3922 if (data !== void 0) this._pendingWorkerUpdate.data = void 0; 3923 else if (diff) this._pendingWorkerUpdate.diff = void 0; 3924 else if (updateCluster) this._pendingWorkerUpdate.updateCluster = void 0; 3925 this._updatePromise = this._dispatchWorkerUpdate(params); 3926 await this._updatePromise; 3927 } 3928 /** 3929 * Create the parameters object that will be sent to the worker and used to load GeoJSON. 3930 */ 3931 async _getLoadGeoJSONParameters(data, diff, updateCluster) { 3932 const params = extend({ 3933 type: this.type, 3934 source: this.id 3935 }, this.workerOptions); 3936 if (typeof data === "string") { 3937 params.request = await this.map._requestManager.transformRequest(browser.resolveURL(data), "Source"); 3938 params.request.collectResourceTiming = this._collectResourceTiming; 3939 return params; 3940 } 3941 if (data !== void 0) { 3942 params.data = data; 3943 return params; 3944 } 3945 if (diff) { 3946 params.dataDiff = diff; 3947 return params; 3948 } 3949 if (updateCluster) { 3950 params.updateCluster = true; 3951 return params; 3952 } 3953 } 3954 /** 3955 * Send the worker update data from the main thread to the worker 3956 */ 3957 async _dispatchWorkerUpdate(optionsPromise) { 3958 this._isUpdatingWorker = true; 3959 this.fire(new MapSourceDataEvent("dataloading")); 3960 try { 3961 const options = await optionsPromise; 3962 const result = await (await this.actorPromise).sendAsync({ 3963 type: "LD", 3964 data: options 3965 }); 3966 this._isUpdatingWorker = false; 3967 if (this._removed || result.abandoned) { 3968 this.fire(new MapSourceDataEvent("dataabort")); 3969 return; 3970 } 3971 if (result.data) this._data = { geojson: result.data }; 3972 const affectedGeometries = this._applyDiffToSource(options.dataDiff); 3973 const shouldReloadTileOptions = this._getShouldReloadTileOptions(affectedGeometries); 3974 const eventData = {}; 3975 this._applyResourceTiming(eventData, result); 3976 this.fire(new MapSourceDataEvent("data", { 3977 ...eventData, 3978 sourceDataType: "metadata" 3979 })); 3980 this.fire(new MapSourceDataEvent("data", { 3981 ...eventData, 3982 sourceDataType: "content", 3983 shouldReloadTileOptions 3984 })); 3985 } catch (err) { 3986 this._isUpdatingWorker = false; 3987 if (this._removed) { 3988 this.fire(new MapSourceDataEvent("dataabort")); 3989 return; 3990 } 3991 this.fire(new ErrorEvent(ensureError(err))); 3992 } finally { 3993 if (this._hasPendingWorkerUpdate()) await this._updateWorkerData(); 3994 } 3995 } 3996 /** 3997 * Apply resource timing data to the event object. 3998 */ 3999 _applyResourceTiming(eventData, result) { 4000 if (!this._collectResourceTiming) return; 4001 const timingData = result.resourceTiming?.[this.id]; 4002 if (!timingData) return; 4003 const resourceTiming = timingData.slice(0); 4004 if (!resourceTiming?.length) return; 4005 extend(eventData, { resourceTiming }); 4006 } 4007 /** 4008 * Apply a diff to this source's data and return the affected feature geometries. 4009 * @param diff - The {@link GeoJSONSourceDiff} to apply. 4010 * @returns The affected geometries, or undefined if the diff is not applicable or all geometries are affected. 4011 */ 4012 _applyDiffToSource(diff) { 4013 if (!diff) return; 4014 const promoteId = t
4014ypeof this.promoteId === "string" ? this.promoteId : void 0
vendor: 210,934 bytes, lines 4014-9134
4014; 4015 if (!this._data.url && !this._data.updateable) { 4016 const updateable = toUpdateable(this._data.geojson, promoteId); 4017 if (!updateable) throw new Error(`GeoJSONSource "${this.id}": GeoJSON data is not compatible with updateData`); 4018 this._data = { updateable }; 4019 } 4020 if (!this._data.updateable) return; 4021 const affectedGeometries = applySourceDiff(this._data.updateable, diff, promoteId); 4022 if (diff.removeAll || this._options.cluster) return; 4023 return affectedGeometries; 4024 } 4025 /** 4026 * Get options for use in determining whether to reload a tile based on the modified features. 4027 * @param affectedGeometries - The feature geometries affected by the update. 4028 * @returns A {@link GeoJSONSourceShouldReloadTileOptions} object which contains an array of affected bounds caused by the update. 4029 */ 4030 _getShouldReloadTileOptions(affectedGeometries) { 4031 if (!affectedGeometries) return void 0; 4032 return { affectedBounds: affectedGeometries.filter(Boolean).map((g) => getGeoJSONBounds(g)) }; 4033 } 4034 /** 4035 * Determine whether a tile should be reloaded based on a set of options associated with a {@link MapSourceDataChangedEvent}. 4036 * @internal 4037 */ 4038 shouldReloadTile(tile, { affectedBounds }) { 4039 if (tile.state === "loading") return true; 4040 if (tile.state === "unloaded") return false; 4041 const { buffer, extent } = this.workerOptions.geojsonVtOptions; 4042 const tileBounds = tileIdToLngLatBounds(tile.tileID.canonical, buffer / extent); 4043 for (const bounds of affectedBounds) if (tileBounds.intersects(bounds)) return true; 4044 return false; 4045 } 4046 loaded() { 4047 return !this._isUpdatingWorker && !this._hasPendingWorkerUpdate(); 4048 } 4049 async loadTile(tile) { 4050 const message = !tile.actor ? "LT" : "RT"; 4051 tile.actor = await this.actorPromise; 4052 const params = { 4053 type: this.type, 4054 uid: tile.uid, 4055 tileID: tile.tileID, 4056 zoom: tile.tileID.overscaledZ, 4057 maxZoom: this.maxzoom, 4058 tileSize: this.tileSize, 4059 source: this.id, 4060 pixelRatio: this.map.getPixelRatio(), 4061 showCollisionBoxes: this.map.showCollisionBoxes, 4062 promoteId: this.promoteId, 4063 subdivisionGranularity: this.map.style.projection.subdivisionGranularity 4064 }; 4065 tile.abortController = new AbortController(); 4066 try { 4067 const data = await (await this.actorPromise).sendAsync({ 4068 type: message, 4069 data: params 4070 }, tile.abortController); 4071 delete tile.abortController; 4072 tile.unloadVectorData(); 4073 if (!tile.aborted) tile.loadVectorData(data, this.map.painter, message === "RT"); 4074 } catch (err) { 4075 delete tile.abortController; 4076 if (tile.aborted || isAbortError(err)) return; 4077 throw err; 4078 } 4079 } 4080 async abortTile(tile) { 4081 if (tile.abortController) { 4082 tile.abortController.abort(); 4083 delete tile.abortController; 4084 } 4085 tile.aborted = true; 4086 } 4087 async unloadTile(tile) { 4088 tile.unloadVectorData(); 4089 await (await this.actorPromise).sendAsync({ 4090 type: "RMT", 4091 data: { 4092 uid: tile.uid, 4093 type: this.type, 4094 source: this.id 4095 } 4096 }); 4097 } 4098 onRemove() { 4099 this._removed = true; 4100 this.actorPromise.then((actor) => actor.sendAsync({ 4101 type: "RS", 4102 data: { 4103 type: this.type, 4104 source: this.id 4105 } 4106 })); 4107 } 4108 serialize() { 4109 return extend({}, this._options, { 4110 type: this.type, 4111 data: this._data.updateable ? { 4112 type: "FeatureCollection", 4113 features: Array.from(this._data.updateable.values()) 4114 } : this._data.url || this._data.geojson 4115 }); 4116 } 4117 hasTransition() { 4118 return false; 4119 } 4120}; 4121//#endregion 4122//#region src/webgl/program/raster_program.ts 4123/** The bilinear warp, which is also the affine one that a rectangle or a parallelogram needs. */ 4124const bilinearImageWarp = [ 4125 0, 4126 0, 4127 1 4128]; 4129const rasterUniforms = (context, locations) => ({ 4130 "u_tl_parent": new Uniform2f(context, locations.u_tl_parent), 4131 "u_scale_parent": new Uniform1f(context, locations.u_scale_parent), 4132 "u_buffer_scale": new Uniform1f(context, locations.u_buffer_scale), 4133 "u_image_warp": new Uniform3f(context, locations.u_image_warp), 4134 "u_fade_t": new Uniform1f(context, locations.u_fade_t), 4135 "u_opacity": new Uniform1f(context, locations.u_opacity), 4136 "u_image0": new Uniform1i(context, locations.u_image0), 4137 "u_image1": new Uniform1i(context, locations.u_image1), 4138 "u_brightness_low": new Uniform1f(context, locations.u_brightness_low), 4139 "u_brightness_high": new Uniform1f(context, locations.u_brightness_high), 4140 "u_saturation_factor": new Uniform1f(context, locations.u_saturation_factor), 4141 "u_contrast_factor": new Uniform1f(context, locations.u_contrast_factor), 4142 "u_spin_weights": new Uniform3f(context, locations.u_spin_weights), 4143 "u_coords_top": new Uniform4f(context, locations.u_coords_top), 4144 "u_coords_bottom": new Uniform4f(context, locations.u_coords_bottom) 4145}); 4146const rasterUniformValues = (parentTL, parentScaleBy, fade, layer, cornerCoords, imageWarp) => ({ 4147 "u_tl_parent": parentTL, 4148 "u_scale_parent": parentScaleBy, 4149 "u_buffer_scale": 1, 4150 "u_image_warp": imageWarp, 4151 "u_fade_t": fade.mix, 4152 "u_opacity": fade.opacity * layer.paint.get("raster-opacity"), 4153 "u_image0": 0, 4154 "u_image1": 1, 4155 "u_brightness_low": layer.paint.get("raster-brightness-min"), 4156 "u_brightness_high": layer.paint.get("raster-brightness-max"), 4157 "u_saturation_factor": saturationFactor(layer.paint.get("raster-saturation")), 4158 "u_contrast_factor": contrastFactor(layer.paint.get("raster-contrast")), 4159 "u_spin_weights": spinWeights(layer.paint.get("raster-hue-rotate")), 4160 "u_coords_top": [ 4161 cornerCoords[0].x, 4162 cornerCoords[0].y, 4163 cornerCoords[1].x, 4164 cornerCoords[1].y 4165 ], 4166 "u_coords_bottom": [ 4167 cornerCoords[3].x, 4168 cornerCoords[3].y, 4169 cornerCoords[2].x, 4170 cornerCoords[2].y 4171 ] 4172}); 4173function spinWeights(angle) { 4174 angle *= Math.PI / 180; 4175 const s = Math.sin(angle); 4176 const c = Math.cos(angle); 4177 return [ 4178 (2 * c + 1) / 3, 4179 (-Math.sqrt(3) * s - c + 1) / 3, 4180 (Math.sqrt(3) * s - c + 1) / 3 4181 ]; 4182} 4183function contrastFactor(contrast) { 4184 return contrast > 0 ? 1 / (1 - contrast) : 1 + contrast; 4185} 4186function saturationFactor(saturation) { 4187 return saturation > 0 ? 1 - 1 / (1.001 - saturation) : -saturation; 4188} 4189//#endregion 4190//#region src/render/mesh.ts 4191var Mesh = class { 4192 constructor(vertexBuffer, indexBuffer, segments) { 4193 this.vertexBuffer = vertexBuffer; 4194 this.indexBuffer = indexBuffer; 4195 this.segments = segments; 4196 } 4197 destroy() { 4198 this.vertexBuffer.destroy(); 4199 this.indexBuffer.destroy(); 4200 this.segments.destroy(); 4201 this.vertexBuffer = null; 4202 this.indexBuffer = null; 4203 this.segments = null; 4204 } 4205}; 4206//#endregion 4207//#region src/data/pos_attributes.ts 4208const posAttributes = createLayout([{ 4209 name: "a_pos", 4210 type: "Int16", 4211 components: 2 4212}]); 4213//#endregion 4214//#region src/util/create_tile_mesh.ts 4215/** 4216* The size of border region for stencil masks, in internal tile coordinates. 4217* Used for globe rendering. 4218*/ 4219const EXTENT_STENCIL_BORDER = EXTENT / 128; 4220/** 4221* @internal 4222* Creates a mesh of a quad that covers the entire tile (covering positions in range 0..EXTENT), 4223* is optionally subdivided into finer quads, optionally includes a border 4224* and optionally extends to the north and/or special pole vertices. 4225* Also allocates and populates WebGL buffers for the mesh. 4226* Forces 16 bit indices that are used throughout MapLibre. 4227* @param context - The WebGL context wrapper. 4228* @param options - Specify options for tile mesh creation such as granularity or border. 4229* @returns The mesh vertices and indices, already allocated and uploaded into WebGL buffers. 4230*/ 4231function createTileMeshWithBuffers(context, options) { 4232 const tileMesh = createTileMesh(options, "16bit"); 4233 const vertices = PosArray.deserialize({ 4234 arrayBuffer: tileMesh.vertices, 4235 length: tileMesh.vertices.byteLength / 2 / 2 4236 }); 4237 const indices = TriangleIndexArray.deserialize({ 4238 arrayBuffer: tileMesh.indices, 4239 length: tileMesh.indices.byteLength / 2 / 3 4240 }); 4241 return new Mesh(context.createVertexBuffer(vertices, posAttributes.members), context.createIndexBuffer(indices), SegmentVector.simpleSegment(0, 0, vertices.length, indices.length)); 4242} 4243/** 4244* Creates a mesh of a quad that covers the entire tile (covering positions in range 0..EXTENT), 4245* is optionally subdivided into finer quads, optionally includes a border 4246* and optionally extends to the north and/or special pole vertices. 4247* Additionally the resulting mesh indices type can be specified using `forceIndicesSize`. 4248* @example 4249* ``` 4250* // Creating a mesh for a tile that can be used for raster layers, hillshade, etc. 4251* const meshBuffers = createTileMesh({ 4252* granularity: map.style.projection.subdivisionGranularity.tile.getGranularityForZoomLevel(tileID.z), 4253* generateBorders: true, 4254* extendToNorthPole: tileID.y === 0, 4255* extendToSouthPole: tileID.y === (1 << tileID.z) - 1, 4256* }, '16bit'); 4257* ``` 4258* @see [Add a custom layer with tiles to a globe](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-custom-layer-with-tiles-to-a-globe/) 4259* @param options - Specify options for tile mesh creation such as granularity or border. 4260* @param forceIndicesSize - Specifies what indices type to use. The values '32bit' and '16bit' force their respective indices size. If undefined, the mesh may use either size, and will pick 16 bit indices if possible. If '16bit' is specified and the mesh exceeds 65536 vertices, an exception is thrown. 4261* @returns Typed arrays of the mesh vertices and indices. 4262*/ 4263function createTileMesh(options, forceIndicesSize) { 4264 const granularity = options.granularity !== void 0 ? Math.max(options.granularity, 1) : 1; 4265 const quadsPerAxisX = granularity + (options.generateBorders ? 2 : 0); 4266 const quadsPerAxisY = granularity + (options.extendToNorthPole || options.generateBorders ? 1 : 0) + (options.extendToSouthPole || options.generateBorders ? 1 : 0); 4267 const verticesPerAxisX = quadsPerAxisX + 1; 4268 const verticesPerAxisY = quadsPerAxisY + 1; 4269 const offsetX = options.generateBorders ? -1 : 0; 4270 const offsetY = options.generateBorders || options.extendToNorthPole ? -1 : 0; 4271 const endX = granularity + (options.generateBorders ? 1 : 0); 4272 const endY = granularity + (options.generateBorders || options.extendToSouthPole ? 1 : 0); 4273 const vertexCount = verticesPerAxisX * verticesPerAxisY; 4274 const indexCount = quadsPerAxisX * quadsPerAxisY * 6; 4275 const overflows16bitIndices = verticesPerAxisX * verticesPerAxisY > 65536; 4276 if (overflows16bitIndices && forceIndicesSize === "16bit") throw new Error("Granularity is too large and meshes would not fit inside 16 bit vertex indices."); 4277 const use32bitIndices = overflows16bitIndices || forceIndicesSize === "32bit"; 4278 const vertices = new Int16Array(vertexCount * 2); 4279 let vertexId = 0; 4280 for (let y = offsetY; y <= endY; y++) for (let x = offsetX; x <= endX; x++) { 4281 let vx = x / granularity * EXTENT; 4282 if (x === -1) vx = -EXTENT_STENCIL_BORDER; 4283 if (x === granularity + 1) vx = EXTENT + EXTENT_STENCIL_BORDER; 4284 let vy = y / granularity * EXTENT; 4285 if (y === -1) vy = options.extendToNorthPole ? NORTH_POLE_Y : -EXTENT_STENCIL_BORDER; 4286 if (y === granularity + 1) vy = options.extendToSouthPole ? SOUTH_POLE_Y : EXTENT + EXTENT_STENCIL_BORDER; 4287 vertices[vertexId++] = vx; 4288 vertices[vertexId++] = vy; 4289 } 4290 const indices = use32bitIndices ? new Uint32Array(indexCount) : new Uint16Array(indexCount); 4291 let indexId = 0; 4292 for (let y = 0; y < quadsPerAxisY; y++) for (let x = 0; x < quadsPerAxisX; x++) { 4293 const v0 = x + y * verticesPerAxisX; 4294 const v1 = x + 1 + y * verticesPerAxisX; 4295 const v2 = x + (y + 1) * verticesPerAxisX; 4296 const v3 = x + 1 + (y + 1) * verticesPerAxisX; 4297 indices[indexId++] = v0; 4298 indices[indexId++] = v2; 4299 indices[indexId++] = v1; 4300 indices[indexId++] = v1; 4301 indices[indexId++] = v2; 4302 indices[indexId++] = v3; 4303 } 4304 return { 4305 vertices: vertices.buffer.slice(0), 4306 indices: indices.buffer.slice(0), 4307 uses32bitIndices: use32bitIndices 4308 }; 4309} 4310//#endregion 4311//#region src/source/image_source.ts 4312/** 4313* How many grid cells per axis the mesh of a subdivided quad has. Within a cell the bilinear warp 4314* is still approximated by one mapping per triangle, so the error falls off with the cell size. 4315*/ 4316const SUBDIVIDED_QUAD_GRANULARITY = 16; 4317/** 4318* How much a warp may foreshorten the image, as the ratio between the largest and the smallest 4319* homogeneous denominator over the quad, while staying purely projective. Ordinary oblique imagery 4320* stays well below it. 4321*/ 4322const PROJECTIVE_FORESHORTENING = 4; 4323/** 4324* The foreshortening at which a warp is taken to be purely bilinear. 4325* 4326* The denominator vanishes on the warp's vanishing line, and that line touches the quad exactly when 4327* the quad degenerates into a triangle, so foreshortening grows without bound as a corner approaches 4328* the diagonal between its neighbours. Left alone, such a quad keeps a valid but wildly lopsided 4329* warp, squeezing nearly the whole image into a sliver along one edge and magnifying a handful of 4330* texels over the rest of the quad. 4331*/ 4332const BILINEAR_FORESHORTENING = 512; 4333/** 4334* A data source containing an image. 4335* (See the [Style Specification](https://maplibre.org/maplibre-style-spec/#sources-image) for detailed documentation of options.) 4336* 4337* @group Sources 4338* 4339* @example 4340* ```ts 4341* // add to map 4342* map.addSource('some id', { 4343* type: 'image', 4344* url: 'https://www.maplibre.org/images/foo.png', 4345* coordinates: [ 4346* [-76.54, 39.18], 4347* [-76.52, 39.18], 4348* [-76.52, 39.17], 4349* [-76.54, 39.17] 4350* ] 4351* }); 4352* 4353* // update coordinates 4354* let mySource = map.getSource('some id'); 4355* mySource.setCoordinates([ 4356* [-76.54335737228394, 39.18579907229748], 4357* [-76.52803659439087, 39.1838364847587], 4358* [-76.5295386314392, 39.17683392507606], 4359* [-76.54520273208618, 39.17876344106642] 4360* ]); 4361* 4362* // update url and coordinates simultaneously 4363* mySource.updateImage({ 4364* url: 'https://www.maplibre.org/images/bar.png', 4365* coordinates: [ 4366* [-76.54335737228394, 39.18579907229748], 4367* [-76.52803659439087, 39.1838364847587], 4368* [-76.5295386314392, 39.17683392507606], 4369* [-76.54520273208618, 39.17876344106642] 4370* ] 4371* }) 4372* 4373* // update with an already-decoded image (no network request) 4374* const bitmap = await createImageBitmap(myCanvas); 4375* mySource.updateImage({image: bitmap}); 4376* 4377* // create an empty source (no url), then feed it entirely via updateImage 4378* map.addSource('empty id', { 4379* type: 'image', 4380* coordinates: [ 4381* [-76.54, 39.18], 4382* [-76.52, 39.18], 4383* [-76.52, 39.17], 4384* [-76.54, 39.17] 4385* ] 4386* }); 4387* (map.getSource('empty id') as ImageSource).updateImage({image: bitmap}); 4388* 4389* map.removeSource('some id'); // remove 4390* ``` 4391*/ 4392var ImageSource = class extends Evented { 4393 /** @internal */ 4394 constructor(id, options, dispatcher, eventedParent) { 4395 super(); 4396 this.imageWarp = bilinearImageWarp; 4397 this.flippedWindingOrder = false; 4398 this._warp = "auto"; 4399 this._imageDirty = false; 4400 this._subdividedQuad = false; 4401 this._subdividedMesh = null; 4402 this.id = id; 4403 this.dispatcher = dispatcher; 4404 this.coordinates = options.coordinates; 4405 this.type = "image"; 4406 this.minzoom = 0; 4407 this.maxzoom = 22; 4408 this.tileSize = 512; 4409 this.tiles = {}; 4410 this._loaded = false; 4411 this.setEventedParent(eventedParent); 4412 this.options = options; 4413 } 4414 async load(newCoordinates) { 4415 if (this.options.url === void 0) { 4416 this._loaded = true; 4417 this._finishLoading(); 4418 return; 4419 } 4420 this._loaded = false; 4421 this.fire(new MapSourceDataEvent("dataloading")); 4422 this.url = this.options.url; 4423 this._abortController = new AbortController(); 4424 try { 4425 const image = await ImageRequest.transformAndGetImage(this.map._requestManager, this.url, "Image", this._abortController); 4426 this._abortController = null; 4427 this._loaded = true; 4428 if (image?.data) { 4429 this._setImage(image.data); 4430 if (newCoordinates) this.coordinates = newCoordinates; 4431 this._finishLoading(); 4432 } else this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Could not load image ${this.url}: the response is empty`))); 4433 } catch (err) { 4434 if (isAbortError(err)) return; 4435 this._abortController = null; 4436 this._loaded = true; 4437 this.fire(new ErrorEvent(ensureError(err))); 4438 } 4439 } 4440 loaded() { 4441 return this._loaded; 4442 } 4443 /** 4444 * Updates the image and, optionally, the coordinates. To avoid having the image flash after changing, 4445 * set the `raster-fade-duration` paint property on the raster layer to 0. 4446 * 4447 * Provide exactly one of `url` (to fetch a new image over the network) or `image` (an 4448 * already-decoded `HTMLImageElement`, `HTMLCanvasElement`, `ImageBitmap` or `ImageData` to 4449 * display directly, without a network request). 4450 * 4451 * @param options - The options object. 4452 */ 4453 updateImage(options) { 4454 if (this._abortController) { 4455 this._abortController.abort(); 4456 this._abortController = null; 4457 } 4458 if ("image" in options) { 4459 this._loaded = true; 4460 this._setImage(options.image); 4461 if (options.coordinates) this.coordinates = options.coordinates; 4462 this._finishLoading(); 4463 return this; 4464 } 4465 if (!options.url) return this; 4466 this.options.url = options.url; 4467 this.load(options.coordinates); 4468 return this; 4469 } 4470 /** Loaded tiles hold `this.texture`, so the wrapper has to outlive the images in it. */ 4471 _setImage(image) { 4472 this.image = image; 4473 this._imageDirty = true; 4474 } 4475 /** 4476 * @internal 4477 * The mesh the image is drawn with, or null to use the tile mesh of the current projection. 4478 * 4479 * The raster vertex shader evaluates {@link imageWarp} per vertex, so a pair of triangles only 4480 * samples it at the four corners. That is enough for the affine mapping of a parallelogram, and 4481 * for a purely projective mapping, whose straight lines survive linear interpolation across the 4482 * diagonal. Anything in between is neither, and would be textured as two independently warped 4483 * halves with a visible seam along that diagonal, so it needs the warp evaluated per grid cell. 4484 * 4485 * A projection that subdivides its own tile meshes already does this, so it keeps them. 4486 */ 4487 getMesh(context, projectionSubdividesTiles) { 4488 if (!this._subdividedQuad || projectionSubdividesTiles) return null; 4489 this._subdividedMesh ??= createTileMeshWithBuffers(context, { granularity: SUBDIVIDED_QUAD_GRANULARITY }); 4490 return this._subdividedMesh; 4491 } 4492 /** Teardown only: dropping the reference alone leaves the allocation to the GC. */ 4493 _disposeTexture() { 4494 this.texture?.destroy(); 4495 this.texture = null; 4496 } 4497 _finishLoading() { 4498 if (this.map) { 4499 this.setCoordinates(this.coordinates); 4500 this.fire(new MapSourceDataEvent("data", { sourceDataType: "metadata" })); 4501 } 4502 } 4503 onAdd(map) { 4504 this.map = map; 4505 this.load(); 4506 } 4507 onRemove() { 4508 if (this._abortController) { 4509 this._abortController.abort(); 4510 this._abortController = null; 4511 } 4512 this._disposeTexture(); 4513 this._subdividedMesh?.destroy(); 4514 this._subdividedMesh = null; 4515 this.image = null; 4516 this.tiles = {}; 4517 } 4518 /** 4519 * @experimental 4520 * Sets how the image is warped onto its coordinates and re-renders the map. 4521 * 4522 * This only has an effect while the coordinates do not form a rectangle, and it is not part of 4523 * the style specification, so it does not survive `map.setStyle`. 4524 * 4525 * @param warp - The warp to use, see {@link ImageSourceWarp}. 4526 * 4527 * @example 4528 * ```ts 4529 * // Keep the image pinned to its corners while the user drags them around. 4530 * map.getSource('some id').setWarp('flat'); 4531 * ``` 4532 */ 4533 setWarp(warp) { 4534 if (this._warp === warp) return this; 4535 this._warp = warp; 4536 if (this.tileCoords) this.setCoordinates(this.coordinates); 4537 return this; 4538 } 4539 /** 4540 * @experimental 4541 * Returns how the image is warped onto its coordinates. 4542 * 4543 * @returns The warp in use, see {@link ImageSourceWarp}. 4544 */ 4545 getWarp() { 4546 return this._warp; 4547 } 4548 /** 4549 * Sets the image's coordinates and re-renders the map. 4550 * 4551 * @param coordinates - Four geographical coordinates, 4552 * represented as arrays of longitude and latitude numbers, which define the corners of the image. 4553 * The coordinates start at the top left corner of the image and proceed in clockwise order. 4554 * They do not have to represent a rectangle. 4555 */ 4556 setCoordinates(coordinates) { 4557 this.coordinates = coordinates; 4558 const cornerCoords = coordinates.map(MercatorCoordinate.fromLngLat); 4559 this.tileID = getCoordinatesCenterTileID(cornerCoords); 4560 this.terrainTileRanges = this._getOverlappingTileRanges(cornerCoords); 4561 this.minzoom = this.maxzoom = this.tileID.z; 4562 this.tileCoords = cornerCoords.map((coord) => this.tileID.getTilePoint(coord)._round()); 4563 this.imageWarp = calculateImageWarp(this.tileCoords, this._warp); 4564 this._subdividedQuad = this.imageWarp[2] > 0 && !isParallelogram(this.tileCoords); 4565 this.flippedWindingOrder = hasWrongWindingOrder(this.tileCoords); 4566 this.fire(new MapSourceDataEvent("data", { sourceDataType: "content" })); 4567 return this; 4568 } 4569 prepare() { 4570 if (Object.keys(this.tiles).length === 0 || !this.image) return; 4571 const context = this.map.painter.context; 4572 const gl = context.gl; 4573 if (!this.texture) { 4574 this.texture = new Texture(context, this.image, gl.RGBA); 4575 this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 4576 } else if (this._imageDirty) { 4577 this.texture.update(this.image); 4578 this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 4579 } 4580 this._imageDirty = false; 4581 let newTilesLoaded = false; 4582 for (const w in this.tiles) { 4583 const tile = this.tiles[w]; 4584 if (tile.state !== "loaded") { 4585 tile.state = "loaded"; 4586 tile.texture = this.texture; 4587 newTilesLoaded = true; 4588 } 4589 } 4590 if (newTilesLoaded) this.fire(new MapSourceDataEvent("data", { 4591 sourceDataType: "idle", 4592 sourceId: this.id 4593 })); 4594 } 4595 async loadTile(tile) { 4596 if (this.tileID?.equals(tile.tileID.canonical)) { 4597 this.tiles[String(tile.tileID.wrap)] = tile; 4598 tile.buckets = {}; 4599 } else tile.state = "errored"; 4600 } 4601 serialize() { 4602 const spec = { 4603 type: "image", 4604 coordinates: this.coordinates 4605 }; 4606 if (this.options.url !== void 0) spec.url = this.options.url; 4607 return spec; 4608 } 4609 hasTransition() { 4610 return false; 4611 } 4612 /** 4613 * Given a list of coordinates, determine overlapping tile ranges for all zoom levels. 4614 * 4615 * @returns Overlapping tile ranges for all zoom levels. 4616 * @internal 4617 */ 4618 _getOverlappingTileRanges(coords) { 4619 const { minX, minY, maxX, maxY } = Bounds.fromPoints(coords); 4620 const ranges = {}; 4621 for (let z = 0; z <= 25; z++) { 4622 const tilesAtZoom = Math.pow(2, z); 4623 const minTileX = Math.floor(minX * tilesAtZoom); 4624 const minTileY = Math.floor(minY * tilesAtZoom); 4625 const maxTileX = Math.floor(maxX * tilesAtZoom); 4626 const maxTileY = Math.floor(maxY * tilesAtZoom); 4627 const minTileXWrapped = (minTileX % tilesAtZoom + tilesAtZoom) % tilesAtZoom; 4628 const maxTileXWrapped = maxTileX % tilesAtZoom; 4629 const minWrap = Math.floor(minTileX / tilesAtZoom); 4630 const maxWrap = Math.floor(maxTileX / tilesAtZoom); 4631 ranges[z] = { 4632 minWrap, 4633 maxWrap, 4634 minTileXWrapped, 4635 maxTileXWrapped, 4636 minTileY, 4637 maxTileY 4638 }; 4639 } 4640 return ranges; 4641 } 4642}; 4643/** 4644* Given a list of coordinates, get their center as a coordinate. 4645* 4646* @returns centerpoint 4647* @internal 4648*/ 4649function getCoordinatesCenterTileID(coords) { 4650 const bounds = Bounds.fromPoints(coords); 4651 const dx = bounds.width(); 4652 const dy = bounds.height(); 4653 const zoom = Math.max(0, Math.floor(-Math.log(Math.max(dx, dy)) / Math.LN2)); 4654 const tilesAtZoom = Math.pow(2, zoom); 4655 return new CanonicalTileID(zoom, Math.floor((bounds.minX + bounds.maxX) / 2 * tilesAtZoom), Math.floor((bounds.minY + bounds.maxY) / 2 * tilesAtZoom)); 4656} 4657function hasWrongWindingOrder(coords) { 4658 const e0x = coords[1].x - coords[0].x; 4659 const e0y = coords[1].y - coords[0].y; 4660 const e1x = coords[2].x - coords[0].x; 4661 return e0x * (coords[2].y - coords[0].y) - e0y * e1x < 0; 4662} 4663/** 4664* How the image is warped onto its coordinates, given the corners in tile space and what the user 4665* asked for. The projective warp is the perspective view of a plane, so it is the one a photograph 4666* wants; the bilinear warp is a rubber sheet pinned at the corners, and is the only one left once 4667* the corners stop describing a perspective view at all. 4668* 4669* Based on Paul S. Heckbert, "Fundamentals of Texture Mapping and Image 4670* Warping", UCB/CSD-89-516, 1989, section 2.2.3 and appendix A.2. 4671* 4672* @see https://www2.eecs.berkeley.edu/Pubs/TechRpts/1989/5504.html 4673* @see https://www.cs.cmu.edu/~ph/texfund/texfund.pdf 4674*/ 4675function calculateImageWarp(cornerCoords, warp) { 4676 if (warp === "flat" || isParallelogram(cornerCoords)) return bilinearImageWarp; 4677 const [topLeft, topRight, bottomRight, bottomLeft] = cornerCoords; 4678 const sumX = topLeft.x - topRight.x + bottomRight.x - bottomLeft.x; 4679 const sumY = topLeft.y - topRight.y + bottomRight.y - bottomLeft.y; 4680 const basis = [ 4681 topRight.x - bottomRight.x, 4682 topRight.y - bottomRight.y, 4683 bottomLeft.x - bottomRight.x, 4684 bottomLeft.y - bottomRight.y 4685 ]; 4686 const [rightX, rightY, downX, downY] = basis; 4687 const determinant$1 = determinant(basis); 4688 const perspectiveX = (sumX * downY - downX * sumY) / determinant$1; 4689 const perspectiveY = (rightX * sumY - sumX * rightY) / determinant$1; 4690 const denominators = [ 4691 1, 4692 1 + perspectiveX, 4693 1 + perspectiveX + perspectiveY, 4694 1 + perspectiveY 4695 ]; 4696 const foreshortening = Math.max(...denominators) / Math.min(...denominators); 4697 const blend = warp === "perspective" ? 0 : bilinearBlend(foreshortening); 4698 if (!(foreshortening >= 1 && foreshortening <= BILINEAR_FORESHORTENING) || blend >= 1) return bilinearImageWarp; 4699 return [ 4700 perspectiveX, 4701 perspectiveY, 4702 blend 4703 ]; 4704} 4705/** 4706* How far a warp of the given foreshortening is blended towards the bilinear one, ramping from 4707* purely projective at {@link PROJECTIVE_FORESHORTENING} to purely bilinear at 4708* {@link BILINEAR_FORESHORTENING}. Blending rather than switching keeps the image continuous as the 4709* quad is reshaped: the two warps only agree for a parallelogram, and diverge by roughly a tenth of 4710* the quad per unit of foreshortening, so a switch would visibly displace the image. 4711*/ 4712function bilinearBlend(foreshortening) { 4713 const ramp = (1 - PROJECTIVE_FORESHORTENING / foreshortening) / (1 - PROJECTIVE_FORESHORTENING / BILINEAR_FORESHORTENING); 4714 return Math.max(0, ramp); 4715} 4716function isParallelogram(cornerCoords) { 4717 const [topLeft, topRight, bottomRight, bottomLeft] = cornerCoords; 4718 return topLeft.x + bottomRight.x === topRight.x + bottomLeft.x && topLeft.y + bottomRight.y === topRight.y + bottomLeft.y; 4719} 4720//#endregion 4721//#region src/source/video_source.ts 4722/** 4723* A data source containing video. 4724* (See the [Style Specification](https://maplibre.org/maplibre-style-spec/#sources-video) for detailed documentation of options.) 4725* 4726* @group Sources 4727* 4728* @example 4729* ```ts 4730* // add to map 4731* map.addSource('some id', { 4732* type: 'video', 4733* url: [ 4734* 'https://www.mapbox.com/blog/assets/baltimore-smoke.mp4', 4735* 'https://www.mapbox.com/blog/assets/baltimore-smoke.webm' 4736* ], 4737* coordinates: [ 4738* [-76.54, 39.18], 4739* [-76.52, 39.18], 4740* [-76.52, 39.17], 4741* [-76.54, 39.17] 4742* ] 4743* }); 4744* 4745* // update 4746* let mySource = map.getSource('some id'); 4747* mySource.setCoordinates([ 4748* [-76.54335737228394, 39.18579907229748], 4749* [-76.52803659439087, 39.1838364847587], 4750* [-76.5295386314392, 39.17683392507606], 4751* [-76.54520273208618, 39.17876344106642] 4752* ]); 4753* 4754* map.removeSource('some id'); // remove 4755* ``` 4756* @see [Add a video](https://maplibre.org/maplibre-gl-js/docs/examples/video-on-a-map/) 4757* 4758* Note that when rendered as a raster layer, the layer's `raster-fade-duration` property will cause the video to fade in. 4759* This happens when playback is started, paused and resumed, or when the video's coordinates are updated. To avoid this behavior, 4760* set the layer's `raster-fade-duration` property to `0`. 4761*/ 4762var VideoSource = class extends ImageSource { 4763 constructor(id, options, dispatcher, eventedParent) { 4764 super(id, options, dispatcher, eventedParent); 4765 this._onPlayingHandler = () => { 4766 this.map?.triggerRepaint(); 4767 }; 4768 this.roundZoom = true; 4769 this.type = "video"; 4770 this.options = options; 4771 } 4772 async load() { 4773 this._loaded = false; 4774 const options = this.options; 4775 this.urls = []; 4776 for (const url of options.urls) this.urls.push((await this.map._requestManager.transformRequest(url, "Source")).url); 4777 try { 4778 const video = await getVideo(this.urls); 4779 this._loaded = true; 4780 if (!video) return; 4781 this.video = video; 4782 this.video.loop = true; 4783 this.video.addEventListener("playing", this._onPlayingHandler); 4784 if (this.map) this.video.play(); 4785 this._finishLoading(); 4786 } catch (err) { 4787 this.fire(new ErrorEvent(ensureError(err))); 4788 } 4789 } 4790 /** 4791 * Pauses the video. 4792 */ 4793 pause() { 4794 if (this.video) this.video.pause(); 4795 } 4796 /** 4797 * Plays the video. 4798 */ 4799 play() { 4800 if (this.video) this.video.play(); 4801 } 4802 /** 4803 * Sets playback to a timestamp, in seconds. 4804 */ 4805 seek(seconds) { 4806 if (this.video) { 4807 const seekableRange = this.video.seekable; 4808 if (seconds < seekableRange.start(0) || seconds > seekableRange.end(0)) this.fire(new ErrorEvent(new ValidationError(`sources.${this.id}`, null, `Playback for this video can be set only between the ${seekableRange.start(0)} and ${seekableRange.end(0)}-second mark.`))); 4809 else this.video.currentTime = seconds; 4810 } 4811 } 4812 /** 4813 * Returns the HTML `video` element. 4814 * 4815 * @returns The HTML `video` element. 4816 */ 4817 getVideo() { 4818 return this.video; 4819 } 4820 onAdd(map) { 4821 if (this.map) return; 4822 this.map = map; 4823 this.load(); 4824 if (this.video) { 4825 this.video.play(); 4826 this.setCoordinates(this.coordinates); 4827 } 4828 } 4829 onRemove() { 4830 super.onRemove(); 4831 if (this.video) { 4832 this.video.removeEventListener("playing", this._onPlayingHandler); 4833 this.video.pause(); 4834 } 4835 } 4836 /** 4837 * Sets the video's coordinates and re-renders the map. 4838 */ 4839 prepare() { 4840 if (Object.keys(this.tiles).length === 0 || this.video.readyState < 2) return; 4841 const context = this.map.painter.context; 4842 const gl = context.gl; 4843 if (!this.texture) { 4844 this.texture = new Texture(context, this.video, gl.RGBA); 4845 this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 4846 } else if (!this.video.paused) { 4847 this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 4848 gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.video); 4849 } 4850 let newTilesLoaded = false; 4851 for (const w in this.tiles) { 4852 const tile = this.tiles[w]; 4853 if (tile.state !== "loaded") { 4854 tile.state = "loaded"; 4855 tile.texture = this.texture; 4856 newTilesLoaded = true; 4857 } 4858 } 4859 if (newTilesLoaded) this.fire(new MapSourceDataEvent("data", { 4860 sourceDataType: "idle", 4861 sourceId: this.id 4862 })); 4863 } 4864 serialize() { 4865 return { 4866 type: "video", 4867 urls: this.urls, 4868 coordinates: this.coordinates 4869 }; 4870 } 4871 hasTransition() { 4872 return this.video && !this.video.paused; 4873 } 4874}; 4875//#endregion 4876//#region src/source/canvas_source.ts 4877/** 4878* A data source containing the contents of an HTML canvas. See {@link CanvasSourceSpecification} for detailed documentation of options. 4879* 4880* @group Sources 4881* 4882* @example 4883* ```ts 4884* // add to map 4885* map.addSource('some id', { 4886* type: 'canvas', 4887* canvas: 'idOfMyHTMLCanvas', 4888* animate: true, 4889* coordinates: [ 4890* [-76.54, 39.18], 4891* [-76.52, 39.18], 4892* [-76.52, 39.17], 4893* [-76.54, 39.17] 4894* ] 4895* }); 4896* 4897* // update 4898* let mySource = map.getSource('some id'); 4899* mySource.setCoordinates([ 4900* [-76.54335737228394, 39.18579907229748], 4901* [-76.52803659439087, 39.1838364847587], 4902* [-76.5295386314392, 39.17683392507606], 4903* [-76.54520273208618, 39.17876344106642] 4904* ]); 4905* 4906* map.removeSource('some id'); // remove 4907* ``` 4908*/ 4909var CanvasSource = class extends ImageSource { 4910 /** @internal */ 4911 constructor(id, options, dispatcher, eventedParent) { 4912 super(id, options, dispatcher, eventedParent); 4913 if (!options.coordinates) this.fire(new ErrorEvent(new ValidationError(`sources.${id}`, null, "missing required property \"coordinates\""))); 4914 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 ErrorEvent(new ValidationError(`sources.${id}`, null, "\"coordinates\" property must be an array of 4 longitude/latitude array pairs"))); 4915 if (options.animate && typeof options.animate !== "boolean") this.fire(new ErrorEvent(new ValidationError(`sources.${id}`, null, "optional \"animate\" property must be a boolean value"))); 4916 if (!options.canvas) this.fire(new ErrorEvent(new ValidationError(`sources.${id}`, null, "missing required property \"canvas\""))); 4917 else if (typeof options.canvas !== "string" && !(options.canvas instanceof HTMLCanvasElement)) this.fire(new ErrorEvent(new 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"))); 4918 this.options = options; 4919 this.animate = options.animate !== void 0 ? options.animate : true; 4920 } 4921 async load() { 4922 this._loaded = true; 4923 this.canvas ||= this.options.canvas instanceof HTMLCanvasElement ? this.options.canvas : document.getElementById(this.options.canvas); 4924 this.width = this.canvas.width; 4925 this.height = this.canvas.height; 4926 if (this._hasInvalidDimensions()) { 4927 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("Canvas dimensions cannot be less than or equal to zero."))); 4928 return; 4929 } 4930 this.play = function() { 4931 this._playing = true; 4932 this.map.triggerRepaint(); 4933 }; 4934 this.pause = function() { 4935 if (this._playing) { 4936 this.prepare(); 4937 this._playing = false; 4938 } 4939 }; 4940 this._finishLoading(); 4941 } 4942 /** 4943 * Returns the HTML `canvas` element. 4944 * 4945 * @returns The HTML `canvas` element. 4946 */ 4947 getCanvas() { 4948 return this.canvas; 4949 } 4950 onAdd(map) { 4951 this.map = map; 4952 this.load(); 4953 if (this.canvas) { 4954 if (this.animate) this.play(); 4955 } 4956 } 4957 onRemove() { 4958 this._playing = false; 4959 super.onRemove(); 4960 } 4961 prepare() { 4962 let resize = false; 4963 if (this.canvas.width !== this.width) { 4964 this.width = this.canvas.width; 4965 resize = true; 4966 } 4967 if (this.canvas.height !== this.height) { 4968 this.height = this.canvas.height; 4969 resize = true; 4970 } 4971 if (this._hasInvalidDimensions()) return; 4972 if (Object.keys(this.tiles).length === 0) return; 4973 const context = this.map.painter.context; 4974 const gl = context.gl; 4975 if (!this.texture) { 4976 this.texture = new Texture(context, this.canvas, gl.RGBA, { premultiply: true }); 4977 this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 4978 } else if (resize || this._playing) this.texture.update(this.canvas, { premultiply: true }); 4979 let newTilesLoaded = false; 4980 for (const w in this.tiles) { 4981 const tile = this.tiles[w]; 4982 if (tile.state !== "loaded") { 4983 tile.state = "loaded"; 4984 tile.texture = this.texture; 4985 newTilesLoaded = true; 4986 } 4987 } 4988 if (newTilesLoaded) this.fire(new MapSourceDataEvent("data", { 4989 sourceDataType: "idle", 4990 sourceId: this.id 4991 })); 4992 } 4993 serialize() { 4994 return { 4995 type: "canvas", 4996 animate: this.animate, 4997 canvas: this.options.canvas, 4998 coordinates: this.coordinates 4999 }; 5000 } 5001 hasTransition() { 5002 return this._playing; 5003 } 5004 _hasInvalidDimensions() { 5005 for (const x of [this.canvas.width, this.canvas.height]) if (isNaN(x) || x <= 0) return true; 5006 return false; 5007 } 5008}; 5009//#endregion 5010//#region src/source/source.ts 5011const registeredSources = {}; 5012/** 5013* Creates a tiled data source instance given an options object. 5014* 5015* @param id - The id for the source. Must not be used by any existing source. 5016* @param specification - Source options, specific to the source type (except for `options.type`, which is always required). 5017* @param source - A source definition object compliant with 5018* [`maplibre-gl-style-spec`](https://maplibre.org/maplibre-style-spec/#sources) or, for a third-party source type, 5019* with that type's requirements. 5020* @param dispatcher - A {@link Dispatcher} instance, which can be used to send messages to the workers. 5021* @returns a newly created source 5022*/ 5023const create = (id, specification, dispatcher, eventedParent) => { 5024 const source = new (getSourceType(specification.type))(id, specification, dispatcher, eventedParent); 5025 if (source.id !== id) throw new Error(`Expected Source id to be ${id} instead of ${source.id}`); 5026 return source; 5027}; 5028const getSourceType = (name) => { 5029 switch (name) { 5030 case "geojson": return GeoJSONSource; 5031 case "image": return ImageSource; 5032 case "raster": return RasterTileSource; 5033 case "raster-dem": return RasterDEMTileSource; 5034 case "vector": return VectorTileSource; 5035 case "video": return VideoSource; 5036 case "canvas": return CanvasSource; 5037 } 5038 return registeredSources[name]; 5039}; 5040const setSourceType = (name, type) => { 5041 registeredSources[name] = type; 5042}; 5043/** 5044* Adds a custom source type, making it available for use with {@link Map.addSource}. 5045* @param name - The name of the source type; source definition objects use this name in the `{type: ...}` field. 5046* @param SourceType - A {@link SourceClass} - which is a constructor for the `Source` interface. 5047* @returns a promise that is resolved when the source type is ready or rejected with an error. 5048*/ 5049const addSourceType = async (name, SourceType) => { 5050 if (getSourceType(name)) throw new Error(`A source type called "${name}" already exists.`); 5051 setSourceType(name, SourceType); 5052}; 5053//#endregion 5054//#region src/data/bucket.ts 5055function deserialize(input, style) { 5056 const output = {}; 5057 if (!style) return output; 5058 for (const bucket of input) { 5059 const layers = bucket.layerIds.map((id) => style.getLayer(id)).filter(Boolean); 5060 if (layers.length === 0) continue; 5061 bucket.layers = layers; 5062 if (bucket.stateDependentLayerIds) bucket.stateDependentLayers = bucket.stateDependentLayerIds.map((lId) => layers.filter((l) => l.id === lId)[0]); 5063 for (const layer of layers) output[layer.id] = bucket; 5064 } 5065 return output; 5066} 5067//#endregion 5068//#region src/source/rtl_text_plugin_status.ts 5069/** 5070* Fired once a plugin set through {@link setRTLTextPlugin} has loaded, so that tiles drawn before it 5071* arrived can be drawn again. 5072* 5073* @deprecated Right-to-left text is drawn without a plugin, so nothing has to be waited for. 5074*/ 5075const RTLPluginLoadedEventName = "RTLPluginLoaded"; 5076//#endregion 5077//#region src/source/rtl_text_plugin_main_thread.ts 5078var RTLMainThreadPlugin = class extends Evented { 5079 constructor(..._args) { 5080 super(..._args); 5081 this.status = "unavailable"; 5082 this.url = null; 5083 this.dispatcher = getGlobalDispatcher(); 5084 } 5085 /** Sync RTL plugin state by broadcasting a message to the worker */ 5086 _syncState(statusToSend) { 5087 this.status = statusToSend; 5088 return this.dispatcher.broadcast("SRPS", { 5089 pluginStatus: statusToSend, 5090 pluginURL: this.url 5091 }).catch((e) => { 5092 this.status = "error"; 5093 throw e; 5094 }); 5095 } 5096 /** This one is exposed to outside */ 5097 getRTLTextPluginStatus() { 5098 return this.status; 5099 } 5100 clearRTLTextPlugin() { 5101 this.status = "unavailable"; 5102 this.url = null; 5103 } 5104 async setRTLTextPlugin(url, deferred = false) { 5105 if (this.url) throw new Error("setRTLTextPlugin cannot be called multiple times."); 5106 this.url = browser.resolveURL(url); 5107 if (!this.url) throw new Error(`requested url ${url} is invalid`); 5108 if (this.status === "unavailable") { 5109 if (deferred) { 5110 this.status = "deferred"; 5111 this._syncState(this.status); 5112 } else return this._requestImport(); 5113 } else if (this.status === "requested") return this._requestImport(); 5114 } 5115 /** Send a message to worker which will import the RTL plugin script */ 5116 async _requestImport() { 5117 await this._syncState("loading"); 5118 this.status = "loaded"; 5119 this.fire(new Event(RTLPluginLoadedEventName)); 5120 } 5121 /** Start a lazy loading process of RTL plugin */ 5122 lazyLoad() { 5123 if (this.status === "unavailable") this.status = "requested"; 5124 else if (this.status === "deferred") this._requestImport(); 5125 } 5126}; 5127let rtlMainThreadPlugin = null; 5128function rtlMainThreadPluginFactory() { 5129 rtlMainThreadPlugin ||= new RTLMainThreadPlugin(); 5130 return rtlMainThreadPlugin; 5131} 5132//#endregion 5133//#region src/tile/tile.ts 5134const CLOCK_SKEW_RETRY_TIMEOUT = 3e4; 5135/** 5136* A tile object is the combination of a Coordinate, which defines 5137* its place, as well as a unique ID and data tracking for its content 5138*/ 5139var Tile = class { 5140 /** 5141 * @param tileID - the tile ID 5142 * @param size - The tile size 5143 */ 5144 constructor(tileID, size) { 5145 this.timeAdded = 0; 5146 this.fadeEndTime = 0; 5147 this.fadeOpacity = 1; 5148 this.tileID = tileID; 5149 this.uid = uniqueId(); 5150 this.uses = 0; 5151 this.tileSize = size; 5152 this.buckets = {}; 5153 this.expirationTime = null; 5154 this.queryPadding = 0; 5155 this.hasSymbolBuckets = false; 5156 this.hasRTLText = false; 5157 this.dependencies = {}; 5158 this.rttObjects = []; 5159 this.rttFingerprint = {}; 5160 this.expiredRequestCount = 0; 5161 this.state = "loading"; 5162 this.featureStateRevision = -1; 5163 } 5164 /** 5165 * Incoming and self-fading raster tiles must remain renderable at zero opacity because 5166 * drawing advances their opacity. Only transparent departing tiles have finished fading. 5167 */ 5168 isRenderable(symbolLayer) { 5169 return this.hasData() && (!this.fadeEndTime || this.fadingDirection !== 0 || this.fadeOpacity > 0) && (symbolLayer || !this.holdingForSymbolFade()); 5170 } 5171 /** 5172 * @internal 5173 * Many-to-one crossfade between a base tile and parent/ancestor tile (when zooming) 5174 */ 5175 setCrossFadeLogic({ fadingRole, fadingDirection, fadingParentID, fadeEndTime }) { 5176 this.resetFadeLogic(); 5177 this.fadingRole = fadingRole; 5178 this.fadingDirection = fadingDirection; 5179 this.fadingParentID = fadingParentID; 5180 this.fadeEndTime = fadeEndTime; 5181 } 5182 /** 5183 * Self fading for edge tiles (when panning map) 5184 */ 5185 setSelfFadeLogic(fadeEndTime) { 5186 this.resetFadeLogic(); 5187 this.selfFading = true; 5188 this.fadeEndTime = fadeEndTime; 5189 } 5190 resetFadeLogic() { 5191 this.fadingRole = null; 5192 this.fadingDirection = null; 5193 this.fadingParentID = null; 5194 this.selfFading = false; 5195 this.timeAdded = now(); 5196 this.fadeEndTime = 0; 5197 this.fadeOpacity = 1; 5198 } 5199 wasRequested() { 5200 return this.state === "errored" || this.state === "loaded" || this.state === "reloading"; 5201 } 5202 clearTextures(painter) { 5203 if (this.demTexture) painter.saveTileTexture(this.demTexture); 5204 this.demTexture = null; 5205 } 5206 /** 5207 * @internal 5208 * Returns the cached RTT object for this stack, or undefined on a cache miss. 5209 */ 5210 getRTT(stack) { 5211 return this.rttObjects[stack]; 5212 } 5213 /** 5214 * @internal 5215 * Allocates a fresh RTT object from the painter's pool and stores it at the 5216 * given stack slot. Callers should check {@link getRTT} first; calling this 5217 * over an existing slot leaks the previous object. 5218 */ 5219 acquireRTT(painter, stack, size) { 5220 return this.rttObjects[stack] = painter.acquireRTT(size); 5221 } 5222 /** 5223 * @internal 5224 * Returns all cached RTT slots to the painter's pool. 5225 */ 5226 releaseRTT(painter) { 5227 if (this.rttObjects.length === 0) return; 5228 for (const obj of this.rttObjects) if (obj) painter.releaseRTT(obj); 5229 this.rttObjects.length = 0; 5230 } 5231 /** 5232 * Given a data object with a 'buffers' property, load it into 5233 * this tile's elementGroups and buffers properties and set loaded 5234 * to true. If the data is null, like in the case of an empty 5235 * GeoJSON tile, no-op but still set loaded to true. 5236 * @param data - The data from the worker 5237 * @param painter - the painter 5238 * @param justReloaded - `true` to just reload 5239 */ 5240 loadVectorData(data, painter, justReloaded) { 5241 if (data?.etagUnmodified === true) { 5242 this.state = "loaded"; 5243 return; 5244 } 5245 if (this.hasData()) this.unloadVectorData(); 5246 this.state = "loaded"; 5247 if (!data) { 5248 this.collisionBoxArray = new CollisionBoxArray(); 5249 return; 5250 } 5251 if (data.featureIndex) { 5252 this.latestFeatureIndex = data.featureIndex; 5253 if (data.rawTileData) { 5254 this.latestRawTileData = data.rawTileData; 5255 this.latestEncoding = data.encoding; 5256 this.latestFeatureIndex.rawTileData = data.rawTileData; 5257 this.latestFeatureIndex.encoding = data.encoding; 5258 } else if (this.latestRawTileData) { 5259 this.latestFeatureIndex.rawTileData = this.latestRawTileData; 5260 this.latestFeatureIndex.encoding = this.latestEncoding; 5261 } 5262 } 5263 this.collisionBoxArray = data.collisionBoxArray; 5264 this.buckets = deserialize(data.buckets, painter?.style); 5265 this.hasSymbolBuckets = false; 5266 for (const id in this.buckets) { 5267 const bucket = this.buckets[id]; 5268 if (bucket instanceof SymbolBucket) { 5269 this.hasSymbolBuckets = true; 5270 if (justReloaded) bucket.justReloaded = true; 5271 else break; 5272 } 5273 } 5274 this.hasRTLText = false; 5275 if (this.hasSymbolBuckets) for (const id in this.buckets) { 5276 const bucket = this.buckets[id]; 5277 if (bucket instanceof SymbolBucket) { 5278 if (bucket.hasRTLText) { 5279 this.hasRTLText = true; 5280 rtlMainThreadPluginFactory().lazyLoad(); 5281 break; 5282 } 5283 } 5284 } 5285 this.queryPadding = 0; 5286 for (const id in this.buckets) { 5287 const bucket = this.buckets[id]; 5288 this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(id).queryRadius(bucket)); 5289 } 5290 if (data.imageAtlas) this.imageAtlas = data.imageAtlas; 5291 if (data.glyphAtlasImage) this.glyphAtlasImage = data.glyphAtlasImage; 5292 this.dashPositions = data.dashPositions; 5293 } 5294 /** 5295 * Release any data or WebGL resources referenced by this tile. 5296 */ 5297 unloadVectorData() { 5298 for (const id in this.buckets) this.buckets[id].destroy(); 5299 this.buckets = {}; 5300 if (this.imageAtlasTexture) this.imageAtlasTexture.destroy(); 5301 if (this.glyphAtlasTexture) this.glyphAtlasTexture.destroy(); 5302 this.imageAtlas = null; 5303 this.dashPositions = null; 5304 this.latestFeatureIndex = null; 5305 this.state = "unloaded"; 5306 } 5307 getBucket(layer) { 5308 return this.buckets[layer.id]; 5309 } 5310 upload(context) { 5311 for (const id in this.buckets) { 5312 const bucket = this.buckets[id]; 5313 if (bucket.uploadPending()) bucket.upload(context); 5314 } 5315 const gl = context.gl; 5316 if (this.imageAtlas && !this.imageAtlas.uploaded) { 5317 this.imageAtlasTexture = new Texture(context, this.imageAtlas.image, gl.RGBA); 5318 this.imageAtlas.uploaded = true; 5319 } 5320 if (this.glyphAtlasImage) { 5321 this.glyphAtlasTexture = new Texture(context, this.glyphAtlasImage, gl.ALPHA); 5322 this.glyphAtlasImage = null; 5323 } 5324 } 5325 prepare(imageManager) { 5326 if (this.imageAtlas) this.imageAtlas.patchUpdatedImages(imageManager, this.imageAtlasTexture); 5327 } 5328 queryRenderedFeatures(layers, serializedLayers, sourceFeatureState, queryGeometry, cameraQueryGeometry, scale, params, transform, maxPitchScaleFactor, pixelPosMatrix, getElevation) { 5329 if (!this.latestFeatureIndex?.rawTileData) return {}; 5330 return this.latestFeatureIndex.query({ 5331 queryGeometry, 5332 cameraQueryGeometry, 5333 scale, 5334 tileSize: this.tileSize, 5335 pixelPosMatrix, 5336 transform, 5337 params, 5338 queryPadding: this.queryPadding * maxPitchScaleFactor, 5339 getElevation 5340 }, layers, serializedLayers, sourceFeatureState); 5341 } 5342 querySourceFeatures(result, params) { 5343 const featureIndex = this.latestFeatureIndex; 5344 if (!featureIndex?.rawTileData) return; 5345 const vtLayers = featureIndex.loadVTLayers(); 5346 const sourceLayer = params?.sourceLayer ? params.sourceLayer : ""; 5347 const layer = vtLayers["_geojsonTileLayer"] || vtLayers[sourceLayer]; 5348 if (!layer) return; 5349 const filter = featureFilter(params?.filter, `querySourceFeatures[${sourceLayer}].filter`, params?.globalState); 5350 const { z, x, y } = this.tileID.canonical; 5351 const coord = { 5352 z, 5353 x, 5354 y 5355 }; 5356 for (let i = 0; i < layer.length; i++) { 5357 const feature = layer.feature(i); 5358 if (filter.needGeometry) { 5359 const evaluationFeature = toEvaluationFeature(feature, true); 5360 if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), evaluationFeature, this.tileID.canonical)) continue; 5361 } else if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) continue; 5362 const id = featureIndex.getId(feature, sourceLayer); 5363 const geojsonFeature = new GeoJSONFeature(feature, z, x, y, id); 5364 geojsonFeature.tile = coord; 5365 result.push(geojsonFeature); 5366 } 5367 } 5368 hasData() { 5369 return this.state === "loaded" || this.state === "reloading" || this.state === "expired"; 5370 } 5371 patternsLoaded() { 5372 return this.imageAtlas && !!Object.keys(this.imageAtlas.patternPositions).length; 5373 } 5374 setExpiryData(data) { 5375 const prior = this.expirationTime; 5376 if (data.cacheControl) { 5377 const parsedCC = parseCacheControl(data.cacheControl); 5378 if (parsedCC["max-age"]) this.expirationTime = Date.now() + parsedCC["max-age"] * 1e3; 5379 } else if (data.expires) this.expirationTime = new Date(data.expires).getTime(); 5380 if (this.expirationTime) { 5381 const now = Date.now(); 5382 let isExpired = false; 5383 if (this.expirationTime > now) isExpired = false; 5384 else if (!prior) isExpired = true; 5385 else if (this.expirationTime < prior) isExpired = true; 5386 else { 5387 const delta = this.expirationTime - prior; 5388 if (!delta) isExpired = true; 5389 else this.expirationTime = now + Math.max(delta, CLOCK_SKEW_RETRY_TIMEOUT); 5390 } 5391 if (isExpired) { 5392 this.expiredRequestCount++; 5393 this.state = "expired"; 5394 } else this.expiredRequestCount = 0; 5395 } 5396 } 5397 getExpiryTimeout() { 5398 if (this.expirationTime) { 5399 if (this.expiredRequestCount) return 1e3 * (1 << Math.min(this.expiredRequestCount - 1, 31)); 5400 else return Math.min(this.expirationTime - (/* @__PURE__ */ new Date()).getTime(), Math.pow(2, 31) - 1); 5401 } 5402 } 5403 setFeatureState(states, painter, revision) { 5404 if (!this.latestFeatureIndex?.rawTileData || Object.keys(states).length === 0) return; 5405 if (this.featureStateRevision === revision) return; 5406 this.featureStateRevision = revision; 5407 const vtLayers = this.latestFeatureIndex.loadVTLayers(); 5408 for (const id in this.buckets) { 5409 if (!painter.style.hasLayer(id)) continue; 5410 const bucket = this.buckets[id]; 5411 const sourceLayerId = bucket.layers[0]["sourceLayer"] || "_geojsonTileLayer"; 5412 const sourceLayer = vtLayers[sourceLayerId]; 5413 const sourceLayerStates = states[sourceLayerId]; 5414 if (!sourceLayer || !sourceLayerStates || sourceLayerStates.length === 0) continue; 5415 bucket.update(sourceLayerStates, sourceLayer, this.imageAtlas?.patternPositions || {}, this.dashPositions || {}); 5416 const layer = painter?.style?.getLayer(id); 5417 if (layer) this.queryPadding = Math.max(this.queryPadding, layer.queryRadius(bucket)); 5418 } 5419 } 5420 holdingForSymbolFade() { 5421 return this.symbolFadeHoldUntil !== void 0; 5422 } 5423 symbolFadeFinished() { 5424 return !this.symbolFadeHoldUntil || this.symbolFadeHoldUntil < now(); 5425 } 5426 clearSymbolFadeHold() { 5427 this.symbolFadeHoldUntil = void 0; 5428 } 5429 setSymbolHoldDuration(duration) { 5430 this.symbolFadeHoldUntil = now() + duration; 5431 } 5432 setDependencies(namespace, dependencies) { 5433 const index = {}; 5434 for (const dep of dependencies) index[dep] = true; 5435 this.dependencies[namespace] = index; 5436 } 5437 hasDependency(namespaces, keys) { 5438 for (const namespace of namespaces) { 5439 const dependencies = this.dependencies[namespace]; 5440 if (dependencies) { 5441 for (const key of keys) if (dependencies[key]) return true; 5442 } 5443 } 5444 return false; 5445 } 5446}; 5447//#endregion 5448//#region src/source/source_state.ts 5449function featureStatesMapToArray(map) { 5450 const result = []; 5451 for (const id in map) result.push({ 5452 id, 5453 state: map[id] 5454 }); 5455 return result; 5456} 5457/** 5458* @internal 5459* SourceFeatureState manages the state and pending changes 5460* to features in a source, separated by source layer. 5461* stateChanges and deletedStates batch all changes to the tile (updates and removes, respectively) 5462* between coalesce() events. addFeatureState() and removeFeatureState() also update their counterpart's 5463* list of changes, such that coalesce() can apply the proper state changes while agnostic to the order of operations. 5464* In deletedStates, all null's denote complete removal of state at that scope 5465*/ 5466var SourceFeatureState = class { 5467 constructor() { 5468 this.state = {}; 5469 this.stateChanges = {}; 5470 this.deletedStates = {}; 5471 this.revision = 0; 5472 } 5473 updateState(sourceLayer, featureId, newState) { 5474 const feature = String(featureId); 5475 this.stateChanges[sourceLayer] ||= {}; 5476 this.stateChanges[sourceLayer][feature] ||= {}; 5477 extend(this.stateChanges[sourceLayer][feature], newState); 5478 if (this.deletedStates[sourceLayer] === null) { 5479 this.deletedStates[sourceLayer] = {}; 5480 for (const ft in this.state[sourceLayer]) if (ft === feature) { 5481 this.deletedStates[sourceLayer][feature] = {}; 5482 for (const prop in this.state[sourceLayer][ft]) if (newState[prop] === void 0) this.deletedStates[sourceLayer][feature][prop] = null; 5483 } else this.deletedStates[sourceLayer][ft] = null; 5484 } else if (this.deletedStates[sourceLayer]?.[feature] === null) { 5485 this.deletedStates[sourceLayer][feature] = {}; 5486 for (const prop in this.state[sourceLayer][feature]) if (newState[prop] === void 0) this.deletedStates[sourceLayer][feature][prop] = null; 5487 } else for (const key in newState) if (this.deletedStates[sourceLayer]?.[feature]?.[key] === null) delete this.deletedStates[sourceLayer][feature][key]; 5488 } 5489 removeFeatureState(sourceLayer, featureId, key) { 5490 if (this.deletedStates[sourceLayer] === null) return; 5491 const feature = String(featureId); 5492 this.deletedStates[sourceLayer] ||= {}; 5493 if (key && featureId !== void 0) { 5494 if (this.deletedStates[sourceLayer][feature] !== null) { 5495 this.deletedStates[sourceLayer][feature] ||= {}; 5496 this.deletedStates[sourceLayer][feature][key] = null; 5497 } 5498 } else if (featureId !== void 0) { 5499 if (this.stateChanges[sourceLayer]?.[feature]) { 5500 this.deletedStates[sourceLayer][feature] = {}; 5501 for (key in this.stateChanges[sourceLayer][feature]) this.deletedStates[sourceLayer][feature][key] = null; 5502 } else this.deletedStates[sourceLayer][feature] = null; 5503 } else this.deletedStates[sourceLayer] = null; 5504 } 5505 getState(sourceLayer, featureId) { 5506 const feature = String(featureId); 5507 const base = this.state[sourceLayer] || {}; 5508 const changes = this.stateChanges[sourceLayer] || {}; 5509 const reconciledState = extend({}, base[feature], changes[feature]); 5510 if (this.deletedStates[sourceLayer] === null) return {}; 5511 else if (this.deletedStates[sourceLayer]) { 5512 const featureDeletions = this.deletedStates[sourceLayer][featureId]; 5513 if (featureDeletions === null) return {}; 5514 for (const prop in featureDeletions) delete reconciledState[prop]; 5515 } 5516 return reconciledState; 5517 } 5518 initializeTileState(tile, painter) { 5519 const layerStates = {}; 5520 for (const sourceLayer in this.state) layerStates[sourceLayer] = featureStatesMapToArray(this.state[sourceLayer]); 5521 tile.setFeatureState(layerStates, painter, this.revision); 5522 } 5523 coalesceChanges(inViewTiles, painter) { 5524 const featuresChangedMap = {}; 5525 for (const sourceLayer in this.stateChanges) { 5526 this.state[sourceLayer] ||= {}; 5527 featuresChangedMap[sourceLayer] ||= {}; 5528 for (const feature in this.stateChanges[sourceLayer]) { 5529 this.state[sourceLayer][feature] ||= {}; 5530 extend(this.state[sourceLayer][feature], this.stateChanges[sourceLayer][feature]); 5531 featuresChangedMap[sourceLayer][feature] = this.state[sourceLayer][feature]; 5532 } 5533 } 5534 for (const sourceLayer in this.deletedStates) { 5535 this.state[sourceLayer] ||= {}; 5536 featuresChangedMap[sourceLayer] ||= {}; 5537 if (this.deletedStates[sourceLayer] === null) for (const ft in this.state[sourceLayer]) { 5538 this.state[sourceLayer][ft] = {}; 5539 featuresChangedMap[sourceLayer][ft] = {}; 5540 } 5541 else for (const feature in this.deletedStates[sourceLayer]) { 5542 if (this.deletedStates[sourceLayer][feature] === null) this.state[sourceLayer][feature] = {}; 5543 else for (const key of Object.keys(this.deletedStates[sourceLayer][feature])) delete this.state[sourceLayer][feature][key]; 5544 featuresChangedMap[sourceLayer][feature] = this.state[sourceLayer][feature]; 5545 } 5546 } 5547 this.stateChanges = {}; 5548 this.deletedStates = {}; 5549 if (Object.keys(featuresChangedMap).length === 0) return; 5550 this.revision++; 5551 const featuresChanged = {}; 5552 for (const sourceLayer in featuresChangedMap) featuresChanged[sourceLayer] = featureStatesMapToArray(featuresChangedMap[sourceLayer]); 5553 inViewTiles.setFeatureState(featuresChanged, painter, this.revision); 5554 } 5555}; 5556//#endregion 5557//#region src/util/primitives/aabb.ts 5558var Aabb = class Aabb { 5559 constructor(min_, max_) { 5560 this.min = min_; 5561 this.max = max_; 5562 this.center = scale$1([], add([], this.min, this.max), .5); 5563 } 5564 quadrant(index) { 5565 const split = [index % 2 === 0, index < 2]; 5566 const qMin = clone(this.min); 5567 const qMax = clone(this.max); 5568 for (let axis = 0; axis < split.length; axis++) { 5569 qMin[axis] = split[axis] ? this.min[axis] : this.center[axis]; 5570 qMax[axis] = split[axis] ? this.center[axis] : this.max[axis]; 5571 } 5572 qMax[2] = this.max[2]; 5573 return new Aabb(qMin, qMax); 5574 } 5575 distanceX(point) { 5576 return Math.max(Math.min(this.max[0], point[0]), this.min[0]) - point[0]; 5577 } 5578 distanceY(point) { 5579 return Math.max(Math.min(this.max[1], point[1]), this.min[1]) - point[1]; 5580 } 5581 /** 5582 * Performs a frustum-aabb intersection test. 5583 */ 5584 intersectsFrustum(frustum) { 5585 let fullyInside = true; 5586 for (const plane of frustum.planes) { 5587 const planeIntersection = this.intersectsPlane(plane); 5588 if (planeIntersection === 0) return 0; 5589 if (planeIntersection === 1) fullyInside = false; 5590 } 5591 if (fullyInside) return 2; 5592 if (frustum.aabb.min[0] > this.max[0] || frustum.aabb.min[1] > this.max[1] || frustum.aabb.min[2] > this.max[2] || frustum.aabb.max[0] < this.min[0] || frustum.aabb.max[1] < this.min[1] || frustum.aabb.max[2] < this.min[2]) return 0; 5593 return 1; 5594 } 5595 /** 5596 * Performs a halfspace-aabb intersection test. 5597 */ 5598 intersectsPlane(plane) { 5599 let distMin = plane[3]; 5600 let distMax = plane[3]; 5601 for (let i = 0; i < 3; i++) if (plane[i] > 0) { 5602 distMin += plane[i] * this.min[i]; 5603 distMax += plane[i] * this.max[i]; 5604 } else { 5605 distMax += plane[i] * this.min[i]; 5606 distMin += plane[i] * this.max[i]; 5607 } 5608 if (distMin >= 0) return 2; 5609 if (distMax < 0) return 0; 5610 return 1; 5611 } 5612}; 5613//#endregion 5614//#region src/util/primitives/frustum.ts 5615var Frustum = class Frustum { 5616 constructor(points, planes, aabb) { 5617 this.points = points; 5618 this.planes = planes; 5619 this.aabb = aabb; 5620 } 5621 static fromInvProjectionMatrix(invProj, worldSize = 1, zoom = 0, horizonPlane, flippedNearFar) { 5622 const clipSpaceCorners = [ 5623 [ 5624 -1, 5625 1, 5626 -1, 5627 1 5628 ], 5629 [ 5630 1, 5631 1, 5632 -1, 5633 1 5634 ], 5635 [ 5636 1, 5637 -1, 5638 -1, 5639 1 5640 ], 5641 [ 5642 -1, 5643 -1, 5644 -1, 5645 1 5646 ], 5647 [ 5648 -1, 5649 1, 5650 1, 5651 1 5652 ], 5653 [ 5654 1, 5655 1, 5656 1, 5657 1 5658 ], 5659 [ 5660 1, 5661 -1, 5662 1, 5663 1 5664 ], 5665 [ 5666 -1, 5667 -1, 5668 1, 5669 1 5670 ] 5671 ]; 5672 const frustumPlanePointIndices = flippedNearFar ? [ 5673 [ 5674 6, 5675 5, 5676 4 5677 ], 5678 [ 5679 0, 5680 1, 5681 2 5682 ], 5683 [ 5684 0, 5685 3, 5686 7 5687 ], 5688 [ 5689 2, 5690 1, 5691 5 5692 ], 5693 [ 5694 3, 5695 2, 5696 6 5697 ], 5698 [ 5699 0, 5700 4, 5701 5 5702 ] 5703 ] : [ 5704 [ 5705 0, 5706 1, 5707 2 5708 ], 5709 [ 5710 6, 5711 5, 5712 4 5713 ], 5714 [ 5715 0, 5716 3, 5717 7 5718 ], 5719 [ 5720 2, 5721 1, 5722 5 5723 ], 5724 [ 5725 3, 5726 2, 5727 6 5728 ], 5729 [ 5730 0, 5731 4, 5732 5 5733 ] 5734 ]; 5735 const scale = Math.pow(2, zoom); 5736 const frustumCoords = clipSpaceCorners.map((v) => unprojectClipSpacePoint(v, invProj, worldSize, scale)); 5737 if (horizonPlane) adjustFarPlaneByHorizonPlane(frustumCoords, frustumPlanePointIndices[0], horizonPlane, flippedNearFar); 5738 const frustumPlanes = frustumPlanePointIndices.map((p) => { 5739 const a = sub([], frustumCoords[p[0]], frustumCoords[p[1]]); 5740 const b = sub([], frustumCoords[p[2]], frustumCoords[p[1]]); 5741 const n = normalize([], cross([], a, b)); 5742 const d = -dot(n, frustumCoords[p[1]]); 5743 return n.concat(d); 5744 }); 5745 const min = [ 5746 Number.POSITIVE_INFINITY, 5747 Number.POSITIVE_INFINITY, 5748 Number.POSITIVE_INFINITY 5749 ]; 5750 const max = [ 5751 Number.NEGATIVE_INFINITY, 5752 Number.NEGATIVE_INFINITY, 5753 Number.NEGATIVE_INFINITY 5754 ]; 5755 for (const p of frustumCoords) for (let i = 0; i < 3; i++) { 5756 min[i] = Math.min(min[i], p[i]); 5757 max[i] = Math.max(max[i], p[i]); 5758 } 5759 return new Frustum(frustumCoords, frustumPlanes, new Aabb(min, max)); 5760 } 5761}; 5762function unprojectClipSpacePoint(point, invProj, worldSize, scale) { 5763 const v = transformMat4([], point, invProj); 5764 const s = 1 / v[3] / worldSize * scale; 5765 return mul(v, v, [ 5766 s, 5767 s, 5768 1 / v[3], 5769 s 5770 ]); 5771} 5772/** 5773* Modifies points in the supplied `frustumCoords` array so that the frustum's far plane only lies as far as the horizon, 5774* which improves frustum culling effectiveness. 5775* @param frustumCoords - Points of the frustum. 5776* @param nearPlanePointsIndices - Which indices in the `frustumCoords` form the near plane. 5777* @param horizonPlane - The horizon plane. 5778*/ 5779function adjustFarPlaneByHorizonPlane(frustumCoords, nearPlanePointsIndices, horizonPlane, flippedNearFar) { 5780 const nearPlanePointsOffset = flippedNearFar ? 4 : 0; 5781 const farPlanePointsOffset = flippedNearFar ? 0 : 4; 5782 let maxDist = 0; 5783 const cornerRayLengths = []; 5784 const cornerRayNormalizedDirections = []; 5785 for (let i = 0; i < 4; i++) { 5786 const dir = sub([], frustumCoords[i + farPlanePointsOffset], frustumCoords[i + nearPlanePointsOffset]); 5787 const len = length(dir); 5788 scale$1(dir, dir, 1 / len); 5789 cornerRayLengths.push(len); 5790 cornerRayNormalizedDirections.push(dir); 5791 } 5792 for (let i = 0; i < 4; i++) { 5793 const dist = rayPlaneIntersection(frustumCoords[i + nearPlanePointsOffset], cornerRayNormalizedDirections[i], horizonPlane); 5794 if (dist !== null && dist >= 0) maxDist = Math.max(maxDist, dist); 5795 else maxDist = Math.max(maxDist, cornerRayLengths[i]); 5796 } 5797 const nearPlaneNormalized = getNormalizedNearPlane(frustumCoords, nearPlanePointsIndices); 5798 const idealFarPlaneDistanceFromNearPlane = getIdealNearFarPlaneDistance(horizonPlane, nearPlaneNormalized); 5799 if (idealFarPlaneDistanceFromNearPlane !== null) { 5800 const idealCornerRayLength = idealFarPlaneDistanceFromNearPlane / dot(cornerRayNormalizedDirections[0], nearPlaneNormalized); 5801 maxDist = Math.min(maxDist, idealCornerRayLength); 5802 } 5803 for (let i = 0; i < 4; i++) { 5804 const targetLength = Math.min(maxDist, cornerRayLengths[i]); 5805 frustumCoords[i + farPlanePointsOffset] = [ 5806 frustumCoords[i + nearPlanePointsOffset][0] + cornerRayNormalizedDirections[i][0] * targetLength, 5807 frustumCoords[i + nearPlanePointsOffset][1] + cornerRayNormalizedDirections[i][1] * targetLength, 5808 frustumCoords[i + nearPlanePointsOffset][2] + cornerRayNormalizedDirections[i][2] * targetLength, 5809 1 5810 ]; 5811 } 5812} 5813/** 5814* Returns the near plane equation with unit length direction. 5815* @param frustumCoords - Points of the frustum. 5816* @param nearPlanePointsIndices - Which indices in the `frustumCoords` form the near plane. 5817*/ 5818function getNormalizedNearPlane(frustumCoords, nearPlanePointsIndices) { 5819 const nearPlaneA = sub([], frustumCoords[nearPlanePointsIndices[0]], frustumCoords[nearPlanePointsIndices[1]]); 5820 const nearPlaneB = sub([], frustumCoords[nearPlanePointsIndices[2]], frustumCoords[nearPlanePointsIndices[1]]); 5821 const nearPlaneNormalized = [ 5822 0, 5823 0, 5824 0, 5825 0 5826 ]; 5827 normalize(nearPlaneNormalized, cross([], nearPlaneA, nearPlaneB)); 5828 nearPlaneNormalized[3] = -dot(nearPlaneNormalized, frustumCoords[nearPlanePointsIndices[0]]); 5829 return nearPlaneNormalized; 5830} 5831/** 5832* Returns the ideal distance between the frustum's near and far plane so that the far plane only lies as far as the horizon. 5833*/ 5834function getIdealNearFarPlaneDistance(horizonPlane, nearPlaneNormalized) { 5835 const horizonPlaneLen = len(horizonPlane); 5836 const normalizedHorizonPlane = scale$2([], horizonPlane, 1 / horizonPlaneLen); 5837 const projectedViewDirection = sub([], nearPlaneNormalized, scale$1([], normalizedHorizonPlane, dot(nearPlaneNormalized, normalizedHorizonPlane))); 5838 const projectedViewLength = len(projectedViewDirection); 5839 if (projectedViewLength > 0) { 5840 const horizonCircleRadius = Math.sqrt(1 - normalizedHorizonPlane[3] * normalizedHorizonPlane[3]); 5841 const horizonCircleCenter = scale$1([], normalizedHorizonPlane, -normalizedHorizonPlane[3]); 5842 const pointFurthestOnHorizonCircle = add([], horizonCircleCenter, scale$1([], projectedViewDirection, horizonCircleRadius / projectedViewLength)); 5843 return pointPlaneSignedDistance(nearPlaneNormalized, pointFurthestOnHorizonCircle); 5844 } else return null; 5845} 5846//#endregion 5847//#region src/geo/projection/covering_tiles.ts 5848/** 5849* A simple/heuristic function that returns whether the tile is visible under the current transform. 5850* @returns an {@link IntersectionResult}. 5851*/ 5852function isTileVisible(frustum, tileBoundingVolume, plane) { 5853 const frustumTest = tileBoundingVolume.intersectsFrustum(frustum); 5854 if (!plane || frustumTest === 0) return frustumTest; 5855 const planeTest = tileBoundingVolume.intersectsPlane(plane); 5856 if (planeTest === 0) return 0; 5857 if (frustumTest === 2 && planeTest === 2) return 2; 5858 return 1; 5859} 5860/** 5861* Definite integral of cos(x)^p. The analytical solution is described in `developer-guides/covering-tiles.md`, 5862* but here the integral is evaluated numerically. 5863* @param p - the power to raise cos(x) to inside the integral 5864* @param x1 - the starting point of the integral. 5865* @param x2 - the ending point of the integral. 5866* @return the integral of cos(x)^p from x=x1 to x=x2 5867*/ 5868function integralOfCosXByP(p, x1, x2) { 5869 const numPoints = 10; 5870 let sum = 0; 5871 const dx = (x2 - x1) / numPoints; 5872 for (let i = 0; i < numPoints; i++) { 5873 const x = x1 + (i + .5) / numPoints * (x2 - x1); 5874 sum += dx * Math.pow(Math.cos(x), p); 5875 } 5876 return sum; 5877} 5878function createCalculateTileZoomFunction(maxZoomLevelsOnScreen, tileCountMaxMinRatio) { 5879 return function(requestedCenterZoom, distanceToTile2D, distanceToTileZ, distanceToCenter3D, cameraVerticalFOV) { 5880 /** 5881 * Controls how tiles are loaded at high pitch angles. Higher numbers cause fewer, lower resolution 5882 * tiles to be loaded. Calculate the value that will result in the selected number of zoom levels in 5883 * the worst-case condition (when the horizon is at the top of the screen). For more information, see 5884 * `developer-guides/covering-tiles.md` 5885 */ 5886 const pitchTileLoadingBehavior = 2 * ((maxZoomLevelsOnScreen - 1) / scaleZoom(Math.cos(degreesToRadians(maxMercatorHorizonAngle - cameraVerticalFOV)) / Math.cos(degreesToRadians(maxMercatorHorizonAngle))) - 1); 5887 const centerPitch = Math.acos(distanceToTileZ / distanceToCenter3D); 5888 const tileCountPitch0 = 2 * integralOfCosXByP(pitchTileLoadingBehavior - 1, 0, degreesToRadians(cameraVerticalFOV / 2)); 5889 const highestPitch = Math.min(degreesToRadians(maxMercatorHorizonAngle), centerPitch + degreesToRadians(cameraVerticalFOV / 2)); 5890 const lowestPitch = Math.min(highestPitch, centerPitch - degreesToRadians(cameraVerticalFOV / 2)); 5891 const tileCount = integralOfCosXByP(pitchTileLoadingBehavior - 1, lowestPitch, highestPitch); 5892 const thisTilePitch = Math.atan(distanceToTile2D / distanceToTileZ); 5893 const distanceToTile3D = Math.hypot(distanceToTile2D, distanceToTileZ); 5894 let thisTileDesiredZ = requestedCenterZoom; 5895 thisTileDesiredZ = thisTileDesiredZ + scaleZoom(distanceToCenter3D / distanceToTile3D / Math.max(.5, Math.cos(degreesToRadians(cameraVerticalFOV / 2)))); 5896 thisTileDesiredZ += pitchTileLoadingBehavior * scaleZoom(Math.cos(thisTilePitch)) / 2; 5897 thisTileDesiredZ -= scaleZoom(Math.max(1, tileCount / tileCountPitch0 / tileCountMaxMinRatio)) / 2; 5898 return thisTileDesiredZ; 5899 }; 5900} 5901const defaultCalculateTileZoom = createCalculateTileZoomFunction(9.314, 3); 5902/** 5903* Return what zoom level of a tile source would most closely cover the tiles displayed by this transform. 5904* @param options - The options, most importantly the source's tile size. 5905* @returns An integer zoom level at which all tiles will be visible. 5906*/ 5907function coveringZoomLevel(transform, options) { 5908 const z = (options.roundZoom ? Math.round : Math.floor)(transform.zoom + scaleZoom(transform.tileSize / options.tileSize)); 5909 return Math.max(0, z); 5910} 5911/** 5912* Without terrain, `getTileBoundingVolume` has no knowledge of extruded features 5913* (eg. 3D buildings): every tile's bounding box is flat at the camera's `elevation`. 5914* That's a fine approximation of what's visible for most views, but it breaks down 5915* as the frustum's bottom edge approaches horizontal (which depends on both `pitch` 5916* and `fov`, not pitch alone) - a tall building can still be poking up into view 5917* long after its tile's ground point has dropped out of the frustum. 5918* 5919* This is an assumed upper bound on real-world feature height (in metres), used to 5920* grow the elevation used for tile culling as that bottom edge nears horizontal, so 5921* such tiles are not dropped too early. It has no effect away from that edge case. 5922*/ 5923const ASSUMED_MAX_FEATURE_HEIGHT_METERS = 500; 5924/** 5925* Angle between the frustum's bottom edge and the mercator horizon below which 5926* the culling elevation starts to grow. 5927*/ 5928const TILE_CULLING_HORIZON_ONSET_DEGREES = 15; 5929/** 5930* Returns the elevation to use when computing tile bounding volumes for culling: 5931* `transform.elevation`, growing by up to the greater of 5932* `ASSUMED_MAX_FEATURE_HEIGHT_METERS` and `maxContentElevation` (when provided) 5933* as the frustum's bottom edge approaches the horizon, where a ground-level 5934* bounding box would cull tiles whose extruded features are still visible. 5935*/ 5936function getElevationForTileCulling(transform, maxContentElevation) { 5937 const bottomEdgeDegreesAboveHorizontal = maxMercatorHorizonAngle - transform.pitch - transform.fov / 2; 5938 const proximityToHorizon = clamp((TILE_CULLING_HORIZON_ONSET_DEGREES - bottomEdgeDegreesAboveHorizontal) / TILE_CULLING_HORIZON_ONSET_DEGREES, 0, 1); 5939 const maxFeatureHeight = Math.max(ASSUMED_MAX_FEATURE_HEIGHT_METERS, maxContentElevation ?? 0); 5940 return transform.elevation + proximityToHorizon * maxFeatureHeight; 5941} 5942/** 5943* Returns a copy of the frustum with its far plane moved away from the camera by `distance`. 5944* Each far corner travels along its own camera ray until it reaches the pushed plane, so the 5945* result stays a pyramid and the corners stay consistent with the side planes. The globe 5946* frustum's far plane is fitted to the surface horizon, so elevated content beyond it would 5947* otherwise be culled while still visible. 5948*/ 5949function pushFrustumFarPlane(frustum, distance, cameraPos) { 5950 const far = frustum.planes[1]; 5951 const points = frustum.points.map((p) => { 5952 const distanceToFar = far[0] * p[0] + far[1] * p[1] + far[2] * p[2] + far[3]; 5953 if (Math.abs(distanceToFar) > 1e-6) return p; 5954 const ray = [ 5955 p[0] - cameraPos[0], 5956 p[1] - cameraPos[1], 5957 p[2] - cameraPos[2] 5958 ]; 5959 const rayDotNormal = far[0] * ray[0] + far[1] * ray[1] + far[2] * ray[2]; 5960 if (rayDotNormal >= -1e-9) return p; 5961 const scale = distance / -rayDotNormal; 5962 return [ 5963 p[0] + ray[0] * scale, 5964 p[1] + ray[1] * scale, 5965 p[2] + ray[2] * scale, 5966 p[3] 5967 ]; 5968 }); 5969 const planes = frustum.planes.map((p, i) => i === 1 ? [ 5970 p[0], 5971 p[1], 5972 p[2], 5973 p[3] + distance 5974 ] : p); 5975 const min = [ 5976 Number.POSITIVE_INFINITY, 5977 Number.POSITIVE_INFINITY, 5978 Number.POSITIVE_INFINITY 5979 ]; 5980 const max = [ 5981 Number.NEGATIVE_INFINITY, 5982 Number.NEGATIVE_INFINITY, 5983 Number.NEGATIVE_INFINITY 5984 ]; 5985 for (const p of points) for (let i = 0; i < 3; i++) { 5986 min[i] = Math.min(min[i], p[i]); 5987 max[i] = Math.max(max[i], p[i]); 5988 } 5989 return new Frustum(points, planes, new Aabb(min, max)); 5990} 5991/** 5992* The horizon culling plane and the frustum's far plane both assume content sits on the 5993* surface. Content elevated to radius r (in planet radii) stays visible up to acos(1/r) 5994* beyond the surface horizon and up to sqrt(r^2-1) farther away, so both are pushed back 5995* accordingly; without this, tiles under highly elevated symbols would be dropped while 5996* the symbols are still in view. 5997* @param frustum - The covering frustum, fitted to the surface horizon. 5998* @param plane - The horizon culling plane used by globe transform. 5999* @param cameraPos - The camera position, in the frustum's space. 6000* @param maxContentElevation - The highest elevation the content may reach, in meters. 6001*/ 6002function expandCullingToContentElevation(frustum, plane, cameraPos, maxContentElevation) { 6003 const len = Math.hypot(plane[0], plane[1], plane[2]); 6004 if (len === 0) return { 6005 frustum, 6006 plane 6007 }; 6008 const shellRadius = 1 + maxContentElevation / earthRadius; 6009 const horizonCos = clamp(-plane[3] / len, -1, 1); 6010 const shellCos = Math.cos(Math.acos(horizonCos) + Math.acos(1 / shellRadius)); 6011 return { 6012 frustum: pushFrustumFarPlane(frustum, Math.sqrt(shellRadius * shellRadius - 1), cameraPos), 6013 plane: [ 6014 plane[0], 6015 plane[1], 6016 plane[2], 6017 -shellCos * len 6018 ] 6019 }; 6020} 6021/** 6022* Returns a list of tiles that optimally covers the screen. Adapted for globe projection. 6023* Correctly handles LOD when moving over the antimeridian. 6024* @param transform - The transform instance. 6025* @param frustum - The covering frustum. 6026* @param plane - The clipping plane used by globe transform, or null. 6027* @param cameraCoord - The x, y, z position of the camera in MercatorCoordinates. 6028* @param centerCoord - The x, y, z position of the center point in MercatorCoordinates. 6029* @param options - Additional coveringTiles options. 6030* @param details - Interface to define required helper functions. 6031* @returns A list of tile coordinates, ordered by ascending distance from camera. 6032*/ 6033function coveringTiles(transform, options) { 6034 let frustum = transform.getCameraFrustum(); 6035 let plane = transform.getClippingPlane(); 6036 if (plane && options.maxContentElevation > 0) ({frustum, plane} = expandCullingToContentElevation(frustum, plane, transform.cameraPosition, options.maxContentElevation)); 6037 const cameraCoord = cameraMercatorCoordinate(transform); 6038 const centerCoord = MercatorCoordinate.fromLngLat(transform.center, transform.elevation); 6039 const elevationForTileCulling = getElevationForTileCulling(transform, options.maxContentElevation); 6040 const detailsProvider = transform.getCoveringTilesDetailsProvider(); 6041 const allowVariableZoom = detailsProvider.allowVariableZoom(transform, options); 6042 const desiredZ = coveringZoomLevel(transform, options); 6043 const minZoom = options.minzoom || 0; 6044 const maxZoom = options.maxzoom !== void 0 ? options.maxzoom : transform.maxZoom; 6045 const nominalZ = Math.min(Math.max(0, desiredZ), maxZoom); 6046 const numTiles = Math.pow(2, nominalZ); 6047 const cameraPoint = [ 6048 numTiles * cameraCoord.x, 6049 numTiles * cameraCoord.y, 6050 0 6051 ]; 6052 const centerPoint = [ 6053 numTiles * centerCoord.x, 6054 numTiles * centerCoord.y, 6055 0 6056 ]; 6057 const distanceToCenter2d = Math.hypot(centerCoord.x - cameraCoord.x, centerCoord.y - cameraCoord.y); 6058 const distanceZ = Math.abs(centerCoord.z - cameraCoord.z); 6059 const distanceToCenter3d = Math.hypot(distanceToCenter2d, distanceZ); 6060 const newRootTile = (wrap) => { 6061 return { 6062 zoom: 0, 6063 x: 0, 6064 y: 0, 6065 wrap, 6066 fullyVisible: false 6067 }; 6068 }; 6069 const stack = []; 6070 const result = []; 6071 if (transform.renderWorldCopies && detailsProvider.allowWorldCopies()) for (let i = 1; i <= 3; i++) { 6072 stack.push(newRootTile(-i)); 6073 stack.push(newRootTile(i)); 6074 } 6075 stack.push(newRootTile(0)); 6076 while (stack.length > 0) { 6077 const it = stack.pop(); 6078 const x = it.x; 6079 const y = it.y; 6080 let fullyVisible = it.fullyVisible; 6081 const tileID = { 6082 x, 6083 y, 6084 z: it.zoom 6085 }; 6086 const boundingVolume = detailsProvider.getTileBoundingVolume(tileID, it.wrap, elevationForTileCulling, options); 6087 if (!fullyVisible) { 6088 const intersectResult = isTileVisible(frustum, boundingVolume, plane); 6089 if (intersectResult === 0) continue; 6090 fullyVisible = intersectResult === 2; 6091 } 6092 const distToTile2d = detailsProvider.distanceToTile2d(cameraCoord.x, cameraCoord.y, tileID, boundingVolume); 6093 let thisTileDesiredZ = desiredZ; 6094 if (allowVariableZoom) thisTileDesiredZ = (options.calculateTileZoom || defaultCalculateTileZoom)(transform.zoom + scaleZoom(transform.tileSize / options.tileSize), distToTile2d, distanceZ, distanceToCenter3d, transform.fov); 6095 thisTileDesiredZ = (options.roundZoom ? Math.round : Math.floor)(thisTileDesiredZ); 6096 thisTileDesiredZ = Math.max(0, thisTileDesiredZ); 6097 const z = Math.min(thisTileDesiredZ, maxZoom); 6098 it.wrap = detailsProvider.getWrap(centerCoord, tileID, it.wrap); 6099 if (it.zoom >= z) { 6100 if (it.zoom < minZoom) continue; 6101 const dz = nominalZ - it.zoom; 6102 const dx = cameraPoint[0] - .5 - (x << dz); 6103 const dy = cameraPoint[1] - .5 - (y << dz); 6104 const overscaledZ = options.reparseOverscaled ? Math.max(it.zoom, thisTileDesiredZ) : it.zoom; 6105 result.push({ 6106 tileID: new OverscaledTileID(it.zoom === maxZoom ? overscaledZ : it.zoom, it.wrap, it.zoom, x, y), 6107 distanceSq: sqrLen([centerPoint[0] - .5 - x, centerPoint[1] - .5 - y]), 6108 tileDistanceToCamera: Math.sqrt(dx * dx + dy * dy) 6109 }); 6110 continue; 6111 } 6112 for (let i = 0; i < 4; i++) { 6113 const childX = (x << 1) + i % 2; 6114 const childY = (y << 1) + (i >> 1); 6115 const childZ = it.zoom + 1; 6116 stack.push({ 6117 zoom: childZ, 6118 x: childX, 6119 y: childY, 6120 wrap: it.wrap, 6121 fullyVisible 6122 }); 6123 } 6124 } 6125 return result.sort((a, b) => a.distanceSq - b.distanceSq).map((a) => a.tileID); 6126} 6127//#endregion 6128//#region src/tile/tile_manager_raster.ts 6129function isRasterType(type) { 6130 return type === "raster" || type === "image" || type === "video"; 6131} 6132/** 6133* Designate fading bases and parents using a many-to-one relationship where the lower children fade in/out 6134* with their parents. Raster shaders are not currently designed for a one-to-many fade relationship. 6135* 6136* Tiles that are candidates for fading out must be loaded and rendered tiles, as loading a tile to then 6137* fade it out would not appear smoothly. The first source of truth for tile fading always starts at the 6138* ideal tile, which continually changes on map adjustment. The state of the previously rendered ideal 6139* tile plane indicates which direction to fade each part of the newer ideal plane (with varying z). 6140* 6141* For a pitched map, the back of the map can have decreasing zooms while the front can have increasing zooms. 6142* Fade logic must therefore adapt dynamically based on the previously rendered ideal tile set. 6143*/ 6144function updateFadingTiles(inViewTiles, idealTileIDs, retain, maxFadingAncestorLevels, sourceMinZoom, sourceMaxZoom, rasterFadeDuration) { 6145 const currentTime = now(); 6146 const edgeTileIDs = getEdgeTiles(idealTileIDs); 6147 for (const idealID of idealTileIDs) { 6148 const idealTile = inViewTiles.getTileById(idealID.key); 6149 if (idealTile.fadingDirection === 0 || idealTile.fadeOpacity === 0) idealTile.resetFadeLogic(); 6150 if (updateFadingAncestor(inViewTiles, idealTile, retain, currentTime, maxFadingAncestorLevels, sourceMinZoom, rasterFadeDuration)) continue; 6151 if (updateFadingDescendents(inViewTiles, idealTile, retain, currentTime, sourceMaxZoom, rasterFadeDuration)) continue; 6152 if (updateFadingEdge(idealTile, edgeTileIDs, currentTime, rasterFadeDuration)) continue; 6153 idealTile.resetFadeLogic(); 6154 } 6155} 6156/** 6157* Many-to-one cross-fade. Set 4 ideal tiles as the fading base for a rendered parent tile 6158* as the fading parent. Here the parent is fading out and the ideal tile is fading in. 6159* 6160* Parent tile - fading out â -- Fading Parent 6161* ââââââââââââââââ¬âââââââ´ââââââââ¬âââââââââââââââ 6162* Ideal tiles - fading in â â â â -- Base Role = Incoming 6163* âââââ¬ââ´ââ¬ââââ âââââ¬ââ´ââ¬ââââ âââââ¬ââ´ââ¬ââââ âââââ¬ââ´ââ¬ââââ 6164* â â â â â â â â â â â â â â â â 6165*/ 6166function updateFadingAncestor(inViewTiles, idealTile, retain, now, maxFadingAncestorLevels, sourceMinZoom, rasterFadeDuration) { 6167 if (!idealTile.hasData()) return false; 6168 const { tileID: idealID, fadingRole, fadingDirection, fadingParentID } = idealTile; 6169 if (fadingRole === 0 && fadingDirection === 1 && fadingParentID) { 6170 retain[fadingParentID.key] = fadingParentID; 6171 return true; 6172 } 6173 const minAncestorZ = Math.max(idealID.overscaledZ - maxFadingAncestorLevels, sourceMinZoom); 6174 for (let ancestorZ = idealID.overscaledZ - 1; ancestorZ >= minAncestorZ; ancestorZ--) { 6175 const ancestorID = idealID.scaledTo(ancestorZ); 6176 const ancestorTile = inViewTiles.getLoadedTile(ancestorID); 6177 if (!ancestorTile) continue; 6178 idealTile.setCrossFadeLogic({ 6179 fadingRole: 0, 6180 fadingDirection: 1, 6181 fadingParentID: ancestorTile.tileID, 6182 fadeEndTime: now + rasterFadeDuration 6183 }); 6184 ancestorTile.setCrossFadeLogic({ 6185 fadingRole: 1, 6186 fadingDirection: 0, 6187 fadeEndTime: now + rasterFadeDuration 6188 }); 6189 retain[ancestorID.key] = ancestorID; 6190 return true; 6191 } 6192 return false; 6193} 6194/** 6195* Many-to-one cross-fade. Search descendents of ideal tiles as the fading base with the ideal tile 6196* as the fading parent. Here the children are fading out and the ideal tile is fading in. 6197* 6198* â 6199* ââââââââââââââââ¬âââââââ´ââââââââ¬âââââââââââââââ 6200* Ideal tiles - fading in â â â â -- Fading Parent 6201* âââââ¬ââ´ââ¬ââââ âââââ¬ââ´ââ¬ââââ âââââ¬ââ´ââ¬ââââ âââââ¬ââ´ââ¬ââââ 6202* Child tiles - fading out â â â â â â â â â â â â â â â â -- Base Role = Departing 6203* 6204* Try direct children first. If none found, try grandchildren. Stops at the first generation that provides a fader. 6205*/ 6206function updateFadingDescendents(inViewTiles, idealTile, retain, now, sourceMaxZoom, rasterFadeDuration) { 6207 if (!idealTile.hasData()) return false; 6208 const idealChildren = idealTile.tileID.children(sourceMaxZoom); 6209 let hasFader = updateFadingChildren(inViewTiles, idealTile, idealChildren, retain, now, sourceMaxZoom, rasterFadeDuration); 6210 if (hasFader) return true; 6211 for (const childID of idealChildren) if (updateFadingChildren(inViewTiles, idealTile, childID.children(sourceMaxZoom), retain, now, sourceMaxZoom, rasterFadeDuration)) hasFader = true; 6212 return hasFader; 6213} 6214function updateFadingChildren(inViewTiles, idealTile, childIDs, retain, now, sourceMaxZoom, rasterFadeDuration) { 6215 if (childIDs[0].overscaledZ >= sourceMaxZoom) return false; 6216 let foundFader = false; 6217 for (const childID of childIDs) { 6218 const childTile = inViewTiles.getLoadedTile(childID); 6219 if (!childTile) continue; 6220 const { fadingRole, fadingDirection, fadingParentID } = childTile; 6221 if (fadingRole !== 0 || fadingDirection !== 0 || !fadingParentID) { 6222 childTile.setCrossFadeLogic({ 6223 fadingRole: 0, 6224 fadingDirection: 0, 6225 fadingParentID: idealTile.tileID, 6226 fadeEndTime: now + rasterFadeDuration 6227 }); 6228 idealTile.setCrossFadeLogic({ 6229 fadingRole: 1, 6230 fadingDirection: 1, 6231 fadeEndTime: now + rasterFadeDuration 6232 }); 6233 } 6234 retain[childID.key] = childID; 6235 foundFader = true; 6236 } 6237 return foundFader; 6238} 6239/** 6240* One-to-one self fading for unloaded edge tiles (for panning sideways on map). for loading tiles over gaps it feels 6241* more natural for them to fade in, however if they are already loaded/cached then there is no need to fade as map will 6242* look cohesive with no gaps. Note that draw_raster determines fade priority, as many-to-one fade supersedes edge fading. 6243*/ 6244function updateFadingEdge(idealTile, edgeTileIDs, now, rasterFadeDuration) { 6245 const idealID = idealTile.tileID; 6246 if (idealTile.selfFading) return true; 6247 if (idealTile.hasData()) return false; 6248 if (edgeTileIDs.has(idealID)) { 6249 const fadeEndTime = now + rasterFadeDuration; 6250 idealTile.setSelfFadeLogic(fadeEndTime); 6251 return true; 6252 } 6253 return false; 6254} 6255function hasRasterTransition(inViewTiles, rasterFadeDuration) { 6256 if (rasterFadeDuration <= 0) return false; 6257 const currentTime = now(); 6258 for (const tile of inViewTiles.getAllTiles()) if (tile.fadeEndTime >= currentTime) return true; 6259 return false; 6260} 6261//#endregion 6262//#region src/tile/tile_manager_raster_dem.ts 6263/** 6264* For raster terrain source, backfill DEM to eliminate visible tile boundaries 6265*/ 6266function backfillDEM(tile, inViewTiles) { 6267 const renderables = inViewTiles.getRenderableIds(); 6268 for (const borderId of renderables) { 6269 if (!tile.neighboringTiles?.[borderId]) continue; 6270 const borderTile = inViewTiles.getTileById(borderId); 6271 if (!tile.neighboringTiles[borderId].backfilled) fillBorder(tile, borderTile); 6272 if (borderTile.neighboringTiles?.[tile.tileID.key]?.backfilled) continue; 6273 fillBorder(borderTile, tile); 6274 } 6275} 6276function fillBorder(tile, borderTile) { 6277 tile.needsHillshadePrepare = true; 6278 tile.needsTerrainPrepare = true; 6279 tile.needsColorReliefPrepare = true; 6280 let dx = borderTile.tileID.canonical.x - tile.tileID.canonical.x; 6281 const dy = borderTile.tileID.canonical.y - tile.tileID.canonical.y; 6282 const dim = Math.pow(2, tile.tileID.canonical.z); 6283 const borderId = borderTile.tileID.key; 6284 if (dx === 0 && dy === 0) return; 6285 if (Math.abs(dy) > 1) return; 6286 if (Math.abs(dx) > 1) { 6287 if (Math.abs(dx + dim) === 1) dx += dim; 6288 else if (Math.abs(dx - dim) === 1) dx -= dim; 6289 } 6290 if (!borderTile.dem || !tile.dem) return; 6291 tile.dem.backfillBorder(borderTile.dem, dx, dy); 6292 if (tile.neighboringTiles?.[borderId]) tile.neighboringTiles[borderId].backfilled = true; 6293} 6294//#endregion 6295//#region src/tile/tile_manager_in_view_tiles.ts 6296var InViewTiles = class { 6297 constructor() { 6298 this._tiles = {}; 6299 } 6300 handleWrapJump(wrapDelta) { 6301 const tiles = {}; 6302 for (const id in this._tiles) { 6303 const tile = this._tiles[id]; 6304 tile.tileID = tile.tileID.unwrapTo(tile.tileID.wrap + wrapDelta); 6305 tiles[tile.tileID.key] = tile; 6306 } 6307 this._tiles = tiles; 6308 } 6309 setFeatureState(featuresChanged, painter, revision) { 6310 for (const id in this._tiles) this._tiles[id].setFeatureState(featuresChanged, painter, revision); 6311 } 6312 getAllTiles() { 6313 return Object.values(this._tiles); 6314 } 6315 getAllIds(sorted = false) { 6316 if (sorted) return Object.values(this._tiles).map((tile) => tile.tileID).sort(compareTileId).map((id) => id.key); 6317 return Object.keys(this._tiles); 6318 } 6319 getTileById(key) { 6320 return this._tiles[key]; 6321 } 6322 setTile(key, tile) { 6323 this._tiles[key] = tile; 6324 } 6325 deleteTileById(key) { 6326 delete this._tiles[key]; 6327 } 6328 /** 6329 * Get a currently loaded tile. 6330 * - a cached tile is not a loaded tile 6331 * @returns the tile if it's in view and had data, null otherwise. 6332 */ 6333 getLoadedTile(tileID) { 6334 const tile = this.getTileById(tileID.key); 6335 if (tile?.hasData()) return tile; 6336 return null; 6337 } 6338 isIdRenderable(id, symbolLayer = false) { 6339 return this.getTileById(id)?.isRenderable(symbolLayer); 6340 } 6341 getRenderableIds(bearingInRadians = 0, symbolLayer) { 6342 const renderables = []; 6343 for (const id of this.getAllIds()) if (this.isIdRenderable(id, symbolLayer)) renderables.push(this.getTileById(id)); 6344 if (symbolLayer) return renderables.sort((a_, b_) => { 6345 const a = a_.tileID; 6346 const b = b_.tileID; 6347 const rotatedA = new Point(a.canonical.x, a.canonical.y)._rotate(-bearingInRadians); 6348 const rotatedB = new Point(b.canonical.x, b.canonical.y)._rotate(-bearingInRadians); 6349 return a.overscaledZ - b.overscaledZ || rotatedB.y - rotatedA.y || rotatedB.x - rotatedA.x; 6350 }).map((tile) => tile.tileID.key); 6351 return renderables.map((tile) => tile.tileID).sort(compareTileId).map((id) => id.key); 6352 } 6353}; 6354//#endregion 6355//#region src/tile/tile_manager.ts 6356/** 6357* @internal 6358* `TileManager` is responsible for 6359* 6360* - creating an instance of `Source` 6361* - caching tiles loaded from an instance of `Source` 6362* - handling incoming source data events events from `Map` and coordinating updates 6363* - providing the current renderable tile coordinates to the `Painter` 6364* - loading the tiles needed to render a given viewport 6365* - retaining the tiles needed as substitutes for pending loading tiles 6366* - retaining the tiles needed for fading between parents and children (for raster sources) 6367* - reloading tiles when source data or dependencies change 6368* - handling tile expiration and refresh timers 6369* - unloading cached tiles not needed to render a given viewport 6370* - managing tile state and feature state 6371*/ 6372var TileManager = class TileManager extends Evented { 6373 static { 6374 this.maxUnderzooming = 10; 6375 } 6376 static { 6377 this.maxOverzooming = 3; 6378 } 6379 constructor(id, options, dispatcher) { 6380 super(); 6381 this._maxContentElevationSeen = 0; 6382 this.id = id; 6383 this.dispatcher = dispatcher; 6384 this.on("data", (e) => { 6385 this._dataHandler(e); 6386 }); 6387 this.on("dataloading", () => { 6388 this._sourceErrored = false; 6389 }); 6390 this.on("error", () => { 6391 this._sourceErrored = this._source.loaded(); 6392 }); 6393 this._source = create(id, options, dispatcher, this); 6394 this._inViewTiles = new InViewTiles(); 6395 this._outOfViewCache = new TileCache(0, (tile) => this._unloadTile(tile)); 6396 this._timers = {}; 6397 this._maxTileCacheSize = null; 6398 this._maxTileCacheZoomLevels = null; 6399 this._rasterFadeDuration = 0; 6400 this._maxFadingAncestorLevels = 5; 6401 this._state = new SourceFeatureState(); 6402 this._didEmitContent = false; 6403 this._updated = false; 6404 } 6405 onAdd(map) { 6406 this.map = map; 6407 this._maxTileCacheSize = map ? map._maxTileCacheSize : null; 6408 this._maxTileCacheZoomLevels = map ? map._maxTileCacheZoomLevels : null; 6409 if (this._source?.onAdd) this._source.onAdd(map); 6410 } 6411 onRemove(map) { 6412 for (const tile of this._inViewTiles.getAllTiles()) tile.unloadVectorData(); 6413 this.clearTiles(); 6414 if (this._source?.onRemove) this._source.onRemove(map); 6415 this._inViewTiles = new InViewTiles(); 6416 } 6417 /** 6418 * Return true if no tile data is pending, tiles will not change unless 6419 * an additional API call is received. 6420 */ 6421 loaded() { 6422 if (this._sourceErrored) return true; 6423 if (!this._sourceLoaded) return false; 6424 if (!this._source.loaded()) return false; 6425 if ((this.used !== void 0 || this.usedForTerrain !== void 0) && !this.used && !this.usedForTerrain) return true; 6426 if (!this._updated) return false; 6427 for (const tile of this._inViewTiles.getAllTiles()) if (tile.state !== "loaded" && tile.state !== "errored") return false; 6428 return true; 6429 } 6430 getSource() { 6431 return this._source; 6432 } 6433 getState() { 6434 return this._state; 6435 } 6436 pause() { 6437 this._paused = true; 6438 } 6439 resume() { 6440 if (!this._paused) return; 6441 const shouldReload = this._shouldReloadOnResume; 6442 this._paused = false; 6443 this._shouldReloadOnResume = false; 6444 if (shouldReload) this.reload(); 6445 if (this.transform) this.update(this.transform, this.terrain); 6446 } 6447 async _loadTile(tile, id, state, hadData) { 6448 try { 6449 const result = await this._source.loadTile(tile); 6450 this._tileLoaded(tile, id, state, hadData, result); 6451 } catch (err) { 6452 tile.state = "errored"; 6453 if (err.status !== 404) this._source.fire(new ErrorEvent(ensureError(err), { tile })); 6454 else this.update(this.transform, this.terrain); 6455 } 6456 } 6457 _unloadTile(tile) { 6458 if (this._source.unloadTile) this._source.unloadTile(tile); 6459 } 6460 _abortTile(tile) { 6461 if (this._source.abortTile) this._source.abortTile(tile); 6462 this._source.fire(new MapSourceDataEvent("dataabort", { 6463 tile, 6464 coord: tile.tileID 6465 })); 6466 } 6467 serialize() { 6468 return this._source.serialize(); 6469 } 6470 prepare(context) { 6471 if (this._source.prepare) this._source.prepare(); 6472 this._state.coalesceChanges(this._inViewTiles, this.map ? this.map.painter : null); 6473 for (const tile of this._inViewTiles.getAllTiles()) { 6474 tile.upload(context); 6475 tile.prepare(this.map.style.imageManager); 6476 } 6477 } 6478 /** 6479 * Return all tile ids ordered with z-order, and cast to numbers 6480 */ 6481 getIds() { 6482 return this._inViewTiles.getAllIds(true); 6483 } 6484 getRenderableIds(symbolLayer) { 6485 return this._inViewTiles.getRenderableIds(this.transform?.bearingInRadians, symbolLayer); 6486 } 6487 hasRenderableParent(tileID) { 6488 const parentZ = tileID.overscaledZ - 1; 6489 if (parentZ >= this._source.minzoom) { 6490 const parentTile = this.getLoadedTile(tileID.scaledTo(parentZ)); 6491 if (parentTile) return this._inViewTiles.isIdRenderable(parentTile.tileID.key); 6492 } 6493 return false; 6494 } 6495 /** 6496 * Reload tiles based on the current state of the source. 6497 * @param sourceDataChanged - If `true`, reload all tiles using a state of 'expired' (errored tiles use 'loading' since they have nothing to show yet), otherwise reload only non-errored tiles using state of 'reloading'. 6498 * @param shouldReloadTileOptions - Set of options associated with a `MapSourceDataChangedEvent` that can be passed back to the associated `Source` determine whether a tile should be reloaded. 6499 */ 6500 reload(sourceDataChanged, shouldReloadTileOptions = void 0) { 6501 if (this._paused) { 6502 this._shouldReloadOnResume = true; 6503 return; 6504 } 6505 this._outOfViewCache.reset(); 6506 for (const id of this._inViewTiles.getAllIds()) { 6507 const tile = this._inViewTiles.getTileById(id); 6508 if (shouldReloadTileOptions && !this._source.shouldReloadTile(tile, shouldReloadTileOptions)) continue; 6509 else if (sourceDataChanged) this._reloadTile(id, tile.state === "errored" ? "loading" : "expired"); 6510 else if (tile.state !== "errored") this._reloadTile(id, "reloading"); 6511 } 6512 } 6513 async _reloadTile(id, state) { 6514 const tile = this._inViewTiles.getTileById(id); 6515 if (!tile) return; 6516 const hadData = tile.hasData(); 6517 if (tile.state !== "loading") tile.state = state; 6518 await this._loadTile(tile, id, state, hadData); 6519 } 6520 _tileLoaded(tile, id, previousState, hadData, result) { 6521 if (!hadData) { 6522 tile.timeAdded = now(); 6523 if (tile.selfFading) tile.fadeEndTime = tile.timeAdded + this._rasterFadeDuration; 6524 } 6525 if (previousState === "expired") tile.refreshedUponExpiration = true; 6526 this._setTileReloadTimer(id, tile); 6527 if (result?.unmodified) return; 6528 if (this.getSource().type === "raster-dem" && tile.dem) backfillDEM(tile, this._inViewTiles); 6529 tile.featureStateRevision = -1; 6530 this._state.initializeTileState(tile, this.map ? this.map.painter : null); 6531 if (!tile.aborted) this._source.fire(new MapSourceDataEvent("data", { 6532 tile, 6533 coord: tile.tileID 6534 })); 6535 } 6536 /** 6537 * Get a specific tile by TileID 6538 */ 6539 getTile(tileID) { 6540 return this.getTileByID(tileID.key); 6541 } 6542 /** 6543 * Get a specific tile by id 6544 */ 6545 getTileByID(id) { 6546 return this._inViewTiles.getTileById(id); 6547 } 6548 /** 6549 * Retain the uppermost loaded children of each provided target tile, within a variable covering zoom range. 6550 * 6551 * On pitched maps, different parts of the screen show different zoom levels simultaneously. 6552 * Ideal tiles are generated using coveringTiles() above, which returns the ideal tile set for 6553 * the current pitched plane, which can carry tiles of varying zooms (overscaledZ). 6554 * See: https://maplibre.org/maplibre-gl-js/docs/examples/level-of-detail-control/ 6555 * 6556 * A fixed `maxCoveringZoom` on a pitched map would incorrectly intersect with some 6557 * ideal tiles and cause distant high-pitch tiles to skip their uppermost children. 6558 * 6559 * To solve this, we calculate the max covering zoom for each ideal tile separately using its 6560 * `overscaledZ`. This effectively makes the "max covering zoom plane" parallel to the 6561 * "ideal tile plane," ensuring that we correctly capture the uppermost children 6562 * of each ideal tile across the pitched view. 6563 * 6564 * Analogy: imagine two sheets of paper in 3D space: 6565 * - one sheet = ideal tiles at varying overscaledZ 6566 * - the second sheet = maxCoveringZoom 6567 * 6568 * @param retainTileMap - this parameters will be updated with the child tiles to keep 6569 * @param idealTilesWithoutData - which of the ideal tiles currently does not have loaded data 6570 * @return a set of tiles that need to be loaded 6571 */ 6572 _retainLoadedChildren(retainTileMap, idealTilesWithoutData) { 6573 const loadedDescendents = this._getLoadedDescendents(idealTilesWithoutData); 6574 const incomplete = /* @__PURE__ */ new Set(); 6575 for (const targetID of idealTilesWithoutData) { 6576 const descendents = loadedDescendents[targetID.key]; 6577 if (!descendents?.length) { 6578 incomplete.add(targetID); 6579 continue; 6580 } 6581 const maxCoveringZoom = targetID.overscaledZ + TileManager.maxOverzooming; 6582 const candidates = descendents.filter((t) => t.tileID.overscaledZ <= maxCoveringZoom); 6583 if (!candidates.length) { 6584 incomplete.add(targetID); 6585 continue; 6586 } 6587 const topZoom = Math.min(...candidates.map((t) => t.tileID.overscaledZ)); 6588 const topIDs = candidates.filter((t) => t.tileID.overscaledZ === topZoom).map((t) => t.tileID); 6589 for (const tileID of topIDs) retainTileMap[tileID.key] = tileID; 6590 if (!this._areDescendentsComplete(topIDs, topZoom, targetID.overscaledZ)) incomplete.add(targetID); 6591 } 6592 return incomplete; 6593 } 6594 /** 6595 * Return dictionary of qualified loaded descendents for each provided target tile id 6596 */ 6597 _getLoadedDescendents(targetTileIDs) { 6598 const loadedDescendents = {}; 6599 for (const tile of this._inViewTiles.getAllTiles().filter((tile) => tile.hasData())) for (const targetID of targetTileIDs) if (tile.tileID.isChildOf(targetID)) { 6600 loadedDescendents[targetID.key] ||= []; 6601 loadedDescendents[targetID.key].push(tile); 6602 } 6603 return loadedDescendents; 6604 } 6605 /** 6606 * Determine if tile ids fully cover the current generation. 6607 * - 1st generation: need 4 children or 1 overscaled child 6608 * - 2nd generation: need 16 children or 1 overscaled child 6609 */ 6610 _areDescendentsComplete(generationIDs, generationZ, ancestorZ) { 6611 if (generationIDs.length === 1 && generationIDs[0].isOverscaled()) return generationIDs[0].overscaledZ === generationZ; 6612 else return Math.pow(4, generationZ - ancestorZ) === generationIDs.length; 6613 } 6614 /** 6615 * Get a currently loaded tile. 6616 * - a cached tile is not a loaded tile 6617 * @returns the tile if it's in view and had data, null otherwise. 6618 */ 6619 getLoadedTile(tileID) { 6620 return this._inViewTiles.getLoadedTile(tileID); 6621 } 6622 /** 6623 * Resizes the tile cache based on the current viewport's size 6624 * or the maxTileCacheSize option passed during map creation 6625 * 6626 * Larger viewports use more tiles and need larger caches. Larger viewports 6627 * are more likely to be found on devices with more memory and on pages where 6628 * the map is more important. 6629 */ 6630 updateCacheSize(transform) { 6631 const approxTilesInView = (Math.ceil(transform.width / this._source.tileSize) + 1) * (Math.ceil(transform.height / this._source.tileSize) + 1); 6632 const commonZoomRange = this._maxTileCacheZoomLevels === null ? config.MAX_TILE_CACHE_ZOOM_LEVELS : this._maxTileCacheZoomLevels; 6633 const viewDependentMaxSize = Math.floor(approxTilesInView * commonZoomRange); 6634 const maxSize = typeof this._maxTileCacheSize === "number" ? Math.min(this._maxTileCacheSize, viewDependentMaxSize) : viewDependentMaxSize; 6635 this._outOfViewCache.setMaxSize(maxSize); 6636 } 6637 handleWrapJump(lng) { 6638 const worldDifference = (lng - (this._prevLng === void 0 ? lng : this._prevLng)) / 360; 6639 const wrapDelta = Math.round(worldDifference); 6640 this._prevLng = lng; 6641 if (wrapDelta) { 6642 this._inViewTiles.handleWrapJump(wrapDelta); 6643 this._resetTileReloadTimers(); 6644 } 6645 } 6646 /** 6647 * The highest elevation this source's symbols may reach, in meters: `symbol-height-offset` 6648 * constants read from the visible symbol layers, data-driven maxima tracked by the loaded 6649 * buckets at layout time. Only data-driven layers scan the loaded tiles, so styles without 6650 * them pay nothing beyond the layer loop. The value is a high-water mark: a maximum seen once is kept even 6651 * after its tile unloads, otherwise dropping the tile would also drop the reason to keep it, 6652 * and the tile could not come back while its content is still visible. The mark resets with 6653 * the tiles in `clearTiles`. 6654 */ 6655 _updateMaxContentElevation() { 6656 let maxElevation = this._maxContentElevationSeen; 6657 const layers = this.map?.style?._layers; 6658 if (!layers) return maxElevation; 6659 const tiles = this._inViewTiles.getAllTiles(); 6660 for (const layerId in layers) { 6661 const layer = layers[layerId]; 6662 if (layer.type !== "symbol" || layer.source !== this.id || layer.isHidden(this.transform.zoom)) continue; 6663 const symbolLayer = layer; 6664 if (!symbolLayer.layout) continue; 6665 const heightOffset = symbolLayer.layout.get("symbol-height-offset"); 6666 maxElevation = Math.max(maxElevation, heightOffset.constantOr(0)); 6667 if (heightOffset.isConstant()) continue; 6668 for (const tile of tiles) { 6669 const bucket = tile.getBucket(layer); 6670 if (bucket && bucket.maxHeightOffset > maxElevation) maxElevation = bucket.maxHeightOffset; 6671 } 6672 } 6673 this._maxContentElevationSeen = maxElevation; 6674 return maxElevation; 6675 } 6676 /** 6677 * Removes tiles that are outside the viewport and adds new tiles that 6678 * are inside the viewport. 6679 */ 6680 update(transform, terrain) { 6681 if (!this._sourceLoaded || this._paused) return; 6682 this.transform = transform; 6683 this.terrain = terrain; 6684 this.updateCacheSize(transform); 6685 this.handleWrapJump(this.transform.center.lng); 6686 let idealTileIDs; 6687 if (!this.used && !this.usedForTerrain) idealTileIDs = []; 6688 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)); 6689 else { 6690 idealTileIDs = coveringTiles(transform, { 6691 tileSize: this.usedForTerrain ? this.tileSize : this._source.tileSize, 6692 minzoom: this._source.minzoom, 6693 maxzoom: this._source.type === "vector" && this.map._zoomLevelsToOverscale !== void 0 ? Math.max(this._source.maxzoom, transform.maxZoom - this.map._zoomLevelsToOverscale) : this._source.maxzoom, 6694 roundZoom: this.usedForTerrain ? false : this._source.roundZoom, 6695 reparseOverscaled: this._source.reparseOverscaled, 6696 terrain, 6697 calculateTileZoom: this._source.calculateTileZoom, 6698 maxContentElevation: this._updateMaxContentElevation() 6699 }); 6700 if (this._source.hasTile) idealTileIDs = idealTileIDs.filter((coord) => this._source.hasTile(coord)); 6701 } 6702 if (this.usedForTerrain) idealTileIDs = this._addTerrainIdealTiles(idealTileIDs); 6703 const noPendingDataEmissions = idealTileIDs.length === 0 && !this._updated && this._didEmitContent; 6704 this._updated = true; 6705 if (noPendingDataEmissions) this.fire(new MapSourceDataEvent("data", { 6706 sourceDataType: "idle", 6707 sourceId: this.id 6708 })); 6709 const zoom = coveringZoomLevel(transform, this._source); 6710 const retain = this._updateRetainedTiles(idealTileIDs, zoom); 6711 const isRaster = isRasterType(this._source.type); 6712 if (isRaster && this._rasterFadeDuration > 0 && !terrain) updateFadingTiles(this._inViewTiles, idealTileIDs, retain, this._maxFadingAncestorLevels, this._source.minzoom, this._source.maxzoom, this._rasterFadeDuration); 6713 if (isRaster) this._cleanUpRasterTiles(retain); 6714 else this._cleanUpVectorTiles(retain); 6715 } 6716 /** 6717 * Remove raster tiles that are no longer retained 6718 */ 6719 _cleanUpRasterTiles(retain) { 6720 for (const id of this._inViewTiles.getAllIds()) if (!retain[id]) this._removeTile(id); 6721 } 6722 /** 6723 * Remove vector tiles that are no longer retained and also not needed for symbol fading 6724 */ 6725 _cleanUpVectorTiles(retain) { 6726 for (const id of this._inViewTiles.getAllIds()) { 6727 const tile = this._inViewTiles.getTileById(id); 6728 if (retain[id]) { 6729 tile.clearSymbolFadeHold(); 6730 continue; 6731 } 6732 if (!tile.hasSymbolBuckets) { 6733 this._removeTile(id); 6734 continue; 6735 } 6736 if (!tile.holdingForSymbolFade()) tile.setSymbolHoldDuration(this.map._fadeDuration); 6737 else if (tile.symbolFadeFinished()) this._removeTile(id); 6738 } 6739 } 6740 /** 6741 * Add ideal tiles needed for 3D terrain rendering 6742 */ 6743 _addTerrainIdealTiles(idealTileIDs) { 6744 const ancestors = []; 6745 for (const tileID of idealTileIDs) if (tileID.canonical.z > this._source.minzoom) { 6746 const parent = tileID.scaledTo(tileID.canonical.z - 1); 6747 ancestors.push(parent); 6748 const parent2 = tileID.scaledTo(Math.max(this._source.minzoom, Math.min(tileID.canonical.z, 5))); 6749 ancestors.push(parent2); 6750 } 6751 return idealTileIDs.concat(ancestors); 6752 } 6753 releaseSymbolFadeTiles() { 6754 for (const id of this._inViewTiles.getAllIds()) if (this._inViewTiles.getTileById(id).holdingForSymbolFade()) this._removeTile(id); 6755 } 6756 /** 6757 * Set tiles to be retained on update of the source. For ideal tiles that do not have data, retain their loaded 6758 * children so they can be displayed as substitutes pending load of each ideal tile (to reduce flickering). 6759 * If no loaded children are available, fallback to seeking loaded parents as an alternative substitute. 6760 */ 6761 _updateRetainedTiles(idealTileIDs, zoom) { 6762 const idealTilesWithoutData = /* @__PURE__ */ new Set(); 6763 for (const idealID of idealTileIDs) if (!this._addTile(idealID).hasData()) idealTilesWithoutData.add(idealID); 6764 const retainTileMap = idealTileIDs.reduce((acc, t) => { 6765 acc[t.key] = t; 6766 return acc; 6767 }, {}); 6768 const tileIdsWithoutData = this._retainLoadedChildren(retainTileMap, idealTilesWithoutData); 6769 const checked = {}; 6770 const minCoveringZoom = Math.max(zoom - TileManager.maxUnderzooming, this._source.minzoom); 6771 for (const tileID of tileIdsWithoutData) { 6772 let tile = this._inViewTiles.getTileById(tileID.key); 6773 let parentWasRequested = tile?.wasRequested(); 6774 for (let overscaledZ = tileID.overscaledZ - 1; overscaledZ >= minCoveringZoom; --overscaledZ) { 6775 const parentId = tileID.scaledTo(overscaledZ); 6776 if (checked[parentId.key]) break; 6777 checked[parentId.key] = true; 6778 tile = this.getTile(parentId); 6779 if (!tile && parentWasRequested) tile = this._addTile(parentId); 6780 if (tile) { 6781 const hasData = tile.hasData(); 6782 if (hasData || !this.map?.cancelPendingTileRequestsWhileZooming || parentWasRequested) retainTileMap[parentId.key] = parentId; 6783 parentWasRequested = tile.wasRequested(); 6784 if (hasData) break; 6785 } 6786 } 6787 } 6788 return retainTileMap; 6789 } 6790 /** 6791 * Add a tile, given its coordinate, to the pyramid. 6792 */ 6793 _addTile(tileID) { 6794 let tile = this._inViewTiles.getTileById(tileID.key); 6795 if (tile) return tile; 6796 tile = this._outOfViewCache.getAndRemove(tileID); 6797 if (tile) { 6798 tile.resetFadeLogic(); 6799 this._setTileReloadTimer(tileID.key, tile); 6800 tile.tileID = tileID; 6801 this._state.initializeTileState(tile, this.map ? this.map.painter : null); 6802 } 6803 const cached = tile; 6804 if (!tile) { 6805 tile = new Tile(tileID, this._source.tileSize * tileID.overscaleFactor()); 6806 this._loadTile(tile, tileID.key, tile.state, false); 6807 } 6808 tile.uses++; 6809 this._inViewTiles.setTile(tileID.key, tile); 6810 if (!cached) this._source.fire(new MapSourceDataEvent("dataloading", { 6811 tile, 6812 coord: tile.tileID 6813 })); 6814 return tile; 6815 } 6816 /** 6817 * Set a timeout to reload the tile after it expires 6818 */ 6819 _setTileReloadTimer(id, tile) { 6820 this._clearTileReloadTimer(id); 6821 const expiryTimeout = tile.getExpiryTimeout(); 6822 if (expiryTimeout) { 6823 const reload = () => { 6824 this._reloadTile(id, "expired"); 6825 delete this._timers[id]; 6826 }; 6827 this._timers[id] = setTimeout(reload, expiryTimeout); 6828 } 6829 } 6830 _clearTileReloadTimer(id) { 6831 const timeout = this._timers[id]; 6832 if (timeout) { 6833 clearTimeout(timeout); 6834 delete this._timers[id]; 6835 } 6836 } 6837 _resetTileReloadTimers() { 6838 for (const id in this._timers) { 6839 clearTimeout(this._timers[id]); 6840 delete this._timers[id]; 6841 } 6842 for (const id of this._inViewTiles.getAllIds()) { 6843 const tile = this._inViewTiles.getTileById(id); 6844 this._setTileReloadTimer(id, tile); 6845 } 6846 } 6847 /** 6848 * Reload any currently renderable tiles that are match one of the incoming `tileId` x/y/z 6849 */ 6850 refreshTiles(tileIds) { 6851 for (const id of this._inViewTiles.getAllIds()) { 6852 const tile = this._inViewTiles.getTileById(id); 6853 if (!this._inViewTiles.isIdRenderable(id) && tile.state != "errored") continue; 6854 if (tileIds.some((tid) => tid.equals(tile.tileID.canonical))) this._reloadTile(id, "expired"); 6855 } 6856 } 6857 /** 6858 * Remove a tile, given its id, from the pyramid 6859 */ 6860 _removeTile(id) { 6861 const tile = this._inViewTiles.getTileById(id); 6862 if (!tile) return; 6863 tile.uses--; 6864 this._inViewTiles.deleteTileById(id); 6865 this._clearTileReloadTimer(id); 6866 if (tile.uses > 0) return; 6867 if (tile.hasData() && tile.state !== "reloading") this._outOfViewCache.add(tile.tileID, tile, tile.getExpiryTimeout()); 6868 else { 6869 tile.aborted = true; 6870 this._abortTile(tile); 6871 this._unloadTile(tile); 6872 } 6873 } 6874 /** @internal 6875 * Handles incoming source data messages (i.e. after the source has been updated via a worker that has fired 6876 * to map.ts data event). For sources with mutable data, the 'content' event fires when the underlying data 6877 * to a source has changed. (i.e. GeoJSONSource.setData and ImageSource.setCoordinates) 6878 */ 6879 _dataHandler(e) { 6880 if (e.dataType !== "source") return; 6881 if (e.sourceDataType === "metadata") { 6882 this._sourceLoaded = true; 6883 return; 6884 } 6885 if (e.sourceDataType !== "content" || !this._sourceLoaded || this._paused) return; 6886 this.reload(e.sourceDataChanged, e.shouldReloadTileOptions); 6887 if (this.transform) this.update(this.transform, this.terrain); 6888 this._didEmitContent = true; 6889 } 6890 /** 6891 * Forgets the recorded maximum content elevation, so the next update recomputes it from the 6892 * current layers and loaded tiles. Called when a symbol layer is removed, since the removed 6893 * layer's heights would otherwise keep expanding tile coverage. 6894 */ 6895 resetMaxContentElevation() { 6896 this._maxContentElevationSeen = 0; 6897 } 6898 /** 6899 * Remove all tiles from this pyramid 6900 */ 6901 clearTiles() { 6902 this._shouldReloadOnResume = false; 6903 this._paused = false; 6904 this.resetMaxContentElevation(); 6905 for (const id of this._inViewTiles.getAllIds()) this._removeTile(id); 6906 this._outOfViewCache.reset(); 6907 } 6908 /** 6909 * Search through our current tiles and attempt to find the tiles that 6910 * cover the given bounds. 6911 * @param pointQueryGeometry - coordinates of the corners of bounding rectangle 6912 * @returns result items have `{tile, minX, maxX, minY, maxY}`, where min/max bounding values are the given bounds transformed in into the coordinate space of this tile. 6913 */ 6914 tilesIn(pointQueryGeometry, maxPitchScaleFactor, has3DLayer) { 6915 const tileResults = []; 6916 const transform = this.transform; 6917 if (!transform) return tileResults; 6918 const allowWorldCopies = transform.getCoveringTilesDetailsProvider().allowWorldCopies(); 6919 const cameraPointQueryGeometry = has3DLayer ? transform.getCameraQueryGeometry(pointQueryGeometry) : pointQueryGeometry; 6920 const project = (point) => transform.screenPointToMercatorCoordinate(point, this.terrain); 6921 const queryGeometry = this.transformBbox(pointQueryGeometry, project, !allowWorldCopies); 6922 const cameraQueryGeometry = this.transformBbox(cameraPointQueryGeometry, project, !allowWorldCopies); 6923 const ids = this.getIds(); 6924 const cameraBounds = Bounds.fromPoints(cameraQueryGeometry); 6925 for (const id of ids) { 6926 const tile = this._inViewTiles.getTileById(id); 6927 if (tile.holdingForSymbolFade()) continue; 6928 const tileIDs = allowWorldCopies ? [tile.tileID] : [tile.tileID.unwrapTo(-1), tile.tileID.unwrapTo(0)]; 6929 const scale = Math.pow(2, transform.zoom - tile.tileID.overscaledZ); 6930 const queryPadding = maxPitchScaleFactor * tile.queryPadding * EXTENT / tile.tileSize / scale; 6931 for (const tileID of tileIDs) { 6932 const tileSpaceBounds = cameraBounds.map((point) => tileID.getTilePoint(new MercatorCoordinate(point.x, point.y))); 6933 tileSpaceBounds.expandBy(queryPadding); 6934 if (tileSpaceBounds.intersects(EXTENT_BOUNDS)) { 6935 const tileSpaceQueryGeometry = queryGeometry.map((c) => tileID.getTilePoint(c)); 6936 const tileSpaceCameraQueryGeometry = cameraQueryGeometry.map((c) => tileID.getTilePoint(c)); 6937 tileResults.push({ 6938 tile, 6939 tileID: allowWorldCopies ? tileID : tileID.unwrapTo(0), 6940 queryGeometry: tileSpaceQueryGeometry, 6941 cameraQueryGeometry: tileSpaceCameraQueryGeometry, 6942 scale 6943 }); 6944 } 6945 } 6946 } 6947 return tileResults; 6948 } 6949 transformBbox(geom, project, checkWrap) { 6950 let transformed = geom.map(project); 6951 if (checkWrap) { 6952 const bounds = Bounds.fromPoints(geom); 6953 bounds.shrinkBy(Math.min(bounds.width(), bounds.height()) * .001); 6954 const projected = bounds.map(project); 6955 if (!Bounds.fromPoints(transformed).covers(projected)) transformed = transformed.map((coord) => coord.x > .5 ? new MercatorCoordinate(coord.x - 1, coord.y, coord.z) : coord); 6956 } 6957 return transformed; 6958 } 6959 getVisibleCoordinates(symbolLayer) { 6960 const coords = this.getRenderableIds(symbolLayer).map((id) => this._inViewTiles.getTileById(id).tileID); 6961 if (this.transform) this.transform.populateCache(coords); 6962 return coords; 6963 } 6964 hasTransition() { 6965 if (this._source.hasTransition()) return true; 6966 return isRasterType(this._source.type) && hasRasterTransition(this._inViewTiles, this._rasterFadeDuration); 6967 } 6968 setRasterFadeDuration(fadeDuration) { 6969 this._rasterFadeDuration = fadeDuration; 6970 } 6971 /** 6972 * Set the value of a particular state for a feature 6973 */ 6974 setFeatureState(sourceLayer, featureId, state) { 6975 sourceLayer ||= GEOJSON_TILE_LAYER_NAME; 6976 this._state.updateState(sourceLayer, featureId, state); 6977 } 6978 /** 6979 * Resets the value of a particular state key for a feature 6980 */ 6981 removeFeatureState(sourceLayer, featureId, key) { 6982 sourceLayer ||= GEOJSON_TILE_LAYER_NAME; 6983 this._state.removeFeatureState(sourceLayer, featureId, key); 6984 } 6985 /** 6986 * Get the entire state object for a feature 6987 */ 6988 getFeatureState(sourceLayer, featureId) { 6989 sourceLayer ||= GEOJSON_TILE_LAYER_NAME; 6990 return this._state.getState(sourceLayer, featureId); 6991 } 6992 /** 6993 * Sets the set of keys that the tile depends on. This allows tiles to 6994 * be reloaded when their dependencies change. 6995 */ 6996 setDependencies(tileKey, namespace, dependencies) { 6997 const tile = this._inViewTiles.getTileById(tileKey); 6998 if (tile) tile.setDependencies(namespace, dependencies); 6999 } 7000 /** 7001 * Reloads all tiles that depend on the given keys. 7002 */ 7003 reloadTilesForDependencies(namespaces, keys) { 7004 for (const id of this._inViewTiles.getAllIds()) if (this._inViewTiles.getTileById(id).hasDependency(namespaces, keys)) this._reloadTile(id, "reloading"); 7005 this._outOfViewCache.filter((tile) => !tile.hasDependency(namespaces, keys)); 7006 } 7007 areTilesLoaded() { 7008 for (const tile of this._inViewTiles.getAllTiles()) if (!(tile.state === "loaded" || tile.state === "errored")) return false; 7009 return true; 7010 } 7011}; 7012//#endregion 7013//#region src/symbol/path_interpolator.ts 7014var PathInterpolator = class { 7015 constructor(points_, padding_) { 7016 this.reset(points_, padding_); 7017 } 7018 reset(points_, padding_) { 7019 this.points = points_ || []; 7020 this._distances = [0]; 7021 for (let i = 1; i < this.points.length; i++) this._distances[i] = this._distances[i - 1] + this.points[i].dist(this.points[i - 1]); 7022 this.length = this._distances[this._distances.length - 1]; 7023 this.padding = Math.min(padding_ || 0, this.length * .5); 7024 this.paddedLength = this.length - this.padding * 2; 7025 } 7026 lerp(t) { 7027 if (this.points.length === 1) return this.points[0]; 7028 t = clamp(t, 0, 1); 7029 let currentIndex = 1; 7030 let distOfCurrentIdx = this._distances[currentIndex]; 7031 const distToTarget = t * this.paddedLength + this.padding; 7032 while (distOfCurrentIdx < distToTarget && currentIndex < this._distances.length) distOfCurrentIdx = this._distances[++currentIndex]; 7033 const idxOfPrevPoint = currentIndex - 1; 7034 const distOfPrevIdx = this._distances[idxOfPrevPoint]; 7035 const segmentLength = distOfCurrentIdx - distOfPrevIdx; 7036 const segmentT = segmentLength > 0 ? (distToTarget - distOfPrevIdx) / segmentLength : 0; 7037 return this.points[idxOfPrevPoint].mult(1 - segmentT).add(this.points[currentIndex].mult(segmentT)); 7038 } 7039}; 7040//#endregion 7041//#region src/symbol/symbol_elevation.ts 7042/** 7043* Resolves the elevation of a symbol at a tile coordinate, combining the terrain elevation with the 7044* symbol's `symbol-height-offset`. `symbol-height-anchor` selects the datum the offset is measured 7045* from: the terrain surface below the symbol (`ground`) or the zero elevation datum (`absolute`), 7046* in which case the terrain below the symbol is ignored. 7047* 7048* Call this where the elevation is needed, so that `getElevation` keeps returning the plain terrain 7049* elevation and never has to be substituted with a wrapper. 7050* 7051* @param getElevation - the terrain elevation function, absent when the map has no terrain 7052* @param x - the tile x coordinate to evaluate the elevation at 7053* @param y - the tile y coordinate to evaluate the elevation at 7054* @param heightOffset - the evaluated `symbol-height-offset` of the symbol, in meters 7055* @param heightAnchorGround - whether `symbol-height-anchor` is `ground` 7056* @returns the elevation in meters, or undefined when the symbol sits at the zero elevation datum, 7057* which lets the projection take its fast path that ignores the z coordinate 7058*/ 7059function getSymbolElevation(getElevation, x, y, heightOffset, heightAnchorGround) { 7060 if (!heightAnchorGround) return heightOffset; 7061 if (!getElevation) return heightOffset !== 0 ? heightOffset : void 0; 7062 return getElevation(x, y) + heightOffset; 7063} 7064//#endregion 7065//#region src/symbol/grid_index.ts 7066function overlapAllowed(overlapA, overlapB) { 7067 let allowed = true; 7068 if (overlapA === "always") {} else if (overlapA === "never" || overlapB === "never") allowed = false; 7069 return allowed; 7070} 7071/** 7072* @internal 7073* GridIndex is a data structure for testing the intersection of 7074* circles and rectangles in a 2d plane. 7075* It is optimized for rapid insertion and querying. 7076* GridIndex splits the plane into a set of "cells" and keeps track 7077* of which geometries intersect with each cell. At query time, 7078* full geometry comparisons are only done for items that share 7079* at least one cell. As long as the geometries are relatively 7080* uniformly distributed across the plane, this greatly reduces 7081* the number of comparisons necessary. 7082*/ 7083var GridIndex = class { 7084 constructor(width, height, cellSize) { 7085 const boxCells = this.boxCells = []; 7086 const circleCells = this.circleCells = []; 7087 this.xCellCount = Math.ceil(width / cellSize); 7088 this.yCellCount = Math.ceil(height / cellSize); 7089 for (let i = 0; i < this.xCellCount * this.yCellCount; i++) { 7090 boxCells.push([]); 7091 circleCells.push([]); 7092 } 7093 this.circleKeys = []; 7094 this.boxKeys = []; 7095 this.bboxes = []; 7096 this.circles = []; 7097 this.width = width; 7098 this.height = height; 7099 this.xScale = this.xCellCount / width; 7100 this.yScale = this.yCellCount / height; 7101 this.boxUid = 0; 7102 this.circleUid = 0; 7103 } 7104 keysLength() { 7105 return this.boxKeys.length + this.circleKeys.length; 7106 } 7107 insert(key, x1, y1, x2, y2) { 7108 this._forEachCell(x1, y1, x2, y2, this._insertBoxCell, this.boxUid++); 7109 this.boxKeys.push(key); 7110 this.bboxes.push(x1); 7111 this.bboxes.push(y1); 7112 this.bboxes.push(x2); 7113 this.bboxes.push(y2); 7114 } 7115 insertCircle(key, x, y, radius) { 7116 this._forEachCell(x - radius, y - radius, x + radius, y + radius, this._insertCircleCell, this.circleUid++); 7117 this.circleKeys.push(key); 7118 this.circles.push(x); 7119 this.circles.push(y); 7120 this.circles.push(radius); 7121 } 7122 _insertBoxCell(x1, y1, x2, y2, cellIndex, uid) { 7123 this.boxCells[cellIndex].push(uid); 7124 } 7125 _insertCircleCell(x1, y1, x2, y2, cellIndex, uid) { 7126 this.circleCells[cellIndex].push(uid); 7127 } 7128 _query(x1, y1, x2, y2, hitTest, overlapMode, predicate) { 7129 if (x2 < 0 || x1 > this.width || y2 < 0 || y1 > this.height) return []; 7130 const result = []; 7131 if (x1 <= 0 && y1 <= 0 && this.width <= x2 && this.height <= y2) { 7132 if (hitTest) return [{ 7133 key: null, 7134 x1, 7135 y1, 7136 x2, 7137 y2 7138 }]; 7139 for (let boxUid = 0; boxUid < this.boxKeys.length; boxUid++) result.push({ 7140 key: this.boxKeys[boxUid], 7141 x1: this.bboxes[boxUid * 4], 7142 y1: this.bboxes[boxUid * 4 + 1], 7143 x2: this.bboxes[boxUid * 4 + 2], 7144 y2: this.bboxes[boxUid * 4 + 3] 7145 }); 7146 for (let circleUid = 0; circleUid < this.circleKeys.length; circleUid++) { 7147 const x = this.circles[circleUid * 3]; 7148 const y = this.circles[circleUid * 3 + 1]; 7149 const radius = this.circles[circleUid * 3 + 2]; 7150 result.push({ 7151 key: this.circleKeys[circleUid], 7152 x1: x - radius, 7153 y1: y - radius, 7154 x2: x + radius, 7155 y2: y + radius 7156 }); 7157 } 7158 } else { 7159 const queryArgs = { 7160 hitTest, 7161 overlapMode, 7162 seenUids: { 7163 box: {}, 7164 circle: {} 7165 } 7166 }; 7167 this._forEachCell(x1, y1, x2, y2, this._queryCell, result, queryArgs, predicate); 7168 } 7169 return result; 7170 } 7171 query(x1, y1, x2, y2) { 7172 return this._query(x1, y1, x2, y2, false, null); 7173 } 7174 hitTest(x1, y1, x2, y2, overlapMode, predicate) { 7175 return this._query(x1, y1, x2, y2, true, overlapMode, predicate).length > 0; 7176 } 7177 hitTestCircle(x, y, radius, overlapMode, predicate) { 7178 const x1 = x - radius; 7179 const x2 = x + radius; 7180 const y1 = y - radius; 7181 const y2 = y + radius; 7182 if (x2 < 0 || x1 > this.width || y2 < 0 || y1 > this.height) return false; 7183 const result = []; 7184 const queryArgs = { 7185 hitTest: true, 7186 overlapMode, 7187 circle: { 7188 x, 7189 y, 7190 radius 7191 }, 7192 seenUids: { 7193 box: {}, 7194 circle: {} 7195 } 7196 }; 7197 this._forEachCell(x1, y1, x2, y2, this._queryCellCircle, result, queryArgs, predicate); 7198 return result.length > 0; 7199 } 7200 _queryCell(x1, y1, x2, y2, cellIndex, result, queryArgs, predicate) { 7201 const { seenUids, hitTest, overlapMode } = queryArgs; 7202 const boxCell = this.boxCells[cellIndex]; 7203 const epsilon = 1e-6; 7204 if (boxCell !== null) { 7205 const bboxes = this.bboxes; 7206 for (const boxUid of boxCell) if (!seenUids.box[boxUid]) { 7207 seenUids.box[boxUid] = true; 7208 const offset = boxUid * 4; 7209 const key = this.boxKeys[boxUid]; 7210 if (x1 <= bboxes[offset + 2] + epsilon && y1 <= bboxes[offset + 3] + epsilon && x2 >= bboxes[offset + 0] - epsilon && y2 >= bboxes[offset + 1] - epsilon && (!predicate || predicate(key))) { 7211 if (!hitTest || !overlapAllowed(overlapMode, key.overlapMode)) { 7212 result.push({ 7213 key, 7214 x1: bboxes[offset], 7215 y1: bboxes[offset + 1], 7216 x2: bboxes[offset + 2], 7217 y2: bboxes[offset + 3] 7218 }); 7219 if (hitTest) return true; 7220 } 7221 } 7222 } 7223 } 7224 const circleCell = this.circleCells[cellIndex]; 7225 if (circleCell !== null) { 7226 const circles = this.circles; 7227 for (const circleUid of circleCell) if (!seenUids.circle[circleUid]) { 7228 seenUids.circle[circleUid] = true; 7229 const offset = circleUid * 3; 7230 const key = this.circleKeys[circleUid]; 7231 if (this._circleAndRectCollide(circles[offset], circles[offset + 1], circles[offset + 2], x1, y1, x2, y2) && (!predicate || predicate(key))) { 7232 if (!hitTest || !overlapAllowed(overlapMode, key.overlapMode)) { 7233 const x = circles[offset]; 7234 const y = circles[offset + 1]; 7235 const radius = circles[offset + 2]; 7236 result.push({ 7237 key, 7238 x1: x - radius, 7239 y1: y - radius, 7240 x2: x + radius, 7241 y2: y + radius 7242 }); 7243 if (hitTest) return true; 7244 } 7245 } 7246 } 7247 } 7248 return false; 7249 } 7250 _queryCellCircle(x1, y1, x2, y2, cellIndex, result, queryArgs, predicate) { 7251 const { circle, seenUids, overlapMode } = queryArgs; 7252 const boxCell = this.boxCells[cellIndex]; 7253 if (boxCell !== null) { 7254 const bboxes = this.bboxes; 7255 for (const boxUid of boxCell) if (!seenUids.box[boxUid]) { 7256 seenUids.box[boxUid] = true; 7257 const offset = boxUid * 4; 7258 const key = this.boxKeys[boxUid]; 7259 if (this._circleAndRectCollide(circle.x, circle.y, circle.radius, bboxes[offset + 0], bboxes[offset + 1], bboxes[offset + 2], bboxes[offset + 3]) && (!predicate || predicate(key)) && !overlapAllowed(overlapMode, key.overlapMode)) { 7260 result.push(true); 7261 return true; 7262 } 7263 } 7264 } 7265 const circleCell = this.circleCells[cellIndex]; 7266 if (circleCell !== null) { 7267 const circles = this.circles; 7268 for (const circleUid of circleCell) if (!seenUids.circle[circleUid]) { 7269 seenUids.circle[circleUid] = true; 7270 const offset = circleUid * 3; 7271 const key = this.circleKeys[circleUid]; 7272 if (this._circlesCollide(circles[offset], circles[offset + 1], circles[offset + 2], circle.x, circle.y, circle.radius) && (!predicate || predicate(key)) && !overlapAllowed(overlapMode, key.overlapMode)) { 7273 result.push(true); 7274 return true; 7275 } 7276 } 7277 } 7278 } 7279 _forEachCell(x1, y1, x2, y2, fn, arg1, arg2, predicate) { 7280 const cx1 = this._convertToXCellCoord(x1); 7281 const cy1 = this._convertToYCellCoord(y1); 7282 const cx2 = this._convertToXCellCoord(x2); 7283 const cy2 = this._convertToYCellCoord(y2); 7284 for (let x = cx1; x <= cx2; x++) for (let y = cy1; y <= cy2; y++) { 7285 const cellIndex = this.xCellCount * y + x; 7286 if (fn.call(this, x1, y1, x2, y2, cellIndex, arg1, arg2, predicate)) return; 7287 } 7288 } 7289 _convertToXCellCoord(x) { 7290 return Math.max(0, Math.min(this.xCellCount - 1, Math.floor(x * this.xScale))); 7291 } 7292 _convertToYCellCoord(y) { 7293 return Math.max(0, Math.min(this.yCellCount - 1, Math.floor(y * this.yScale))); 7294 } 7295 _circlesCollide(x1, y1, r1, x2, y2, r2) { 7296 const dx = x2 - x1; 7297 const dy = y2 - y1; 7298 const bothRadii = r1 + r2; 7299 return bothRadii * bothRadii > dx * dx + dy * dy; 7300 } 7301 _circleAndRectCollide(circleX, circleY, radius, x1, y1, x2, y2) { 7302 const halfRectWidth = (x2 - x1) / 2; 7303 const distX = Math.abs(circleX - (x1 + halfRectWidth)); 7304 if (distX > halfRectWidth + radius) return false; 7305 const halfRectHeight = (y2 - y1) / 2; 7306 const distY = Math.abs(circleY - (y1 + halfRectHeight)); 7307 if (distY > halfRectHeight + radius) return false; 7308 if (distX <= halfRectWidth || distY <= halfRectHeight) return true; 7309 const dx = distX - halfRectWidth; 7310 const dy = distY - halfRectHeight; 7311 return dx * dx + dy * dy <= radius * radius; 7312 } 7313}; 7314//#endregion 7315//#region src/util/fast_maths.ts 7316function fastInvertTransformMat4(dst, src) { 7317 const sXYSqInv = 1 / (src[0] * src[0] + src[1] * src[1] + src[2] * src[2]); 7318 const sZSqInv = 1 / (src[8] * src[8] + src[9] * src[9] + src[10] * src[10]); 7319 const s0 = src[0] * sXYSqInv; 7320 const s4 = src[4] * sXYSqInv; 7321 const s8 = src[8] * sZSqInv; 7322 const s1 = src[1] * sXYSqInv; 7323 const s5 = src[5] * sXYSqInv; 7324 const s9 = src[9] * sZSqInv; 7325 const s2 = src[2] * sXYSqInv; 7326 const s6 = src[6] * sXYSqInv; 7327 const s10 = src[10] * sZSqInv; 7328 dst[0] = s0; 7329 dst[1] = s4; 7330 dst[2] = s8; 7331 dst[4] = s1; 7332 dst[5] = s5; 7333 dst[6] = s9; 7334 dst[8] = s2; 7335 dst[9] = s6; 7336 dst[10] = s10; 7337 const t0 = src[12]; 7338 const t1 = src[13]; 7339 const t2 = src[14]; 7340 dst[12] = -s0 * t0 - s1 * t1 - s2 * t2; 7341 dst[13] = -s4 * t0 - s5 * t1 - s6 * t2; 7342 dst[14] = -s8 * t0 - s9 * t1 - s10 * t2; 7343 dst[3] = 0; 7344 dst[7] = 0; 7345 dst[11] = 0; 7346 dst[15] = 1; 7347 return dst; 7348} 7349function fastInvertProjMat4(dst, src) { 7350 dst[0] = 1 / src[0]; 7351 dst[1] = 0; 7352 dst[2] = 0; 7353 dst[3] = 0; 7354 dst[4] = 0; 7355 dst[5] = 1 / src[5]; 7356 dst[6] = 0; 7357 dst[7] = 0; 7358 dst[8] = 0; 7359 dst[9] = 0; 7360 dst[10] = 0; 7361 dst[11] = 1 / src[14]; 7362 dst[12] = 0; 7363 dst[13] = 0; 7364 dst[14] = -1; 7365 dst[15] = src[10] / src[14]; 7366 return dst; 7367} 7368function fastInvertSkewMat4(dst, src) { 7369 const invDetXY = 1 / (src[0] * src[5] - src[1] * src[4]); 7370 dst[0] = src[5] * invDetXY; 7371 dst[1] = -src[1] * invDetXY; 7372 dst[2] = 0; 7373 dst[3] = 0; 7374 dst[4] = -src[4] * invDetXY; 7375 dst[5] = src[0] * invDetXY; 7376 dst[6] = 0; 7377 dst[7] = 0; 7378 dst[8] = 0; 7379 dst[9] = 0; 7380 dst[10] = 1 / src[10]; 7381 dst[11] = 0; 7382 dst[12] = 0; 7383 dst[13] = 0; 7384 dst[14] = 0; 7385 dst[15] = 1 / src[15]; 7386 return dst; 7387} 7388//#endregion 7389//#region src/symbol/projection.ts 7390/** 7391* Pre-allocate objects to avoid online allocation 7392*/ 7393const tmpMat4 = create$1(); 7394function getPitchedLabelPlaneMatrix(rotateWithMap, transform, pixelsToTileUnits) { 7395 const m = create$1(); 7396 if (!rotateWithMap) { 7397 const { vecSouth, vecEast } = getTileSkewVectors(transform); 7398 const skew = create$2(); 7399 skew[0] = vecEast[0]; 7400 skew[1] = vecEast[1]; 7401 skew[2] = vecSouth[0]; 7402 skew[3] = vecSouth[1]; 7403 invert(skew, skew); 7404 m[0] = skew[0]; 7405 m[1] = skew[1]; 7406 m[4] = skew[2]; 7407 m[5] = skew[3]; 7408 } 7409 scale(m, m, [ 7410 1 / pixelsToTileUnits, 7411 1 / pixelsToTileUnits, 7412 1 7413 ]); 7414 return m; 7415} 7416function getGlCoordMatrix(pitchWithMap, rotateWithMap, transform, pixelsToTileUnits) { 7417 if (pitchWithMap) { 7418 const m = create$1(); 7419 if (!rotateWithMap) { 7420 const { vecSouth, vecEast } = getTileSkewVectors(transform); 7421 m[0] = vecEast[0]; 7422 m[1] = vecEast[1]; 7423 m[4] = vecSouth[0]; 7424 m[5] = vecSouth[1]; 7425 } 7426 scale(m, m, [ 7427 pixelsToTileUnits, 7428 pixelsToTileUnits, 7429 1 7430 ]); 7431 return m; 7432 } else return transform.pixelsToClipSpaceMatrix; 7433} 7434function getTileSkewVectors(transform) { 7435 const cosRoll = Math.cos(transform.rollInRadians); 7436 const sinRoll = Math.sin(transform.rollInRadians); 7437 const cosPitch = Math.cos(transform.pitchInRadians); 7438 const cosBearing = Math.cos(transform.bearingInRadians); 7439 const sinBearing = Math.sin(transform.bearingInRadians); 7440 const vecSouth = create$3(); 7441 vecSouth[0] = -cosBearing * cosPitch * sinRoll - sinBearing * cosRoll; 7442 vecSouth[1] = -sinBearing * cosPitch * sinRoll + cosBearing * cosRoll; 7443 const vecSouthLen = length$1(vecSouth); 7444 if (vecSouthLen < 1e-9) zero(vecSouth); 7445 else scale$3(vecSouth, vecSouth, 1 / vecSouthLen); 7446 const vecEast = create$3(); 7447 vecEast[0] = cosBearing * cosPitch * cosRoll - sinBearing * sinRoll; 7448 vecEast[1] = sinBearing * cosPitch * cosRoll + cosBearing * sinRoll; 7449 const vecEastLen = length$1(vecEast); 7450 if (vecEastLen < 1e-9) zero(vecEast); 7451 else scale$3(vecEast, vecEast, 1 / vecEastLen); 7452 return { 7453 vecEast, 7454 vecSouth 7455 }; 7456} 7457/** 7458* Resolves the elevation of the symbol described by `projectionContext` at a tile coordinate. 7459*/ 7460function elevationAt(projectionContext, x, y) { 7461 return getSymbolElevation(projectionContext.getElevation, x, y, projectionContext.heightOffset ?? 0, projectionContext.heightAnchorGround ?? true); 7462} 7463/** 7464* Projects a point using a specified matrix, including the perspective divide. 7465* Uses a fast path if `elevation` is undefined. 7466*/ 7467function projectWithMatrix(x, y, matrix, elevation) { 7468 let pos; 7469 if (elevation != null) { 7470 pos = [ 7471 x, 7472 y, 7473 elevation, 7474 1 7475 ]; 7476 transformMat4(pos, pos, matrix); 7477 } else { 7478 pos = [ 7479 x, 7480 y, 7481 0, 7482 1 7483 ]; 7484 xyTransformMat4(pos, pos, matrix); 7485 } 7486 const w = pos[3]; 7487 return { 7488 point: new Point(pos[0] / w, pos[1] / w), 7489 signedDistanceFromCamera: w, 7490 isOccluded: false 7491 }; 7492} 7493function getPerspectiveRatio(cameraToCenterDistance, signedDistanceFromCamera) { 7494 return .5 + .5 * (cameraToCenterDistance / signedDistanceFromCamera); 7495} 7496function isVisible(p, clippingBuffer) { 7497 return p.x >= -clippingBuffer[0] && p.x <= clippingBuffer[0] && p.y >= -clippingBuffer[1] && p.y <= clippingBuffer[1]; 7498} 7499function updateLineLabels(bucket, painter, isText, pitchedLabelPlaneMatrix, pitchedLabelPlaneMatrixInverse, pitchWithMap, keepUpright, rotateToLine, unwrappedTileID, viewportWidth, viewportHeight, translation, getElevation) { 7500 const sizeData = isText ? bucket.textSizeData : bucket.iconSizeData; 7501 const partiallyEvaluatedSize = evaluateSizeForZoom(sizeData, painter.transform.zoom); 7502 const clippingBuffer = [256 / painter.width * 2 + 1, 256 / painter.height * 2 + 1]; 7503 const dynamicLayoutVertexArray = isText ? bucket.text.dynamicLayoutVertexArray : bucket.icon.dynamicLayoutVertexArray; 7504 dynamicLayoutVertexArray.clear(); 7505 const lineVertexArray = bucket.lineVertexArray; 7506 const placedSymbols = isText ? bucket.text.placedSymbolArray : bucket.icon.placedSymbolArray; 7507 const aspectRatio = painter.transform.width / painter.transform.height; 7508 let useVertical = false; 7509 for (let s = 0; s < placedSymbols.length; s++) { 7510 const symbol = placedSymbols.get(s); 7511 if (symbol.hidden || symbol.writingMode === 2 && !useVertical) { 7512 hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray); 7513 continue; 7514 } 7515 useVertical = false; 7516 const tileAnchorPoint = new Point(symbol.anchorX, symbol.anchorY); 7517 const projectionContext = { 7518 getElevation, 7519 pitchedLabelPlaneMatrix, 7520 lineVertexArray, 7521 pitchWithMap, 7522 projectionCache: { 7523 projections: {}, 7524 offsets: {}, 7525 cachedAnchorPoint: void 0, 7526 anyProjectionOccluded: false 7527 }, 7528 transform: painter.transform, 7529 tileAnchorPoint, 7530 unwrappedTileID, 7531 width: viewportWidth, 7532 height: viewportHeight, 7533 translation 7534 }; 7535 const anchorPos = projectTileCoordinatesToClipSpace(symbol.anchorX, symbol.anchorY, projectionContext); 7536 if (!isVisible(anchorPos.point, clippingBuffer)) { 7537 hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray); 7538 continue; 7539 } 7540 const cameraToAnchorDistance = anchorPos.signedDistanceFromCamera; 7541 const perspectiveRatio = getPerspectiveRatio(painter.transform.cameraToCenterDistance, cameraToAnchorDistance); 7542 const fontSize = evaluateSizeForFeature(sizeData, partiallyEvaluatedSize, symbol); 7543 const pitchScaledFontSize = pitchWithMap ? fontSize * painter.transform.getPitchedTextCorrection(symbol.anchorX, symbol.anchorY, unwrappedTileID) / perspectiveRatio : fontSize * perspectiveRatio; 7544 const placeUnflipped = placeGlyphsAlongLine({ 7545 projectionContext, 7546 pitchedLabelPlaneMatrixInverse, 7547 symbol, 7548 fontSize: pitchScaledFontSize, 7549 flip: false, 7550 keepUpright, 7551 glyphOffsetArray: bucket.glyphOffsetArray, 7552 dynamicLayoutVertexArray, 7553 aspectRatio, 7554 rotateToLine 7555 }); 7556 useVertical = placeUnflipped.useVertical; 7557 if (placeUnflipped.notEnoughRoom || useVertical || placeUnflipped.needsFlipping && placeGlyphsAlongLine({ 7558 projectionContext, 7559 pitchedLabelPlaneMatrixInverse, 7560 symbol, 7561 fontSize: pitchScaledFontSize, 7562 flip: true, 7563 keepUpright, 7564 glyphOffsetArray: bucket.glyphOffsetArray, 7565 dynamicLayoutVertexArray, 7566 aspectRatio, 7567 rotateToLine 7568 }).notEnoughRoom) hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray); 7569 } 7570 if (isText) bucket.text.dynamicLayoutVertexBuffer.updateData(dynamicLayoutVertexArray); 7571 else bucket.icon.dynamicLayoutVertexBuffer.updateData(dynamicLayoutVertexArray); 7572} 7573function placeFirstAndLastGlyph(fontScale, glyphOffsetArray, lineOffsetX, lineOffsetY, flip, symbol, rotateToLine, projectionContext) { 7574 const glyphEndIndex = symbol.glyphStartIndex + symbol.numGlyphs; 7575 const lineStartIndex = symbol.lineStartIndex; 7576 const lineEndIndex = symbol.lineStartIndex + symbol.lineLength; 7577 const firstGlyphOffset = glyphOffsetArray.getoffsetX(symbol.glyphStartIndex); 7578 const lastGlyphOffset = glyphOffsetArray.getoffsetX(glyphEndIndex - 1); 7579 const firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, symbol.segment, lineStartIndex, lineEndIndex, projectionContext, rotateToLine); 7580 if (!firstPlacedGlyph) return null; 7581 const lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, symbol.segment, lineStartIndex, lineEndIndex, projectionContext, rotateToLine); 7582 if (!lastPlacedGlyph) return null; 7583 if (projectionContext.projectionCache.anyProjectionOccluded) return null; 7584 return { 7585 first: firstPlacedGlyph, 7586 last: lastPlacedGlyph 7587 }; 7588} 7589function requiresOrientationChange(writingMode, firstPoint, lastPoint, aspectRatio) { 7590 if (writingMode === 1) { 7591 if (Math.abs(lastPoint.y - firstPoint.y) > Math.abs(lastPoint.x - firstPoint.x) * aspectRatio) return { useVertical: true }; 7592 } 7593 if (writingMode === 2 ? firstPoint.y < lastPoint.y : firstPoint.x > lastPoint.x) return { needsFlipping: true }; 7594 return null; 7595} 7596function placeGlyphsAlongLine(args) { 7597 const { projectionContext, pitchedLabelPlaneMatrixInverse, symbol, fontSize, flip, keepUpright, glyphOffsetArray, dynamicLayoutVertexArray, aspectRatio, rotateToLine } = args; 7598 const fontScale = fontSize / 24; 7599 const lineOffsetX = symbol.lineOffsetX * fontScale; 7600 const lineOffsetY = symbol.lineOffsetY * fontScale; 7601 let placedGlyphs; 7602 if (symbol.numGlyphs > 1) { 7603 const glyphEndIndex = symbol.glyphStartIndex + symbol.numGlyphs; 7604 const lineStartIndex = symbol.lineStartIndex; 7605 const lineEndIndex = symbol.lineStartIndex + symbol.lineLength; 7606 const firstAndLastGlyph = placeFirstAndLastGlyph(fontScale, glyphOffsetArray, lineOffsetX, lineOffsetY, flip, symbol, rotateToLine, projectionContext); 7607 if (!firstAndLastGlyph) return { notEnoughRoom: true }; 7608 const firstPoint = projectFromLabelPlaneToClipSpace(firstAndLastGlyph.first.point.x, firstAndLastGlyph.first.point.y, projectionContext, pitchedLabelPlaneMatrixInverse); 7609 const lastPoint = projectFromLabelPlaneToClipSpace(firstAndLastGlyph.last.point.x, firstAndLastGlyph.last.point.y, projectionContext, pitchedLabelPlaneMatrixInverse); 7610 if (keepUpright && !flip) { 7611 const orientationChange = requiresOrientationChange(symbol.writingMode, firstPoint, lastPoint, aspectRatio); 7612 if (orientationChange) return orientationChange; 7613 } 7614 placedGlyphs = [firstAndLastGlyph.first]; 7615 for (let glyphIndex = symbol.glyphStartIndex + 1; glyphIndex < glyphEndIndex - 1; glyphIndex++) { 7616 const placedGlyph = placeGlyphAlongLine(fontScale * glyphOffsetArray.getoffsetX(glyphIndex), lineOffsetX, lineOffsetY, flip, symbol.segment, lineStartIndex, lineEndIndex, projectionContext, rotateToLine); 7617 if (!placedGlyph) return { notEnoughRoom: true }; 7618 placedGlyphs.push(placedGlyph); 7619 } 7620 placedGlyphs.push(firstAndLastGlyph.last); 7621 } else { 7622 if (keepUpright && !flip) { 7623 const a = projectTileCoordinatesToLabelPlane(projectionContext.tileAnchorPoint.x, projectionContext.tileAnchorPoint.y, projectionContext).point; 7624 const tileVertexIndex = symbol.lineStartIndex + symbol.segment + 1; 7625 const tileSegmentEnd = new Point(projectionContext.lineVertexArray.getx(tileVertexIndex), projectionContext.lineVertexArray.gety(tileVertexIndex)); 7626 const projectedVertex = projectTileCoordinatesToLabelPlane(tileSegmentEnd.x, tileSegmentEnd.y, projectionContext); 7627 const b = projectedVertex.signedDistanceFromCamera > 0 ? projectedVertex.point : projectTruncatedLineSegmentToLabelPlane(projectionContext.tileAnchorPoint, tileSegmentEnd, a, 1, projectionContext); 7628 const clipSpaceA = projectFromLabelPlaneToClipSpace(a.x, a.y, projectionContext, pitchedLabelPlaneMatrixInverse); 7629 const clipSpaceB = projectFromLabelPlaneToClipSpace(b.x, b.y, projectionContext, pitchedLabelPlaneMatrixInverse); 7630 const orientationChange = requiresOrientationChange(symbol.writingMode, clipSpaceA, clipSpaceB, aspectRatio); 7631 if (orientationChange) return orientationChange; 7632 } 7633 const singleGlyph = placeGlyphAlongLine(fontScale * glyphOffsetArray.getoffsetX(symbol.glyphStartIndex), lineOffsetX, lineOffsetY, flip, symbol.segment, symbol.lineStartIndex, symbol.lineStartIndex + symbol.lineLength, projectionContext, rotateToLine); 7634 if (!singleGlyph || projectionContext.projectionCache.anyProjectionOccluded) return { notEnoughRoom: true }; 7635 placedGlyphs = [singleGlyph]; 7636 } 7637 for (const glyph of placedGlyphs) addDynamicAttributes(dynamicLayoutVertexArray, glyph.point, glyph.angle); 7638 return {}; 7639} 7640/** 7641* Takes a line and direction from `previousTilePoint` to `currentTilePoint`, projects it to the correct label plane, 7642* and returns a projected point along this projected line that is `minimumLength` distance away from `previousProjectedPoint`. 7643* Projects a "virtual" vertex along a line segment. 7644* @param previousTilePoint - Line start point, in tile coordinates. 7645* @param currentTilePoint - Line end point, in tile coordinates. 7646* @param previousProjectedPoint - Projection of `previousTilePoint` into label plane 7647* @param minimumLength - Distance in the projected space along the line for the returned point. 7648* @param projectionContext - Projection context, used to get terrain's `getElevation`, and to project the points to screen pixels. 7649*/ 7650function projectTruncatedLineSegmentToLabelPlane(previousTilePoint, currentTilePoint, previousProjectedPoint, minimumLength, projectionContext) { 7651 const unitVertexToBeProjected = previousTilePoint.add(previousTilePoint.sub(currentTilePoint)._unit()); 7652 const projectedUnitVertex = projectTileCoordinatesToLabelPlane(unitVertexToBeProjected.x, unitVertexToBeProjected.y, projectionContext).point; 7653 const projectedUnitSegment = previousProjectedPoint.sub(projectedUnitVertex); 7654 return previousProjectedPoint.add(projectedUnitSegment._mult(minimumLength / projectedUnitSegment.mag())); 7655} 7656/** 7657* Transform a vertex from tile coordinates to label plane coordinates 7658* @param index - index of vertex to project 7659* @param projectionContext - necessary data to project a vertex 7660* @returns the vertex projected to the label plane 7661*/ 7662function projectLineVertexToLabelPlane(index, projectionContext, syntheticVertexArgs) { 7663 const cache = projectionContext.projectionCache; 7664 if (cache.projections[index]) return cache.projections[index]; 7665 const currentVertex = new Point(projectionContext.lineVertexArray.getx(index), projectionContext.lineVertexArray.gety(index)); 7666 const projection = projectTileCoordinatesToLabelPlane(currentVertex.x, currentVertex.y, projectionContext); 7667 if (projection.signedDistanceFromCamera > 0) { 7668 cache.projections[index] = projection.point; 7669 cache.anyProjectionOccluded ||= projection.isOccluded; 7670 return projection.point; 7671 } 7672 const previousLineVertexIndex = index - syntheticVertexArgs.direction; 7673 const previousTilePoint = syntheticVertexArgs.distanceFromAnchor === 0 ? projectionContext.tileAnchorPoint : new Point(projectionContext.lineVertexArray.getx(previousLineVertexIndex), projectionContext.lineVertexArray.gety(previousLineVertexIndex)); 7674 const minimumLength = syntheticVertexArgs.absOffsetX - syntheticVertexArgs.distanceFromAnchor + 1; 7675 return projectTruncatedLineSegmentToLabelPlane(previousTilePoint, currentVertex, syntheticVertexArgs.previousVertex, minimumLength, projectionContext); 7676} 7677/** 7678* Projects the given point in tile coordinates to the correct label plane. 7679* If pitchWithMap is true, the (rotated) map plane in pixels is used, 7680* otherwise screen pixels are used. 7681*/ 7682function projectTileCoordinatesToLabelPlane(x, y, projectionContext) { 7683 const translatedX = x + projectionContext.translation[0]; 7684 const translatedY = y + projectionContext.translation[1]; 7685 let projection; 7686 if (projectionContext.pitchWithMap) { 7687 projection = projectWithMatrix(translatedX, translatedY, projectionContext.pitchedLabelPlaneMatrix, elevationAt(projectionContext, translatedX, translatedY)); 7688 projection.isOccluded = false; 7689 } else { 7690 projection = projectionContext.transform.projectTileCoordinates(translatedX, translatedY, projectionContext.unwrappedTileID, elevationAt(projectionContext, translatedX, translatedY)); 7691 projection.point.x = (projection.point.x * .5 + .5) * projectionContext.width; 7692 projection.point.y = (-projection.point.y * .5 + .5) * projectionContext.height; 7693 } 7694 return projection; 7695} 7696function projectFromLabelPlaneToClipSpace(x, y, projectionContext, pitchedLabelPlaneMatrixInverse) { 7697 if (projectionContext.pitchWithMap) { 7698 const pos = [ 7699 x, 7700 y, 7701 0, 7702 1 7703 ]; 7704 transformMat4(pos, pos, pitchedLabelPlaneMatrixInverse); 7705 const tileX = pos[0] / pos[3]; 7706 const tileY = pos[1] / pos[3]; 7707 return projectionContext.transform.projectTileCoordinates(tileX, tileY, projectionContext.unwrappedTileID, elevationAt(projectionContext, tileX, tileY)).point; 7708 } else return { 7709 x: x / projectionContext.width * 2 - 1, 7710 y: 1 - y / projectionContext.height * 2 7711 }; 7712} 7713/** 7714* Projects the given point in tile coordinates to the GL clip space (-1..1). 7715*/ 7716function projectTileCoordinatesToClipSpace(x, y, projectionContext) { 7717 return projectionContext.transform.projectTileCoordinates(x, y, projectionContext.unwrappedTileID, elevationAt(projectionContext, x, y)); 7718} 7719/** 7720* Calculate the normal vector for a line segment 7721* @param segmentVector - will be mutated as a tiny optimization 7722* @param offset - magnitude of resulting vector 7723* @param direction - direction of line traversal 7724* @returns a normal vector from the segment, with magnitude equal to offset amount 7725*/ 7726function transformToOffsetNormal(segmentVector, offset, direction) { 7727 return segmentVector._unit()._perp()._mult(offset * direction); 7728} 7729/** 7730* Construct offset line segments for the current segment and the next segment, then extend/shrink 7731* the segments until they intersect. If the segments are parallel, then they will touch with no modification. 7732* 7733* @param index - Index of the current vertex 7734* @param prevToCurrentOffsetNormal - Normal vector of the line segment from the previous vertex to the current vertex 7735* @param currentVertex - Current (non-offset) vertex projected to the label plane 7736* @param lineStartIndex - Beginning index for the line this label is on 7737* @param lineEndIndex - End index for the line this label is on 7738* @param offsetPreviousVertex - The previous vertex projected to the label plane, and then offset along the previous segments normal 7739* @param lineOffsetY - Magnitude of the offset 7740* @param projectionContext - Necessary data for tile-to-label-plane projection 7741* @returns The point at which the current and next line segments intersect, once offset and extended/shrunk to their meeting point 7742*/ 7743function findOffsetIntersectionPoint(index, prevToCurrentOffsetNormal, currentVertex, lineStartIndex, lineEndIndex, offsetPreviousVertex, lineOffsetY, projectionContext, syntheticVertexArgs) { 7744 if (projectionContext.projectionCache.offsets[index]) return projectionContext.projectionCache.offsets[index]; 7745 const offsetCurrentVertex = currentVertex.add(prevToCurrentOffsetNormal); 7746 if (index + syntheticVertexArgs.direction < lineStartIndex || index + syntheticVertexArgs.direction >= lineEndIndex) { 7747 projectionContext.projectionCache.offsets[index] = offsetCurrentVertex; 7748 return offsetCurrentVertex; 7749 } 7750 const nextVertex = projectLineVertexToLabelPlane(index + syntheticVertexArgs.direction, projectionContext, syntheticVertexArgs); 7751 const currentToNextOffsetNormal = transformToOffsetNormal(nextVertex.sub(currentVertex), lineOffsetY, syntheticVertexArgs.direction); 7752 const offsetNextSegmentBegin = currentVertex.add(currentToNextOffsetNormal); 7753 const offsetNextSegmentEnd = nextVertex.add(currentToNextOffsetNormal); 7754 projectionContext.projectionCache.offsets[index] = findLineIntersection(offsetPreviousVertex, offsetCurrentVertex, offsetNextSegmentBegin, offsetNextSegmentEnd) || offsetCurrentVertex; 7755 return projectionContext.projectionCache.offsets[index]; 7756} 7757function placeGlyphAlongLine(offsetX, lineOffsetX, lineOffsetY, flip, anchorSegment, lineStartIndex, lineEndIndex, projectionContext, rotateToLine) { 7758 const combinedOffsetX = flip ? offsetX - lineOffsetX : offsetX + lineOffsetX; 7759 let direction = combinedOffsetX > 0 ? 1 : -1; 7760 let angle = 0; 7761 if (flip) { 7762 direction *= -1; 7763 angle = Math.PI; 7764 } 7765 if (direction < 0) angle += Math.PI; 7766 let currentIndex = direction > 0 ? lineStartIndex + anchorSegment : lineStartIndex + anchorSegment + 1; 7767 let anchorPoint; 7768 if (projectionContext.projectionCache.cachedAnchorPoint) anchorPoint = projectionContext.projectionCache.cachedAnchorPoint; 7769 else { 7770 anchorPoint = projectTileCoordinatesToLabelPlane(projectionContext.tileAnchorPoint.x, projectionContext.tileAnchorPoint.y, projectionContext).point; 7771 projectionContext.projectionCache.cachedAnchorPoint = anchorPoint; 7772 } 7773 let currentVertex = anchorPoint; 7774 let previousVertex = anchorPoint; 7775 let offsetIntersectionPoint; 7776 let offsetPreviousVertex; 7777 let distanceFromAnchor = 0; 7778 let currentSegmentDistance = 0; 7779 const absOffsetX = Math.abs(combinedOffsetX); 7780 const pathVertices = []; 7781 let currentLineSegment; 7782 while (distanceFromAnchor + currentSegmentDistance <= absOffsetX) { 7783 currentIndex += direction; 7784 if (currentIndex < lineStartIndex || currentIndex >= lineEndIndex) return null; 7785 distanceFromAnchor += currentSegmentDistance; 7786 previousVertex = currentVertex; 7787 offsetPreviousVertex = offsetIntersectionPoint; 7788 const syntheticVertexArgs = { 7789 absOffsetX, 7790 direction, 7791 distanceFromAnchor, 7792 previousVertex 7793 }; 7794 currentVertex = projectLineVertexToLabelPlane(currentIndex, projectionContext, syntheticVertexArgs); 7795 if (lineOffsetY === 0) { 7796 pathVertices.push(previousVertex); 7797 currentLineSegment = currentVertex.sub(previousVertex); 7798 } else { 7799 let prevToCurrentOffsetNormal; 7800 const prevToCurrent = currentVertex.sub(previousVertex); 7801 if (prevToCurrent.mag() === 0) prevToCurrentOffsetNormal = transformToOffsetNormal(projectLineVertexToLabelPlane(currentIndex + direction, projectionContext, syntheticVertexArgs).sub(currentVertex), lineOffsetY, direction); 7802 else prevToCurrentOffsetNormal = transformToOffsetNormal(prevToCurrent, lineOffsetY, direction); 7803 offsetPreviousVertex ||= previousVertex.add(prevToCurrentOffsetNormal); 7804 offsetIntersectionPoint = findOffsetIntersectionPoint(currentIndex, prevToCurrentOffsetNormal, currentVertex, lineStartIndex, lineEndIndex, offsetPreviousVertex, lineOffsetY, projectionContext, syntheticVertexArgs); 7805 pathVertices.push(offsetPreviousVertex); 7806 currentLineSegment = offsetIntersectionPoint.sub(offsetPreviousVertex); 7807 } 7808 currentSegmentDistance = currentLineSegment.mag(); 7809 } 7810 const segmentInterpolationT = (absOffsetX - distanceFromAnchor) / currentSegmentDistance; 7811 const p = currentLineSegment._mult(segmentInterpolationT)._add(offsetPreviousVertex || previousVertex); 7812 const segmentAngle = angle + Math.atan2(currentVertex.y - previousVertex.y, currentVertex.x - previousVertex.x); 7813 pathVertices.push(p); 7814 return { 7815 point: p, 7816 angle: rotateToLine ? segmentAngle : 0, 7817 path: pathVertices 7818 }; 7819} 7820const hiddenGlyphAttributes = new Float32Array([ 7821 -Infinity, 7822 -Infinity, 7823 0, 7824 -Infinity, 7825 -Infinity, 7826 0, 7827 -Infinity, 7828 -Infinity, 7829 0, 7830 -Infinity, 7831 -Infinity, 7832 0 7833]); 7834function hideGlyphs(num, dynamicLayoutVertexArray) { 7835 for (let i = 0; i < num; i++) { 7836 const offset = dynamicLayoutVertexArray.length; 7837 dynamicLayoutVertexArray.resize(offset + 4); 7838 dynamicLayoutVertexArray.float32.set(hiddenGlyphAttributes, offset * 3); 7839 } 7840} 7841function xyTransformMat4(out, a, m) { 7842 const x = a[0], y = a[1]; 7843 out[0] = m[0] * x + m[4] * y + m[12]; 7844 out[1] = m[1] * x + m[5] * y + m[13]; 7845 out[3] = m[3] * x + m[7] * y + m[15]; 7846 return out; 7847} 7848/** 7849* Takes a path of points that was previously projected using the `pitchedLabelPlaneMatrix` 7850* and projects it using the map projection's (mercator/globe...) `projectTileCoordinates` function. 7851* Returns a new array of the projected points. 7852* Does not modify the input array. 7853*/ 7854function projectPathSpecialProjection(projectedPath, projectionContext) { 7855 const inverseLabelPlaneMatrix = tmpMat4; 7856 fastInvertSkewMat4(inverseLabelPlaneMatrix, projectionContext.pitchedLabelPlaneMatrix); 7857 return projectedPath.map((p) => { 7858 const backProjected = projectWithMatrix(p.x, p.y, inverseLabelPlaneMatrix, elevationAt(projectionContext, p.x, p.y)); 7859 const projected = projectionContext.transform.projectTileCoordinates(backProjected.point.x, backProjected.point.y, projectionContext.unwrappedTileID, elevationAt(projectionContext, backProjected.point.x, backProjected.point.y)); 7860 projected.point.x = (projected.point.x * .5 + .5) * projectionContext.width; 7861 projected.point.y = (-projected.point.y * .5 + .5) * projectionContext.height; 7862 return projected; 7863 }); 7864} 7865/** 7866* Takes a path of points projected to screenspace, finds the longest continuous unoccluded segment of that path 7867* and returns it. 7868* Does not modify the input array. 7869*/ 7870function pathSlicedToLongestUnoccluded(path) { 7871 let longestUnoccludedStart = 0; 7872 let longestUnoccludedLength = 0; 7873 let currentUnoccludedStart = 0; 7874 let currentUnoccludedLength = 0; 7875 for (let i = 0; i < path.length; i++) if (path[i].isOccluded) { 7876 currentUnoccludedStart = i + 1; 7877 currentUnoccludedLength = 0; 7878 } else { 7879 currentUnoccludedLength++; 7880 if (currentUnoccludedLength > longestUnoccludedLength) { 7881 longestUnoccludedLength = currentUnoccludedLength; 7882 longestUnoccludedStart = currentUnoccludedStart; 7883 } 7884 } 7885 return path.slice(longestUnoccludedStart, longestUnoccludedStart + longestUnoccludedLength); 7886} 7887/** 7888* @internal 7889* A collision index used to prevent symbols from overlapping. It keep tracks of 7890* where previous symbols have been placed and is used to check if a new 7891* symbol overlaps with any previously added symbols. 7892* 7893* There are two steps to insertion: first placeCollisionBox/Circles checks if 7894* there's room for a symbol, then insertCollisionBox/Circles actually puts the 7895* symbol in the index. The two step process allows paired symbols to be inserted 7896* together even if they overlap. 7897*/ 7898var CollisionIndex = class { 7899 constructor(transform, grid = new GridIndex(transform.width + 200, transform.height + 200, 25), ignoredGrid = new GridIndex(transform.width + 200, transform.height + 200, 25)) { 7900 this.transform = transform; 7901 this.grid = grid; 7902 this.ignoredGrid = ignoredGrid; 7903 this.pitchFactor = Math.cos(transform.pitch * Math.PI / 180) * transform.cameraToCenterDistance; 7904 this.screenRightBoundary = transform.width + 100; 7905 this.screenBottomBoundary = transform.height + 100; 7906 this.gridRightBoundary = transform.width + 200; 7907 this.gridBottomBoundary = transform.height + 200; 7908 this.perspectiveRatioCutoff = .6; 7909 } 7910 placeCollisionBox(collisionBox, overlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, collisionGroupPredicate, getElevation, shift, simpleProjectionMatrix, heightOffset = 0, heightAnchorGround = true) { 7911 const x = collisionBox.anchorPointX + translation[0]; 7912 const y = collisionBox.anchorPointY + translation[1]; 7913 const projectedPoint = this.projectAndGetPerspectiveRatio(x, y, unwrappedTileID, getSymbolElevation(getElevation, x, y, heightOffset, heightAnchorGround), simpleProjectionMatrix); 7914 const tileToViewport = textPixelRatio * projectedPoint.perspectiveRatio; 7915 let projectedBox; 7916 if (!pitchWithMap && !rotateWithMap) { 7917 const pointX = projectedPoint.x + (shift ? shift.x * tileToViewport : 0); 7918 const pointY = projectedPoint.y + (shift ? shift.y * tileToViewport : 0); 7919 projectedBox = { 7920 allPointsOccluded: false, 7921 box: [ 7922 pointX + collisionBox.x1 * tileToViewport, 7923 pointY + collisionBox.y1 * tileToViewport, 7924 pointX + collisionBox.x2 * tileToViewport, 7925 pointY + collisionBox.y2 * tileToViewport 7926 ] 7927 }; 7928 } else projectedBox = this._projectCollisionBox(collisionBox, tileToViewport, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, projectedPoint, getElevation, shift, simpleProjectionMatrix, heightOffset, heightAnchorGround); 7929 const [tlX, tlY, brX, brY] = projectedBox.box; 7930 const occluded = pitchWithMap ? projectedBox.allPointsOccluded : projectedPoint.isOccluded; 7931 let unplaceable = occluded; 7932 unplaceable ||= projectedPoint.perspectiveRatio < this.perspectiveRatioCutoff; 7933 unplaceable ||= !this.isInsideGrid(tlX, tlY, brX, brY); 7934 if (unplaceable || overlapMode !== "always" && this.grid.hitTest(tlX, tlY, brX, brY, overlapMode, collisionGroupPredicate)) return { 7935 box: [ 7936 tlX, 7937 tlY, 7938 brX, 7939 brY 7940 ], 7941 placeable: false, 7942 offscreen: false, 7943 occluded 7944 }; 7945 return { 7946 box: [ 7947 tlX, 7948 tlY, 7949 brX, 7950 brY 7951 ], 7952 placeable: true, 7953 offscreen: this.isOffscreen(tlX, tlY, brX, brY), 7954 occluded 7955 }; 7956 } 7957 placeCollisionCircles(overlapMode, symbol, lineVertexArray, glyphOffsetArray, fontSize, unwrappedTileID, pitchedLabelPlaneMatrix, showCollisionCircles, pitchWithMap, collisionGroupPredicate, circlePixelDiameter, textPixelPadding, translation, getElevation) { 7958 const placedCollisionCircles = []; 7959 const tileUnitAnchorPoint = new Point(symbol.anchorX, symbol.anchorY); 7960 const perspectiveRatio = this.getPerspectiveRatio(tileUnitAnchorPoint.x, tileUnitAnchorPoint.y, unwrappedTileID, getElevation?.(tileUnitAnchorPoint.x, tileUnitAnchorPoint.y)); 7961 const labelPlaneFontScale = (pitchWithMap ? fontSize * this.transform.getPitchedTextCorrection(symbol.anchorX, symbol.anchorY, unwrappedTileID) / perspectiveRatio : fontSize * perspectiveRatio) / 24; 7962 const projectionCache = { 7963 projections: {}, 7964 offsets: {}, 7965 cachedAnchorPoint: void 0, 7966 anyProjectionOccluded: false 7967 }; 7968 const lineOffsetX = symbol.lineOffsetX * labelPlaneFontScale; 7969 const lineOffsetY = symbol.lineOffsetY * labelPlaneFontScale; 7970 const projectionContext = { 7971 getElevation, 7972 pitchedLabelPlaneMatrix, 7973 lineVertexArray, 7974 pitchWithMap, 7975 projectionCache, 7976 transform: this.transform, 7977 tileAnchorPoint: tileUnitAnchorPoint, 7978 unwrappedTileID, 7979 width: this.transform.width, 7980 height: this.transform.height, 7981 translation 7982 }; 7983 const firstAndLastGlyph = placeFirstAndLastGlyph(labelPlaneFontScale, glyphOffsetArray, lineOffsetX, lineOffsetY, false, symbol, false, projectionContext); 7984 let collisionDetected = false; 7985 let inGrid = false; 7986 let entirelyOffscreen = true; 7987 if (firstAndLastGlyph) { 7988 const radius = circlePixelDiameter * .5 * perspectiveRatio + textPixelPadding; 7989 const screenPlaneMin = new Point(-100, -100); 7990 const screenPlaneMax = new Point(this.screenRightBoundary, this.screenBottomBoundary); 7991 const interpolator = new PathInterpolator(); 7992 const first = firstAndLastGlyph.first; 7993 const last = firstAndLastGlyph.last; 7994 let projectedPath = []; 7995 for (let i = first.path.length - 1; i >= 1; i--) projectedPath.push(first.path[i]); 7996 for (let i = 1; i < last.path.length; i++) projectedPath.push(last.path[i]); 7997 const circleDist = radius * 2.5; 7998 if (pitchWithMap) { 7999 const screenSpacePath = this.projectPathToScreenSpace(projectedPath, projectionContext); 8000 if (screenSpacePath.some((point) => point.signedDistanceFromCamera <= 0)) projectedPath = []; 8001 else projectedPath = screenSpacePath.map((p) => p.point); 8002 } 8003 let segments = []; 8004 if (projectedPath.length > 0) { 8005 const minPoint = projectedPath[0].clone(); 8006 const maxPoint = projectedPath[0].clone(); 8007 for (let i = 1; i < projectedPath.length; i++) { 8008 minPoint.x = Math.min(minPoint.x, projectedPath[i].x); 8009 minPoint.y = Math.min(minPoint.y, projectedPath[i].y); 8010 maxPoint.x = Math.max(maxPoint.x, projectedPath[i].x); 8011 maxPoint.y = Math.max(maxPoint.y, projectedPath[i].y); 8012 } 8013 if (minPoint.x >= screenPlaneMin.x && maxPoint.x <= screenPlaneMax.x && minPoint.y >= screenPlaneMin.y && maxPoint.y <= screenPlaneMax.y) segments = [projectedPath]; 8014 else if (maxPoint.x < screenPlaneMin.x || minPoint.x > screenPlaneMax.x || maxPoint.y < screenPlaneMin.y || minPoint.y > screenPlaneMax.y) segments = []; 8015 else segments = clipLine([projectedPath], screenPlaneMin.x, screenPlaneMin.y, screenPlaneMax.x, screenPlaneMax.y); 8016 } 8017 for (const seg of segments) { 8018 interpolator.reset(seg, radius * .25); 8019 let numCircles = 0; 8020 if (interpolator.length <= .5 * radius) numCircles = 1; 8021 else numCircles = Math.ceil(interpolator.paddedLength / circleDist) + 1; 8022 for (let i = 0; i < numCircles; i++) { 8023 const t = i / Math.max(numCircles - 1, 1); 8024 const circlePosition = interpolator.lerp(t); 8025 const centerX = circlePosition.x + 100; 8026 const centerY = circlePosition.y + 100; 8027 placedCollisionCircles.push(centerX, centerY, radius, 0); 8028 const x1 = centerX - radius; 8029 const y1 = centerY - radius; 8030 const x2 = centerX + radius; 8031 const y2 = centerY + radius; 8032 entirelyOffscreen &&= this.isOffscreen(x1, y1, x2, y2); 8033 inGrid ||= this.isInsideGrid(x1, y1, x2, y2); 8034 if (overlapMode !== "always" && this.grid.hitTestCircle(centerX, centerY, radius, overlapMode, collisionGroupPredicate)) { 8035 collisionDetected = true; 8036 if (!showCollisionCircles) return { 8037 circles: [], 8038 offscreen: false, 8039 collisionDetected 8040 }; 8041 } 8042 } 8043 } 8044 } 8045 return { 8046 circles: !showCollisionCircles && collisionDetected || !inGrid || perspectiveRatio < this.perspectiveRatioCutoff ? [] : placedCollisionCircles, 8047 offscreen: entirelyOffscreen, 8048 collisionDetected 8049 }; 8050 } 8051 projectPathToScreenSpace(projectedPath, projectionContext) { 8052 return pathSlicedToLongestUnoccluded(projectPathSpecialProjection(projectedPath, projectionContext)); 8053 } 8054 /** 8055 * Because the geometries in the CollisionIndex are an approximation of the shape of 8056 * symbols on the map, we use the CollisionIndex to look up the symbol part of 8057 * `queryRenderedFeatures`. 8058 */ 8059 queryRenderedSymbols(viewportQueryGeometry) { 8060 if (viewportQueryGeometry.length === 0 || this.grid.keysLength() === 0 && this.ignoredGrid.keysLength() === 0) return {}; 8061 const query = []; 8062 const bounds = new Bounds(); 8063 for (const point of viewportQueryGeometry) { 8064 const gridPoint = new Point(point.x + 100, point.y + 100); 8065 bounds.extend(gridPoint); 8066 query.push(gridPoint); 8067 } 8068 const { minX, minY, maxX, maxY } = bounds; 8069 const features = this.grid.query(minX, minY, maxX, maxY).concat(this.ignoredGrid.query(minX, minY, maxX, maxY)); 8070 const seenFeatures = {}; 8071 const result = {}; 8072 for (const feature of features) { 8073 const featureKey = feature.key; 8074 if (seenFeatures[featureKey.bucketInstanceId] === void 0) seenFeatures[featureKey.bucketInstanceId] = {}; 8075 if (seenFeatures[featureKey.bucketInstanceId][featureKey.featureIndex]) continue; 8076 const bbox = [ 8077 new Point(feature.x1, feature.y1), 8078 new Point(feature.x2, feature.y1), 8079 new Point(feature.x2, feature.y2), 8080 new Point(feature.x1, feature.y2) 8081 ]; 8082 if (!polygonIntersectsPolygon(query, bbox)) continue; 8083 seenFeatures[featureKey.bucketInstanceId][featureKey.featureIndex] = true; 8084 if (result[featureKey.bucketInstanceId] === void 0) result[featureKey.bucketInstanceId] = []; 8085 result[featureKey.bucketInstanceId].push(featureKey.featureIndex); 8086 } 8087 return result; 8088 } 8089 insertCollisionBox(collisionBox, overlapMode, ignorePlacement, bucketInstanceId, featureIndex, collisionGroupID) { 8090 const grid = ignorePlacement ? this.ignoredGrid : this.grid; 8091 const key = { 8092 bucketInstanceId, 8093 featureIndex, 8094 collisionGroupID, 8095 overlapMode 8096 }; 8097 grid.insert(key, collisionBox[0], collisionBox[1], collisionBox[2], collisionBox[3]); 8098 } 8099 insertCollisionCircles(collisionCircles, overlapMode, ignorePlacement, bucketInstanceId, featureIndex, collisionGroupID) { 8100 const grid = ignorePlacement ? this.ignoredGrid : this.grid; 8101 const key = { 8102 bucketInstanceId, 8103 featureIndex, 8104 collisionGroupID, 8105 overlapMode 8106 }; 8107 for (let k = 0; k < collisionCircles.length; k += 4) grid.insertCircle(key, collisionCircles[k], collisionCircles[k + 1], collisionCircles[k + 2]); 8108 } 8109 projectAndGetPerspectiveRatio(x, y, unwrappedTileID, elevation, simpleProjectionMatrix) { 8110 if (simpleProjectionMatrix) { 8111 let pos; 8112 if (elevation != null) { 8113 pos = [ 8114 x, 8115 y, 8116 elevation, 8117 1 8118 ]; 8119 transformMat4(pos, pos, simpleProjectionMatrix); 8120 } else { 8121 pos = [ 8122 x, 8123 y, 8124 0, 8125 1 8126 ]; 8127 xyTransformMat4(pos, pos, simpleProjectionMatrix); 8128 } 8129 const w = pos[3]; 8130 return { 8131 x: (pos[0] / w + 1) / 2 * this.transform.width + 100, 8132 y: (-pos[1] / w + 1) / 2 * this.transform.height + 100, 8133 perspectiveRatio: .5 + .5 * (this.transform.cameraToCenterDistance / w), 8134 isOccluded: false, 8135 signedDistanceFromCamera: w 8136 }; 8137 } else { 8138 const projected = this.transform.projectTileCoordinates(x, y, unwrappedTileID, elevation); 8139 return { 8140 x: (projected.point.x + 1) / 2 * this.transform.width + 100, 8141 y: (-projected.point.y + 1) / 2 * this.transform.height + 100, 8142 perspectiveRatio: .5 + .5 * (this.transform.cameraToCenterDistance / projected.signedDistanceFromCamera), 8143 isOccluded: projected.isOccluded, 8144 signedDistanceFromCamera: projected.signedDistanceFromCamera 8145 }; 8146 } 8147 } 8148 getPerspectiveRatio(x, y, unwrappedTileID, elevation) { 8149 const projected = this.transform.projectTileCoordinates(x, y, unwrappedTileID, elevation); 8150 return .5 + .5 * (this.transform.cameraToCenterDistance / projected.signedDistanceFromCamera); 8151 } 8152 isOffscreen(x1, y1, x2, y2) { 8153 return x2 < 100 || x1 >= this.screenRightBoundary || y2 < 100 || y1 > this.screenBottomBoundary; 8154 } 8155 isInsideGrid(x1, y1, x2, y2) { 8156 return x2 >= 0 && x1 < this.gridRightBoundary && y2 >= 0 && y1 < this.gridBottomBoundary; 8157 } 8158 getViewportMatrix() { 8159 const m = identity([]); 8160 translate(m, m, [ 8161 -100, 8162 -100, 8163 0 8164 ]); 8165 return m; 8166 } 8167 /** 8168 * Applies all layout+paint properties of the given box in order to find as good approximation of its screen-space bounding box as possible. 8169 */ 8170 _projectCollisionBox(collisionBox, tileToViewport, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translation, projectedPoint, getElevation, shift, simpleProjectionMatrix, heightOffset = 0, heightAnchorGround = true) { 8171 let vecEastX = 1; 8172 let vecEastY = 0; 8173 let vecSouthX = 0; 8174 let vecSouthY = 1; 8175 const translatedAnchorX = collisionBox.anchorPointX + translation[0]; 8176 const translatedAnchorY = collisionBox.anchorPointY + translation[1]; 8177 if (rotateWithMap && !pitchWithMap) { 8178 const projectedEast = this.projectAndGetPerspectiveRatio(translatedAnchorX + 1, translatedAnchorY, unwrappedTileID, getSymbolElevation(getElevation, translatedAnchorX + 1, translatedAnchorY, heightOffset, heightAnchorGround), simpleProjectionMatrix); 8179 const toEastX = projectedEast.x - projectedPoint.x; 8180 const toEastY = projectedEast.y - projectedPoint.y; 8181 const angle = Math.atan(toEastY / toEastX) + (toEastX < 0 ? Math.PI : 0); 8182 const sin = Math.sin(angle); 8183 const cos = Math.cos(angle); 8184 vecEastX = cos; 8185 vecEastY = sin; 8186 vecSouthX = -sin; 8187 vecSouthY = cos; 8188 } else if (!rotateWithMap && pitchWithMap) { 8189 const skew = getTileSkewVectors(this.transform); 8190 vecEastX = skew.vecEast[0]; 8191 vecEastY = skew.vecEast[1]; 8192 vecSouthX = skew.vecSouth[0]; 8193 vecSouthY = skew.vecSouth[1]; 8194 } 8195 let basePointX = projectedPoint.x; 8196 let basePointY = projectedPoint.y; 8197 let distanceMultiplier = tileToViewport; 8198 if (pitchWithMap) { 8199 basePointX = translatedAnchorX; 8200 basePointY = translatedAnchorY; 8201 const zoomFraction = this.transform.zoom - tileID.overscaledZ; 8202 distanceMultiplier = Math.pow(2, -zoomFraction); 8203 distanceMultiplier *= this.transform.getPitchedTextCorrection(translatedAnchorX, translatedAnchorY, unwrappedTileID); 8204 if (!shift) { 8205 const distanceRatio = projectedPoint.signedDistanceFromCamera / this.transform.cameraToCenterDistance; 8206 const perspectiveRatio = clamp(.5 + .5 * distanceRatio, 0, 4); 8207 distanceMultiplier *= perspectiveRatio; 8208 } 8209 } 8210 if (shift) { 8211 basePointX += vecEastX * shift.x * distanceMultiplier + vecSouthX * shift.y * distanceMultiplier; 8212 basePointY += vecEastY * shift.x * distanceMultiplier + vecSouthY * shift.y * distanceMultiplier; 8213 } 8214 const offsetXmin = collisionBox.x1 * distanceMultiplier; 8215 const offsetXmax = collisionBox.x2 * distanceMultiplier; 8216 const offsetXhalf = (offsetXmin + offsetXmax) / 2; 8217 const offsetYmin = collisionBox.y1 * distanceMultiplier; 8218 const offsetYmax = collisionBox.y2 * distanceMultiplier; 8219 const offsetYhalf = (offsetYmin + offsetYmax) / 2; 8220 const offsetsArray = [ 8221 { 8222 offsetX: offsetXmin, 8223 offsetY: offsetYmin 8224 }, 8225 { 8226 offsetX: offsetXhalf, 8227 offsetY: offsetYmin 8228 }, 8229 { 8230 offsetX: offsetXmax, 8231 offsetY: offsetYmin 8232 }, 8233 { 8234 offsetX: offsetXmax, 8235 offsetY: offsetYhalf 8236 }, 8237 { 8238 offsetX: offsetXmax, 8239 offsetY: offsetYmax 8240 }, 8241 { 8242 offsetX: offsetXhalf, 8243 offsetY: offsetYmax 8244 }, 8245 { 8246 offsetX: offsetXmin, 8247 offsetY: offsetYmax 8248 }, 8249 { 8250 offsetX: offsetXmin, 8251 offsetY: offsetYhalf 8252 } 8253 ]; 8254 let points = []; 8255 for (const { offsetX, offsetY } of offsetsArray) points.push(new Point(basePointX + vecEastX * offsetX + vecSouthX * offsetY, basePointY + vecEastY * offsetX + vecSouthY * offsetY)); 8256 let anyPointVisible = false; 8257 if (pitchWithMap) { 8258 const projected = points.map((p) => this.projectAndGetPerspectiveRatio(p.x, p.y, unwrappedTileID, getSymbolElevation(getElevation, p.x, p.y, heightOffset, heightAnchorGround), simpleProjectionMatrix)); 8259 anyPointVisible = projected.some((p) => !p.isOccluded); 8260 points = projected.map((p) => new Point(p.x, p.y)); 8261 } else anyPointVisible = true; 8262 return { 8263 box: getAABB(points), 8264 allPointsOccluded: !anyPointVisible 8265 }; 8266 } 8267}; 8268//#endregion 8269//#region src/symbol/placement.ts 8270var OpacityState = class { 8271 constructor(prevState, increment, placed, skipFade) { 8272 if (prevState) this.opacity = Math.max(0, Math.min(1, prevState.opacity + (prevState.placed ? increment : -increment))); 8273 else this.opacity = skipFade && placed ? 1 : 0; 8274 this.placed = placed; 8275 } 8276 isHidden() { 8277 return this.opacity === 0 && !this.placed; 8278 } 8279}; 8280var JointOpacityState = class { 8281 constructor(prevState, increment, placedText, placedIcon, skipFade) { 8282 this.text = new OpacityState(prevState ? prevState.text : null, increment, placedText, skipFade); 8283 this.icon = new OpacityState(prevState ? prevState.icon : null, increment, placedIcon, skipFade); 8284 } 8285 isHidden() { 8286 return this.text.isHidden() && this.icon.isHidden(); 8287 } 8288}; 8289var JointPlacement = class { 8290 constructor(text, icon, skipFade) { 8291 this.text = text; 8292 this.icon = icon; 8293 this.skipFade = skipFade; 8294 } 8295}; 8296var RetainedQueryData = class { 8297 constructor(bucketInstanceId, featureIndex, sourceLayerIndex, bucketIndex, tileID) { 8298 this.bucketInstanceId = bucketInstanceId; 8299 this.featureIndex = featureIndex; 8300 this.sourceLayerIndex = sourceLayerIndex; 8301 this.bucketIndex = bucketIndex; 8302 this.tileID = tileID; 8303 } 8304}; 8305var CollisionGroups = class { 8306 constructor(crossSourceCollisions) { 8307 this.crossSourceCollisions = crossSourceCollisions; 8308 this.maxGroupID = 0; 8309 this.collisionGroups = {}; 8310 } 8311 get(sourceID) { 8312 if (!this.crossSourceCollisions) { 8313 if (!this.collisionGroups[sourceID]) { 8314 const nextGroupID = ++this.maxGroupID; 8315 this.collisionGroups[sourceID] = { 8316 ID: nextGroupID, 8317 predicate: (key) => { 8318 return key.collisionGroupID === nextGroupID; 8319 } 8320 }; 8321 } 8322 return this.collisionGroups[sourceID]; 8323 } else return { 8324 ID: 0, 8325 predicate: null 8326 }; 8327 } 8328}; 8329function calculateVariableLayoutShift(anchor, width, height, textOffset, textBoxScale) { 8330 const { horizontalAlign, verticalAlign } = getAnchorAlignment(anchor); 8331 const shiftX = -(horizontalAlign - .5) * width; 8332 const shiftY = -(verticalAlign - .5) * height; 8333 return new Point(shiftX + textOffset[0] * textBoxScale, shiftY + textOffset[1] * textBoxScale); 8334} 8335var Placement = class { 8336 constructor(transform, terrain, fadeDuration, crossSourceCollisions, prevPlacement) { 8337 this.transform = transform.clone(); 8338 this.terrain = terrain; 8339 this.collisionIndex = new CollisionIndex(this.transform); 8340 this.placements = {}; 8341 this.opacities = {}; 8342 this.variableOffsets = {}; 8343 this.stale = false; 8344 this.commitTime = 0; 8345 this.fadeDuration = fadeDuration; 8346 this.retainedQueryData = {}; 8347 this.collisionGroups = new CollisionGroups(crossSourceCollisions); 8348 this.collisionCircleArrays = {}; 8349 this.collisionBoxArrays = /* @__PURE__ */ new Map(); 8350 this.prevPlacement = prevPlacement; 8351 if (prevPlacement) prevPlacement.prevPlacement = void 0; 8352 this.placedOrientations = {}; 8353 } 8354 _getTerrainElevationFunc(tileID) { 8355 const terrain = this.terrain; 8356 if (!terrain) return void 0; 8357 return (x, y) => terrain.getElevation(tileID, x, y); 8358 } 8359 getBucketParts(results, styleLayer, tile, sortAcrossTiles) { 8360 const symbolBucket = tile.getBucket(styleLayer); 8361 const bucketFeatureIndex = tile.latestFeatureIndex; 8362 if (!symbolBucket || !bucketFeatureIndex || styleLayer.id !== symbolBucket.layerIds[0]) return; 8363 const collisionBoxArray = tile.collisionBoxArray; 8364 const layout = symbolBucket.layers[0].layout; 8365 const paint = symbolBucket.layers[0].paint; 8366 const scale = Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ); 8367 const textPixelRatio = tile.tileSize / EXTENT; 8368 const unwrappedTileID = tile.tileID.toUnwrapped(); 8369 const rotateWithMap = layout.get("text-rotation-alignment") === "map"; 8370 const pixelsToTiles = pixelsToTileUnits(tile, 1, this.transform.zoom); 8371 const translationText = translatePosition(this.collisionIndex.transform, tile, paint.get("text-translate"), paint.get("text-translate-anchor")); 8372 const translationIcon = translatePosition(this.collisionIndex.transform, tile, paint.get("icon-translate"), paint.get("icon-translate-anchor")); 8373 const pitchedLabelPlaneMatrix = getPitchedLabelPlaneMatrix(rotateWithMap, this.transform, pixelsToTiles); 8374 this.retainedQueryData[symbolBucket.bucketInstanceId] = new RetainedQueryData(symbolBucket.bucketInstanceId, bucketFeatureIndex, symbolBucket.sourceLayerIndex, symbolBucket.index, tile.tileID); 8375 const parameters = { 8376 bucket: symbolBucket, 8377 layout, 8378 translationText, 8379 translationIcon, 8380 unwrappedTileID, 8381 pitchedLabelPlaneMatrix, 8382 scale, 8383 textPixelRatio, 8384 holdingForFade: tile.holdingForSymbolFade(), 8385 collisionBoxArray, 8386 partiallyEvaluatedTextSize: evaluateSizeForZoom(symbolBucket.textSizeData, this.transform.zoom), 8387 collisionGroup: this.collisionGroups.get(symbolBucket.sourceID) 8388 }; 8389 if (sortAcrossTiles) for (const range of symbolBucket.sortKeyRanges) { 8390 const { sortKey, symbolInstanceStart, symbolInstanceEnd } = range; 8391 results.push({ 8392 sortKey, 8393 symbolInstanceStart, 8394 symbolInstanceEnd, 8395 parameters 8396 }); 8397 } 8398 else results.push({ 8399 symbolInstanceStart: 0, 8400 symbolInstanceEnd: symbolBucket.symbolInstances.length, 8401 parameters 8402 }); 8403 } 8404 attemptAnchorPlacement(textAnchorOffset, textBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, tileID, unwrappedTileID, collisionGroup, textOverlapMode, symbolInstance, bucket, orientation, translationText, translationIcon, iconBox, getElevation, simpleProjectionMatrix, heightOffset, heightAnchorGround) { 8405 const anchor = TextAnchorEnum[textAnchorOffset.textAnchor]; 8406 const textOffset = [textAnchorOffset.textOffset0, textAnchorOffset.textOffset1]; 8407 const shift = calculateVariableLayoutShift(anchor, width, height, textOffset, textBoxScale); 8408 const placedGlyphBoxes = this.collisionIndex.placeCollisionBox(textBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, shift, simpleProjectionMatrix, heightOffset, heightAnchorGround); 8409 if (iconBox) { 8410 if (!this.collisionIndex.placeCollisionBox(iconBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationIcon, collisionGroup.predicate, getElevation, shift, simpleProjectionMatrix, heightOffset, heightAnchorGround).placeable) return; 8411 } 8412 if (placedGlyphBoxes.placeable) { 8413 let prevAnchor; 8414 if (this.prevPlacement?.variableOffsets[symbolInstance.crossTileID] && this.prevPlacement?.placements[symbolInstance.crossTileID]?.text) prevAnchor = this.prevPlacement.variableOffsets[symbolInstance.crossTileID].anchor; 8415 if (symbolInstance.crossTileID === 0) throw new Error("symbolInstance.crossTileID can't be 0"); 8416 this.variableOffsets[symbolInstance.crossTileID] = { 8417 textOffset, 8418 width, 8419 height, 8420 anchor, 8421 textBoxScale, 8422 prevAnchor 8423 }; 8424 this.markUsedJustification(bucket, anchor, symbolInstance, orientation); 8425 if (bucket.allowVerticalPlacement) { 8426 this.markUsedOrientation(bucket, orientation, symbolInstance); 8427 this.placedOrientations[symbolInstance.crossTileID] = orientation; 8428 } 8429 return { 8430 shift, 8431 placedGlyphBoxes 8432 }; 8433 } 8434 } 8435 placeLayerBucketPart(bucketPart, seenCrossTileIDs, showCollisionBoxes) { 8436 const { bucket, layout, translationText, translationIcon, unwrappedTileID, pitchedLabelPlaneMatrix, textPixelRatio, holdingForFade, collisionBoxArray, partiallyEvaluatedTextSize, collisionGroup } = bucketPart.parameters; 8437 const textOptional = layout.get("text-optional"); 8438 const iconOptional = layout.get("icon-optional"); 8439 const textOverlapMode = getOverlapMode(layout, "text-overlap", "text-allow-overlap"); 8440 const textAlwaysOverlap = textOverlapMode === "always"; 8441 const iconOverlapMode = getOverlapMode(layout, "icon-overlap", "icon-allow-overlap"); 8442 const iconAlwaysOverlap = iconOverlapMode === "always"; 8443 const rotateWithMap = layout.get("text-rotation-alignment") === "map"; 8444 const pitchWithMap = layout.get("text-pitch-alignment") === "map"; 8445 const hasIconTextFit = layout.get("icon-text-fit") !== "none"; 8446 const zOrderByViewportY = layout.get("symbol-z-order") === "viewport-y"; 8447 const heightAnchorGround = layout.get("symbol-height-anchor") === "ground"; 8448 const alwaysShowText = textAlwaysOverlap && (iconAlwaysOverlap || !bucket.hasIconData() || iconOptional); 8449 const alwaysShowIcon = iconAlwaysOverlap && (textAlwaysOverlap || !bucket.hasTextData() || textOptional); 8450 if (!bucket.collisionArrays && collisionBoxArray) bucket.deserializeCollisionBoxes(collisionBoxArray); 8451 const tileID = this.retainedQueryData[bucket.bucketInstanceId].tileID; 8452 const getElevation = this._getTerrainElevationFunc(tileID); 8453 const simpleProjectionMatrix = this.transform.getFastPathSimpleProjectionMatrix(tileID); 8454 const placeSymbol = (symbolInstance, collisionArrays, symbolIndex) => { 8455 if (seenCrossTileIDs[symbolInstance.crossTileID]) return; 8456 if (holdingForFade) { 8457 this.placements[symbolInstance.crossTileID] = new JointPlacement(false, false, false); 8458 return; 8459 } 8460 const symbolHeightOffset = symbolInstance.heightOffset; 8461 let placeText = false; 8462 let placeIcon = false; 8463 let offscreen = true; 8464 let shift = null; 8465 let placed = { 8466 box: null, 8467 placeable: false, 8468 offscreen: null, 8469 occluded: false 8470 }; 8471 let placedVerticalText = { 8472 box: null, 8473 placeable: false, 8474 offscreen: null 8475 }; 8476 let placedGlyphBoxes = null; 8477 let placedGlyphCircles = null; 8478 let placedIconBoxes = null; 8479 let textFeatureIndex = 0; 8480 let verticalTextFeatureIndex = 0; 8481 let iconFeatureIndex = 0; 8482 if (collisionArrays.textFeatureIndex) textFeatureIndex = collisionArrays.textFeatureIndex; 8483 else if (symbolInstance.useRuntimeCollisionCircles) textFeatureIndex = symbolInstance.featureIndex; 8484 if (collisionArrays.verticalTextFeatureIndex) verticalTextFeatureIndex = collisionArrays.verticalTextFeatureIndex; 8485 const textBox = collisionArrays.textBox; 8486 if (textBox) { 8487 const updatePreviousOrientationIfNotPlaced = (isPlaced) => { 8488 let previousOrientation = 1; 8489 if (bucket.allowVerticalPlacement && !isPlaced && this.prevPlacement) { 8490 const prevPlacedOrientation = this.prevPlacement.placedOrientations[symbolInstance.crossTileID]; 8491 if (prevPlacedOrientation) { 8492 this.placedOrientations[symbolInstance.crossTileID] = prevPlacedOrientation; 8493 previousOrientation = prevPlacedOrientation; 8494 this.markUsedOrientation(bucket, previousOrientation, symbolInstance); 8495 } 8496 } 8497 return previousOrientation; 8498 }; 8499 const placeTextForPlacementModes = (placeHorizontalFn, placeVerticalFn) => { 8500 if (bucket.allowVerticalPlacement && symbolInstance.numVerticalGlyphVertices > 0 && collisionArrays.verticalTextBox) for (const placementMode of bucket.writingModes) { 8501 if (placementMode === 2) { 8502 placed = placeVerticalFn(); 8503 placedVerticalText = placed; 8504 } else placed = placeHorizontalFn(); 8505 if (placed?.placeable) break; 8506 } 8507 else placed = placeHorizontalFn(); 8508 }; 8509 const textAnchorOffsetStart = symbolInstance.textAnchorOffsetStartIndex; 8510 const textAnchorOffsetEnd = symbolInstance.textAnchorOffsetEndIndex; 8511 if (textAnchorOffsetEnd === textAnchorOffsetStart) { 8512 const placeBox = (collisionTextBox, orientation) => { 8513 const placedFeature = this.collisionIndex.placeCollisionBox(collisionTextBox, textOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, void 0, simpleProjectionMatrix, symbolHeightOffset, heightAnchorGround); 8514 if (placedFeature?.placeable) { 8515 this.markUsedOrientation(bucket, orientation, symbolInstance); 8516 this.placedOrientations[symbolInstance.crossTileID] = orientation; 8517 } 8518 return placedFeature; 8519 }; 8520 const placeHorizontal = () => { 8521 return placeBox(textBox, 1); 8522 }; 8523 const placeVertical = () => { 8524 const verticalTextBox = collisionArrays.verticalTextBox; 8525 if (bucket.allowVerticalPlacement && symbolInstance.numVerticalGlyphVertices > 0 && verticalTextBox) return placeBox(verticalTextBox, 2); 8526 return { 8527 box: null, 8528 offscreen: null 8529 }; 8530 }; 8531 placeTextForPlacementModes(placeHorizontal, placeVertical); 8532 updatePreviousOrientationIfNotPlaced(placed?.placeable); 8533 } else { 8534 let prevAnchor = TextAnchorEnum[this.prevPlacement?.variableOffsets[symbolInstance.crossTileID]?.anchor]; 8535 const placeBoxForVariableAnchors = (collisionTextBox, collisionIconBox, orientation) => { 8536 const width = collisionTextBox.x2 - collisionTextBox.x1; 8537 const height = collisionTextBox.y2 - collisionTextBox.y1; 8538 const textBoxScale = symbolInstance.textBoxScale; 8539 const variableIconBox = hasIconTextFit && iconOverlapMode === "never" ? collisionIconBox : null; 8540 let placedBox = null; 8541 let placementPasses = textOverlapMode === "never" ? 1 : 2; 8542 let overlapMode = "never"; 8543 if (prevAnchor) placementPasses++; 8544 for (let pass = 0; pass < placementPasses; pass++) { 8545 for (let i = textAnchorOffsetStart; i < textAnchorOffsetEnd; i++) { 8546 const textAnchorOffset = bucket.textAnchorOffsets.get(i); 8547 if (prevAnchor && textAnchorOffset.textAnchor !== prevAnchor) continue; 8548 const result = this.attemptAnchorPlacement(textAnchorOffset, collisionTextBox, width, height, textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, tileID, unwrappedTileID, collisionGroup, overlapMode, symbolInstance, bucket, orientation, translationText, translationIcon, variableIconBox, getElevation, simpleProjectionMatrix, symbolHeightOffset, heightAnchorGround); 8549 if (result) { 8550 placedBox = result.placedGlyphBoxes; 8551 if (placedBox?.placeable) { 8552 placeText = true; 8553 shift = result.shift; 8554 return placedBox; 8555 } 8556 } 8557 } 8558 if (prevAnchor) prevAnchor = null; 8559 else overlapMode = textOverlapMode; 8560 } 8561 if (showCollisionBoxes && !placedBox) placedBox = { 8562 box: this.collisionIndex.placeCollisionBox(textBox, "always", textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationText, collisionGroup.predicate, getElevation, void 0, simpleProjectionMatrix, symbolHeightOffset, heightAnchorGround).box, 8563 offscreen: false, 8564 placeable: false, 8565 occluded: false 8566 }; 8567 return placedBox; 8568 }; 8569 const placeHorizontal = () => { 8570 return placeBoxForVariableAnchors(textBox, collisionArrays.iconBox, 1); 8571 }; 8572 const placeVertical = () => { 8573 const verticalTextBox = collisionArrays.verticalTextBox; 8574 const wasPlaced = placed?.placeable; 8575 if (bucket.allowVerticalPlacement && !wasPlaced && symbolInstance.numVerticalGlyphVertices > 0 && verticalTextBox) return placeBoxForVariableAnchors(verticalTextBox, collisionArrays.verticalIconBox, 2); 8576 return { 8577 box: null, 8578 occluded: true, 8579 offscreen: null 8580 }; 8581 }; 8582 placeTextForPlacementModes(placeHorizontal, placeVertical); 8583 if (placed) { 8584 placeText = placed.placeable; 8585 offscreen = placed.offscreen; 8586 } 8587 const prevOrientation = updatePreviousOrientationIfNotPlaced(placed?.placeable); 8588 if (!placeText && this.prevPlacement) { 8589 const prevOffset = this.prevPlacement.variableOffsets[symbolInstance.crossTileID]; 8590 if (prevOffset) { 8591 this.variableOffsets[symbolInstance.crossTileID] = prevOffset; 8592 this.markUsedJustification(bucket, prevOffset.anchor, symbolInstance, prevOrientation); 8593 } 8594 } 8595 } 8596 } 8597 placedGlyphBoxes = placed; 8598 placeText = placedGlyphBoxes?.placeable; 8599 offscreen = placedGlyphBoxes?.offscreen; 8600 if (symbolInstance.useRuntimeCollisionCircles && symbolInstance.centerJustifiedTextSymbolIndex >= 0) { 8601 const placedSymbol = bucket.text.placedSymbolArray.get(symbolInstance.centerJustifiedTextSymbolIndex); 8602 const fontSize = evaluateSizeForFeature(bucket.textSizeData, partiallyEvaluatedTextSize, placedSymbol); 8603 const textPixelPadding = layout.get("text-padding"); 8604 const circlePixelDiameter = symbolInstance.collisionCircleDiameter; 8605 placedGlyphCircles = this.collisionIndex.placeCollisionCircles(textOverlapMode, placedSymbol, bucket.lineVertexArray, bucket.glyphOffsetArray, fontSize, unwrappedTileID, pitchedLabelPlaneMatrix, showCollisionBoxes, pitchWithMap, collisionGroup.predicate, circlePixelDiameter, textPixelPadding, translationText, getElevation); 8606 if (placedGlyphCircles.circles.length && placedGlyphCircles.collisionDetected && !showCollisionBoxes) warnOnce("Collisions detected, but collision boxes are not shown"); 8607 placeText = textAlwaysOverlap || placedGlyphCircles.circles.length > 0 && !placedGlyphCircles.collisionDetected; 8608 offscreen &&= placedGlyphCircles.offscreen; 8609 } 8610 if (collisionArrays.iconFeatureIndex) iconFeatureIndex = collisionArrays.iconFeatureIndex; 8611 if (collisionArrays.iconBox) { 8612 const placeIconFeature = (iconBox) => { 8613 return this.collisionIndex.placeCollisionBox(iconBox, iconOverlapMode, textPixelRatio, tileID, unwrappedTileID, pitchWithMap, rotateWithMap, translationIcon, collisionGroup.predicate, getElevation, hasIconTextFit && shift ? shift : void 0, simpleProjectionMatrix, symbolHeightOffset, heightAnchorGround); 8614 }; 8615 if (placedVerticalText && placedVerticalText.placeable && collisionArrays.verticalIconBox) { 8616 placedIconBoxes = placeIconFeature(collisionArrays.verticalIconBox); 8617 placeIcon = placedIconBoxes.placeable; 8618 } else { 8619 placedIconBoxes = placeIconFeature(collisionArrays.iconBox); 8620 placeIcon = placedIconBoxes.placeable; 8621 } 8622 offscreen &&= placedIconBoxes.offscreen; 8623 } 8624 const iconWithoutText = textOptional || symbolInstance.numHorizontalGlyphVertices === 0 && symbolInstance.numVerticalGlyphVertices === 0; 8625 const textWithoutIcon = iconOptional || symbolInstance.numIconVertices === 0; 8626 if (!iconWithoutText && !textWithoutIcon) placeIcon = placeText = placeIcon && placeText; 8627 else if (!textWithoutIcon) placeText = placeIcon && placeText; 8628 else if (!iconWithoutText) placeIcon &&= placeText; 8629 const hasTextBox = placeText && placedGlyphBoxes.placeable; 8630 const hasIconBox = placeIcon && placedIconBoxes.placeable; 8631 if (hasTextBox) { 8632 if (placedVerticalText && placedVerticalText.placeable && verticalTextFeatureIndex) this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, textOverlapMode, layout.get("text-ignore-placement"), bucket.bucketInstanceId, verticalTextFeatureIndex, collisionGroup.ID); 8633 else this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, textOverlapMode, layout.get("text-ignore-placement"), bucket.bucketInstanceId, textFeatureIndex, collisionGroup.ID); 8634 } 8635 if (hasIconBox) this.collisionIndex.insertCollisionBox(placedIconBoxes.box, iconOverlapMode, layout.get("icon-ignore-placement"), bucket.bucketInstanceId, iconFeatureIndex, collisionGroup.ID); 8636 if (placedGlyphCircles) { 8637 if (placeText) this.collisionIndex.insertCollisionCircles(placedGlyphCircles.circles, textOverlapMode, layout.get("text-ignore-placement"), bucket.bucketInstanceId, textFeatureIndex, collisionGroup.ID); 8638 } 8639 if (showCollisionBoxes) this.storeCollisionData(bucket.bucketInstanceId, symbolIndex, collisionArrays, placedGlyphBoxes, placedIconBoxes, placedGlyphCircles); 8640 if (symbolInstance.crossTileID === 0) throw new Error("symbolInstance.crossTileID can't be 0"); 8641 if (bucket.bucketInstanceId === 0) throw new Error("bucket.bucketInstanceId can't be 0"); 8642 const textVisible = (placeText || alwaysShowText) && !placedGlyphBoxes?.occluded; 8643 const iconVisible = (placeIcon || alwaysShowIcon) && !placedIconBoxes?.occluded; 8644 this.placements[symbolInstance.crossTileID] = new JointPlacement(textVisible, iconVisible, offscreen || bucket.justReloaded); 8645 seenCrossTileIDs[symbolInstance.crossTileID] = true; 8646 }; 8647 if (zOrderByViewportY) { 8648 if (bucketPart.symbolInstanceStart !== 0) throw new Error("bucket.bucketInstanceId should be 0"); 8649 const symbolIndexes = bucket.getSortedSymbolIndexes(-this.transform.bearingInRadians); 8650 for (let i = symbolIndexes.length - 1; i >= 0; --i) { 8651 const symbolIndex = symbolIndexes[i]; 8652 placeSymbol(bucket.symbolInstances.get(symbolIndex), bucket.collisionArrays[symbolIndex], symbolIndex); 8653 } 8654 } else for (let i = bucketPart.symbolInstanceStart; i < bucketPart.symbolInstanceEnd; i++) placeSymbol(bucket.symbolInstances.get(i), bucket.collisionArrays[i], i); 8655 bucket.justReloaded = false; 8656 } 8657 storeCollisionData(bucketInstanceId, symbolIndex, collisionArrays, placedGlyphBoxes, placedIconBoxes, placedGlyphCircles) { 8658 if (collisionArrays.textBox || collisionArrays.iconBox) { 8659 let boxArray; 8660 if (this.collisionBoxArrays.has(bucketInstanceId)) boxArray = this.collisionBoxArrays.get(bucketInstanceId); 8661 else { 8662 boxArray = /* @__PURE__ */ new Map(); 8663 this.collisionBoxArrays.set(bucketInstanceId, boxArray); 8664 } 8665 let realCollisionBox; 8666 if (boxArray.has(symbolIndex)) realCollisionBox = boxArray.get(symbolIndex); 8667 else { 8668 realCollisionBox = { 8669 text: null, 8670 icon: null 8671 }; 8672 boxArray.set(symbolIndex, realCollisionBox); 8673 } 8674 if (collisionArrays.textBox) realCollisionBox.text = placedGlyphBoxes.box; 8675 if (collisionArrays.iconBox) realCollisionBox.icon = placedIconBoxes.box; 8676 } 8677 if (placedGlyphCircles) { 8678 let circleArray = this.collisionCircleArrays[bucketInstanceId]; 8679 if (circleArray === void 0) circleArray = this.collisionCircleArrays[bucketInstanceId] = []; 8680 for (let i = 0; i < placedGlyphCircles.circles.length; i += 4) { 8681 circleArray.push(placedGlyphCircles.circles[i + 0] - 100); 8682 circleArray.push(placedGlyphCircles.circles[i + 1] - 100); 8683 circleArray.push(placedGlyphCircles.circles[i + 2]); 8684 circleArray.push(placedGlyphCircles.collisionDetected ? 1 : 0); 8685 } 8686 } 8687 } 8688 markUsedJustification(bucket, placedAnchor, symbolInstance, orientation) { 8689 const justifications = { 8690 "left": symbolInstance.leftJustifiedTextSymbolIndex, 8691 "center": symbolInstance.centerJustifiedTextSymbolIndex, 8692 "right": symbolInstance.rightJustifiedTextSymbolIndex 8693 }; 8694 let autoIndex; 8695 if (orientation === 2) autoIndex = symbolInstance.verticalPlacedTextSymbolIndex; 8696 else autoIndex = justifications[getAnchorJustification(placedAnchor)]; 8697 const indexes = [ 8698 symbolInstance.leftJustifiedTextSymbolIndex, 8699 symbolInstance.centerJustifiedTextSymbolIndex, 8700 symbolInstance.rightJustifiedTextSymbolIndex, 8701 symbolInstance.verticalPlacedTextSymbolIndex 8702 ]; 8703 for (const index of indexes) if (index >= 0) { 8704 if (autoIndex >= 0 && index !== autoIndex) bucket.text.placedSymbolArray.get(index).crossTileID = 0; 8705 else bucket.text.placedSymbolArray.get(index).crossTileID = symbolInstance.crossTileID; 8706 } 8707 } 8708 markUsedOrientation(bucket, orientation, symbolInstance) { 8709 const horizontal = orientation === 1 || orientation === 3 ? orientation : 0; 8710 const vertical = orientation === 2 ? orientation : 0; 8711 const horizontalIndexes = [ 8712 symbolInstance.leftJustifiedTextSymbolIndex, 8713 symbolInstance.centerJustifiedTextSymbolIndex, 8714 symbolInstance.rightJustifiedTextSymbolIndex 8715 ]; 8716 for (const index of horizontalIndexes) bucket.text.placedSymbolArray.get(index).placedOrientation = horizontal; 8717 if (symbolInstance.verticalPlacedTextSymbolIndex) bucket.text.placedSymbolArray.get(symbolInstance.verticalPlacedTextSymbolIndex).placedOrientation = vertical; 8718 } 8719 commit(now) { 8720 this.commitTime = now; 8721 this.zoomAtLastRecencyCheck = this.transform.zoom; 8722 const prevPlacement = this.prevPlacement; 8723 let placementChanged = false; 8724 this.prevZoomAdjustment = prevPlacement ? prevPlacement.zoomAdjustment(this.transform.zoom) : 0; 8725 const increment = prevPlacement ? prevPlacement.symbolFadeChange(now) : 1; 8726 const prevOpacities = prevPlacement ? prevPlacement.opacities : {}; 8727 const prevOffsets = prevPlacement ? prevPlacement.variableOffsets : {}; 8728 const prevOrientations = prevPlacement ? prevPlacement.placedOrientations : {}; 8729 for (const crossTileID in this.placements) { 8730 const jointPlacement = this.placements[crossTileID]; 8731 const prevOpacity = prevOpacities[crossTileID]; 8732 if (prevOpacity) { 8733 this.opacities[crossTileID] = new JointOpacityState(prevOpacity, increment, jointPlacement.text, jointPlacement.icon); 8734 placementChanged ||= jointPlacement.text !== prevOpacity.text.placed; 8735 placementChanged ||= jointPlacement.icon !== prevOpacity.icon.placed; 8736 } else { 8737 this.opacities[crossTileID] = new JointOpacityState(null, increment, jointPlacement.text, jointPlacement.icon, jointPlacement.skipFade); 8738 placementChanged ||= jointPlacement.text || jointPlacement.icon; 8739 } 8740 } 8741 for (const crossTileID in prevOpacities) { 8742 const prevOpacity = prevOpacities[crossTileID]; 8743 if (!this.opacities[crossTileID]) { 8744 const jointOpacity = new JointOpacityState(prevOpacity, increment, false, false); 8745 if (!jointOpacity.isHidden()) { 8746 this.opacities[crossTileID] = jointOpacity; 8747 placementChanged ||= prevOpacity.text.placed; 8748 placementChanged ||= prevOpacity.icon.placed; 8749 } 8750 } 8751 } 8752 for (const crossTileID in prevOffsets) if (!this.variableOffsets[crossTileID] && this.opacities[crossTileID] && !this.opacities[crossTileID].isHidden()) this.variableOffsets[crossTileID] = prevOffsets[crossTileID]; 8753 for (const crossTileID in prevOrientations) if (!this.placedOrientations[crossTileID] && this.opacities[crossTileID] && !this.opacities[crossTileID].isHidden()) this.placedOrientations[crossTileID] = prevOrientations[crossTileID]; 8754 if (prevPlacement && prevPlacement.lastPlacementChangeTime === void 0) throw new Error("Last placement time for previous placement is not defined"); 8755 if (placementChanged) this.lastPlacementChangeTime = now; 8756 else if (typeof this.lastPlacementChangeTime !== "number") this.lastPlacementChangeTime = prevPlacement ? prevPlacement.lastPlacementChangeTime : now; 8757 } 8758 updateLayerOpacities(styleLayer, tiles) { 8759 const seenCrossTileIDs = {}; 8760 for (const tile of tiles) { 8761 const symbolBucket = tile.getBucket(styleLayer); 8762 if (symbolBucket && tile.latestFeatureIndex && styleLayer.id === symbolBucket.layerIds[0]) this.updateBucketOpacities(symbolBucket, tile.tileID, seenCrossTileIDs, tile.collisionBoxArray); 8763 } 8764 } 8765 updateBucketOpacities(bucket, tileID, seenCrossTileIDs, collisionBoxArray) { 8766 if (bucket.hasTextData()) { 8767 bucket.text.opacityVertexArray.clear(); 8768 bucket.text.hasVisibleVertices = false; 8769 } 8770 if (bucket.hasIconData()) { 8771 bucket.icon.opacityVertexArray.clear(); 8772 bucket.icon.hasVisibleVertices = false; 8773 } 8774 if (bucket.hasIconCollisionBoxData()) bucket.iconCollisionBox.collisionVertexArray.clear(); 8775 if (bucket.hasTextCollisionBoxData()) bucket.textCollisionBox.collisionVertexArray.clear(); 8776 const layer = bucket.layers[0]; 8777 const layout = layer.layout; 8778 const duplicateOpacityState = new JointOpacityState(null, 0, false, false, true); 8779 const textAllowOverlap = layout.get("text-allow-overlap"); 8780 const iconAllowOverlap = layout.get("icon-allow-overlap"); 8781 const hasVariablePlacement = layer._unevaluatedLayout.hasValue("text-variable-anchor") || layer._unevaluatedLayout.hasValue("text-variable-anchor-offset"); 8782 const rotateWithMap = layout.get("text-rotation-alignment") === "map"; 8783 const pitchWithMap = layout.get("text-pitch-alignment") === "map"; 8784 const hasIconTextFit = layout.get("icon-text-fit") !== "none"; 8785 const defaultOpacityState = new JointOpacityState(null, 0, textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || layout.get("icon-optional")), iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || layout.get("text-optional")), true); 8786 if (!bucket.collisionArrays && collisionBoxArray && (bucket.hasIconCollisionBoxData() || bucket.hasTextCollisionBoxData())) bucket.deserializeCollisionBoxes(collisionBoxArray); 8787 const addOpacities = (iconOrText, numVertices, opacity) => { 8788 for (let i = 0; i < numVertices / 4; i++) iconOrText.opacityVertexArray.emplaceBack(opacity); 8789 iconOrText.hasVisibleVertices ||= opacity !== PACKED_HIDDEN_OPACITY; 8790 }; 8791 const boxArrays = this.collisionBoxArrays.get(bucket.bucketInstanceId); 8792 for (let s = 0; s < bucket.symbolInstances.length; s++) { 8793 const symbolInstance = bucket.symbolInstances.get(s); 8794 const { numHorizontalGlyphVertices, numVerticalGlyphVertices, crossTileID } = symbolInstance; 8795 const isDuplicate = seenCrossTileIDs[crossTileID]; 8796 let opacityState = this.opacities[crossTileID]; 8797 if (isDuplicate) opacityState = duplicateOpacityState; 8798 else if (!opacityState) { 8799 opacityState = defaultOpacityState; 8800 this.opacities[crossTileID] = opacityState; 8801 } 8802 seenCrossTileIDs[crossTileID] = true; 8803 const hasText = numHorizontalGlyphVertices > 0 || numVerticalGlyphVertices > 0; 8804 const hasIcon = symbolInstance.numIconVertices > 0; 8805 const placedOrientation = this.placedOrientations[symbolInstance.crossTileID]; 8806 const horizontalHidden = placedOrientation === 2; 8807 const verticalHidden = placedOrientation === 1 || placedOrientation === 3; 8808 if (hasText) { 8809 const packedOpacity = packOpacity(opacityState.text); 8810 const horizontalOpacity = horizontalHidden ? PACKED_HIDDEN_OPACITY : packedOpacity; 8811 addOpacities(bucket.text, numHorizontalGlyphVertices, horizontalOpacity); 8812 const verticalOpacity = verticalHidden ? PACKED_HIDDEN_OPACITY : packedOpacity; 8813 addOpacities(bucket.text, numVerticalGlyphVertices, verticalOpacity); 8814 const symbolHidden = opacityState.text.isHidden(); 8815 const textSymbolIndexes = [ 8816 symbolInstance.rightJustifiedTextSymbolIndex, 8817 symbolInstance.centerJustifiedTextSymbolIndex, 8818 symbolInstance.leftJustifiedTextSymbolIndex 8819 ]; 8820 for (const index of textSymbolIndexes) if (index >= 0) bucket.text.placedSymbolArray.get(index).hidden = symbolHidden || horizontalHidden ? 1 : 0; 8821 if (symbolInstance.verticalPlacedTextSymbolIndex >= 0) bucket.text.placedSymbolArray.get(symbolInstance.verticalPlacedTextSymbolIndex).hidden = symbolHidden || verticalHidden ? 1 : 0; 8822 const prevOffset = this.variableOffsets[symbolInstance.crossTileID]; 8823 if (prevOffset) this.markUsedJustification(bucket, prevOffset.anchor, symbolInstance, placedOrientation); 8824 const prevOrientation = this.placedOrientations[symbolInstance.crossTileID]; 8825 if (prevOrientation) { 8826 this.markUsedJustification(bucket, "left", symbolInstance, prevOrientation); 8827 this.markUsedOrientation(bucket, prevOrientation, symbolInstance); 8828 } 8829 } 8830 if (hasIcon) { 8831 const packedOpacity = packOpacity(opacityState.icon); 8832 const useHorizontal = !(hasIconTextFit && symbolInstance.verticalPlacedIconSymbolIndex && horizontalHidden); 8833 if (symbolInstance.placedIconSymbolIndex >= 0) { 8834 const horizontalOpacity = useHorizontal ? packedOpacity : PACKED_HIDDEN_OPACITY; 8835 addOpacities(bucket.icon, symbolInstance.numIconVertices, horizontalOpacity); 8836 bucket.icon.placedSymbolArray.get(symbolInstance.placedIconSymbolIndex).hidden = opacityState.icon.isHidden(); 8837 } 8838 if (symbolInstance.verticalPlacedIconSymbolIndex >= 0) { 8839 const verticalOpacity = !useHorizontal ? packedOpacity : PACKED_HIDDEN_OPACITY; 8840 addOpacities(bucket.icon, symbolInstance.numVerticalIconVertices, verticalOpacity); 8841 bucket.icon.placedSymbolArray.get(symbolInstance.verticalPlacedIconSymbolIndex).hidden = opacityState.icon.isHidden(); 8842 } 8843 } 8844 const realBoxes = boxArrays?.has(s) ? boxArrays.get(s) : { 8845 text: null, 8846 icon: null 8847 }; 8848 if (bucket.hasIconCollisionBoxData() || bucket.hasTextCollisionBoxData()) { 8849 const collisionArrays = bucket.collisionArrays[s]; 8850 if (collisionArrays) { 8851 let shift = new Point(0, 0); 8852 if (collisionArrays.textBox || collisionArrays.verticalTextBox) { 8853 let used = true; 8854 if (hasVariablePlacement) { 8855 const variableOffset = this.variableOffsets[crossTileID]; 8856 if (variableOffset) { 8857 shift = calculateVariableLayoutShift(variableOffset.anchor, variableOffset.width, variableOffset.height, variableOffset.textOffset, variableOffset.textBoxScale); 8858 if (rotateWithMap) shift._rotate(pitchWithMap ? -this.transform.bearingInRadians : this.transform.bearingInRadians); 8859 } else used = false; 8860 } 8861 if (collisionArrays.textBox || collisionArrays.verticalTextBox) { 8862 let hidden; 8863 if (collisionArrays.textBox) hidden = horizontalHidden; 8864 if (collisionArrays.verticalTextBox) hidden = verticalHidden; 8865 updateCollisionVertices(bucket.textCollisionBox.collisionVertexArray, opacityState.text.placed, !used || hidden, realBoxes.text, shift.x, shift.y); 8866 } 8867 } 8868 if (collisionArrays.iconBox || collisionArrays.verticalIconBox) { 8869 const verticalIconUsed = Boolean(!verticalHidden && collisionArrays.verticalIconBox); 8870 let hidden; 8871 if (collisionArrays.iconBox) hidden = verticalIconUsed; 8872 if (collisionArrays.verticalIconBox) hidden = !verticalIconUsed; 8873 updateCollisionVertices(bucket.iconCollisionBox.collisionVertexArray, opacityState.icon.placed, hidden, realBoxes.icon, hasIconTextFit ? shift.x : 0, hasIconTextFit ? shift.y : 0); 8874 } 8875 } 8876 } 8877 } 8878 bucket.sortFeatures(-this.transform.bearingInRadians); 8879 if (this.retainedQueryData[bucket.bucketInstanceId]) this.retainedQueryData[bucket.bucketInstanceId].featureSortOrder = bucket.featureSortOrder; 8880 if (bucket.hasTextData() && bucket.text.opacityVertexBuffer) bucket.text.opacityVertexBuffer.updateData(bucket.text.opacityVertexArray); 8881 if (bucket.hasIconData() && bucket.icon.opacityVertexBuffer) bucket.icon.opacityVertexBuffer.updateData(bucket.icon.opacityVertexArray); 8882 if (bucket.hasIconCollisionBoxData() && bucket.iconCollisionBox.collisionVertexBuffer) bucket.iconCollisionBox.collisionVertexBuffer.updateData(bucket.iconCollisionBox.collisionVertexArray); 8883 if (bucket.hasTextCollisionBoxData() && bucket.textCollisionBox.collisionVertexBuffer) bucket.textCollisionBox.collisionVertexBuffer.updateData(bucket.textCollisionBox.collisionVertexArray); 8884 if (bucket.text.opacityVertexArray.length !== bucket.text.layoutVertexArray.length / 4) throw new Error(`bucket.text.opacityVertexArray.length (= ${bucket.text.opacityVertexArray.length}) !== bucket.text.layoutVertexArray.length (= ${bucket.text.layoutVertexArray.length}) / 4`); 8885 if (bucket.icon.opacityVertexArray.length !== bucket.icon.layoutVertexArray.length / 4) throw new Error(`bucket.icon.opacityVertexArray.length (= ${bucket.icon.opacityVertexArray.length}) !== bucket.icon.layoutVertexArray.length (= ${bucket.icon.layoutVertexArray.length}) / 4`); 8886 if (bucket.bucketInstanceId in this.collisionCircleArrays) { 8887 bucket.collisionCircleArray = this.collisionCircleArrays[bucket.bucketInstanceId]; 8888 delete this.collisionCircleArrays[bucket.bucketInstanceId]; 8889 } 8890 } 8891 symbolFadeChange(now) { 8892 return this.fadeDuration === 0 ? 1 : (now - this.commitTime) / this.fadeDuration + this.prevZoomAdjustment; 8893 } 8894 zoomAdjustment(zoom) { 8895 return Math.max(0, (this.transform.zoom - zoom) / 1.5); 8896 } 8897 hasTransitions(now) { 8898 return this.stale || now - this.lastPlacementChangeTime < this.fadeDuration; 8899 } 8900 stillRecent(now, zoom) { 8901 const durationAdjustment = this.zoomAtLastRecencyCheck === zoom ? 1 - this.zoomAdjustment(zoom) : 1; 8902 this.zoomAtLastRecencyCheck = zoom; 8903 return this.commitTime + this.fadeDuration * durationAdjustment > now; 8904 } 8905 setStale() { 8906 this.stale = true; 8907 } 8908}; 8909function updateCollisionVertices(collisionVertexArray, placed, notUsed, realBox, shiftX, shiftY) { 8910 if (!realBox || realBox.length === 0) realBox = [ 8911 0, 8912 0, 8913 0, 8914 0 8915 ]; 8916 const tlX = realBox[0] - 100; 8917 const tlY = realBox[1] - 100; 8918 const brX = realBox[2] - 100; 8919 const brY = realBox[3] - 100; 8920 collisionVertexArray.emplaceBack(placed ? 1 : 0, notUsed ? 1 : 0, shiftX || 0, shiftY || 0, tlX, tlY); 8921 collisionVertexArray.emplaceBack(placed ? 1 : 0, notUsed ? 1 : 0, shiftX || 0, shiftY || 0, brX, tlY); 8922 collisionVertexArray.emplaceBack(placed ? 1 : 0, notUsed ? 1 : 0, shiftX || 0, shiftY || 0, brX, brY); 8923 collisionVertexArray.emplaceBack(placed ? 1 : 0, notUsed ? 1 : 0, shiftX || 0, shiftY || 0, tlX, brY); 8924} 8925const shift25 = Math.pow(2, 25); 8926const shift24 = Math.pow(2, 24); 8927const shift17 = Math.pow(2, 17); 8928const shift16 = Math.pow(2, 16); 8929const shift9 = Math.pow(2, 9); 8930const shift8 = Math.pow(2, 8); 8931const shift1 = Math.pow(2, 1); 8932function packOpacity(opacityState) { 8933 if (opacityState.opacity === 0 && !opacityState.placed) return 0; 8934 else if (opacityState.opacity === 1 && opacityState.placed) return 4294967295; 8935 const targetBit = opacityState.placed ? 1 : 0; 8936 const opacityBits = Math.floor(opacityState.opacity * 127); 8937 return opacityBits * shift25 + targetBit * shift24 + opacityBits * shift17 + targetBit * shift16 + opacityBits * shift9 + targetBit * shift8 + opacityBits * shift1 + targetBit; 8938} 8939const PACKED_HIDDEN_OPACITY = 0; 8940//#endregion 8941//#region src/style/pauseable_placement.ts 8942var LayerPlacement = class { 8943 constructor(styleLayer) { 8944 this._sortAcrossTiles = styleLayer.layout.get("symbol-z-order") !== "viewport-y" && !styleLayer.layout.get("symbol-sort-key").isConstant(); 8945 this._currentTileIndex = 0; 8946 this._currentPartIndex = 0; 8947 this._seenCrossTileIDs = {}; 8948 this._bucketParts = []; 8949 } 8950 continuePlacement(tiles, placement, showCollisionBoxes, styleLayer, shouldPausePlacement) { 8951 const bucketParts = this._bucketParts; 8952 while (this._currentTileIndex < tiles.length) { 8953 const tile = tiles[this._currentTileIndex]; 8954 placement.getBucketParts(bucketParts, styleLayer, tile, this._sortAcrossTiles); 8955 this._currentTileIndex++; 8956 if (shouldPausePlacement()) return true; 8957 } 8958 if (this._sortAcrossTiles) { 8959 this._sortAcrossTiles = false; 8960 bucketParts.sort((a, b) => a.sortKey - b.sortKey); 8961 } 8962 while (this._currentPartIndex < bucketParts.length) { 8963 const bucketPart = bucketParts[this._currentPartIndex]; 8964 placement.placeLayerBucketPart(bucketPart, this._seenCrossTileIDs, showCollisionBoxes); 8965 this._currentPartIndex++; 8966 if (shouldPausePlacement()) return true; 8967 } 8968 return false; 8969 } 8970}; 8971var PauseablePlacement = class { 8972 constructor(transform, terrain, order, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions, prevPlacement) { 8973 this.placement = new Placement(transform, terrain, fadeDuration, crossSourceCollisions, prevPlacement); 8974 this._currentPlacementIndex = order.length - 1; 8975 this._forceFullPlacement = forceFullPlacement; 8976 this._showCollisionBoxes = showCollisionBoxes; 8977 this._done = false; 8978 } 8979 isDone() { 8980 return this._done; 8981 } 8982 continuePlacement(order, layers, layerTiles) { 8983 const startTime = now(); 8984 const shouldPausePlacement = () => { 8985 return this._forceFullPlacement ? false : now() - startTime > 2; 8986 }; 8987 while (this._currentPlacementIndex >= 0) { 8988 const layer = layers[order[this._currentPlacementIndex]]; 8989 const placementZoom = this.placement.collisionIndex.transform.zoom; 8990 if (isSymbolStyleLayer(layer) && layer.layout && (!layer.minzoom || layer.minzoom <= placementZoom) && (!layer.maxzoom || layer.maxzoom > placementZoom)) { 8991 this._inProgressLayer ||= new LayerPlacement(layer); 8992 if (this._inProgressLayer.continuePlacement(layerTiles[layer.source], this.placement, this._showCollisionBoxes, layer, shouldPausePlacement)) return; 8993 delete this._inProgressLayer; 8994 } 8995 this._currentPlacementIndex--; 8996 } 8997 this._done = true; 8998 } 8999 commit(now) { 9000 this.placement.commit(now); 9001 return this.placement; 9002 } 9003}; 9004//#endregion 9005//#region node_modules/kdbush/index.js 9006const ARRAY_TYPES = [ 9007 Int8Array, 9008 Uint8Array, 9009 Uint8ClampedArray, 9010 Int16Array, 9011 Uint16Array, 9012 Int32Array, 9013 Uint32Array, 9014 Float32Array, 9015 Float64Array 9016]; 9017/** @typedef {Int8ArrayConstructor | Uint8ArrayConstructor | Uint8ClampedArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor} TypedArrayConstructor */ 9018/** @typedef {Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array} TypedArray */ 9019const VERSION = 1; 9020const HEADER_SIZE = 8; 9021const STACK = /* @__PURE__ */ new Uint32Array(96); 9022var KDBush = class KDBush { 9023 /** 9024 * Creates an index from raw `ArrayBuffer` data. 9025 * @param {ArrayBufferLike} data 9026 */ 9027 static from(data) { 9028 if (!data || data.byteLength === void 0 || data.buffer) throw new Error("Data must be an instance of ArrayBuffer or SharedArrayBuffer."); 9029 const [magic, versionAndType] = new Uint8Array(data, 0, 2); 9030 if (magic !== 219) throw new Error("Data does not appear to be in a KDBush format."); 9031 const version = versionAndType >> 4; 9032 if (version !== VERSION) throw new Error(`Got v${version} data when expected v${VERSION}.`); 9033 const ArrayType = ARRAY_TYPES[versionAndType & 15]; 9034 if (!ArrayType) throw new Error("Unrecognized array type."); 9035 const [nodeSize] = new Uint16Array(data, 2, 1); 9036 const [numItems] = new Uint32Array(data, 4, 1); 9037 return new KDBush(numItems, nodeSize, ArrayType, void 0, data); 9038 } 9039 /** 9040 * Creates an index that will hold a given number of items. 9041 * @param {number} numItems 9042 * @param {number} [nodeSize=64] Size of the KD-tree node (64 by default). 9043 * @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default). 9044 * @param {ArrayBufferConstructor | SharedArrayBufferConstructor} [ArrayBufferType=ArrayBuffer] The array buffer type used for storage (`ArrayBuffer` by default). 9045 * @param {ArrayBufferLike} [data] (For internal use only) 9046 */ 9047 constructor(numItems, nodeSize = 64, ArrayType = Float64Array, ArrayBufferType = ArrayBuffer, data) { 9048 if (isNaN(numItems) || numItems < 0) throw new Error(`Unexpected numItems value: ${numItems}.`); 9049 this.numItems = +numItems; 9050 this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535); 9051 this.ArrayType = ArrayType; 9052 this.IndexArrayType = numItems < 65536 ? Uint16Array : Uint32Array; 9053 const arrayTypeIndex = ARRAY_TYPES.indexOf(this.ArrayType); 9054 const coordsByteSize = numItems * 2 * this.ArrayType.BYTES_PER_ELEMENT; 9055 const idsByteSize = numItems * this.IndexArrayType.BYTES_PER_ELEMENT; 9056 const padCoords = (8 - idsByteSize % 8) % 8; 9057 if (arrayTypeIndex < 0) throw new Error(`Unexpected typed array class: ${ArrayType}.`); 9058 if (data) { 9059 this.data = data; 9060 this.ids = new this.IndexArrayType(data, HEADER_SIZE, numItems); 9061 this.coords = new ArrayType(data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2); 9062 this._pos = numItems * 2; 9063 this._finished = true; 9064 } else { 9065 const data = this.data = new ArrayBufferType(HEADER_SIZE + coordsByteSize + idsByteSize + padCoords); 9066 this.ids = new this.IndexArrayType(data, HEADER_SIZE, numItems); 9067 this.coords = new ArrayType(data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2); 9068 this._pos = 0; 9069 this._finished = false; 9070 new Uint8Array(data, 0, 2).set([219, 16 + arrayTypeIndex]); 9071 new Uint16Array(data, 2, 1)[0] = nodeSize; 9072 new Uint32Array(data, 4, 1)[0] = numItems; 9073 } 9074 } 9075 /** 9076 * Add a point to the index. 9077 * @param {number} x 9078 * @param {number} y 9079 * @returns {number} An incremental index associated with the added item (starting from `0`). 9080 */ 9081 add(x, y) { 9082 const index = this._pos >> 1; 9083 this.ids[index] = index; 9084 this.coords[this._pos++] = x; 9085 this.coords[this._pos++] = y; 9086 return index; 9087 } 9088 /** 9089 * Perform indexing of the added points. 9090 */ 9091 finish() { 9092 const numAdded = this._pos >> 1; 9093 if (numAdded !== this.numItems) throw new Error(`Added ${numAdded} items when expected ${this.numItems}.`); 9094 sort(this.ids, this.coords, this.nodeSize, 0, this.numItems - 1, 0); 9095 this._finished = true; 9096 return this; 9097 } 9098 /** 9099 * Search the index for items within a given bounding box. 9100 * @param {number} minX 9101 * @param {number} minY 9102 * @param {number} maxX 9103 * @param {number} maxY 9104 * @returns {number[]} An array of indices correponding to the found items. 9105 */ 9106 range(minX, minY, maxX, maxY) { 9107 if (!this._finished) throw new Error("Data not yet indexed - call index.finish()."); 9108 const { ids, coords, nodeSize } = this; 9109 STACK[0] = 0; 9110 STACK[1] = ids.length - 1; 9111 STACK[2] = 0; 9112 let sp = 3; 9113 const result = []; 9114 while (sp > 0) { 9115 const axis = STACK[--sp]; 9116 const right = STACK[--sp]; 9117 const left = STACK[--sp]; 9118 if (right - left <= nodeSize) { 9119 for (let i = left; i <= right; i++) { 9120 const x = coords[2 * i]; 9121 const y = coords[2 * i + 1]; 9122 if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]); 9123 } 9124 continue; 9125 } 9126 const m = left + right >> 1; 9127 const x = coords[2 * m]; 9128 const y = coords[2 * m + 1]; 9129 if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]); 9130 if (axis === 0 ? minX <= x : minY <= y) { 9131 STACK[sp++] = left; 9132 STACK[sp++] = m - 1; 9133 STACK[sp++] = 1 - axis; 9134 }
vendor: 1,866 bytes, lines 9135-9191
9135 if (axis === 0 ? maxX >= x : maxY >= y) { 9136 STACK[sp++] = m + 1; 9137 STACK[sp++] = right; 9138 STACK[sp++] = 1 - axis; 9139 } 9140 } 9141 return result; 9142 } 9143 /** 9144 * Search the index for items within a given radius. 9145 * @param {number} qx 9146 * @param {number} qy 9147 * @param {number} r Query radius. 9148 * @returns {number[]} An array of indices correponding to the found items. 9149 */ 9150 within(qx, qy, r) { 9151 const result = []; 9152 this.withinInto(qx, qy, r, result); 9153 return result; 9154 } 9155 /** 9156 * Search the index for items within a given radius, writing matching ids into `out` 9157 * via indexed assignment (`out[i] = id`). Accepts any indexed-writable container â 9158 * a typed array sized to the expected upper bound (allocation-free, fast) or a plain 9159 * `Array` (which will grow as needed). Returns the number of matches written. 9160 * @param {number} qx 9161 * @param {number} qy 9162 * @param {number} r Query radius. 9163 * @param {number[] | TypedArray} out Container to write matching ids into. 9164 * @returns {number} The number of matches written to `out`. 9165 */ 9166 withinInto(qx, qy, r, out) { 9167 if (!this._finished) throw new Error("Data not yet indexed - call index.finish()."); 9168 const { ids, coords, nodeSize } = this; 9169 STACK[0] = 0; 9170 STACK[1] = ids.length - 1; 9171 STACK[2] = 0; 9172 let sp = 3; 9173 let count = 0; 9174 const r2 = r * r; 9175 while (sp > 0) { 9176 const axis = STACK[--sp]; 9177 const right = STACK[--sp]; 9178 const left = STACK[--sp]; 9179 if (right - left <= nodeSize) { 9180 for (let i = left; i <= right; i++) if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) out[count++] = ids[i]; 9181 continue; 9182 } 9183 const m = left + right >> 1; 9184 const x = coords[2 * m]; 9185 const y = coords[2 * m + 1]; 9186 if (sqDist(x, y, qx, qy) <= r2) out[count++] = ids[m]; 9187 if (axis === 0 ? qx - r <= x : qy - r <= y) { 9188 STACK[sp++] = left; 9189 STACK[sp++] = m - 1; 9190 STACK[sp++] = 1 - axis; 9191 }
vendor: 50,119 bytes, lines 9192-9559
9192 if (axis === 0 ? qx + r >= x : qy + r >= y) { 9193 STACK[sp++] = m + 1; 9194 STACK[sp++] = right; 9195 STACK[sp++] = 1 - axis; 9196 } 9197 } 9198 return count; 9199 } 9200}; 9201/** 9202* @param {Uint16Array | Uint32Array} ids 9203* @param {TypedArray} coords 9204* @param {number} nodeSize 9205* @param {number} left 9206* @param {number} right 9207* @param {number} axis 9208*/ 9209function sort(ids, coords, nodeSize, left, right, axis) { 9210 if (right - left <= nodeSize) return; 9211 const m = left + right >> 1; 9212 select(ids, coords, m, left, right, axis); 9213 sort(ids, coords, nodeSize, left, m - 1, 1 - axis); 9214 sort(ids, coords, nodeSize, m + 1, right, 1 - axis); 9215} 9216/** 9217* Custom Floyd-Rivest selection algorithm: sort ids and coords so that 9218* [left..k-1] items are smaller than k-th item (on either x or y axis) 9219* @param {Uint16Array | Uint32Array} ids 9220* @param {TypedArray} coords 9221* @param {number} k 9222* @param {number} left 9223* @param {number} right 9224* @param {number} axis 9225*/ 9226function select(ids, coords, k, left, right, axis) { 9227 while (right > left) { 9228 if (right - left > 600) { 9229 const n = right - left + 1; 9230 const m = k - left + 1; 9231 const z = Math.log(n); 9232 const s = .5 * Math.exp(2 * z / 3); 9233 const sd = .5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); 9234 select(ids, coords, k, Math.max(left, Math.floor(k - m * s / n + sd)), Math.min(right, Math.floor(k + (n - m) * s / n + sd)), axis); 9235 } 9236 const t = coords[2 * k + axis]; 9237 let i = left; 9238 let j = right; 9239 swapItem(ids, coords, left, k); 9240 if (coords[2 * right + axis] > t) swapItem(ids, coords, left, right); 9241 while (i < j) { 9242 swapItem(ids, coords, i, j); 9243 i++; 9244 j--; 9245 while (coords[2 * i + axis] < t) i++; 9246 while (coords[2 * j + axis] > t) j--; 9247 } 9248 if (coords[2 * left + axis] === t) swapItem(ids, coords, left, j); 9249 else { 9250 j++; 9251 swapItem(ids, coords, j, right); 9252 } 9253 if (j <= k) left = j + 1; 9254 if (k <= j) right = j - 1; 9255 } 9256} 9257/** 9258* @param {Uint16Array | Uint32Array} ids 9259* @param {TypedArray} coords 9260* @param {number} i 9261* @param {number} j 9262*/ 9263function swapItem(ids, coords, i, j) { 9264 swap(ids, i, j); 9265 swap(coords, 2 * i, 2 * j); 9266 swap(coords, 2 * i + 1, 2 * j + 1); 9267} 9268/** 9269* @param {TypedArray} arr 9270* @param {number} i 9271* @param {number} j 9272*/ 9273function swap(arr, i, j) { 9274 const tmp = arr[i]; 9275 arr[i] = arr[j]; 9276 arr[j] = tmp; 9277} 9278/** 9279* @param {number} ax 9280* @param {number} ay 9281* @param {number} bx 9282* @param {number} by 9283*/ 9284function sqDist(ax, ay, bx, by) { 9285 const dx = ax - bx; 9286 const dy = ay - by; 9287 return dx * dx + dy * dy; 9288} 9289//#endregion 9290//#region src/symbol/cross_tile_symbol_index.ts 9291const roundingFactor = 512 / EXTENT / 2; 9292var TileLayerIndex = class { 9293 constructor(tileID, symbolInstances, bucketInstanceId) { 9294 this.tileID = tileID; 9295 this.bucketInstanceId = bucketInstanceId; 9296 this._symbolsByKey = {}; 9297 const symbolInstancesByKey = /* @__PURE__ */ new Map(); 9298 for (let i = 0; i < symbolInstances.length; i++) { 9299 const symbolInstance = symbolInstances.get(i); 9300 const key = symbolInstance.key; 9301 const instances = symbolInstancesByKey.get(key); 9302 if (instances) instances.push(symbolInstance); 9303 else symbolInstancesByKey.set(key, [symbolInstance]); 9304 } 9305 for (const [key, symbols] of symbolInstancesByKey) { 9306 const entry = { 9307 positions: symbols.map((symbolInstance) => ({ 9308 x: Math.floor(symbolInstance.anchorX * roundingFactor), 9309 y: Math.floor(symbolInstance.anchorY * roundingFactor) 9310 })), 9311 crossTileIDs: symbols.map((v) => v.crossTileID) 9312 }; 9313 if (entry.positions.length > 128) { 9314 const index = new KDBush(entry.positions.length, 16, Uint16Array); 9315 for (const { x, y } of entry.positions) index.add(x, y); 9316 index.finish(); 9317 delete entry.positions; 9318 entry.index = index; 9319 } 9320 this._symbolsByKey[key] = entry; 9321 } 9322 } 9323 getScaledCoordinates(symbolInstance, childTileID) { 9324 const { x: localX, y: localY, z: localZ } = this.tileID.canonical; 9325 const { x, y, z } = childTileID.canonical; 9326 const zDifference = z - localZ; 9327 const scale = roundingFactor / Math.pow(2, zDifference); 9328 const xWorld = (x * EXTENT + symbolInstance.anchorX) * scale; 9329 const yWorld = (y * EXTENT + symbolInstance.anchorY) * scale; 9330 const xOffset = localX * EXTENT * roundingFactor; 9331 const yOffset = localY * EXTENT * roundingFactor; 9332 return { 9333 x: Math.floor(xWorld - xOffset), 9334 y: Math.floor(yWorld - yOffset) 9335 }; 9336 } 9337 findMatches(symbolInstances, newTileID, zoomCrossTileIDs) { 9338 const tolerance = this.tileID.canonical.z < newTileID.canonical.z ? 1 : Math.pow(2, this.tileID.canonical.z - newTileID.canonical.z); 9339 for (let i = 0; i < symbolInstances.length; i++) { 9340 const symbolInstance = symbolInstances.get(i); 9341 if (symbolInstance.crossTileID) continue; 9342 const entry = this._symbolsByKey[symbolInstance.key]; 9343 if (!entry) continue; 9344 const scaledSymbolCoord = this.getScaledCoordinates(symbolInstance, newTileID); 9345 if (entry.index) this.findMatchesForIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs); 9346 else if (entry.positions) this.findMatchesForNonIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs); 9347 } 9348 } 9349 /** 9350 * Matches a symbol instance against an entry backed by a KDBush spatial index 9351 * (used for keys with more than {@link KDBUSH_THRESHHOLD} symbols). 9352 * 9353 * Matches any symbol with the same key whose coordinates are within one grid 9354 * unit (with a 4px grid, this covers a 12px by 12px area). The lowest-index 9355 * unclaimed candidate is claimed in a single pass rather than sorting the 9356 * whole result set on every query: `range()` can return many candidates where 9357 * symbols are coincident (e.g. stacked point markers), so a per-query sort 9358 * would dominate placement cost on dense layers. 9359 * 9360 * Once a symbol is marked duplicate against a parent symbol, no other symbol 9361 * at the same zoom level may duplicate against the same parent (see issue #5993). 9362 */ 9363 findMatchesForIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs) { 9364 const indexes = entry.index.range(scaledSymbolCoord.x - tolerance, scaledSymbolCoord.y - tolerance, scaledSymbolCoord.x + tolerance, scaledSymbolCoord.y + tolerance); 9365 let bestIndex = Infinity; 9366 for (const candidate of indexes) if (candidate < bestIndex && !zoomCrossTileIDs[entry.crossTileIDs[candidate]]) bestIndex = candidate; 9367 if (bestIndex !== Infinity) { 9368 const crossTileID = entry.crossTileIDs[bestIndex]; 9369 zoomCrossTileIDs[crossTileID] = true; 9370 symbolInstance.crossTileID = crossTileID; 9371 } 9372 } 9373 /** 9374 * Matches a symbol instance against an entry that stores its positions as a 9375 * plain array (used for keys with at most {@link KDBUSH_THRESHHOLD} symbols). 9376 * 9377 * Matches any symbol with the same key whose coordinates are within one grid 9378 * unit (with a 4px grid, this covers a 12px by 12px area). Positions are 9379 * stored in symbol-instance order, so the first unclaimed match is also the 9380 * lowest-index one. 9381 * 9382 * Once a symbol is marked duplicate against a parent symbol, no other symbol 9383 * at the same zoom level may duplicate against the same parent (see issue #5993). 9384 */ 9385 findMatchesForNonIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs) { 9386 for (let i = 0; i < entry.positions.length; i++) { 9387 const thisTileSymbol = entry.positions[i]; 9388 const crossTileID = entry.crossTileIDs[i]; 9389 if (Math.abs(thisTileSymbol.x - scaledSymbolCoord.x) <= tolerance && Math.abs(thisTileSymbol.y - scaledSymbolCoord.y) <= tolerance && !zoomCrossTileIDs[crossTileID]) { 9390 zoomCrossTileIDs[crossTileID] = true; 9391 symbolInstance.crossTileID = crossTileID; 9392 break; 9393 } 9394 } 9395 } 9396 getCrossTileIDsLists() { 9397 return Object.values(this._symbolsByKey).map(({ crossTileIDs }) => crossTileIDs); 9398 } 9399}; 9400var CrossTileIDs = class { 9401 constructor() { 9402 this.maxCrossTileID = 0; 9403 } 9404 generate() { 9405 return ++this.maxCrossTileID; 9406 } 9407}; 9408var CrossTileSymbolLayerIndex = class { 9409 constructor() { 9410 this.indexes = {}; 9411 this.usedCrossTileIDs = {}; 9412 this.lng = 0; 9413 } 9414 handleWrapJump(lng) { 9415 const wrapDelta = Math.round((lng - this.lng) / 360); 9416 if (wrapDelta !== 0) for (const zoom in this.indexes) { 9417 const zoomIndexes = this.indexes[zoom]; 9418 const newZoomIndex = {}; 9419 for (const key in zoomIndexes) { 9420 const index = zoomIndexes[key]; 9421 index.tileID = index.tileID.unwrapTo(index.tileID.wrap + wrapDelta); 9422 newZoomIndex[index.tileID.key] = index; 9423 } 9424 this.indexes[zoom] = newZoomIndex; 9425 } 9426 this.lng = lng; 9427 } 9428 addBucket(tileID, bucket, crossTileIDs) { 9429 if (this.indexes[tileID.overscaledZ]?.[tileID.key]) { 9430 if (this.indexes[tileID.overscaledZ][tileID.key].bucketInstanceId === bucket.bucketInstanceId) return false; 9431 else this.removeBucketCrossTileIDs(tileID.overscaledZ, this.indexes[tileID.overscaledZ][tileID.key]); 9432 } 9433 for (let i = 0; i < bucket.symbolInstances.length; i++) { 9434 const symbolInstance = bucket.symbolInstances.get(i); 9435 symbolInstance.crossTileID = 0; 9436 } 9437 this.usedCrossTileIDs[tileID.overscaledZ] ||= {}; 9438 const zoomCrossTileIDs = this.usedCrossTileIDs[tileID.overscaledZ]; 9439 for (const zoom in this.indexes) { 9440 const zoomIndexes = this.indexes[zoom]; 9441 if (Number(zoom) > tileID.overscaledZ) for (const id in zoomIndexes) { 9442 const childIndex = zoomIndexes[id]; 9443 if (childIndex.tileID.isChildOf(tileID)) childIndex.findMatches(bucket.symbolInstances, tileID, zoomCrossTileIDs); 9444 } 9445 else { 9446 const parentIndex = zoomIndexes[tileID.scaledTo(Number(zoom)).key]; 9447 if (parentIndex) parentIndex.findMatches(bucket.symbolInstances, tileID, zoomCrossTileIDs); 9448 } 9449 } 9450 for (let i = 0; i < bucket.symbolInstances.length; i++) { 9451 const symbolInstance = bucket.symbolInstances.get(i); 9452 if (!symbolInstance.crossTileID) { 9453 symbolInstance.crossTileID = crossTileIDs.generate(); 9454 zoomCrossTileIDs[symbolInstance.crossTileID] = true; 9455 } 9456 } 9457 if (this.indexes[tileID.overscaledZ] === void 0) this.indexes[tileID.overscaledZ] = {}; 9458 this.indexes[tileID.overscaledZ][tileID.key] = new TileLayerIndex(tileID, bucket.symbolInstances, bucket.bucketInstanceId); 9459 return true; 9460 } 9461 removeBucketCrossTileIDs(zoom, removedBucket) { 9462 for (const crossTileIDs of removedBucket.getCrossTileIDsLists()) for (const crossTileID of crossTileIDs) delete this.usedCrossTileIDs[zoom][crossTileID]; 9463 } 9464 removeStaleBuckets(currentIDs) { 9465 let tilesChanged = false; 9466 for (const z in this.indexes) { 9467 const zoomIndexes = this.indexes[z]; 9468 for (const tileKey in zoomIndexes) if (!currentIDs[zoomIndexes[tileKey].bucketInstanceId]) { 9469 this.removeBucketCrossTileIDs(z, zoomIndexes[tileKey]); 9470 delete zoomIndexes[tileKey]; 9471 tilesChanged = true; 9472 } 9473 } 9474 return tilesChanged; 9475 } 9476}; 9477var CrossTileSymbolIndex = class { 9478 constructor() { 9479 this.layerIndexes = {}; 9480 this.crossTileIDs = new CrossTileIDs(); 9481 this.maxBucketInstanceId = 0; 9482 this.bucketsInCurrentPlacement = {}; 9483 } 9484 addLayer(styleLayer, tiles, lng) { 9485 let layerIndex = this.layerIndexes[styleLayer.id]; 9486 if (layerIndex === void 0) layerIndex = this.layerIndexes[styleLayer.id] = new CrossTileSymbolLayerIndex(); 9487 let symbolBucketsChanged = false; 9488 const currentBucketIDs = {}; 9489 layerIndex.handleWrapJump(lng); 9490 for (const tile of tiles) { 9491 const symbolBucket = tile.getBucket(styleLayer); 9492 if (styleLayer.id !== symbolBucket?.layerIds[0]) continue; 9493 if (!symbolBucket.bucketInstanceId) { 9494 this.maxBucketInstanceId += 1; 9495 symbolBucket.bucketInstanceId = this.maxBucketInstanceId; 9496 } 9497 if (layerIndex.addBucket(tile.tileID, symbolBucket, this.crossTileIDs)) symbolBucketsChanged = true; 9498 currentBucketIDs[symbolBucket.bucketInstanceId] = true; 9499 } 9500 if (layerIndex.removeStaleBuckets(currentBucketIDs)) symbolBucketsChanged = true; 9501 return symbolBucketsChanged; 9502 } 9503 pruneUnusedLayers(usedLayers) { 9504 const usedLayerMap = {}; 9505 for (const usedLayer of usedLayers) usedLayerMap[usedLayer] = true; 9506 for (const layerId in this.layerIndexes) if (!usedLayerMap[layerId]) delete this.layerIndexes[layerId]; 9507 } 9508}; 9509//#endregion 9510//#region src/shaders/glsl/_prelude.fragment.glsl.g.ts 9511var _prelude_fragment_glsl_g_default = "#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\nout highp vec4 fragColor;layout(std140) uniform ProjectionUBO {highp mat4 u_projection_matrix;highp mat4 u_projection_fallback_matrix;highp vec4 u_projection_tile_mercator_coords;highp vec4 u_projection_clipping_plane;highp float u_projection_transition;highp int u_projection_clip_antimeridian;};layout(std140) uniform FrameUBO {highp vec2 u_units_to_pixels;highp vec2 u_world_size;highp float u_camera_to_center_distance;highp float u_symbol_fade_change;highp float u_aspect_ratio;highp float u_device_pixel_ratio;highp vec2 u_viewport_size;highp vec2 u_pixel_extrude_scale;highp float u_pitch;};"; 9512//#endregion 9513//#region src/shaders/glsl/_prelude.vertex.glsl.g.ts 9514var _prelude_vertex_glsl_g_default = "#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 uint packedOpacity) {return vec2(float(packedOpacity >> 1u)/127.0,float(packedOpacity & 1u));}vec4 decode_color(const vec2 encodedColor) {return vec4(unpack_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;}mat3 rotationMatrixFromAxisAngle(vec3 u,float angle) {float c=cos(angle);float s=sin(angle);float c2=1.0-c;return mat3(u.x*u.x*c2+ c,u.x*u.y*c2-u.z*s,u.x*u.z*c2+u.y*s,u.y*u.x*c2+u.z*s,u.y*u.y*c2+ c,u.y*u.z*c2-u.x*s,u.z*u.x*c2-u.y*s,u.z*u.y*c2+u.x*s,u.z*u.z*c2+ c\n);}\n#ifdef TERRAIN3D\nuniform sampler2D u_terrain;uniform highp sampler2D u_depth;layout(std140) uniform TerrainUBO {highp mat4 u_terrain_matrix;highp vec4 u_terrain_unpack;highp float u_terrain_dim;highp float u_terrain_exaggeration;};\n#endif\nconst highp vec4 bitSh=vec4(256.*256.*256.,256.*256.,256.,1.);const highp vec4 bitShifts=vec4(1.)/bitSh;highp float unpack(highp vec4 color) {return dot(color,bitShifts);}highp float depthOpacity(vec3 frag) {\n#ifdef TERRAIN3D\nhighp float d=unpack(texture(u_depth,frag.xy*0.5+0.5))+0.0001-frag.z;return 1.0-max(0.0,min(1.0,-d*500.0));\n#else\nreturn 1.0;\n#endif\n}float calculate_visibility(vec4 pos) {\n#ifdef TERRAIN3D\nvec3 frag=pos.xyz/pos.w;highp float d=depthOpacity(frag);if (d > 0.95) return 1.0;return (d+depthOpacity(frag+vec3(0.0,0.01,0.0)))/2.0;\n#else\nreturn 1.0;\n#endif\n}float ele(ivec2 pos) {\n#ifdef TERRAIN3D\nvec4 rgb=(texelFetch(u_terrain,pos,0)*255.0)*u_terrain_unpack;return rgb.r+rgb.g+rgb.b-u_terrain_unpack.a;\n#else\nreturn 0.0;\n#endif\n}float get_elevation(vec2 pos) {\n#ifdef TERRAIN3D\n#ifdef GLOBE\nif ((pos.y <-32767.5) || (pos.y > 32766.5)) {return 0.0;}\n#endif\nvec2 coord=(u_terrain_matrix*vec4(pos,0.0,1.0)).xy*u_terrain_dim+1.5;vec2 f=fract(coord);ivec2 c=ivec2(floor(coord));ivec2 hi=textureSize(u_terrain,0)-1;float tl=ele(clamp(c,ivec2(0),hi));float tr=ele(clamp(c+ivec2(1,0),ivec2(0),hi));float bl=ele(clamp(c+ivec2(0,1),ivec2(0),hi));float br=ele(clamp(c+ivec2(1,1),ivec2(0),hi));float elevation=mix(mix(tl,tr,f.x),mix(bl,br,f.x),f.y);return elevation*u_terrain_exaggeration;\n#else\nreturn 0.0;\n#endif\n}const float PI=3.141592653589793;\n#define PROJECTION_UBO\nlayout(std140) uniform ProjectionUBO {highp mat4 u_projection_matrix;highp mat4 u_projection_fallback_matrix;highp vec4 u_projection_tile_mercator_coords;highp vec4 u_projection_clipping_plane;highp float u_projection_transition;highp int u_projection_clip_antimeridian;};layout(std140) uniform FrameUBO {highp vec2 u_units_to_pixels;highp vec2 u_world_size;highp float u_camera_to_center_distance;highp float u_symbol_fade_change;highp float u_aspect_ratio;highp float u_device_pixel_ratio;highp vec2 u_viewport_size;highp vec2 u_pixel_extrude_scale;highp float u_pitch;};"; 9515//#endregion 9516//#region src/shaders/glsl/background.fragment.glsl.g.ts 9517var background_fragment_glsl_g_default = "uniform vec4 u_color;uniform float u_opacity;void main() {fragColor=u_color*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}"; 9518//#endregion 9519//#region src/shaders/glsl/background.vertex.glsl.g.ts 9520var background_vertex_glsl_g_default = "layout(location=0) in vec2 a_pos;void main() {gl_Position=projectTile(a_pos);}"; 9521//#endregion 9522//#region src/shaders/glsl/background_pattern.fragment.glsl.g.ts 9523var background_pattern_fragment_glsl_g_default = "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;in vec2 v_pos_a;in 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=texture(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=texture(u_image,pos2);fragColor=mix(color1,color2,u_mix)*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}"; 9524//#endregion 9525//#region src/shaders/glsl/background_pattern.vertex.glsl.g.ts 9526var background_pattern_vertex_glsl_g_default = "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;layout(location=0) in vec2 a_pos;out vec2 v_pos_a;out vec2 v_pos_b;void main() {gl_Position=projectTile(a_pos);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);}"; 9527//#endregion 9528//#region src/shaders/glsl/circle.fragment.glsl.g.ts 9529var circle_fragment_glsl_g_default = "in vec3 v_data;flat in float v_visibility;\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define mediump float radius\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define highp vec4 stroke_color\n#pragma maplibre: define mediump float stroke_width\n#pragma maplibre: define lowp float stroke_opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize mediump float radius\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize highp vec4 stroke_color\n#pragma maplibre: initialize mediump float stroke_width\n#pragma maplibre: initialize lowp float stroke_opacity\nvec2 extrude=v_data.xy;float extrude_length=length(extrude);float antialiased_blur=v_data.z;float opacity_t=smoothstep(0.0,antialiased_blur,extrude_length-1.0);float color_t=stroke_width < 0.01 ? 0.0 : smoothstep(antialiased_blur,0.0,extrude_length-radius/(radius+stroke_width));fragColor=v_visibility*opacity_t*mix(color*opacity,stroke_color*stroke_opacity,color_t);const float epsilon=0.5/255.0;if (fragColor.r < epsilon && fragColor.g < epsilon && fragColor.b < epsilon && fragColor.a < epsilon) {discard;}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}"; 9530//#endregion 9531//#region src/shaders/glsl/circle.vertex.glsl.g.ts 9532var circle_vertex_glsl_g_default = "uniform bool u_scale_with_map;uniform bool u_pitch_with_map;uniform vec2 u_extrude_scale;uniform highp float u_globe_extrude_scale;uniform vec2 u_translate;layout(location=0) in ivec2 a_pos;out vec3 v_data;flat out float v_visibility;\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define mediump float radius\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define highp vec4 stroke_color\n#pragma maplibre: define mediump float stroke_width\n#pragma maplibre: define lowp float stroke_opacity\nvoid main(void) {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize mediump float radius\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize highp vec4 stroke_color\n#pragma maplibre: initialize mediump float stroke_width\n#pragma maplibre: initialize lowp float stroke_opacity\nivec2 pos_raw=a_pos+32768;vec2 extrude=vec2(pos_raw & 7)/7.0*2.0-1.0;vec2 circle_center=vec2(pos_raw >> 3)+u_translate;float ele=get_elevation(circle_center);v_visibility=calculate_visibility(projectTileWithElevation(circle_center,ele));if (u_pitch_with_map) {\n#ifdef GLOBE\nvec3 center_vector=projectToSphere(circle_center);\n#endif\nfloat angle_scale=u_globe_extrude_scale;vec2 corner_position=circle_center;if (u_scale_with_map) {angle_scale*=(radius+stroke_width);corner_position+=extrude*u_extrude_scale*(radius+stroke_width);} else {\n#ifdef GLOBE\nvec4 projected_center=interpolateProjection(circle_center,center_vector,ele);\n#else\nvec4 projected_center=projectTileWithElevation(circle_center,ele);\n#endif\ncorner_position+=extrude*u_extrude_scale*(radius+stroke_width)*(projected_center.w/u_camera_to_center_distance);angle_scale*=(radius+stroke_width)*(projected_center.w/u_camera_to_center_distance);}\n#ifdef GLOBE\nvec2 angles=extrude*angle_scale;vec3 corner_vector=globeRotateVector(center_vector,angles);gl_Position=interpolateProjection(corner_position,corner_vector,ele);\n#else\ngl_Position=projectTileWithElevation(corner_position,ele);\n#endif\n} else {gl_Position=projectTileWithElevation(circle_center,ele);if (gl_Position.z/gl_Position.w > 1.0) {gl_Position.xy=vec2(10000.0);}if (u_scale_with_map) {gl_Position.xy+=extrude*(radius+stroke_width)*u_extrude_scale*u_camera_to_center_distance;} else {gl_Position.xy+=extrude*(radius+stroke_width)*u_extrude_scale*gl_Position.w;}}float antialiasblur=-max(1.0/u_device_pixel_ratio/(radius+stroke_width),blur);v_data=vec3(extrude.x,extrude.y,antialiasblur);}"; 9533//#endregion 9534//#region src/shaders/glsl/clipping_mask.fragment.glsl.g.ts 9535var clipping_mask_fragment_glsl_g_default = "void main() {fragColor=vec4(1.0);}"; 9536//#endregion 9537//#region src/shaders/shaders.ts 9538const shaders = { 9539 prelude: prepare(_prelude_fragment_glsl_g_default, _prelude_vertex_glsl_g_default), 9540 projectionMercator: prepare("\nvoid clipAntimeridian() {}", "float projectLineThickness(float tileY) {return 1.0;}float projectCircleRadius(float tileY) {return 1.0;}vec4 projectTile(vec2 p) {vec4 result=u_projection_matrix*vec4(p,0.0,1.0);return result;}vec4 projectTile(vec2 p,vec2 rawPos) {vec4 result=u_projection_matrix*vec4(p,0.0,1.0);if (rawPos.y <-32767.5 || rawPos.y > 32766.5) {result.z=-10000000.0;}return result;}vec4 projectTileWithElevation(vec2 posInTile,float elevation) {return u_projection_matrix*vec4(posInTile,elevation,1.0);}vec4 projectTileFor3D(vec2 posInTile,float elevation) {return projectTileWithElevation(posInTile,elevation);}"), 9541 projectionGlobe: prepare("in highp float v_projection_tile_x;void clipAntimeridian() {if (u_projection_clip_antimeridian !=0 && (v_projection_tile_x < 0.0 || v_projection_tile_x >=8192.0)) {discard;}}", "#define GLOBE_RADIUS 6371008.8\n#ifndef PROJECTION_UBO\nuniform highp vec4 u_projection_tile_mercator_coords;uniform highp vec4 u_projection_clipping_plane;uniform highp float u_projection_transition;uniform mat4 u_projection_fallback_matrix;\n#endif\nout highp float v_projection_tile_x;vec3 globeRotateVector(vec3 vec,vec2 angles) {vec3 axisRight=vec3(vec.z,0.0,-vec.x);vec3 axisUp=cross(axisRight,vec);axisRight=normalize(axisRight);axisUp=normalize(axisUp);vec2 t=tan(angles);return normalize(vec+axisRight*t.x+axisUp*t.y);}mat3 globeGetRotationMatrix(vec3 spherePos) {vec3 axisRight=vec3(spherePos.z,0.0,-spherePos.x);vec3 axisDown=cross(axisRight,spherePos);axisRight=normalize(axisRight);axisDown=normalize(axisDown);return mat3(axisRight,axisDown,spherePos\n);}float circumferenceRatioAtTileY(float tileY) {float mercator_pos_y=u_projection_tile_mercator_coords.y+u_projection_tile_mercator_coords.w*tileY;float t=exp(PI-(mercator_pos_y*PI*2.0));return (2.0*t)/(t*t+1.0);}float projectLineThickness(float tileY) {float thickness=1.0/circumferenceRatioAtTileY(tileY);if (u_projection_transition < 0.999) {return mix(1.0,thickness,u_projection_transition);} else {return thickness;}}vec3 projectToSphere(vec2 translatedPos,vec2 rawPos) {vec2 mercator_pos=u_projection_tile_mercator_coords.xy+u_projection_tile_mercator_coords.zw*translatedPos;float spherical_x=mercator_pos.x*PI*2.0+PI;float t=exp(PI-(mercator_pos.y*PI*2.0));float t2=t*t;float denom=t2+1.0;float sin_sy=(t2-1.0)/denom;float cos_sy=(2.0*t)/denom;vec3 pos=vec3(sin(spherical_x)*cos_sy,sin_sy,cos(spherical_x)*cos_sy\n);if (rawPos.y <-32767.5) {pos=vec3(0.0,1.0,0.0);}if (rawPos.y > 32766.5) {pos=vec3(0.0,-1.0,0.0);}return pos;}vec3 projectToSphere(vec2 posInTile) {return projectToSphere(posInTile,vec2(0.0,0.0));}float globeComputeClippingZ(vec3 spherePos) {return (1.0-(dot(spherePos,u_projection_clipping_plane.xyz)+u_projection_clipping_plane.w));}vec4 interpolateProjection(vec2 posInTile,vec3 spherePos,float elevation) {v_projection_tile_x=posInTile.x;vec3 elevatedPos=spherePos*(1.0+elevation/GLOBE_RADIUS);vec4 globePosition=u_projection_matrix*vec4(elevatedPos,1.0);globePosition.z=globeComputeClippingZ(elevatedPos)*globePosition.w;if (u_projection_transition > 0.999) {return globePosition;}vec4 flatPosition=u_projection_fallback_matrix*vec4(posInTile,elevation,1.0);const float z_globeness_threshold=0.2;vec4 result=globePosition;result.z=mix(0.0,globePosition.z,clamp((u_projection_transition-z_globeness_threshold)/(1.0-z_globeness_threshold),0.0,1.0));result.xyw=mix(flatPosition.xyw,globePosition.xyw,u_projection_transition);if ((posInTile.y <-32767.5) || (posInTile.y > 32766.5)) {result=globePosition;const float poles_hidden_anim_percentage=0.02;result.z=mix(globePosition.z,100.0,pow(max((1.0-u_projection_transition)/poles_hidden_anim_percentage,0.0),8.0));}return result;}vec4 interpolateProjectionFor3D(vec2 posInTile,vec3 spherePos,float elevation) {v_projection_tile_x=posInTile.x;vec3 elevatedPos=spherePos*(1.0+elevation/GLOBE_RADIUS);vec4 globePosition=u_projection_matrix*vec4(elevatedPos,1.0);if (u_projection_transition > 0.999) {return globePosition;}vec4 fallbackPosition=u_projection_fallback_matrix*vec4(posInTile,elevation,1.0);return mix(fallbackPosition,globePosition,u_projection_transition);}vec4 projectTile(vec2 posInTile) {return interpolateProjection(posInTile,projectToSphere(posInTile),0.0);}vec4 projectTile(vec2 posInTile,vec2 rawPos) {return interpolateProjection(posInTile,projectToSphere(posInTile,rawPos),0.0);}vec4 projectTileWithElevation(vec2 posInTile,float elevation) {return interpolateProjection(posInTile,projectToSphere(posInTile),elevation);}vec4 projectTileFor3D(vec2 posInTile,float elevation) {vec3 spherePos=projectToSphere(posInTile,posInTile);return interpolateProjectionFor3D(posInTile,spherePos,elevation);}"), 9542 background: prepare(background_fragment_glsl_g_default, background_vertex_glsl_g_default), 9543 backgroundPattern: prepare(background_pattern_fragment_glsl_g_default, background_pattern_vertex_glsl_g_default), 9544 circle: prepare(circle_fragment_glsl_g_default, circle_vertex_glsl_g_default), 9545 clippingMask: prepare(clipping_mask_fragment_glsl_g_default, "layout(location=0) in vec2 a_pos;void main() {gl_Position=projectTile(a_pos);}"), 9546 heatmap: prepare("uniform highp float u_intensity;in vec2 v_extrude;\n#pragma maplibre: define highp float weight\n#define GAUSS_COEF 0.3989422804014327\nvoid main() {\n#pragma maplibre: 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);fragColor=vec4(val,1.0,1.0,1.0);\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform float u_extrude_scale;uniform float u_opacity;uniform float u_intensity;uniform highp float u_globe_extrude_scale;layout(location=0) in ivec2 a_pos;out vec2 v_extrude;\n#pragma maplibre: define highp float weight\n#pragma maplibre: 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 maplibre: initialize highp float weight\n#pragma maplibre: initialize mediump float radius\nivec2 pos_raw=a_pos+32768;vec2 unscaled_extrude=vec2(pos_raw & 7)/7.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;vec2 circle_center=vec2(pos_raw >> 3);\n#ifdef GLOBE\nvec2 angles=v_extrude*radius*u_globe_extrude_scale;vec3 center_vector=projectToSphere(circle_center);vec3 corner_vector=globeRotateVector(center_vector,angles);gl_Position=interpolateProjection(circle_center+extrude,corner_vector,0.0);\n#else\ngl_Position=projectTileFor3D(circle_center+extrude,get_elevation(circle_center));\n#endif\n}"), 9547 heatmapTexture: prepare("uniform sampler2D u_image;uniform sampler2D u_color_ramp;uniform float u_opacity;in vec2 v_pos;void main() {float t=texture(u_image,v_pos).r;vec4 color=texture(u_color_ramp,vec2(t,0.5));fragColor=color*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(0.0);\n#endif\n}", "uniform mat4 u_matrix;layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=u_matrix*vec4(a_pos*u_world_size,0,1);v_pos.x=a_pos.x;v_pos.y=1.0-a_pos.y;}"), 9548 collisionBox: prepare("flat in float v_placed;flat in float v_notUsed;void main() {float alpha=0.5;fragColor=vec4(1.0,0.0,0.0,1.0)*alpha;if (v_placed > 0.5) {fragColor=vec4(0.0,0.0,1.0,0.5)*alpha;}if (v_notUsed > 0.5) {fragColor*=.1;}}", "layout(location=0) in vec2 a_anchor_pos;layout(location=1) in vec2 a_placed;layout(location=2) in vec2 a_box_real;flat out float v_placed;flat out float v_notUsed;void main() {gl_Position=projectTileWithElevation(a_anchor_pos,get_elevation(a_anchor_pos));gl_Position.xy=((a_box_real+0.5)*u_pixel_extrude_scale*2.0-1.0)*vec2(1.0,-1.0)*gl_Position.w;if (gl_Position.z/gl_Position.w < 1.1) {gl_Position.z=0.5;}v_placed=a_placed.x;v_notUsed=a_placed.y;}"), 9549 collisionCircle: prepare("flat in float v_radius;in vec2 v_extrude;flat in float v_collision;void main() {float alpha=0.5;float stroke_radius=0.9;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);fragColor=color*alpha*opacity_t;}", "layout(location=0) in vec2 a_pos;layout(location=1) in float a_radius;layout(location=2) in vec2 a_flags;flat out float v_radius;out vec2 v_extrude;flat out float v_collision;void main() {float radius=a_radius;float collision=a_flags.x;float vertexIdx=a_flags.y;vec2 quadVertexOffset=vec2(mix(-1.0,1.0,float(vertexIdx >=2.0)),mix(-1.0,1.0,float(vertexIdx >=1.0 && vertexIdx <=2.0)));vec2 quadVertexExtent=quadVertexOffset*radius;float padding_factor=1.2;v_radius=radius;v_extrude=quadVertexExtent*padding_factor;v_collision=collision;gl_Position=vec4((a_pos/u_viewport_size*2.0-1.0)*vec2(1.0,-1.0),0.0,1.0)+vec4(quadVertexExtent*padding_factor/u_viewport_size*2.0,0.0,0.0);}"), 9550 colorRelief: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform sampler2D u_image;uniform vec4 u_unpack;uniform sampler2D u_elevation_stops;uniform sampler2D u_color_stops;uniform int u_color_ramp_size;uniform float u_opacity;in vec2 v_pos;float getElevation(vec2 coord) {vec4 data=texture(u_image,coord)*255.0;data.a=-1.0;return dot(data,u_unpack);}float getElevationStop(int stop) {vec4 data=texelFetch(u_elevation_stops,ivec2(stop,0),0)*255.0;data.a=-1.0;return dot(data,u_unpack);}void main() {float el=getElevation(v_pos);int r=(u_color_ramp_size-1);int l=0;float el_l=getElevationStop(l);float el_r=getElevationStop(r);while(r-l > 1){int m=(r+l)/2;float el_m=getElevationStop(m);if(el < el_m){r=m;el_r=el_m;}else\n{l=m;el_l=el_m;}}float x=(float(l)+(el-el_l)/(el_r-el_l)+0.5)/float(u_color_ramp_size);fragColor=u_opacity*texture(u_color_stops,vec2(x,0));\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_dimension;layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=projectTile(a_pos,a_pos);highp vec2 epsilon=2.0/u_dimension;float scale=(u_dimension.x-4.0)/u_dimension.x;v_pos=(a_pos/8192.0)*scale+epsilon;if (a_pos.y <-32767.5) {v_pos.y=0.0;}if (a_pos.y > 32766.5) {v_pos.y=1.0;}}"), 9551 debug: prepare("uniform highp vec4 u_color;uniform sampler2D u_overlay;in vec2 v_uv;void main() {vec4 overlay_color=texture(u_overlay,v_uv);fragColor=mix(u_color,overlay_color,overlay_color.a);}", "layout(location=0) in vec2 a_pos;out vec2 v_uv;uniform float u_overlay_scale;void main() {v_uv=a_pos/8192.0;gl_Position=projectTileWithElevation(a_pos*u_overlay_scale,get_elevation(a_pos));}"), 9552 depth: prepare(clipping_mask_fragment_glsl_g_default, "layout(location=0) in vec2 a_pos;void main() {\n#ifdef GLOBE\ngl_Position=projectTileFor3D(a_pos,0.0);\n#else\ngl_Position=u_projection_matrix*vec4(a_pos,0.0,1.0);\n#endif\n}"), 9553 fill: prepare("#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float opacity\nfragColor=color*opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_fill_translate;layout(location=0) in vec2 a_pos;\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float opacity\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}gl_Position=projectTile(a_pos+u_fill_translate,a_pos);}"), 9554 fillOutline: prepare("in vec2 v_pos;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define highp vec4 outline_color\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 outline_color\n#pragma maplibre: initialize lowp float opacity\nfloat dist=length(v_pos-gl_FragCoord.xy);float alpha=1.0-smoothstep(0.0,1.0,dist);fragColor=outline_color*(alpha*opacity);\n#ifdef GLOBE\nif (v_depth > 1.0) {discard;}\n#endif\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_fill_translate;layout(location=0) in vec2 a_pos;out vec2 v_pos;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define highp vec4 outline_color\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 outline_color\n#pragma maplibre: initialize lowp float opacity\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}gl_Position=projectTile(a_pos+u_fill_translate,a_pos);v_pos=(gl_Position.xy/gl_Position.w+1.0)/2.0*u_world_size;\n#ifdef GLOBE\nv_depth=gl_Position.z/gl_Position.w;\n#endif\n}"), 9555 fillOutlinePattern: prepare("uniform vec2 u_texsize;uniform sampler2D u_image;uniform float u_fade;uniform bool u_sdf_pattern;in vec2 v_pos_a;in vec2 v_pos_b;in vec2 v_pos;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: 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=texture(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=texture(u_image,pos2);float dist=length(v_pos-gl_FragCoord.xy);float alpha=1.0-smoothstep(0.0,1.0,dist);if (u_sdf_pattern) {highp float sdf_edge=(256.0-64.0)/256.0;highp float sdf_gamma_a=max(fwidth(color1.a)*0.5,1.0/255.0/16.0);highp float sdf_gamma_b=max(fwidth(color2.a)*0.5,1.0/255.0/16.0);float sdf_alpha_a=smoothstep(sdf_edge-sdf_gamma_a,sdf_edge+sdf_gamma_a,color1.a);float sdf_alpha_b=smoothstep(sdf_edge-sdf_gamma_b,sdf_edge+sdf_gamma_b,color2.a);fragColor=mix(color*sdf_alpha_a,color*sdf_alpha_b,u_fade)*alpha*opacity;} else {fragColor=mix(color1,color2,u_fade)*alpha*opacity;}\n#ifdef GLOBE\nif (v_depth > 1.0) {discard;}\n#endif\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;uniform vec2 u_fill_translate;layout(location=0) in vec2 a_pos;out vec2 v_pos_a;out vec2 v_pos_b;out vec2 v_pos;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: initialize lowp float pixel_ratio_to\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}vec2 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=projectTile(a_pos+u_fill_translate,a_pos);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_size;\n#ifdef GLOBE\nv_depth=gl_Position.z/gl_Position.w;\n#endif\n}"), 9556 fillPattern: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform vec2 u_texsize;uniform float u_fade;uniform bool u_sdf_pattern;uniform sampler2D u_image;in vec2 v_pos_a;in vec2 v_pos_b;\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: 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=texture(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=texture(u_image,pos2);if (u_sdf_pattern) {highp float sdf_edge=(256.0-64.0)/256.0;highp float sdf_gamma_a=max(fwidth(color1.a)*0.5,1.0/255.0/16.0);highp float sdf_gamma_b=max(fwidth(color2.a)*0.5,1.0/255.0/16.0);float sdf_alpha_a=smoothstep(sdf_edge-sdf_gamma_a,sdf_edge+sdf_gamma_a,color1.a);float sdf_alpha_b=smoothstep(sdf_edge-sdf_gamma_b,sdf_edge+sdf_gamma_b,color2.a);fragColor=mix(color*sdf_alpha_a,color*sdf_alpha_b,u_fade)*opacity;} else {fragColor=mix(color1,color2,u_fade)*opacity;}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_pixel_coord_upper;uniform vec2 u_pixel_coord_lower;uniform vec3 u_scale;uniform vec2 u_fill_translate;layout(location=0) in vec2 a_pos;out vec2 v_pos_a;out vec2 v_pos_b;\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: initialize lowp float pixel_ratio_to\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}vec2 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=projectTile(a_pos+u_fill_translate,a_pos);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);}"), 9557 fillExtrusion: prepare("in vec4 v_color;void main() {fragColor=v_color;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec3 u_lightcolor;uniform lowp vec3 u_lightpos;uniform lowp vec3 u_lightpos_globe;uniform lowp float u_lightintensity;uniform float u_vertical_gradient;uniform lowp float u_opacity;uniform vec2 u_fill_translate;layout(location=0) in vec2 a_pos;layout(location=1) in ivec4 a_normal_ed;\n#ifdef TERRAIN3D\nlayout(location=2) in vec2 a_centroid;\n#endif\nout vec4 v_color;\n#pragma maplibre: define highp float base\n#pragma maplibre: define highp float height\n#pragma maplibre: define highp vec4 color\nvoid main() {\n#pragma maplibre: initialize highp float base\n#pragma maplibre: initialize highp float height\n#pragma maplibre: initialize highp vec4 color\nvec3 normal=vec3(a_normal_ed.xyz);float gradient_base=max(0.0,base);float gradient_height=max(0.0,height);\n#ifdef TERRAIN3D\nfloat height_terrain3d_offset=get_elevation(a_centroid);float base_terrain3d_offset=height_terrain3d_offset-(base > 0.0 ? 0.0 : 10.0);\n#else\nfloat height_terrain3d_offset=0.0;float base_terrain3d_offset=0.0;\n#endif\nbase=max(0.0,base)+base_terrain3d_offset;height=max(0.0,height)+height_terrain3d_offset;float t=float(a_normal_ed.x & 1);float elevation=t > 0.0 ? height : base;vec2 posInTile=a_pos+u_fill_translate;\n#ifdef GLOBE\nvec3 spherePos=projectToSphere(posInTile,a_pos);gl_Position=interpolateProjectionFor3D(posInTile,spherePos,elevation);\n#else\ngl_Position=u_projection_matrix*vec4(posInTile,elevation,1.0);\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;vec3 normalForLighting=normal/16384.0;float directional=clamp(dot(normalForLighting,u_lightpos),0.0,1.0);\n#ifdef GLOBE\nmat3 rotMatrix=globeGetRotationMatrix(spherePos);normalForLighting=rotMatrix*normalForLighting;directional=mix(directional,clamp(dot(normalForLighting,u_lightpos_globe),0.0,1.0),u_projection_transition);\n#endif\ndirectional=mix((1.0-u_lightintensity),max((1.0-colorvalue+u_lightintensity),1.0),directional);if (normal.y !=0.0) {directional*=((1.0-u_vertical_gradient)+(u_vertical_gradient*clamp((t+gradient_base)*pow(gradient_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;}"), 9558 fillExtrusionPattern: prepare("uniform vec2 u_texsize;uniform float u_fade;uniform sampler2D u_image;in vec2 v_pos_a;in vec2 v_pos_b;in vec4 v_lighting;\n#pragma maplibre: define lowp float base\n#pragma maplibre: define lowp float height\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\nvoid main() {\n#pragma maplibre: initialize lowp float base\n#pragma maplibre: initialize lowp float height\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: 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=texture(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=texture(u_image,pos2);vec4 mixedColor=mix(color1,color2,u_fade);fragColor=mixedColor*v_lighting;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "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 vec2 u_fill_translate;uniform vec3 u_lightcolor;uniform lowp vec3 u_lightpos;uniform lowp vec3 u_lightpos_globe;uniform lowp float u_lightintensity;layout(location=0) in vec2 a_pos;layout(location=1) in ivec4 a_normal_ed;\n#ifdef TERRAIN3D\nlayout(location=2) in vec2 a_centroid;\n#endif\n#ifdef GLOBE\nout vec3 v_sphere_pos;\n#endif\nout vec2 v_pos_a;out vec2 v_pos_b;out vec4 v_lighting;\n#pragma maplibre: define lowp float base\n#pragma maplibre: define lowp float height\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\nvoid main() {\n#pragma maplibre: initialize lowp float base\n#pragma maplibre: initialize lowp float height\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: 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 normal=vec3(a_normal_ed.xyz);float edgedistance=float(a_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;float gradient_base=max(0.0,base);float gradient_height=max(0.0,height);\n#ifdef TERRAIN3D\nfloat height_terrain3d_offset=get_elevation(a_centroid);float base_terrain3d_offset=height_terrain3d_offset-(base > 0.0 ? 0.0 : 10.0);\n#else\nfloat height_terrain3d_offset=0.0;float base_terrain3d_offset=0.0;\n#endif\nbase=max(0.0,base)+base_terrain3d_offset;height=max(0.0,height)+height_terrain3d_offset;float t=float(a_normal_ed.x & 1);float elevation=t > 0.0 ? height : base;vec2 posInTile=a_pos+u_fill_translate;\n#ifdef GLOBE\nvec3 spherePos=projectToSphere(posInTile,a_pos);vec3 elevatedPos=spherePos*(1.0+elevation/GLOBE_RADIUS);v_sphere_pos=elevatedPos;gl_Position=interpolateProjectionFor3D(posInTile,spherePos,elevation);\n#else\ngl_Position=u_projection_matrix*vec4(posInTile,elevation,1.0);\n#endif\nvec2 pos=a_normal_ed.x==1 && a_normal_ed.y==0 && a_normal_ed.z==16384\n? a_pos\n: vec2(edgedistance,elevation*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/16383.0,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*=((1.0-u_vertical_gradient)+(u_vertical_gradient*clamp((t+gradient_base)*pow(gradient_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;}"), 9559 hillshadePrepare: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform sampler2D u_image;in vec2 v_pos;uniform vec2 u_dimension;uniform float u_zoom;uniform vec4 u_unpack;float getElevation(ivec2 texel) {vec4 data=texelFetch(u_image,texel,0)*255.0;data.a=-1.0;return dot(data,u_unpack);}void main() {ivec2 pos=ivec2(gl_FragCoord.xy)+ivec2(1);float tileSize=u_dimension.x-4.0;float a=getElevation(pos+ivec2(-1,-1));float b=getElevation(pos+ivec2(0,-1));float c=getElevation(pos+ivec2(1,-1));float d=getElevation(pos+ivec2(-1,0));float e=getElevation(pos);float f=getElevation(pos+ivec2(1,0));float g=getElevation(pos+ivec2(-1,1));float h=getElevation(pos+ivec2(0,1));float i=getElevation(pos+ivec2(1,1));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((c+f+f+i)-(a+d+d+g),(g+h+h+i)-(a+b+b+c))*tileSize/p
vendor: 6,984 bytes, lines 9559-9561
9559ow(2.0,exaggeration+(28.2562-u_zoom));fragColor=clamp(vec4(deriv.x/8.0+0.5,deriv.y/8.0+0.5,1.0,1.0),0.0,1.0);\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform mat4 u_matrix;uniform vec2 u_dimension;layout(location=0) in vec2 a_pos;layout(location=1) in vec2 a_texture_pos;out 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;}"), 9560 hillshade: prepare("uniform sampler2D u_image;in highp vec2 v_pos;uniform vec2 u_latrange;uniform float u_exaggeration;uniform vec4 u_accent;uniform int u_method;uniform float u_altitudes[NUM_ILLUMINATION_SOURCES];uniform float u_azimuths[NUM_ILLUMINATION_SOURCES];uniform vec4 u_shadows[NUM_ILLUMINATION_SOURCES];uniform vec4 u_highlights[NUM_ILLUMINATION_SOURCES];\n#define PI 3.141592653589793\n#define STANDARD 0\n#define COMBINED 1\n#define IGOR 2\n#define MULTIDIRECTIONAL 3\n#define BASIC 4\nfloat get_aspect(vec2 deriv){return deriv.x !=0.0 ? atan(deriv.y,-deriv.x) : PI/2.0*(deriv.y > 0.0 ? 1.0 :-1.0);}void igor_hillshade(vec2 deriv){deriv=deriv*u_exaggeration*2.0;float aspect=get_aspect(deriv);float azimuth=u_azimuths[0]+PI;float slope_stength=atan(length(deriv))*2.0/PI;float aspect_strength=1.0-abs(mod((aspect+azimuth)/PI+0.5,2.0)-1.0);float shadow_strength=slope_stength*aspect_strength;float highlight_strength=slope_stength*(1.0-aspect_strength);fragColor=u_shadows[0]*shadow_strength+u_highlights[0]*highlight_strength;}void standard_hillshade(vec2 deriv){float azimuth=u_azimuths[0]+PI;float slope=atan(0.625*length(deriv));float aspect=get_aspect(deriv);float intensity=u_exaggeration;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_shadows[0],u_highlights[0],shade)*sin(scaledSlope)*clamp(intensity*2.0,0.0,1.0);fragColor=accent_color*(1.0-shade_color.a)+shade_color;}void basic_hillshade(vec2 deriv){deriv=deriv*u_exaggeration*2.0;float azimuth=u_azimuths[0]+PI;float cos_az=cos(azimuth);float sin_az=sin(azimuth);float cos_alt=cos(u_altitudes[0]);float sin_alt=sin(u_altitudes[0]);float cang=(sin_alt-(deriv.y*cos_az*cos_alt-deriv.x*sin_az*cos_alt))/sqrt(1.0+dot(deriv,deriv));float shade=clamp(cang,0.0,1.0);if(shade > 0.5){fragColor=u_highlights[0]*(2.0*shade-1.0);}else\n{fragColor=u_shadows[0]*(1.0-2.0*shade);}}void multidirectional_hillshade(vec2 deriv){deriv=deriv*u_exaggeration*2.0;fragColor=vec4(0,0,0,0);for(int i=0; i < NUM_ILLUMINATION_SOURCES; i++){float cos_alt=cos(u_altitudes[i]);float sin_alt=sin(u_altitudes[i]);float cos_az=-cos(u_azimuths[i]);float sin_az=-sin(u_azimuths[i]);float cang=(sin_alt-(deriv.y*cos_az*cos_alt-deriv.x*sin_az*cos_alt))/sqrt(1.0+dot(deriv,deriv));float shade=clamp(cang,0.0,1.0);if(shade > 0.5){fragColor+=u_highlights[i]*(2.0*shade-1.0)/float(NUM_ILLUMINATION_SOURCES);}else\n{fragColor+=u_shadows[i]*(1.0-2.0*shade)/float(NUM_ILLUMINATION_SOURCES);}}}void combined_hillshade(vec2 deriv){deriv=deriv*u_exaggeration*2.0;float azimuth=u_azimuths[0]+PI;float cos_az=cos(azimuth);float sin_az=sin(azimuth);float cos_alt=cos(u_altitudes[0]);float sin_alt=sin(u_altitudes[0]);float cang=acos((sin_alt-(deriv.y*cos_az*cos_alt-deriv.x*sin_az*cos_alt))/sqrt(1.0+dot(deriv,deriv)));cang=clamp(cang,0.0,PI/2.0);float shade=cang*atan(length(deriv))*4.0/PI/PI;float highlight=(PI/2.0-cang)*atan(length(deriv))*4.0/PI/PI;fragColor=u_shadows[0]*shade+u_highlights[0]*highlight;}void main() {highp vec2 size=vec2(textureSize(u_image,0));highp vec2 texturePos=(v_pos*(size-2.0)+1.0)/size;vec4 pixel=texture(u_image,texturePos);float scaleFactor=cos(radians((u_latrange[0]-u_latrange[1])*(1.0-v_pos.y)+u_latrange[1]));vec2 deriv=((pixel.rg*8.0)-4.0)/scaleFactor;if (u_method==BASIC) {basic_hillshade(deriv);} else if (u_method==COMBINED) {combined_hillshade(deriv);} else if (u_method==IGOR) {igor_hillshade(deriv);} else if (u_method==MULTIDIRECTIONAL) {multidirectional_hillshade(deriv);} else if (u_method==STANDARD) {standard_hillshade(deriv);} else {standard_hillshade(deriv);}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform mat4 u_matrix;layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=projectTile(a_pos,a_pos);v_pos=a_pos/8192.0;if (a_pos.y <-32767.5) {v_pos.y=0.0;}if (a_pos.y > 32766.5) {v_pos.y=1.0;}}"), 9561 line: prepare("flat in vec2 v_width2;in vec2 v_normal;in float v_gamma_scale;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\nclipAntimeridian();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);fragColor=color*(alpha*opacity);\n#ifdef GLOBE\nif (v_depth > 1.0) {discard;}\n#endif\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "\n#define scale 0.015873016\nlayout(location=0) in ivec2 a_pos_normal;layout(location=1) in uvec4 a_data;uniform vec2 u_translation;uniform mediump float u_ratio;out vec2 v_normal;flat out vec2 v_width2;out float v_gamma_scale;out highp float v_linesofar;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define mediump float gapwidth\n#pragma maplibre: define lowp float offset\n#pragma maplibre: define mediump float width\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump float gapwidth\n#pragma maplibre: initialize lowp float offset\n#pragma maplibre: initialize mediump float width\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=vec2(ivec2(a_data.xy)-128);float a_direction=float(int(a_data.z & 3u)-1);v_linesofar=float((a_data.z >> 2u)+a_data.w*64u)*2.0;vec2 pos=vec2(a_pos_normal >> 1);mediump vec2 normal=vec2(a_pos_normal & 1);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);
vendor: 63,850 bytes, lines 9561-10178
9561float adjustedThickness=projectLineThickness(pos.y);vec4 projected_no_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation);vec4 projected_with_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation+dist/u_ratio*adjustedThickness);gl_Position=projected_with_extrude;\n#ifdef GLOBE\nv_depth=gl_Position.z/gl_Position.w;\n#endif\n#ifdef TERRAIN3D\nv_gamma_scale=1.0;\n#else\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length((projected_with_extrude.xy-projected_no_extrude.xy)/projected_with_extrude.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#endif\nv_width2=vec2(outset,inset);}"), 9562 lineGradient: prepare("uniform sampler2D u_image;flat in vec2 v_width2;in vec2 v_normal;in float v_gamma_scale;in highp vec2 v_uv;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\nclipAntimeridian();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);vec4 color=texture(u_image,v_uv);fragColor=color*(alpha*opacity);\n#ifdef GLOBE\nif (v_depth > 1.0) {discard;}\n#endif\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "\n#define scale 0.015873016\nlayout(location=0) in ivec2 a_pos_normal;layout(location=1) in uvec4 a_data;layout(location=2) in float a_uv_x;layout(location=3) in float a_split_index;uniform vec2 u_translation;uniform mediump float u_ratio;uniform float u_image_height;out vec2 v_normal;flat out vec2 v_width2;out float v_gamma_scale;out highp vec2 v_uv;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define mediump float gapwidth\n#pragma maplibre: define lowp float offset\n#pragma maplibre: define mediump float width\nvoid main() {\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump float gapwidth\n#pragma maplibre: initialize lowp float offset\n#pragma maplibre: initialize mediump float width\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=vec2(ivec2(a_data.xy)-128);float a_direction=float(int(a_data.z & 3u)-1);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=vec2(a_pos_normal >> 1);mediump vec2 normal=vec2(a_pos_normal & 1);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);float adjustedThickness=projectLineThickness(pos.y);vec4 projected_no_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation);vec4 projected_with_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation+dist/u_ratio*adjustedThickness);gl_Position=projected_with_extrude;\n#ifdef GLOBE\nv_depth=gl_Position.z/gl_Position.w;\n#endif\n#ifdef TERRAIN3D\nv_gamma_scale=1.0;\n#else\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length((projected_with_extrude.xy-projected_no_extrude.xy)/projected_with_extrude.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#endif\nv_width2=vec2(outset,inset);}"), 9563 linePattern: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nuniform vec2 u_texsize;uniform float u_fade;uniform mediump vec3 u_scale;uniform sampler2D u_image;in vec2 v_normal;flat in vec2 v_width2;in float v_linesofar;in float v_gamma_scale;flat in float v_width;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: initialize lowp float pixel_ratio_to\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\nclipAntimeridian();vec2 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(texture(u_image,pos_a),texture(u_image,pos_b),u_fade);fragColor=color*alpha*opacity;\n#ifdef GLOBE\nif (v_depth > 1.0) {discard;}\n#endif\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "\n#define scale 0.015873016\n#define LINE_DISTANCE_SCALE 2.0\nlayout(location=0) in ivec2 a_pos_normal;layout(location=1) in uvec4 a_data;uniform vec2 u_translation;uniform mediump float u_ratio;out vec2 v_normal;flat out vec2 v_width2;out float v_linesofar;out float v_gamma_scale;flat out float v_width;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp float offset\n#pragma maplibre: define mediump float gapwidth\n#pragma maplibre: define mediump float width\n#pragma maplibre: define lowp float floorwidth\n#pragma maplibre: define lowp vec4 pattern_from\n#pragma maplibre: define lowp vec4 pattern_to\n#pragma maplibre: define lowp float pixel_ratio_from\n#pragma maplibre: define lowp float pixel_ratio_to\nvoid main() {\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize lowp float offset\n#pragma maplibre: initialize mediump float gapwidth\n#pragma maplibre: initialize mediump float width\n#pragma maplibre: initialize lowp float floorwidth\n#pragma maplibre: initialize mediump vec4 pattern_from\n#pragma maplibre: initialize mediump vec4 pattern_to\n#pragma maplibre: initialize lowp float pixel_ratio_from\n#pragma maplibre: initialize lowp float pixel_ratio_to\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=vec2(ivec2(a_data.xy)-128);float a_direction=float(int(a_data.z & 3u)-1);float a_linesofar=float((a_data.z >> 2u)+a_data.w*64u)*LINE_DISTANCE_SCALE;vec2 pos=vec2(a_pos_normal >> 1);mediump vec2 normal=vec2(a_pos_normal & 1);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);float adjustedThickness=projectLineThickness(pos.y);vec4 projected_no_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation);vec4 projected_with_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation+dist/u_ratio*adjustedThickness);gl_Position=projected_with_extrude;\n#ifdef GLOBE\nv_depth=gl_Position.z/gl_Position.w;\n#endif\n#ifdef TERRAIN3D\nv_gamma_scale=1.0;\n#else\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length((projected_with_extrude.xy-projected_no_extrude.xy)/projected_with_extrude.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#endif\nv_linesofar=a_linesofar;v_width2=vec2(outset,inset);v_width=floorwidth;}"), 9564 lineSDF: prepare("uniform lowp float u_lineatlas_width;uniform sampler2D u_image;uniform float u_mix;in vec2 v_normal;flat in vec2 v_width2;in vec2 v_tex_a;in vec2 v_tex_b;in float v_gamma_scale;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define mediump float width\n#pragma maplibre: define lowp float floorwidth\n#pragma maplibre: define mediump vec4 dasharray_from\n#pragma maplibre: define mediump vec4 dasharray_to\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump float width\n#pragma maplibre: initialize lowp float floorwidth\n#pragma maplibre: initialize mediump vec4 dasharray_from\n#pragma maplibre: initialize mediump vec4 dasharray_to\nclipAntimeridian();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 sdfdist_a=texture(u_image,v_tex_a).a;float sdfdist_b=texture(u_image,v_tex_b).a;float sdfdist=mix(sdfdist_a,sdfdist_b,u_mix);float sdfgamma=(u_lineatlas_width/256.0/u_device_pixel_ratio)/min(dasharray_from.w,dasharray_to.w);alpha*=smoothstep(0.5-sdfgamma/floorwidth,0.5+sdfgamma/floorwidth,sdfdist);fragColor=color*(alpha*opacity);\n#ifdef GLOBE\nif (v_depth > 1.0) {discard;}\n#endif\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "\n#define scale 0.015873016\n#define LINE_DISTANCE_SCALE 2.0\nlayout(location=0) in ivec2 a_pos_normal;layout(location=1) in uvec4 a_data;uniform vec2 u_translation;uniform mediump float u_ratio;uniform float u_tileratio;uniform float u_crossfade_from;uniform float u_crossfade_to;uniform float u_lineatlas_height;out vec2 v_normal;flat out vec2 v_width2;out vec2 v_tex_a;out vec2 v_tex_b;out float v_gamma_scale;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define highp vec4 color\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define mediump float gapwidth\n#pragma maplibre: define lowp float offset\n#pragma maplibre: define mediump float width\n#pragma maplibre: define lowp float floorwidth\n#pragma maplibre: define mediump vec4 dasharray_from\n#pragma maplibre: define mediump vec4 dasharray_to\nvoid main() {\n#pragma maplibre: initialize highp vec4 color\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump float gapwidth\n#pragma maplibre: initialize lowp float offset\n#pragma maplibre: initialize mediump float width\n#pragma maplibre: initialize lowp float floorwidth\n#pragma maplibre: initialize mediump vec4 dasharray_from\n#pragma maplibre: initialize mediump vec4 dasharray_to\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=vec2(ivec2(a_data.xy)-128);float a_direction=float(int(a_data.z & 3u)-1);float a_linesofar=float((a_data.z >> 2u)+a_data.w*64u)*LINE_DISTANCE_SCALE;vec2 pos=vec2(a_pos_normal >> 1);mediump vec2 normal=vec2(a_pos_normal & 1);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);float adjustedThickness=projectLineThickness(pos.y);vec4 projected_no_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation);vec4 projected_with_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation+dist/u_ratio*adjustedThickness);gl_Position=projected_with_extrude;\n#ifdef GLOBE\nv_depth=gl_Position.z/gl_Position.w;\n#endif\n#ifdef TERRAIN3D\nv_gamma_scale=1.0;\n#else\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length((projected_with_extrude.xy-projected_no_extrude.xy)/projected_with_extrude.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#endif\nfloat u_patternscale_a_x=u_tileratio/dasharray_from.w/u_crossfade_from;float u_patternscale_a_y=-dasharray_from.z/2.0/u_lineatlas_height;float u_patternscale_b_x=u_tileratio/dasharray_to.w/u_crossfade_to;float u_patternscale_b_y=-dasharray_to.z/2.0/u_lineatlas_height;v_tex_a=vec2(a_linesofar*u_patternscale_a_x/floorwidth,normal.y*u_patternscale_a_y+(float(dasharray_from.y)+0.5)/u_lineatlas_height);v_tex_b=vec2(a_linesofar*u_patternscale_b_x/floorwidth,normal.y*u_patternscale_b_y+(float(dasharray_to.y)+0.5)/u_lineatlas_height);v_width2=vec2(outset,inset);}"), 9565 lineGradientSDF: prepare("uniform sampler2D u_image;uniform sampler2D u_image_dash;uniform float u_mix;uniform lowp float u_lineatlas_width;in vec2 v_normal;flat in vec2 v_width2;in vec2 v_tex_a;in vec2 v_tex_b;in float v_gamma_scale;in highp vec2 v_uv;\n#ifdef GLOBE\nin float v_depth;\n#endif\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define mediump float width\n#pragma maplibre: define lowp float floorwidth\n#pragma maplibre: define mediump vec4 dasharray_from\n#pragma maplibre: define mediump vec4 dasharray_to\nvoid main() {\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump float width\n#pragma maplibre: initialize lowp float floorwidth\n#pragma maplibre: initialize mediump vec4 dasharray_from\n#pragma maplibre: initialize mediump vec4 dasharray_to\nclipAntimeridian();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);vec4 color=texture(u_image,v_uv);float sdfdist_a=texture(u_image_dash,v_tex_a).a;float sdfdist_b=texture(u_image_dash,v_tex_b).a;float sdfdist=mix(sdfdist_a,sdfdist_b,u_mix);float sdfgamma=(u_lineatlas_width/256.0)/min(dasharray_from.w,dasharray_to.w);float dash_alpha=smoothstep(0.5-sdfgamma/floorwidth,0.5+sdfgamma/floorwidth,sdfdist);fragColor=color*(alpha*dash_alpha*opacity);\n#ifdef GLOBE\nif (v_depth > 1.0) {discard;}\n#endif\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "\n#define scale 0.015873016\n#define LINE_DISTANCE_SCALE 2.0\nlayout(location=0) in ivec2 a_pos_normal;layout(location=1) in uvec4 a_data;layout(location=2) in float a_uv_x;layout(location=3) in float a_split_index;uniform vec2 u_translation;uniform mediump float u_ratio;uniform float u_image_height;uniform float u_tileratio;uniform float u_crossfade_from;uniform float u_crossfade_to;uniform float u_lineatlas_height;out vec2 v_normal;flat out vec2 v_width2;out float v_gamma_scale;out highp vec2 v_uv;out vec2 v_tex_a;out vec2 v_tex_b;\n#ifdef GLOBE\nout float v_depth;\n#endif\n#pragma maplibre: define lowp float blur\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define mediump float gapwidth\n#pragma maplibre: define lowp float offset\n#pragma maplibre: define mediump float width\n#pragma maplibre: define lowp float floorwidth\n#pragma maplibre: define mediump vec4 dasharray_from\n#pragma maplibre: define mediump vec4 dasharray_to\nvoid main() {\n#pragma maplibre: initialize lowp float blur\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize mediump float gapwidth\n#pragma maplibre: initialize lowp float offset\n#pragma maplibre: initialize mediump float width\n#pragma maplibre: initialize lowp float floorwidth\n#pragma maplibre: initialize mediump vec4 dasharray_from\n#pragma maplibre: initialize mediump vec4 dasharray_to\nif (opacity < 0.01) {gl_Position=vec4(-2.0,-2.0,-2.0,1.0);return;}float ANTIALIASING=1.0/u_device_pixel_ratio/2.0;vec2 a_extrude=vec2(ivec2(a_data.xy)-128);float a_direction=float(int(a_data.z & 3u)-1);float a_linesofar=float((a_data.z >> 2u)+a_data.w*64u)*LINE_DISTANCE_SCALE;float texel_height=1.0/u_image_height;float half_texel_height=0.5*texel_height;v_uv=vec2(a_uv_x,a_split_index*texel_height-half_texel_height);vec2 pos=vec2(a_pos_normal >> 1);mediump vec2 normal=vec2(a_pos_normal & 1);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);float adjustedThickness=projectLineThickness(pos.y);vec4 projected_no_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation);vec4 projected_with_extrude=projectTile(pos+offset2/u_ratio*adjustedThickness+u_translation+dist/u_ratio*adjustedThickness);gl_Position=projected_with_extrude;\n#ifdef GLOBE\nv_depth=gl_Position.z/gl_Position.w;\n#endif\n#ifdef TERRAIN3D\nv_gamma_scale=1.0;\n#else\nfloat extrude_length_without_perspective=length(dist);float extrude_length_with_perspective=length((projected_with_extrude.xy-projected_no_extrude.xy)/projected_with_extrude.w*u_units_to_pixels);v_gamma_scale=extrude_length_without_perspective/extrude_length_with_perspective;\n#endif\nfloat u_patternscale_a_x=u_tileratio/dasharray_from.w/u_crossfade_from;float u_patternscale_a_y=-dasharray_from.z/2.0/u_lineatlas_height;float u_patternscale_b_x=u_tileratio/dasharray_to.w/u_crossfade_to;float u_patternscale_b_y=-dasharray_to.z/2.0/u_lineatlas_height;v_tex_a=vec2(a_linesofar*u_patternscale_a_x/floorwidth,normal.y*u_patternscale_a_y+(float(dasharray_from.y)+0.5)/u_lineatlas_height);v_tex_b=vec2(a_linesofar*u_patternscale_b_x/floorwidth,normal.y*u_patternscale_b_y+(float(dasharray_to.y)+0.5)/u_lineatlas_height);v_width2=vec2(outset,inset);}"), 9566 layerOpacity: prepare("uniform sampler2D u_image;uniform float u_opacity;in vec2 v_pos;void main() {fragColor=texture(u_image,v_pos)*u_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(0.0);\n#endif\n}", "layout(location=0) in vec2 a_pos;out vec2 v_pos;void main() {gl_Position=vec4(a_pos.x*2.0-1.0,1.0-a_pos.y*2.0,0.0,1.0);v_pos.x=a_pos.x;v_pos.y=1.0-a_pos.y;}"), 9567 raster: prepare("uniform float u_fade_t;uniform float u_opacity;uniform sampler2D u_image0;uniform sampler2D u_image1;in vec3 v_pos0;in vec3 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=texture(u_image0,v_pos0.xy/v_pos0.z);vec4 color1=texture(u_image1,v_pos1.xy/v_pos1.z);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(dot(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);fragColor=vec4(mix(u_high_vec,u_low_vec,rgb)*color.a,color.a);\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "uniform vec2 u_tl_parent;uniform float u_scale_parent;uniform float u_buffer_scale;uniform vec3 u_image_warp;uniform vec4 u_coords_top;uniform vec4 u_coords_bottom;layout(location=0) in vec2 a_pos;out vec3 v_pos0;out vec3 v_pos1;void main() {vec2 fractionalPos=a_pos/8192.0;vec2 topLeft=u_coords_top.xy;vec2 topRight=u_coords_top.zw;vec2 bottomLeft=u_coords_bottom.xy;vec2 bottomRight=u_coords_bottom.zw;vec2 bilinearPos=mix(mix(topLeft,topRight,fractionalPos.x),mix(bottomLeft,bottomRight,fractionalPos.x),fractionalPos.y);float denominator=dot(u_image_warp.xy,fractionalPos)+1.0;vec2 acrossTop=topRight-topLeft+u_image_warp.x*topRight;vec2 downLeft=bottomLeft-topLeft+u_image_warp.y*bottomLeft;vec2 projectivePos=(acrossTop*fractionalPos.x+downLeft*fractionalPos.y+topLeft)/denominator;vec2 position=mix(projectivePos,bilinearPos,u_image_warp.z);gl_Position=projectTile(position,position);vec2 texturePos=((fractionalPos-0.5)/u_buffer_scale)+0.5;\n#ifdef GLOBE\nif (a_pos.y <-32767.5) {texturePos.y=0.0;}if (a_pos.y > 32766.5) {texturePos.y=1.0;}\n#endif\nfloat perspectiveRatio=mix(1.0/denominator,1.0,u_image_warp.z);v_pos0=vec3(texturePos*perspectiveRatio,perspectiveRatio);vec2 parentPos=(texturePos*u_scale_parent)+u_tl_parent;v_pos1=vec3(parentPos*perspectiveRatio,perspectiveRatio);}"), 9568 symbolIcon: prepare("uniform sampler2D u_texture;in vec2 v_tex;flat in float v_total_opacity;void main() {fragColor=texture(u_texture,v_tex)*v_total_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec4 a_pixeloffset;layout(location=3) in vec3 a_projected_pos;layout(location=4) in uint a_fade_opacity;layout(location=5) in float a_height_offset;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 bool u_rotate_symbol;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;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform vec2 u_translation;uniform float u_pitched_scale;uniform bool u_is_offset;uniform bool u_height_anchor_ground;out vec2 v_tex;flat out float v_total_opacity;\n#pragma maplibre: define lowp float opacity\nvoid main() {\n#pragma maplibre: initialize lowp float opacity\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);vec2 a_pxoffset=a_pixeloffset.xy;vec2 a_minFontScale=a_pixeloffset.zw/256.0;float ele=a_height_offset+(u_height_anchor_ground ? get_elevation(a_pos) : 0.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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);vec2 fade_opacity=unpack_opacity(a_fade_opacity);float fade_change=fade_opacity[1] > 0.5 ? u_symbol_fade_change :-u_symbol_fade_change;float visibility=calculate_visibility(projectedPoint);v_total_opacity=opacity*max(0.0,min(visibility,fade_opacity[0]+fade_change));if (v_total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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(0.5+0.5*distance_ratio,0.0,4.0);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset/16.0)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}gl_Position=finalPos;v_tex=a_tex/u_texsize;}"), 9569 symbolSDF: prepare("#define SDF_PX 8.0\nuniform bool u_is_halo;uniform bool u_is_plain;uniform sampler2D u_texture;uniform highp float u_gamma_scale;uniform bool u_is_text;in vec2 v_data0;in vec3 v_data1;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: 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 total_opacity=v_data1[2];float fontScale=u_is_text ? size/24.0 : size;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float inner_edge=(256.0-64.0)/256.0;lowp float dist=texture(u_texture,tex).a;lowp vec4 color_alpha_out_text,color_alpha_out_halo;if (u_is_plain){highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(inner_edge-gamma_scaled,inner_edge+gamma_scaled,dist);color_alpha_out_text=total_opacity*alpha*fill_color;}if (u_is_halo) {float gamma_halo=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);float inner_edge_halo=inner_edge+gamma_halo*gamma_scale;highp float gamma_scaled_halo=gamma_halo*gamma_scale;highp float alpha_halo=smoothstep(inner_edge_halo-gamma_scaled_halo,inner_edge_halo+gamma_scaled_halo,dist);highp float halo_edge=(6.0-halo_width/fontScale)/SDF_PX;alpha_halo= min(smoothstep(halo_edge-gamma_scaled_halo,halo_edge+gamma_scaled_halo,dist),1.0-alpha_halo);color_alpha_out_halo=total_opacity*alpha_halo*halo_color;}if (u_is_plain && u_is_halo) {fragColor=color_alpha_out_text+(1.-color_alpha_out_text.a)*color_alpha_out_halo;} else if (u_is_halo){fragColor=color_alpha_out_halo;} else {fragColor=color_alpha_out_text;}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec4 a_pixeloffset;layout(location=3) in vec3 a_projected_pos;layout(location=4) in uint a_fade_opacity;layout(location=5) in float a_height_offset;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_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform bool u_rotate_symbol;uniform vec2 u_texsize;uniform vec2 u_translation;uniform float u_pitched_scale;uniform bool u_is_offset;uniform bool u_height_anchor_ground;out vec2 v_data0;out vec3 v_data1;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);vec2 a_pxoffset=a_pixeloffset.xy/16.0;vec2 a_minFontScale=a_pixeloffset.zw/256.0;float ele=a_height_offset+(u_height_anchor_ground ? get_elevation(a_pos) : 0.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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);vec2 fade_opacity=unpack_opacity(a_fade_opacity);float visibility=calculate_visibility(projectedPoint);float fade_change=fade_opacity[1] > 0.5 ? u_symbol_fade_change :-u_symbol_fade_change;float interpolated_fade_opacity=max(0.0,min(visibility,fade_opacity[0]+fade_change));float total_opacity=opacity*interpolated_fade_opacity;if (total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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(0.5+0.5*distance_ratio,0.0,4.0);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=u_is_text ? size/24.0 : size;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*max(a_minFontScale,fontScale)+a_pxoffset)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}float gamma_scale=finalPos.w;gl_Position=finalPos;v_data0=a_tex/u_texsize;v_data1=vec3(gamma_scale,size,total_opacity);}"), 9570 symbolTextAndIcon: prepare("#define SDF_PX 8.0\n#define SDF 1.0\n#define ICON 0.0\nuniform bool u_is_halo;uniform bool u_is_text;uniform sampler2D u_texture;uniform sampler2D u_texture_icon;uniform highp float u_gamma_scale;in vec4 v_data0;in vec3 v_data1;flat in float v_is_sdf;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nfloat total_opacity=v_data1[2];if (v_is_sdf==ICON) {vec2 tex_icon=v_data0.zw;fragColor=texture(u_texture_icon,tex_icon)*total_opacity;\n#ifdef OVERDRAW_INSPECTOR\nfragColor=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;highp float gamma=EDGE_GAMMA/(fontScale*u_gamma_scale);lowp float buff=(256.0-64.0)/256.0;lowp float dist=texture(u_texture,tex).a;lowp vec4 color_alpha_out,color_alpha_out_halo;if (u_is_text) {highp float gamma_scaled=gamma*gamma_scale;highp float alpha=smoothstep(buff-gamma_scaled,buff+gamma_scaled,dist);color_alpha_out=fill_color*(alpha*total_opacity);}if (u_is_halo) {highp float gamma_halo=(halo_blur*1.19/SDF_PX+EDGE_GAMMA)/(fontScale*u_gamma_scale);lowp float buff_halo=(6.0-halo_width/fontScale)/SDF_PX;highp float gamma_scaled_halo=gamma_halo*gamma_scale;highp float alpha_halo=smoothstep(buff_halo-gamma_scaled_halo,buff_halo+gamma_scaled_halo,dist);color_alpha_out_halo=halo_color*(alpha_halo*total_opacity);}if (u_is_text && u_is_halo) {fragColor=color_alpha_out+(1.-color_alpha_out.a)*color_alpha_out_halo;} else if (u_is_halo) {fragColor=color_alpha_out_halo;} else {fragColor=color_alpha_out;}\n#ifdef OVERDRAW_INSPECTOR\nfragColor=vec4(1.0);\n#endif\n}", "layout(location=0) in vec4 a_pos_offset;layout(location=1) in uvec4 a_data;layout(location=2) in vec3 a_projected_pos;layout(location=3) in uint a_fade_opacity;layout(location=4) in float a_height_offset;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_label_plane_matrix;uniform mat4 u_coord_matrix;uniform bool u_is_text;uniform bool u_pitch_with_map;uniform bool u_rotate_symbol;uniform vec2 u_texsize;uniform vec2 u_texsize_icon;uniform bool u_is_along_line;uniform bool u_is_variable_anchor;uniform vec2 u_translation;uniform float u_pitched_scale;uniform bool u_is_offset;uniform bool u_height_anchor_ground;out vec4 v_data0;out vec3 v_data1;flat out float v_is_sdf;\n#pragma maplibre: define highp vec4 fill_color\n#pragma maplibre: define highp vec4 halo_color\n#pragma maplibre: define lowp float opacity\n#pragma maplibre: define lowp float halo_width\n#pragma maplibre: define lowp float halo_blur\nvoid main() {\n#pragma maplibre: initialize highp vec4 fill_color\n#pragma maplibre: initialize highp vec4 halo_color\n#pragma maplibre: initialize lowp float opacity\n#pragma maplibre: initialize lowp float halo_width\n#pragma maplibre: initialize lowp float halo_blur\nvec2 a_pos=a_pos_offset.xy;vec2 a_offset=a_pos_offset.zw;vec2 a_tex=vec2(a_data.xy);vec2 a_size=vec2(a_data.zw);float a_size_min=float(a_data.z >> 1u);float is_sdf=float(a_data.z & 1u);float ele=a_height_offset+(u_height_anchor_ground ? get_elevation(a_pos) : 0.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;}vec2 translated_a_pos=a_pos+u_translation;vec4 projectedPoint=projectTileWithElevation(translated_a_pos,ele);vec2 fade_opacity=unpack_opacity(a_fade_opacity);float visibility=calculate_visibility(projectedPoint);float fade_change=fade_opacity[1] > 0.5 ? u_symbol_fade_change :-u_symbol_fade_change;float interpolated_fade_opacity=max(0.0,min(visibility,fade_opacity[0]+fade_change));float total_opacity=opacity*interpolated_fade_opacity;if (total_opacity < 0.1){gl_Position=vec4(-2.,-2.,-2.,1.);return;}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(0.5+0.5*distance_ratio,0.0,4.0);if (!u_is_offset) {size*=perspective_ratio;}float fontScale=size/24.0;highp float symbol_rotation=0.0;if (u_rotate_symbol) {vec4 offsetProjectedPoint=projectTileWithElevation(translated_a_pos+vec2(1,0),ele);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;if (u_is_along_line || u_is_variable_anchor) {projected_pos=vec4(a_projected_pos.xy,ele,1.0);} else if (u_pitch_with_map) {projected_pos=u_label_plane_matrix*vec4(a_projected_pos.xy+u_translation,ele,1.0);} else {projected_pos=u_label_plane_matrix*projectTileWithElevation(a_projected_pos.xy+u_translation,ele);}float z=float(u_pitch_with_map)*projected_pos.z/projected_pos.w;float projectionScaling=1.0;\n#ifdef GLOBE\nif(u_pitch_with_map && !u_is_along_line) {float anchor_pos_tile_y=(u_coord_matrix*vec4(projected_pos.xy/projected_pos.w,z,1.0)).y;projectionScaling=mix(projectionScaling,1.0/circumferenceRatioAtTileY(anchor_pos_tile_y)*u_pitched_scale,u_projection_transition);}\n#endif\nvec4 finalPos=u_coord_matrix*vec4(projected_pos.xy/projected_pos.w+rotation_matrix*(a_offset/32.0*fontScale)*projectionScaling,z,1.0);if(u_pitch_with_map) {finalPos=projectTileWithElevation(finalPos.xy,finalPos.z);}float gamma_scale=finalPos.w;gl_Position=finalPos;v_data0.xy=a_tex/u_texsize;v_data0.zw=a_tex/u_texsize_icon;v_data1=vec3(gamma_scale,size,total_opacity);v_is_sdf=is_sdf;}"), 9571 terrain: prepare("uniform sampler2D u_texture;uniform vec4 u_fog_color;uniform vec4 u_horizon_color;uniform float u_fog_ground_blend;uniform float u_fog_ground_blend_opacity;uniform float u_horizon_fog_blend;uniform bool u_is_globe_mode;in vec2 v_texture_pos;in float v_fog_depth;const float gamma=2.2;vec4 gammaToLinear(vec4 color) {return pow(color,vec4(gamma));}vec4 linearToGamma(vec4 color) {return pow(color,vec4(1.0/gamma));}void main() {vec4 surface_color=texture(u_texture,vec2(v_texture_pos.x,1.0-v_texture_pos.y));if (!u_is_globe_mode && u_fog_ground_blend_opacity > 0.0 && v_fog_depth > u_fog_ground_blend) {vec4 surface_color_linear=gammaToLinear(surface_color);float blend_color=smoothstep(0.0,1.0,max((v_fog_depth-u_horizon_fog_blend)/(1.0-u_horizon_fog_blend),0.0));vec4 fog_horizon_color_linear=mix(gammaToLinear(u_fog_color),gammaToLinear(u_horizon_color),blend_color);float factor_fog=max(v_fog_depth-u_fog_ground_blend,0.0)/(1.0-u_fog_ground_blend);fragColor=linearToGamma(mix(surface_color_linear,fog_horizon_color_linear,pow(factor_fog,2.0)*u_fog_ground_blend_opacity));} else {fragColor=surface_color;}}", "layout(location=0) in vec3 a_pos3d;uniform mat4 u_fog_matrix;uniform float u_ele_delta;out vec2 v_texture_pos;out float v_fog_depth;void main() {float ele=get_elevation(a_pos3d.xy);float ele_delta=a_pos3d.z==1.0 ? u_ele_delta : 0.0;v_texture_pos=a_pos3d.xy/8192.0;gl_Position=projectTileFor3D(a_pos3d.xy,ele-ele_delta);vec4 pos=u_fog_matrix*vec4(a_pos3d.xy,ele,1.0);v_fog_depth=pos.z/pos.w*0.5+0.5;}"), 9572 terrainDepth: prepare("in float v_depth;const highp vec4 bitSh=vec4(256.*256.*256.,256.*256.,256.,1.);const highp vec4 bitMsk=vec4(0.,vec3(1./256.0));highp vec4 pack(highp float value) {highp vec4 comp=fract(value*bitSh);comp-=comp.xxyz*bitMsk;return comp;}void main() {fragColor=pack(v_depth);}", "layout(location=0) in vec3 a_pos3d;uniform float u_ele_delta;out float v_depth;void main() {float ele=get_elevation(a_pos3d.xy);float ele_delta=a_pos3d.z==1.0 ? u_ele_delta : 0.0;gl_Position=projectTileFor3D(a_pos3d.xy,ele-ele_delta);v_depth=gl_Position.z/gl_Position.w;}"), 9573 atmosphere: prepare("#ifdef GL_ES\nprecision highp float;\n#endif\nin vec3 view_direction;uniform vec3 u_sun_pos;uniform vec3 u_globe_position;uniform float u_globe_radius;uniform float u_atmosphere_blend;/**Shader use from https:*Made some change to adapt to MapLibre Globe geometry*/const float PI=3.141592653589793;const int iSteps=5;const int jSteps=3;/*radius of the planet*/const float EARTH_RADIUS=6371e3;/*radius of the atmosphere*/const float ATMOS_RADIUS=6471e3;vec2 rsi(vec3 r0,vec3 rd,float sr) {float a=dot(rd,rd);float b=2.0*dot(rd,r0);float c=dot(r0,r0)-(sr*sr);float d=(b*b)-4.0*a*c;if (d < 0.0) return vec2(1e5,-1e5);return vec2((-b-sqrt(d))/(2.0*a),(-b+sqrt(d))/(2.0*a));}vec4 atmosphere(vec3 r,vec3 r0,vec3 pSun,float iSun,float rPlanet,float rAtmos,vec3 kRlh,float kMie,float shRlh,float shMie,float g) {pSun=normalize(pSun);r=normalize(r);vec2 p=rsi(r0,r,rAtmos);if (p.x > p.y) {return vec4(0.0,0.0,0.0,1.0);}if (p.x < 0.0) {p.x=0.0;}vec3 pos=r0+r*p.x;vec2 p2=rsi(r0,r,rPlanet);if (p2.x <=p2.y && p2.x > 0.0) {p.y=min(p.y,p2.x);}float iStepSize=(p.y-p.x)/float(iSteps);float iTime=p.x+iStepSize*0.5;vec3 totalRlh=vec3(0,0,0);vec3 totalMie=vec3(0,0,0);float iOdRlh=0.0;float iOdMie=0.0;float mu=dot(r,pSun);float mumu=mu*mu;float gg=g*g;float pRlh=3.0/(16.0*PI)*(1.0+mumu);float pMie=3.0/(8.0*PI)*((1.0-gg)*(mumu+1.0))/(pow(1.0+gg-2.0*mu*g,1.5)*(2.0+gg));for (int i=0; i < iSteps; i++) {vec3 iPos=r0+r*iTime;float iHeight=length(iPos)-rPlanet;float odStepRlh=exp(-iHeight/shRlh)*iStepSize;float odStepMie=exp(-iHeight/shMie)*iStepSize;iOdRlh+=odStepRlh;iOdMie+=odStepMie;float jStepSize=rsi(iPos,pSun,rAtmos).y/float(jSteps);float jTime=jStepSize*0.5;float jOdRlh=0.0;float jOdMie=0.0;for (int j=0; j < jSteps; j++) {vec3 jPos=iPos+pSun*jTime;float jHeight=length(jPos)-rPlanet;jOdRlh+=exp(-jHeight/shRlh)*jStepSize;jOdMie+=exp(-jHeight/shMie)*jStepSize;jTime+=jStepSize;}vec3 attn=exp(-(kMie*(iOdMie+jOdMie)+kRlh*(iOdRlh+jOdRlh)));totalRlh+=odStepRlh*attn;totalMie+=odStepMie*attn;iTime+=iStepSize;}float opacity=exp(-(length(kRlh)*length(totalRlh)+kMie*length(totalMie)));vec3 color=iSun*(pRlh*kRlh*totalRlh+pMie*kMie*totalMie);return vec4(color,opacity);}void main() {vec3 scale_camera_pos=-u_globe_position*EARTH_RADIUS/u_globe_radius;vec4 color=atmosphere(normalize(view_direction),scale_camera_pos,u_sun_pos,22.0,EARTH_RADIUS,ATMOS_RADIUS,vec3(5.5e-6,13.0e-6,22.4e-6),21e-6,8e3,1.2e3,0.758\n);color.rgb=1.0-exp(-1.0*color.rgb);color=pow(color,vec4(1.0/2.2));fragColor=vec4(color.rgb,1.0-color.a)*u_atmosphere_blend;}", "layout(location=0) in vec2 a_pos;uniform mat4 u_inv_proj_matrix;out vec3 view_direction;void main() {view_direction=(u_inv_proj_matrix*vec4(a_pos,0.0,1.0)).xyz;gl_Position=vec4(a_pos,0.0,1.0);}"), 9574 sky: prepare("uniform vec4 u_sky_color;uniform vec4 u_horizon_color;uniform vec2 u_horizon;uniform vec2 u_horizon_normal;uniform float u_sky_horizon_blend;uniform float u_sky_blend;uniform vec3 u_globe_position;uniform float u_globe_radius;uniform float u_atmosphere_blend;in vec3 v_view_direction;const float ATMOSPHERE_HEIGHT_TO_GLOBE_RADIUS=100000.0/6371000.0;void main() {float x=gl_FragCoord.x;float y=gl_FragCoord.y;float blend=(y-u_horizon.y)*u_horizon_normal.y+(x-u_horizon.x)*u_horizon_normal.x;float opacity=1.0;if (u_sky_blend > 0.0) {vec3 ray=normalize(v_view_direction);float globe_distance=length(u_globe_position);float angle_to_globe_center=acos(clamp(dot(ray,u_globe_position)/globe_distance,-1.0,1.0));float horizon_angle=asin(min(u_globe_radius/globe_distance,1.0));blend=mix(blend,((angle_to_globe_center-horizon_angle)*u_camera_to_center_distance+1.0)*u_device_pixel_ratio,u_sky_blend);float closest_approach=max(dot(ray,u_globe_position),0.0);float lowest_altitude=sqrt(max(globe_distance*globe_distance-closest_approach*closest_approach,0.0))-u_globe_radius;float atmosphere_height=u_globe_radius*ATMOSPHERE_HEIGHT_TO_GLOBE_RADIUS;float inside_atmosphere=1.0-smoothstep(atmosphere_height,10.0*atmosphere_height,globe_distance-u_globe_radius);float through_atmosphere=1.0-smoothstep(0.0,atmosphere_height,lowest_altitude);opacity=inside_atmosphere*through_atmosphere*(1.0-u_atmosphere_blend);}if (blend > 0.0) {if (blend < u_sky_horizon_blend) {fragColor=mix(u_sky_color,u_horizon_color,pow(1.0-blend/u_sky_horizon_blend,2.0));} else {fragColor=u_sky_color;}}fragColor*=mix(1.0,opacity,u_sky_blend);}", "layout(location=0) in vec2 a_pos;uniform mat4 u_inv_proj_matrix;out vec3 v_view_direction;void main() {v_view_direction=(u_inv_proj_matrix*vec4(a_pos,0.0,1.0)).xyz;gl_Position=vec4(a_pos,1.0,1.0);}") 9575}; 9576/** Expand #pragmas to #ifdefs, extract attributes and uniforms */ 9577function prepare(fragmentSource, vertexSource) { 9578 const re = /#pragma maplibre: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g; 9579 const vertexAttributes = vertexSource.match(/in ([\w]+) ([\w]+)/g); 9580 const fragmentUniforms = fragmentSource.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g); 9581 const vertexUniforms = vertexSource.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g); 9582 const shaderUniforms = vertexUniforms ? vertexUniforms.concat(fragmentUniforms) : fragmentUniforms; 9583 let locationCounter = vertexAttributes ? vertexAttributes.length : 0; 9584 const fragmentPragmas = {}; 9585 fragmentSource = fragmentSource.replace(re, (match, operation, precision, type, name) => { 9586 fragmentPragmas[name] = true; 9587 if (operation === "define") return ` 9588#ifndef HAS_UNIFORM_u_${name} 9589in ${precision} ${type} ${name}; 9590#else 9591uniform ${precision} ${type} u_${name}; 9592#endif 9593`; 9594 else return ` 9595#ifdef HAS_UNIFORM_u_${name} 9596 ${precision} ${type} ${name} = u_${name}; 9597#endif 9598`; 9599 }); 9600 vertexSource = vertexSource.replace(re, (match, operation, precision, type, name) => { 9601 const attrType = type === "float" ? "vec2" : "vec4"; 9602 const unpackType = name.match(/color/) ? "color" : attrType; 9603 if (fragmentPragmas[name]) { 9604 if (operation === "define") return ` 9605#ifndef HAS_UNIFORM_u_${name} 9606uniform lowp float u_${name}_t; 9607layout(location = ${locationCounter++}) in ${precision} ${attrType} a_${name}; 9608out ${precision} ${type} ${name}; 9609#else 9610uniform ${precision} ${type} u_${name}; 9611#endif 9612`; 9613 else if (unpackType === "vec4") return ` 9614#ifndef HAS_UNIFORM_u_${name} 9615 ${name} = a_${name}; 9616#else 9617 ${precision} ${type} ${name} = u_${name}; 9618#endif 9619`; 9620 else return ` 9621#ifndef HAS_UNIFORM_u_${name} 9622 ${name} = unpack_mix_${unpackType}(a_${name}, u_${name}_t); 9623#else 9624 ${precision} ${type} ${name} = u_${name}; 9625#endif 9626`; 9627 } else if (operation === "define") return ` 9628#ifndef HAS_UNIFORM_u_${name} 9629uniform lowp float u_${name}_t; 9630layout(location = ${locationCounter++}) in ${precision} ${attrType} a_${name}; 9631#else 9632uniform ${precision} ${type} u_${name}; 9633#endif 9634`; 9635 else if (unpackType === "vec4") return ` 9636#ifndef HAS_UNIFORM_u_${name} 9637 ${precision} ${type} ${name} = a_${name}; 9638#else 9639 ${precision} ${type} ${name} = u_${name}; 9640#endif 9641`; 9642 else return ` 9643#ifndef HAS_UNIFORM_u_${name} 9644 ${precision} ${type} ${name} = unpack_mix_${unpackType}(a_${name}, u_${name}_t); 9645#else 9646 ${precision} ${type} ${name} = u_${name}; 9647#endif 9648`; 9649 }); 9650 return { 9651 fragmentSource, 9652 vertexSource, 9653 staticAttributes: vertexAttributes, 9654 staticUniforms: shaderUniforms 9655 }; 9656} 9657//#endregion 9658//#region src/geo/projection/mercator_projection.ts 9659const MercatorShaderDefine = "#define PROJECTION_MERCATOR"; 9660const MercatorShaderVariantKey = "mercator"; 9661var MercatorProjection = class { 9662 constructor() { 9663 this._cachedMesh = null; 9664 } 9665 get name() { 9666 return "mercator"; 9667 } 9668 get useSubdivision() { 9669 return false; 9670 } 9671 get shaderVariantName() { 9672 return MercatorShaderVariantKey; 9673 } 9674 get shaderDefine() { 9675 return MercatorShaderDefine; 9676 } 9677 get shaderPreludeCode() { 9678 return shaders.projectionMercator; 9679 } 9680 get vertexShaderPreludeCode() { 9681 return shaders.projectionMercator.vertexSource; 9682 } 9683 get subdivisionGranularity() { 9684 return SubdivisionGranularitySetting.noSubdivision; 9685 } 9686 get useGlobeControls() { 9687 return false; 9688 } 9689 get transitionState() { 9690 return 0; 9691 } 9692 destroy() {} 9693 getMeshFromTileID(context, _tileID, _hasBorder, _allowPoles, _usage) { 9694 if (this._cachedMesh) return this._cachedMesh; 9695 const tileExtentArray = new PosArray(); 9696 tileExtentArray.emplaceBack(0, 0); 9697 tileExtentArray.emplaceBack(EXTENT, 0); 9698 tileExtentArray.emplaceBack(0, EXTENT); 9699 tileExtentArray.emplaceBack(EXTENT, EXTENT); 9700 const tileExtentBuffer = context.createVertexBuffer(tileExtentArray, posAttributes.members); 9701 const tileExtentSegments = SegmentVector.simpleSegment(0, 0, 4, 2); 9702 const quadTriangleIndices = new TriangleIndexArray(); 9703 quadTriangleIndices.emplaceBack(1, 0, 2); 9704 quadTriangleIndices.emplaceBack(1, 2, 3); 9705 const quadTriangleIndexBuffer = context.createIndexBuffer(quadTriangleIndices); 9706 this._cachedMesh = new Mesh(tileExtentBuffer, quadTriangleIndexBuffer, tileExtentSegments); 9707 return this._cachedMesh; 9708 } 9709 recalculate() {} 9710 hasTransition() { 9711 return false; 9712 } 9713}; 9714//#endregion 9715//#region src/geo/edge_insets.ts 9716/** 9717* An `EdgeInset` object represents screen space padding applied to the edges of the viewport. 9718* This shifts the apparent center or the vanishing point of the map. This is useful for adding floating UI elements 9719* on top of the map and having the vanishing point shift as UI elements resize. 9720* 9721* @group Geography and Geometry 9722*/ 9723var EdgeInsets = class EdgeInsets { 9724 constructor(top = 0, bottom = 0, left = 0, right = 0) { 9725 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"); 9726 this.top = top; 9727 this.bottom = bottom; 9728 this.left = left; 9729 this.right = right; 9730 } 9731 /** 9732 * Interpolates the inset in-place. 9733 * This maintains the current inset value for any inset not present in `target`. 9734 * @param start - interpolation start 9735 * @param target - interpolation target 9736 * @param t - interpolation step/weight 9737 * @returns the insets 9738 */ 9739 interpolate(start, target, t) { 9740 if (target.top != null && start.top != null) this.top = interpolateFactory.number(start.top, target.top, t); 9741 if (target.bottom != null && start.bottom != null) this.bottom = interpolateFactory.number(start.bottom, target.bottom, t); 9742 if (target.left != null && start.left != null) this.left = interpolateFactory.number(start.left, target.left, t); 9743 if (target.right != null && start.right != null) this.right = interpolateFactory.number(start.right, target.right, t); 9744 return this; 9745 } 9746 /** 9747 * Utility method that computes the new apparent center or vanishing point after applying insets. 9748 * This is in pixels and with the top left being (0.0) and +y being downwards. 9749 * 9750 * @param width - the width 9751 * @param height - the height 9752 * @returns the point 9753 */ 9754 getCenter(width, height) { 9755 const x = clamp((this.left + width - this.right) / 2, 0, width); 9756 const y = clamp((this.top + height - this.bottom) / 2, 0, height); 9757 return new Point(x, y); 9758 } 9759 equals(other) { 9760 return this.top === other.top && this.bottom === other.bottom && this.left === other.left && this.right === other.right; 9761 } 9762 clone() { 9763 return new EdgeInsets(this.top, this.bottom, this.left, this.right); 9764 } 9765 /** 9766 * Returns the current state as json, useful when you want to have a 9767 * read-only representation of the inset. 9768 * 9769 * @returns state as json 9770 */ 9771 toJSON() { 9772 return { 9773 top: this.top, 9774 bottom: this.bottom, 9775 left: this.left, 9776 right: this.right 9777 }; 9778 } 9779}; 9780//#endregion 9781//#region src/geo/transform_helper.ts 9782/** 9783* If a path crossing the antimeridian would be shorter, extend the final coordinate so that 9784* interpolating between the two endpoints will cross it. 9785* @param center - The LngLat object of the desired center. This object will be mutated. 9786*/ 9787function normalizeCenter(tr, center) { 9788 if (!tr.renderWorldCopies || tr.lngRange) return; 9789 const delta = center.lng - tr.center.lng; 9790 center.lng += delta > 180 ? -360 : delta < -180 ? 360 : 0; 9791} 9792function getTileZoom(zoom) { 9793 return Math.max(0, Math.floor(zoom)); 9794} 9795/** 9796* @internal 9797* This class stores all values that define a transform's state, 9798* such as center, zoom, minZoom, etc. 9799* This can be used as a helper for implementing the ITransform interface. 9800*/ 9801var TransformHelper = class { 9802 constructor(callbacks, options) { 9803 this.applyConstrain = (lngLat, zoom) => { 9804 if (this._constrainOverride !== null) return this._constrainOverride(lngLat, zoom); 9805 else return this._callbacks.defaultConstrain(lngLat, zoom); 9806 }; 9807 this._callbacks = callbacks; 9808 this._tileSize = 512; 9809 this._renderWorldCopies = options?.renderWorldCopies === void 0 ? true : !!options?.renderWorldCopies; 9810 this._minZoom = options?.minZoom || 0; 9811 this._maxZoom = options?.maxZoom || 22; 9812 this._minPitch = options?.minPitch === void 0 || options?.minPitch === null ? 0 : options?.minPitch; 9813 this._maxPitch = options?.maxPitch === void 0 || options?.maxPitch === null ? 60 : options?.maxPitch; 9814 this._constrainOverride = options?.constrainOverride ?? null; 9815 this.setMaxBounds(); 9816 this._width = 0; 9817 this._height = 0; 9818 this._center = new LngLat(0, 0); 9819 this._elevation = 0; 9820 this._zoom = 0; 9821 this._tileZoom = getTileZoom(this._zoom); 9822 this._scale = zoomScale(this._zoom); 9823 this._bearingInRadians = 0; 9824 this._fovInRadians = .6435011087932844; 9825 this._pitchInRadians = 0; 9826 this._rollInRadians = 0; 9827 this._unmodified = true; 9828 this._edgeInsets = new EdgeInsets(); 9829 this._minElevationForCurrentTile = 0; 9830 this._autoCalculateNearFarZ = true; 9831 } 9832 apply(thatI, constrain) { 9833 this._constrainOverride = thatI.constrainOverride; 9834 this._latRange = thatI.latRange; 9835 this._lngRange = thatI.lngRange; 9836 this._width = thatI.width; 9837 this._height = thatI.height; 9838 this._center = thatI.center; 9839 this._elevation = thatI.elevation; 9840 this._minElevationForCurrentTile = thatI.minElevationForCurrentTile; 9841 this._zoom = thatI.zoom; 9842 this._tileZoom = getTileZoom(this._zoom); 9843 this._scale = zoomScale(this._zoom); 9844 this._bearingInRadians = thatI.bearingInRadians; 9845 this._fovInRadians = thatI.fovInRadians; 9846 this._pitchInRadians = thatI.pitchInRadians; 9847 this._rollInRadians = thatI.rollInRadians; 9848 this._unmodified = thatI.unmodified; 9849 this._edgeInsets = new EdgeInsets(thatI.padding.top, thatI.padding.bottom, thatI.padding.left, thatI.padding.right); 9850 this._minZoom = thatI.minZoom; 9851 this._maxZoom = thatI.maxZoom; 9852 this._minPitch = thatI.minPitch; 9853 this._maxPitch = thatI.maxPitch; 9854 this._renderWorldCopies = thatI.renderWorldCopies; 9855 this._cameraToCenterDistance = thatI.cameraToCenterDistance; 9856 this._nearZ = thatI.nearZ; 9857 this._farZ = thatI.farZ; 9858 this._autoCalculateNearFarZ = thatI.autoCalculateNearFarZ; 9859 if (constrain) this.constrainInternal(); 9860 this._calcMatrices(); 9861 } 9862 get pixelsToClipSpaceMatrix() { 9863 return this._pixelsToClipSpaceMatrix; 9864 } 9865 get clipSpaceToPixelsMatrix() { 9866 return this._clipSpaceToPixelsMatrix; 9867 } 9868 get minElevationForCurrentTile() { 9869 return this._minElevationForCurrentTile; 9870 } 9871 setMinElevationForCurrentTile(ele) { 9872 this._minElevationForCurrentTile = ele; 9873 } 9874 get tileSize() { 9875 return this._tileSize; 9876 } 9877 get tileZoom() { 9878 return this._tileZoom; 9879 } 9880 get scale() { 9881 return this._scale; 9882 } 9883 /** 9884 * Gets the transform's width in pixels. Use {@link resize} to set the transform's size. 9885 */ 9886 get width() { 9887 return this._width; 9888 } 9889 /** 9890 * Gets the transform's height in pixels. Use {@link resize} to set the transform's size. 9891 */ 9892 get height() { 9893 return this._height; 9894 } 9895 /** 9896 * Gets the transform's bearing in radians. 9897 */ 9898 get bearingInRadians() { 9899 return this._bearingInRadians; 9900 } 9901 get lngRange() { 9902 return this._lngRange; 9903 } 9904 get latRange() { 9905 return this._latRange; 9906 } 9907 get pixelsToGLUnits() { 9908 return this._pixelsToGLUnits; 9909 } 9910 get minZoom() { 9911 return this._minZoom; 9912 } 9913 setMinZoom(zoom) { 9914 if (this._minZoom === zoom) return; 9915 this._minZoom = zoom; 9916 const unmodified = this._unmodified; 9917 this.setZoom(this.applyConstrain(this._center, this.zoom).zoom); 9918 this._unmodified = unmodified; 9919 } 9920 get maxZoom() { 9921 return this._maxZoom; 9922 } 9923 setMaxZoom(zoom) { 9924 if (this._maxZoom === zoom) return; 9925 this._maxZoom = zoom; 9926 const unmodified = this._unmodified; 9927 this.setZoom(this.applyConstrain(this._center, this.zoom).zoom); 9928 this._unmodified = unmodified; 9929 } 9930 get minPitch() { 9931 return this._minPitch; 9932 } 9933 setMinPitch(pitch) { 9934 if (this._minPitch === pitch) return; 9935 this._minPitch = pitch; 9936 const unmodified = this._unmodified; 9937 this.setPitch(Math.max(this.pitch, pitch)); 9938 this._unmodified = unmodified; 9939 } 9940 get maxPitch() { 9941 return this._maxPitch; 9942 } 9943 setMaxPitch(pitch) { 9944 if (this._maxPitch === pitch) return; 9945 this._maxPitch = pitch; 9946 const unmodified = this._unmodified; 9947 this.setPitch(Math.min(this.pitch, pitch)); 9948 this._unmodified = unmodified; 9949 } 9950 get renderWorldCopies() { 9951 return this._renderWorldCopies; 9952 } 9953 setRenderWorldCopies(renderWorldCopies) { 9954 if (renderWorldCopies === void 0) renderWorldCopies = true; 9955 else if (renderWorldCopies === null) renderWorldCopies = false; 9956 this._renderWorldCopies = renderWorldCopies; 9957 } 9958 get constrainOverride() { 9959 return this._constrainOverride; 9960 } 9961 setConstrainOverride(constrain) { 9962 if (constrain === void 0) constrain = null; 9963 if (this._constrainOverride === constrain) return; 9964 this._constrainOverride = constrain; 9965 this.constrainInternal(); 9966 this._calcMatrices(); 9967 } 9968 get worldSize() { 9969 return this._tileSize * this._scale; 9970 } 9971 get centerOffset() { 9972 return this.centerPoint._sub(this.size._div(2)); 9973 } 9974 /** 9975 * Gets the transform's dimensions packed into a Point object. 9976 */ 9977 get size() { 9978 return new Point(this._width, this._height); 9979 } 9980 get bearing() { 9981 return this._bearingInRadians / Math.PI * 180; 9982 } 9983 setBearing(bearing) { 9984 const b = wrap(bearing, -180, 180) * Math.PI / 180; 9985 if (this._bearingInRadians === b) return; 9986 this._unmodified = false; 9987 this._bearingInRadians = b; 9988 this._calcMatrices(); 9989 this._rotationMatrix = create$2(); 9990 rotate(this._rotationMatrix, this._rotationMatrix, -this._bearingInRadians); 9991 } 9992 get rotationMatrix() { 9993 return this._rotationMatrix; 9994 } 9995 get pitchInRadians() { 9996 return this._pitchInRadians; 9997 } 9998 get pitch() { 9999 return this._pitchInRadians / Math.PI * 180; 10000 } 10001 setPitch(pitch) { 10002 const p = clamp(pitch, this.minPitch, this.maxPitch) / 180 * Math.PI; 10003 if (this._pitchInRadians === p) return; 10004 this._unmodified = false; 10005 this._pitchInRadians = p; 10006 this._calcMatrices(); 10007 } 10008 get rollInRadians() { 10009 return this._rollInRadians; 10010 } 10011 get roll() { 10012 return this._rollInRadians / Math.PI * 180; 10013 } 10014 setRoll(roll) { 10015 const r = roll / 180 * Math.PI; 10016 if (this._rollInRadians === r) return; 10017 this._unmodified = false; 10018 this._rollInRadians = r; 10019 this._calcMatrices(); 10020 } 10021 get fovInRadians() { 10022 return this._fovInRadians; 10023 } 10024 get fov() { 10025 return radiansToDegrees(this._fovInRadians); 10026 } 10027 setFov(fov) { 10028 fov = clamp(fov, .1, 150); 10029 if (this.fov === fov) return; 10030 this._unmodified = false; 10031 this._fovInRadians = degreesToRadians(fov); 10032 this._calcMatrices(); 10033 } 10034 get zoom() { 10035 return this._zoom; 10036 } 10037 setZoom(zoom) { 10038 const constrainedZoom = this.applyConstrain(this._center, zoom).zoom; 10039 if (this._zoom === constrainedZoom) return; 10040 this._unmodified = false; 10041 this._zoom = constrainedZoom; 10042 this._tileZoom = Math.max(0, Math.floor(constrainedZoom)); 10043 this._scale = zoomScale(constrainedZoom); 10044 this.constrainInternal(); 10045 this._calcMatrices(); 10046 } 10047 get center() { 10048 return this._center; 10049 } 10050 setCenter(center) { 10051 if (center.lat === this._center.lat && center.lng === this._center.lng) return; 10052 this._unmodified = false; 10053 this._center = center; 10054 this.constrainInternal(); 10055 this._calcMatrices(); 10056 } 10057 /** 10058 * Elevation at current center point, meters above sea level 10059 */ 10060 get elevation() { 10061 return this._elevation; 10062 } 10063 setElevation(elevation) { 10064 if (elevation === this._elevation) return; 10065 this._elevation = elevation; 10066 this.constrainInternal(); 10067 this._calcMatrices(); 10068 } 10069 get padding() { 10070 return this._edgeInsets.toJSON(); 10071 } 10072 setPadding(padding) { 10073 if (this._edgeInsets.equals(padding)) return; 10074 this._unmodified = false; 10075 this._edgeInsets.interpolate(this._edgeInsets, padding, 1); 10076 this._calcMatrices(); 10077 } 10078 /** 10079 * The center of the screen in pixels with the top-left corner being (0,0) 10080 * and +y axis pointing downwards. This accounts for padding. 10081 */ 10082 get centerPoint() { 10083 return this._edgeInsets.getCenter(this._width, this._height); 10084 } 10085 /** 10086 * @internal 10087 */ 10088 get pixelsPerMeter() { 10089 return this._pixelPerMeter; 10090 } 10091 get unmodified() { 10092 return this._unmodified; 10093 } 10094 get cameraToCenterDistance() { 10095 return this._cameraToCenterDistance; 10096 } 10097 get nearZ() { 10098 return this._nearZ; 10099 } 10100 get farZ() { 10101 return this._farZ; 10102 } 10103 get autoCalculateNearFarZ() { 10104 return this._autoCalculateNearFarZ; 10105 } 10106 overrideNearFarZ(nearZ, farZ) { 10107 this._autoCalculateNearFarZ = false; 10108 this._nearZ = nearZ; 10109 this._farZ = farZ; 10110 this._calcMatrices(); 10111 } 10112 clearNearFarZOverride() { 10113 this._autoCalculateNearFarZ = true; 10114 this._calcMatrices(); 10115 } 10116 /** 10117 * Returns if the padding params match 10118 * 10119 * @param padding - the padding to check against 10120 * @returns true if they are equal, false otherwise 10121 */ 10122 isPaddingEqual(padding) { 10123 return this._edgeInsets.equals(padding); 10124 } 10125 /** 10126 * Helper method to update edge-insets in place 10127 * 10128 * @param start - the starting padding 10129 * @param target - the target padding 10130 * @param t - the step/weight 10131 */ 10132 interpolatePadding(start, target, t) { 10133 this._unmodified = false; 10134 this._edgeInsets.interpolate(start, target, t); 10135 this.constrainInternal(); 10136 this._calcMatrices(); 10137 } 10138 resize(width, height, constrain = true) { 10139 this._width = width; 10140 this._height = height; 10141 if (constrain) this.constrainInternal(); 10142 this._calcMatrices(); 10143 } 10144 /** 10145 * Returns the maximum geographical bounds the map is constrained to, or `null` if none set. 10146 * @returns max bounds 10147 */ 10148 getMaxBounds() { 10149 if (this._latRange?.length !== 2 || this._lngRange?.length !== 2) return null; 10150 return new LngLatBounds([this._lngRange[0], this._latRange[0]], [this._lngRange[1], this._latRange[1]]); 10151 } 10152 /** 10153 * Sets or clears the map's geographical constraints. 10154 * @param bounds - A {@link LngLatBounds} object describing the new geographic boundaries of the map. 10155 */ 10156 setMaxBounds(bounds) { 10157 if (bounds) { 10158 this._lngRange = [bounds.getWest(), bounds.getEast()]; 10159 this._latRange = [bounds.getSouth(), bounds.getNorth()]; 10160 this.constrainInternal(); 10161 } else { 10162 this._lngRange = null; 10163 this._latRange = [-MAX_VALID_LATITUDE, MAX_VALID_LATITUDE]; 10164 } 10165 } 10166 /** 10167 * When the map is pitched, some of the 3D features that intersect a query will not intersect 10168 * the query at the surface of the earth. Instead the feature may be closer and only intersect 10169 * the query because it extrudes into the air. 10170 * @param queryGeometry - For point queries, the line from the query point to the "camera point", 10171 * for other geometries, the envelope of the query geometry and the "camera point" 10172 * @returns a geometry that includes all of the original query as well as all possible ares of the 10173 * screen where the *base* of a visible extrusion could be. 10174 * 10175 */ 10176 getCameraQueryGeometry(cameraPoint, queryGeometry) { 10177 if (queryGeometry.length === 1) return [queryGeometry[0], cameraPoint]; 10178 else {
vendor: 15,237 bytes, lines 10179-10602
10179 const { minX, minY, maxX, maxY } = Bounds.fromPoints(queryGeometry).extend(cameraPoint); 10180 return [ 10181 new Point(minX, minY), 10182 new Point(maxX, minY), 10183 new Point(maxX, maxY), 10184 new Point(minX, maxY), 10185 new Point(minX, minY) 10186 ]; 10187 } 10188 } 10189 /** 10190 * @internal 10191 * Snaps the transform's center, zoom, etc. into the valid range. 10192 */ 10193 constrainInternal() { 10194 if (!this.center || !this._width || !this._height || this._constraining) return; 10195 this._constraining = true; 10196 const unmodified = this._unmodified; 10197 const { center, zoom } = this.applyConstrain(this.center, this.zoom); 10198 this.setCenter(center); 10199 this.setZoom(zoom); 10200 this._unmodified = unmodified; 10201 this._constraining = false; 10202 } 10203 /** 10204 * This function is called every time one of the transform's defining properties (center, pitch, etc.) changes. 10205 * This function should update the transform's internal data, such as matrices. 10206 * Any derived `_calcMatrices` function should also call the base function first. The base function only depends on the `_width` and `_height` fields. 10207 * While either dimension is zero there is no view to build, so the derived function is not called at all. 10208 */ 10209 _calcMatrices() { 10210 this._pixelPerMeter = mercatorZfromAltitude(1, this.center.lat) * this.worldSize; 10211 if (!this._width || !this._height) return; 10212 this._pixelsToGLUnits = [2 / this._width, -2 / this._height]; 10213 let m = identity(/* @__PURE__ */ new Float64Array(16)); 10214 scale(m, m, [ 10215 this._width / 2, 10216 -this._height / 2, 10217 1 10218 ]); 10219 translate(m, m, [ 10220 1, 10221 -1, 10222 0 10223 ]); 10224 this._clipSpaceToPixelsMatrix = m; 10225 m = identity(/* @__PURE__ */ new Float64Array(16)); 10226 scale(m, m, [ 10227 1, 10228 -1, 10229 1 10230 ]); 10231 translate(m, m, [ 10232 -1, 10233 -1, 10234 0 10235 ]); 10236 scale(m, m, [ 10237 2 / this._width, 10238 2 / this._height, 10239 1 10240 ]); 10241 this._pixelsToClipSpaceMatrix = m; 10242 const halfFov = this.fovInRadians / 2; 10243 this._cameraToCenterDistance = .5 / Math.tan(halfFov) * this._height; 10244 this._callbacks.calcMatrices(); 10245 } 10246 calculateCenterFromCameraLngLatAlt(lnglat, alt, bearing, pitch) { 10247 const cameraBearing = bearing !== void 0 ? bearing : this.bearing; 10248 const cameraPitch = pitch = pitch !== void 0 ? pitch : this.pitch; 10249 const { distanceToCenter, clampedElevation } = this._distanceToCenterFromAltElevationPitch(alt, this.elevation, cameraPitch); 10250 const { x, y } = cameraDirectionFromPitchBearing(cameraPitch, cameraBearing); 10251 const camMercator = MercatorCoordinate.fromLngLat(lnglat, alt); 10252 let metersPerMercUnit = altitudeFromMercatorZ(1, camMercator.y); 10253 let centerMercator; 10254 let dMercator; 10255 let iter = 0; 10256 const maxIter = 10; 10257 do { 10258 iter += 1; 10259 if (iter > maxIter) break; 10260 dMercator = distanceToCenter / metersPerMercUnit; 10261 const dx = x * dMercator; 10262 const dy = y * dMercator; 10263 centerMercator = new MercatorCoordinate(camMercator.x + dx, camMercator.y + dy); 10264 metersPerMercUnit = 1 / centerMercator.meterInMercatorCoordinateUnits(); 10265 } while (Math.abs(distanceToCenter - dMercator * metersPerMercUnit) > 1e-12); 10266 return { 10267 center: centerMercator.toLngLat(), 10268 elevation: clampedElevation, 10269 zoom: scaleZoom(this.height / 2 / Math.tan(this.fovInRadians / 2) / dMercator / this.tileSize) 10270 }; 10271 } 10272 recalculateZoomAndCenter(elevation) { 10273 if (this.elevation - elevation === 0) return; 10274 const mercUnitsPerPixel = 1 / this.worldSize; 10275 const originalPixelsPerMeter = mercatorZfromAltitude(1, this.center.lat) * this.worldSize; 10276 const originalCenterMercator = MercatorCoordinate.fromLngLat(this.center, this.elevation); 10277 const originalCenterPixelX = originalCenterMercator.x / mercUnitsPerPixel; 10278 const originalCenterPixelY = originalCenterMercator.y / mercUnitsPerPixel; 10279 const originalCenterPixelZ = originalCenterMercator.z / mercUnitsPerPixel; 10280 const cameraPitch = this.pitch; 10281 const cameraBearing = this.bearing; 10282 const { x, y, z } = cameraDirectionFromPitchBearing(cameraPitch, cameraBearing); 10283 const dCamPixel = this.cameraToCenterDistance; 10284 const camPixelX = originalCenterPixelX + dCamPixel * -x; 10285 const camPixelY = originalCenterPixelY + dCamPixel * -y; 10286 const camPixelZ = originalCenterPixelZ + dCamPixel * z; 10287 const { distanceToCenter, clampedElevation } = this._distanceToCenterFromAltElevationPitch(camPixelZ / originalPixelsPerMeter, elevation, cameraPitch); 10288 const distanceToCenterPixels = distanceToCenter * originalPixelsPerMeter; 10289 const centerPixelX = camPixelX + x * distanceToCenterPixels; 10290 const centerPixelY = camPixelY + y * distanceToCenterPixels; 10291 const center = new MercatorCoordinate(centerPixelX * mercUnitsPerPixel, centerPixelY * mercUnitsPerPixel, 0).toLngLat(); 10292 const mercUnitsPerMeter = mercatorZfromAltitude(1, center.lat); 10293 const zoom = scaleZoom(this.height / 2 / Math.tan(this.fovInRadians / 2) / distanceToCenter / mercUnitsPerMeter / this.tileSize); 10294 this._elevation = clampedElevation; 10295 this._center = center; 10296 this.setZoom(zoom); 10297 } 10298 _distanceToCenterFromAltElevationPitch(alt, elevation, pitch) { 10299 const dzNormalized = -Math.cos(degreesToRadians(pitch)); 10300 const altitudeAGL = alt - elevation; 10301 let distanceToCenter; 10302 let clampedElevation = elevation; 10303 if (dzNormalized * altitudeAGL >= 0 || Math.abs(dzNormalized) < .1) { 10304 distanceToCenter = 1e4; 10305 clampedElevation = alt + distanceToCenter * dzNormalized; 10306 } else distanceToCenter = -altitudeAGL / dzNormalized; 10307 return { 10308 distanceToCenter, 10309 clampedElevation 10310 }; 10311 } 10312 getCameraPoint() { 10313 const pitch = this.pitchInRadians; 10314 const offset = Math.tan(pitch) * (this.cameraToCenterDistance || 1); 10315 return this.centerPoint.add(new Point(offset * Math.sin(this.rollInRadians), offset * Math.cos(this.rollInRadians))); 10316 } 10317 getMercatorTileCoordinates(overscaledTileID) { 10318 if (!overscaledTileID) return [ 10319 0, 10320 0, 10321 1, 10322 1 10323 ]; 10324 const scale = overscaledTileID.canonical.z >= 0 ? 1 << overscaledTileID.canonical.z : Math.pow(2, overscaledTileID.canonical.z); 10325 return [ 10326 overscaledTileID.canonical.x / scale, 10327 overscaledTileID.canonical.y / scale, 10328 1 / scale / EXTENT, 10329 1 / scale / EXTENT 10330 ]; 10331 } 10332}; 10333//#endregion 10334//#region src/geo/projection/mercator_covering_tiles_details_provider.ts 10335var MercatorCoveringTilesDetailsProvider = class { 10336 distanceToTile2d(pointX, pointY, _tileID, boundingVolume) { 10337 const aabb = boundingVolume; 10338 const distanceX = aabb.distanceX([pointX, pointY]); 10339 const distanceY = aabb.distanceY([pointX, pointY]); 10340 return Math.hypot(distanceX, distanceY); 10341 } 10342 /** 10343 * Returns the wrap value for a given tile, computed so that tiles will remain loaded when crossing the antimeridian. 10344 */ 10345 getWrap(centerCoord, tileID, parentWrap) { 10346 return parentWrap; 10347 } 10348 /** 10349 * Returns the AABB of the specified tile. 10350 * @param tileID - Tile x, y and z for zoom. 10351 */ 10352 getTileBoundingVolume(tileID, wrap, elevation, options) { 10353 let minElevation = Math.min(0, elevation); 10354 let maxElevation = Math.max(0, elevation); 10355 if (options?.terrain) { 10356 const overscaledTileID = new OverscaledTileID(tileID.z, wrap, tileID.z, tileID.x, tileID.y); 10357 const minMax = options.terrain.getMinMaxElevation(overscaledTileID); 10358 minElevation = minMax.minElevation ?? minElevation; 10359 maxElevation = minMax.maxElevation ?? maxElevation; 10360 } 10361 const numTiles = 1 << tileID.z; 10362 return new Aabb([ 10363 wrap + tileID.x / numTiles, 10364 tileID.y / numTiles, 10365 minElevation 10366 ], [ 10367 wrap + (tileID.x + 1) / numTiles, 10368 (tileID.y + 1) / numTiles, 10369 maxElevation 10370 ]); 10371 } 10372 allowVariableZoom(transform, options) { 10373 const zfov = transform.fov * (Math.abs(Math.cos(transform.rollInRadians)) * transform.height + Math.abs(Math.sin(transform.rollInRadians)) * transform.width) / transform.height; 10374 const maxConstantZoomPitch = clamp(78.5 - zfov / 2, 0, 60); 10375 return !!options.terrain || transform.pitch > maxConstantZoomPitch; 10376 } 10377 allowWorldCopies() { 10378 return true; 10379 } 10380 prepareNextFrame() {} 10381}; 10382//#endregion 10383//#region src/data/pos3d_attributes.ts 10384const pos3dAttributes = createLayout([{ 10385 name: "a_pos3d", 10386 type: "Int16", 10387 components: 3 10388}]); 10389//#endregion 10390//#region src/tile/terrain_tile_manager.ts 10391/** 10392* @internal 10393* This class is a helper for the Terrain-class, it: 10394* 10395* - loads raster-dem tiles 10396* - manages all renderToTexture tiles. 10397* - caches previous rendered tiles. 10398* - finds all necessary renderToTexture tiles for a OverscaledTileID area 10399* - finds the corresponding raster-dem tile for OverscaledTileID 10400*/ 10401var TerrainTileManager = class extends Evented { 10402 constructor(tileManager) { 10403 super(); 10404 this._lastTilesetChange = now(); 10405 this.tileManager = tileManager; 10406 this._tiles = {}; 10407 this._renderableTilesKeys = []; 10408 this._sourceTileCache = {}; 10409 this.minzoom = 0; 10410 this.maxzoom = 22; 10411 this.deltaZoom = 1; 10412 this.tileSize = tileManager._source.tileSize * 2 ** this.deltaZoom; 10413 tileManager.usedForTerrain = true; 10414 tileManager.tileSize = this.tileSize; 10415 } 10416 destruct() { 10417 this.tileManager.usedForTerrain = false; 10418 this.tileManager.tileSize = null; 10419 this.releaseAllRTT(); 10420 } 10421 getSource() { 10422 return this.tileManager._source; 10423 } 10424 /** 10425 * Load Terrain Tiles, create internal render-to-texture tiles, free GPU memory. 10426 * @param transform - the operation to do 10427 * @param terrain - the terrain 10428 * @returns true when the set of renderable tiles changed 10429 */ 10430 update(transform, terrain) { 10431 this.tileManager.update(transform, terrain); 10432 this._renderableTilesKeys = []; 10433 const keys = {}; 10434 let changed = false; 10435 for (const tileID of coveringTiles(transform, { 10436 tileSize: this.tileSize, 10437 minzoom: this.minzoom, 10438 maxzoom: this.maxzoom, 10439 reparseOverscaled: false, 10440 terrain 10441 })) { 10442 keys[tileID.key] = true; 10443 this._renderableTilesKeys.push(tileID.key); 10444 if (!this._tiles[tileID.key]) { 10445 tileID.terrainRttPosMatrix32f = /* @__PURE__ */ new Float32Array(16); 10446 ortho(tileID.terrainRttPosMatrix32f, 0, EXTENT, EXTENT, 0, 0, 1); 10447 this._tiles[tileID.key] = new Tile(tileID, this.tileSize); 10448 this._lastTilesetChange = now(); 10449 changed = true; 10450 } 10451 } 10452 for (const key in this._tiles) if (!keys[key]) { 10453 this._tiles[key].releaseRTT(this.tileManager.map.painter); 10454 delete this._tiles[key]; 10455 changed = true; 10456 } 10457 return changed; 10458 } 10459 /** 10460 * Release the RTT objects for `tileID` (and its ancestors/descendants), 10461 */ 10462 releaseRTT(tileID) { 10463 for (const key in this._tiles) { 10464 const tile = this._tiles[key]; 10465 if (tile.tileID.equals(tileID) || tile.tileID.isChildOf(tileID) || tileID.isChildOf(tile.tileID)) tile.releaseRTT(this.tileManager.map.painter); 10466 } 10467 } 10468 /** 10469 * Release the A RTT objects for all tiles. 10470 */ 10471 releaseAllRTT() { 10472 for (const key in this._tiles) this._tiles[key].releaseRTT(this.tileManager.map.painter); 10473 } 10474 /** 10475 * get a list of tiles, which are loaded and should be rendered in the current scene 10476 * @returns the renderable tiles 10477 */ 10478 getRenderableTiles() { 10479 return this._renderableTilesKeys.map((key) => this.getTileByID(key)); 10480 } 10481 /** 10482 * get terrain tile by the TileID key 10483 * @param id - the tile id 10484 * @returns the tile 10485 */ 10486 getTileByID(id) { 10487 return this._tiles[id]; 10488 } 10489 /** 10490 * Searches for the corresponding current renderable terrain-tiles 10491 * @param tileID - the tile to look for 10492 * @returns the tiles that were found 10493 */ 10494 getTerrainCoords(tileID, terrainTileRanges) { 10495 if (terrainTileRanges) return this._getTerrainCoordsForTileRanges(tileID, terrainTileRanges); 10496 else return this._getTerrainCoordsForRegularTile(tileID); 10497 } 10498 /** 10499 * Searches for the corresponding current renderable terrain-tiles. 10500 * Includes terrain tiles that are either: 10501 * - the same as the tileID 10502 * - a parent of the tileID 10503 * - a child of the tileID 10504 * @param tileID - the tile to look for 10505 * @returns the tiles that were found 10506 */ 10507 _getTerrainCoordsForRegularTile(tileID) { 10508 const coords = {}; 10509 for (const key of this._renderableTilesKeys) { 10510 const terrainTileID = this._tiles[key].tileID; 10511 const coord = tileID.clone(); 10512 const mat = createMat4f64(); 10513 if (terrainTileID.canonical.equals(tileID.canonical)) ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1); 10514 else if (terrainTileID.canonical.isChildOf(tileID.canonical)) { 10515 const dz = terrainTileID.canonical.z - tileID.canonical.z; 10516 const dx = terrainTileID.canonical.x - (terrainTileID.canonical.x >> dz << dz); 10517 const dy = terrainTileID.canonical.y - (terrainTileID.canonical.y >> dz << dz); 10518 const size = EXTENT >> dz; 10519 ortho(mat, 0, size, size, 0, 0, 1); 10520 translate(mat, mat, [ 10521 -dx * size, 10522 -dy * size, 10523 0 10524 ]); 10525 } else if (tileID.canonical.isChildOf(terrainTileID.canonical)) { 10526 const dz = tileID.canonical.z - terrainTileID.canonical.z; 10527 const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz); 10528 const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz); 10529 const size = EXTENT >> dz; 10530 ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1); 10531 translate(mat, mat, [ 10532 dx * size, 10533 dy * size, 10534 0 10535 ]); 10536 scale(mat, mat, [ 10537 1 / 2 ** dz, 10538 1 / 2 ** dz, 10539 0 10540 ]); 10541 } else continue; 10542 coord.terrainRttPosMatrix32f = new Float32Array(mat); 10543 coords[key] = coord; 10544 } 10545 return coords; 10546 } 10547 /** 10548 * Searches for the corresponding current renderable terrain-tiles. 10549 * Includes terrain tiles that are within terrain tile ranges. 10550 * @param tileID - the tile to look for 10551 * @returns the tiles that were found 10552 */ 10553 _getTerrainCoordsForTileRanges(tileID, terrainTileRanges) { 10554 const coords = {}; 10555 for (const key of this._renderableTilesKeys) { 10556 const terrainTileID = this._tiles[key].tileID; 10557 if (!this._isWithinTileRanges(terrainTileID, terrainTileRanges)) continue; 10558 const coord = tileID.clone(); 10559 const mat = createMat4f64(); 10560 if (terrainTileID.canonical.z === tileID.canonical.z) { 10561 const dx = tileID.canonical.x - terrainTileID.canonical.x + tileID.wrap * (1 << tileID.canonical.z); 10562 const dy = tileID.canonical.y - terrainTileID.canonical.y; 10563 ortho(mat, 0, EXTENT, EXTENT, 0, 0, 1); 10564 translate(mat, mat, [ 10565 dx * EXTENT, 10566 dy * EXTENT, 10567 0 10568 ]); 10569 } else if (terrainTileID.canonical.z > tileID.canonical.z) { 10570 const dz = terrainTileID.canonical.z - tileID.canonical.z; 10571 const dx = terrainTileID.canonical.x - (terrainTileID.canonical.x >> dz << dz) + tileID.wrap * (1 << terrainTileID.canonical.z); 10572 const dy = terrainTileID.canonical.y - (terrainTileID.canonical.y >> dz << dz); 10573 const dx2 = tileID.canonical.x - (terrainTileID.canonical.x >> dz); 10574 const dy2 = tileID.canonical.y - (terrainTileID.canonical.y >> dz); 10575 const size = EXTENT >> dz; 10576 ortho(mat, 0, size, size, 0, 0, 1); 10577 translate(mat, mat, [ 10578 -dx * size + dx2 * EXTENT, 10579 -dy * size + dy2 * EXTENT, 10580 0 10581 ]); 10582 } else { 10583 const dz = tileID.canonical.z - terrainTileID.canonical.z; 10584 const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz) + tileID.wrap * (1 << tileID.canonical.z); 10585 const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz); 10586 const dx2 = (tileID.canonical.x >> dz) - terrainTileID.canonical.x; 10587 const dy2 = (tileID.canonical.y >> dz) - terrainTileID.canonical.y; 10588 const size = EXTENT << dz; 10589 ortho(mat, 0, size, size, 0, 0, 1); 10590 translate(mat, mat, [ 10591 dx * EXTENT + dx2 * size, 10592 dy * EXTENT + dy2 * size, 10593 0 10594 ]); 10595 } 10596 coord.terrainRttPosMatrix32f = new Float32Array(mat); 10597 coords[key] = coord; 10598 } 10599 return coords; 10600 } 10601 /** 10602 * find the covering raster-dem tile
vendor: 37,543 bytes, lines 10603-11610
10603 * @param tileID - the tile to look for 10604 * @param searchForDEM - Optional parameter to search for (parent) source tiles with loaded dem. 10605 * @returns the tile 10606 */ 10607 getSourceTile(tileID, searchForDEM) { 10608 const source = this.tileManager._source; 10609 let z = tileID.overscaledZ - this.deltaZoom; 10610 if (z > source.maxzoom) z = source.maxzoom; 10611 if (z < source.minzoom) return void 0; 10612 this._sourceTileCache[tileID.key] ||= tileID.scaledTo(z).key; 10613 let tile = this.findTileInCaches(this._sourceTileCache[tileID.key]); 10614 if (!tile?.dem && searchForDEM) while (z >= source.minzoom && !tile?.dem) tile = this.findTileInCaches(tileID.scaledTo(z--).key); 10615 return tile; 10616 } 10617 findTileInCaches(key) { 10618 let tile = this.tileManager.getTileByID(key); 10619 if (tile) return tile; 10620 tile = this.tileManager._outOfViewCache.getByKey(key); 10621 return tile; 10622 } 10623 /** 10624 * gets whether any tiles were loaded after a specific time. This is used to update the depth framebuffer. 10625 * @param time - the time 10626 * @returns true if any tiles came into view at or after the specified time 10627 */ 10628 anyTilesAfterTime(time = now()) { 10629 return this._lastTilesetChange >= time; 10630 } 10631 /** 10632 * Checks whether a tile is within the canonical tile ranges. 10633 * @param tileID - Tile to check 10634 * @param canonicalTileRanges - Canonical tile ranges 10635 * @returns 10636 */ 10637 _isWithinTileRanges(tileID, canonicalTileRanges) { 10638 const range = canonicalTileRanges[tileID.canonical.z]; 10639 return !!range && (tileID.wrap > range.minWrap || tileID.wrap < range.maxWrap || tileID.canonical.x >= range.minTileXWrapped && tileID.canonical.x <= range.maxTileXWrapped && tileID.canonical.y >= range.minTileY && tileID.canonical.y <= range.maxTileY); 10640 } 10641}; 10642//#endregion 10643//#region src/render/terrain.ts 10644const MAX_BISECTIONS = 40; 10645const HIT_EPSILON_M = 1e-6; 10646/** Keeps the elevation bracket non-degenerate when the terrain is entirely flat, such as unloaded DEMs. */ 10647const BRACKET_PADDING_M = 10; 10648/** Tile coordinates run from 0 up to but not including `EXTENT`; a point on the far edge is clamped back into the tile. */ 10649const MAX_TILE_COORD = EXTENT * (1 - 1e-12); 10650/** 10651* Offset, in DEM pixels, from a tile coordinate scaled by `dim` to the pixel index `DEMData.sampleBilinear` expects. 10652* DEM pixel `i` describes the cell centred at tile coordinate `(i + 0.5) / dim`, the same placement hillshade and 10653* color-relief use, so a sample between two cell centres interpolates the pixels on either side of it. 10654*/ 10655const DEM_CELL_CENTER_OFFSET = -.5; 10656/** 10657* @internal 10658* This is the main class which handles most of the 3D Terrain logic. It has the following topics: 10659* 10660* 1. loads raster-dem tiles via the internal tileManager this.tileManager 10661* 2. creates a depth-framebuffer, which is used to calculate the visibility of coordinates 10662* 3. stores all render-to-texture tiles in the this.tileManager._tiles 10663* 4. calculates the elevation for a specific tile-coordinate 10664* 5. creates a terrain-mesh 10665* 10666* A note about the GPU resource-usage: 10667* 10668* Framebuffers: 10669* 10670* - one for the depth framebuffer with the size of the map-div. 10671* - one for rendering a tile to texture with the size of tileSize (= 512x512). 10672* 10673* Textures: 10674* 10675* - one texture for an empty raster-dem tile with size 1x1 10676* - one texture for an empty depth-buffer, when terrain is disabled with size 1x1 10677* - one texture for an each loaded raster-dem with size of the source.tileSize 10678* - one texture for the depth-framebuffer with the size of the map-div. 10679* - finally for each render-to-texture tile (= this._tiles) a set of textures 10680* for each render stack (The stack-concept is documented in painter.ts). 10681* 10682* Normally there exists 1-3 Textures per tile, depending on the stylesheet. 10683* Each Textures has the size 2*tileSize (= 1024x1024). Also there exists a 10684* cache of the last 150 newest rendered tiles. 10685* 10686*/ 10687var Terrain = class { 10688 constructor(painter, tileManager, options, terrainSkirtLength = "auto") { 10689 this._meshCache = {}; 10690 this.painter = painter; 10691 this.tileManager = new TerrainTileManager(tileManager); 10692 this.options = options; 10693 this.exaggeration = typeof options.exaggeration === "number" ? options.exaggeration : 1; 10694 this._terrainSkirtLength = terrainSkirtLength; 10695 this.qualityFactor = 2; 10696 this.meshSize = 128; 10697 this._demMatrixCache = /* @__PURE__ */ new Map(); 10698 this._elevationSamplerCache = /* @__PURE__ */ new Map(); 10699 } 10700 destroy() { 10701 if (this._fbo) { 10702 this._fbo.destroy(); 10703 this._fbo = null; 10704 } 10705 if (this._fboDepthTexture) { 10706 this._fboDepthTexture.destroy(); 10707 this._fboDepthTexture = null; 10708 } 10709 if (this._emptyDemTexture) { 10710 this._emptyDemTexture.destroy(); 10711 this._emptyDemTexture = null; 10712 } 10713 if (this._emptyDepthTexture) { 10714 this._emptyDepthTexture.destroy(); 10715 this._emptyDepthTexture = null; 10716 } 10717 for (const key in this._meshCache) this._meshCache[key].destroy(); 10718 this._meshCache = {}; 10719 this.tileManager.destruct(); 10720 } 10721 /** 10722 * Get the elevation-value from original dem-data for a given tile-coordinate. 10723 * Coordinates that fall outside `[0, extent)` are normalized to the 10724 * appropriate neighbor tile before lookup. 10725 * @param tileID - the tile to get the elevation for 10726 * @param x - x coordinate relative to the tile, may be outside `[0, extent)` 10727 * @param y - y coordinate relative to the tile, may be outside `[0, extent)` 10728 * @param extent - optional, default 8192 10729 * @returns the elevation 10730 */ 10731 getDEMElevation(tileID, x, y, extent = EXTENT) { 10732 const normalized = tileID.normalizeCoordinates(x, y, extent); 10733 if (!normalized) return 0; 10734 const sampler = this.getElevationSampler(normalized.tileID); 10735 return sampler ? sampler(normalized.x, normalized.y, extent) : 0; 10736 } 10737 /** 10738 * Get the elevation for given {@link LngLat} in respect of exaggeration. 10739 * @param lnglat - the location 10740 * @param zoom - the zoom, use {@link getElevationForLngLat} if you don't want a specific zoom level, but more accurate results. 10741 * @returns the elevation 10742 */ 10743 getElevationForLngLatZoom(lnglat, zoom) { 10744 if (!isInBoundsForZoomLngLat(zoom, lnglat.wrap())) return 0; 10745 const { tileID, mercatorX, mercatorY } = this._getOverscaledTileIDFromLngLatZoom(lnglat, zoom); 10746 return this.getElevation(tileID, mercatorX % EXTENT, mercatorY % EXTENT, EXTENT); 10747 } 10748 /** 10749 * Get the elevation for given {@link LngLat} in respect of exaggeration. 10750 * Where the location is covered by a rendered tile with loaded DEM data this samples the 10751 * rendered surface, so the result agrees with what is drawn; elsewhere it traverses up the 10752 * zoom levels to find the first tile with data to return. 10753 * @param lnglat - the location 10754 * @returns the elevation 10755 */ 10756 getElevationForLngLat(lnglat, transform) { 10757 const index = this.getCoverageIndex(); 10758 if (index) { 10759 const mercator = MercatorCoordinate.fromLngLat(lnglat); 10760 const sample = sampleAt(index, this.exaggeration, mercator.x, mercator.y); 10761 if (sample.demLoaded) return sample.elevation; 10762 } 10763 const terrainCoveringTiles = coveringTiles(transform, { 10764 maxzoom: this.tileManager.maxzoom, 10765 minzoom: this.tileManager.minzoom, 10766 tileSize: 512, 10767 terrain: this 10768 }); 10769 let zoom = 0; 10770 for (const tile of terrainCoveringTiles) if (tile.canonical.z > zoom) zoom = Math.min(tile.canonical.z, this.tileManager.maxzoom); 10771 return this.getElevationForLngLatZoom(lnglat, zoom); 10772 } 10773 /** 10774 * Get the elevation for given coordinate in respect of exaggeration. 10775 * @param tileID - the tile id 10776 * @param x - x coordinate relative to the tile, may be outside `[0, extent)` 10777 * @param y - y coordinate relative to the tile, may be outside `[0, extent)` 10778 * @param extent - optional, default 8192 10779 * @returns the elevation 10780 */ 10781 getElevation(tileID, x, y, extent = EXTENT) { 10782 return this.getDEMElevation(tileID, x, y, extent) * this.exaggeration; 10783 } 10784 /** 10785 * Clear CPU elevation samplers that may retain a previously selected DEM tile. 10786 * @internal 10787 */ 10788 resetElevationCache() { 10789 this._elevationSamplerCache.clear(); 10790 this._coverageIndex = void 0; 10791 } 10792 /** 10793 * Index of the tiles the terrain currently renders, for sampling the terrain surface on the CPU. 10794 * Built on first use and kept until {@link resetElevationCache}. 10795 * @returns the index, or null when no terrain tile is renderable 10796 */ 10797 getCoverageIndex() { 10798 if (this._coverageIndex === void 0) this._coverageIndex = this._buildCoverageIndex(); 10799 return this._coverageIndex; 10800 } 10801 _buildCoverageIndex() { 10802 const zooms = []; 10803 const samplerPerTile = /* @__PURE__ */ new Map(); 10804 let minElevation = 0; 10805 let maxElevation = 0; 10806 for (const tile of this.tileManager.getRenderableTiles()) { 10807 if (!tile) continue; 10808 const { canonical, wrap } = tile.tileID; 10809 if (!zooms.includes(canonical.z)) zooms.push(canonical.z); 10810 const sampler = this.getElevationSampler(tile.tileID); 10811 samplerPerTile.set(`${wrap}/${canonical.z}/${canonical.x}/${canonical.y}`, sampler); 10812 const { minElevation: tileMin, maxElevation: tileMax } = this.getMinMaxElevation(tile.tileID); 10813 minElevation = Math.min(minElevation, tileMin ?? 0); 10814 maxElevation = Math.max(maxElevation, tileMax ?? 0); 10815 } 10816 if (samplerPerTile.size === 0) return null; 10817 zooms.sort((a, b) => b - a); 10818 return { 10819 zooms, 10820 samplerPerTile, 10821 minElevation: minElevation - BRACKET_PADDING_M, 10822 maxElevation: maxElevation + BRACKET_PADDING_M 10823 }; 10824 } 10825 /** 10826 * Get a function that samples the raw DEM elevation of a tile, without exaggeration. 10827 * The sampler places DEM pixels at cell centres, matching `get_elevation` in the vertex shader prelude. 10828 * @param tileID - the tile id 10829 * @returns the sampler, or null when the tile's DEM data is not loaded 10830 */ 10831 getElevationSampler(tileID) { 10832 const key = tileID.key; 10833 const cachedSampler = this._elevationSamplerCache.get(key); 10834 if (cachedSampler) return cachedSampler; 10835 const sourceTile = this.tileManager.getSourceTile(tileID, true); 10836 const dem = sourceTile?.dem; 10837 if (!sourceTile || !dem) return null; 10838 const matrix = this._getDEMTileMatrix(tileID, sourceTile); 10839 const demPixelScaleX = matrix[0] * dem.dim; 10840 const demPixelScaleY = matrix[5] * dem.dim; 10841 const demPixelOffsetX = matrix[12] * dem.dim + DEM_CELL_CENTER_OFFSET; 10842 const demPixelOffsetY = matrix[13] * dem.dim + DEM_CELL_CENTER_OFFSET; 10843 const sampler = (x, y, extent) => { 10844 const extentScale = extent === 8192 ? 1 : EXTENT / extent; 10845 return dem.sampleBilinear(x * extentScale * demPixelScaleX + demPixelOffsetX, y * extentScale * demPixelScaleY + demPixelOffsetY); 10846 }; 10847 this._elevationSamplerCache.set(key, sampler); 10848 return sampler; 10849 } 10850 /** 10851 * Get the matrix that maps a tile's coordinates into the DEM tile it is rendered with. 10852 * The transform is derived from the loaded DEM tile's own zoom level, not from the source's 10853 * declared maxzoom: getSourceTile falls back to a loaded parent tile while the deepest DEM 10854 * tile is still loading, and the scale and offset must match the tile that is actually used. 10855 * @param tileID - the tile id 10856 * @param sourceTile - the DEM tile that is used for this tile, either its own tile or a loaded parent 10857 * @returns the matrix that maps the tile's coordinates onto the DEM tile 10858 */ 10859 _getDEMTileMatrix(tileID, sourceTile) { 10860 const matrixKey = `${sourceTile.tileID.key}/${tileID.key}`; 10861 const cachedMatrix = this._demMatrixCache.get(matrixKey); 10862 if (cachedMatrix) return cachedMatrix; 10863 const dz = tileID.canonical.z - sourceTile.tileID.canonical.z; 10864 const dx = tileID.canonical.x - (tileID.canonical.x >> dz << dz); 10865 const dy = tileID.canonical.y - (tileID.canonical.y >> dz << dz); 10866 const demMatrix = fromScaling(/* @__PURE__ */ new Float64Array(16), [ 10867 1 / (EXTENT << dz), 10868 1 / (EXTENT << dz), 10869 0 10870 ]); 10871 translate(demMatrix, demMatrix, [ 10872 dx * EXTENT, 10873 dy * EXTENT, 10874 0 10875 ]); 10876 this._demMatrixCache.set(matrixKey, demMatrix); 10877 return demMatrix; 10878 } 10879 /** 10880 * returns a Terrain Object for a tile. Unless the tile corresponds to data (e.g. tile is loading), return a flat dem object 10881 * @param tileID - the tile to get the terrain for 10882 * @returns the terrain data to use in the program 10883 */ 10884 getTerrainData(tileID) { 10885 if (!this._emptyDemTexture) { 10886 const context = this.painter.context; 10887 const image = new RGBAImage({ 10888 width: 1, 10889 height: 1 10890 }, /* @__PURE__ */ new Uint8Array(4)); 10891 this._emptyDepthTexture = new Texture(context, image, context.gl.RGBA, { premultiply: false }); 10892 this._emptyDemUnpack = [ 10893 0, 10894 0, 10895 0, 10896 0 10897 ]; 10898 this._emptyDemTexture = new Texture(context, new RGBAImage({ 10899 width: 1, 10900 height: 1 10901 }), context.gl.RGBA, { premultiply: false }); 10902 this._emptyDemTexture.bind(context.gl.NEAREST, context.gl.CLAMP_TO_EDGE); 10903 this._emptyDemMatrix = identity([]); 10904 } 10905 const sourceTile = this.tileManager.getSourceTile(tileID, true); 10906 if (sourceTile?.dem && (!sourceTile.demTexture || sourceTile.needsTerrainPrepare)) { 10907 const context = this.painter.context; 10908 sourceTile.demTexture ||= this.painter.getTileTexture(sourceTile.dem.stride); 10909 if (sourceTile.demTexture) sourceTile.demTexture.update(sourceTile.dem.getPixels(), { premultiply: false }); 10910 else sourceTile.demTexture = new Texture(context, sourceTile.dem.getPixels(), context.gl.RGBA, { premultiply: false }); 10911 sourceTile.demTexture.bind(context.gl.NEAREST, context.gl.CLAMP_TO_EDGE); 10912 sourceTile.needsTerrainPrepare = false; 10913 } 10914 const terrainMatrix = sourceTile ? this._getDEMTileMatrix(tileID, sourceTile) : this._emptyDemMatrix; 10915 return { 10916 "u_depth": 2, 10917 "u_terrain": 3, 10918 "u_terrain_dim": sourceTile?.dem?.dim || 1, 10919 "u_terrain_matrix": terrainMatrix, 10920 "u_terrain_unpack": sourceTile?.dem?.getUnpackVector() || this._emptyDemUnpack, 10921 "u_terrain_exaggeration": this.exaggeration, 10922 texture: (sourceTile?.demTexture || this._emptyDemTexture).texture, 10923 depthTexture: (this._fboDepthTexture || this._emptyDepthTexture).texture, 10924 tile: sourceTile 10925 }; 10926 } 10927 /** 10928 * get a framebuffer as big as the map-div, which will be used to render depth into a texture 10929 * @returns the frame buffer 10930 */ 10931 getFramebuffer() { 10932 const painter = this.painter; 10933 const width = painter.width / devicePixelRatio; 10934 const height = painter.height / devicePixelRatio; 10935 if (this._fbo && (this._fbo.width !== width || this._fbo.height !== height)) { 10936 this._fbo.destroy(); 10937 this._fboDepthTexture.destroy(); 10938 delete this._fbo; 10939 delete this._fboDepthTexture; 10940 } 10941 if (!this._fboDepthTexture) { 10942 this._fboDepthTexture = new Texture(painter.context, { 10943 width, 10944 height, 10945 data: null 10946 }, painter.context.gl.RGBA, { premultiply: false }); 10947 this._fboDepthTexture.bind(painter.context.gl.NEAREST, painter.context.gl.CLAMP_TO_EDGE); 10948 } 10949 if (!this._fbo) { 10950 this._fbo = painter.context.createFramebuffer(width, height, true, false); 10951 this._fbo.depthAttachment.set(painter.context.createRenderbuffer(painter.context.gl.DEPTH_COMPONENT16, width, height)); 10952 } 10953 this._fbo.colorAttachment.set(this._fboDepthTexture.texture); 10954 return this._fbo; 10955 } 10956 /** 10957 * create a regular mesh which will be used by all terrain-tiles 10958 * @returns the created regular mesh 10959 */ 10960 getTerrainMesh(tileId) { 10961 const globeEnabled = this.painter.style.projection?.transitionState > 0; 10962 const northPole = globeEnabled && tileId.canonical.y === 0; 10963 const southPole = globeEnabled && tileId.canonical.y === (1 << tileId.canonical.z) - 1; 10964 const key = `m_${northPole ? "n" : ""}_${southPole ? "s" : ""}`; 10965 if (this._meshCache[key]) return this._meshCache[key]; 10966 const context = this.painter.context; 10967 const vertexArray = new Pos3dArray(); 10968 const indexArray = new TriangleIndexArray(); 10969 const meshSize = this.meshSize; 10970 const delta = EXTENT / meshSize; 10971 const meshSize2 = meshSize * meshSize; 10972 for (let y = 0; y <= meshSize; y++) for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, y * delta, 0); 10973 for (let y = 0; y < meshSize2; y += meshSize + 1) for (let x = 0; x < meshSize; x++) { 10974 indexArray.emplaceBack(x + y, meshSize + x + y + 1, meshSize + x + y + 2); 10975 indexArray.emplaceBack(x + y, meshSize + x + y + 2, x + y + 1); 10976 } 10977 if (this._terrainSkirtLength !== "none") this._buildSkirts(vertexArray, indexArray, meshSize, delta, northPole, southPole); 10978 const mesh = new Mesh(context.createVertexBuffer(vertexArray, pos3dAttributes.members), context.createIndexBuffer(indexArray), SegmentVector.simpleSegment(0, 0, vertexArray.length, indexArray.length)); 10979 this._meshCache[key] = mesh; 10980 return mesh; 10981 } 10982 /** 10983 * Calculates the height of the tile skirts for the "auto" strategy. 10984 * @see {@link MapOptions.terrainSkirtLength} 10985 * @param zoom - current zoomlevel 10986 * @returns the elevation delta in meters 10987 */ 10988 getSkirtLength(zoom) { 10989 return 2 * Math.PI * earthRadius / Math.pow(2, Math.max(zoom, 0)) / 5; 10990 } 10991 getMinTileElevationForLngLatZoom(lnglat, zoom) { 10992 if (!isInBoundsForZoomLngLat(zoom, lnglat.wrap())) return 0; 10993 const { tileID } = this._getOverscaledTileIDFromLngLatZoom(lnglat, zoom); 10994 return this.getMinMaxElevation(tileID).minElevation ?? 0; 10995 } 10996 /** 10997 * Get the minimum and maximum elevation contained in a tile. This includes any 10998 * exaggeration included in the terrain. 10999 * 11000 * @param tileID - ID of the tile to be used as a source for the min/max elevation 11001 * @returns the minimum and maximum elevation found in the tile, including the terrain's 11002 * exaggeration 11003 */ 11004 getMinMaxElevation(tileID) { 11005 const tile = this.tileManager.getSourceTile(tileID, true); 11006 const minMax = { 11007 minElevation: null, 11008 maxElevation: null 11009 }; 11010 if (tile?.dem) { 11011 minMax.minElevation = tile.dem.min * this.exaggeration; 11012 minMax.maxElevation = tile.dem.max * this.exaggeration; 11013 } 11014 return minMax; 11015 } 11016 _getOverscaledTileIDFromLngLatZoom(lnglat, zoom) { 11017 const mercatorCoordinate = MercatorCoordinate.fromLngLat(lnglat.wrap()); 11018 const worldSize = (1 << zoom) * EXTENT; 11019 const mercatorX = mercatorCoordinate.x * worldSize; 11020 const mercatorY = mercatorCoordinate.y * worldSize; 11021 const tileX = Math.floor(mercatorX / EXTENT), tileY = Math.floor(mercatorY / EXTENT); 11022 return { 11023 tileID: new OverscaledTileID(zoom, 0, zoom, tileX, tileY), 11024 mercatorX, 11025 mercatorY 11026 }; 11027 } 11028 /** Add an extra frame around the mesh to avoid hairline gaps (stitching) on tile boundaries with different zoomlevels. 11029 * @see {@link MapOptions.terrainSkirtLength} 11030 */ 11031 _buildSkirts(vertexArray, indexArray, meshSize, delta, northPole, southPole) { 11032 const offsetTop = vertexArray.length; 11033 const offsetTopEdge = 0; 11034 const offsetBottom = offsetTop + (meshSize + 1); 11035 const offsetBottomEdge = (meshSize + 1) * meshSize; 11036 const northY = northPole ? NORTH_POLE_Y : 0; 11037 const northZ = northPole ? 0 : 1; 11038 const southY = southPole ? SOUTH_POLE_Y : EXTENT; 11039 const southZ = southPole ? 0 : 1; 11040 for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, northY, northZ); 11041 for (let x = 0; x <= meshSize; x++) vertexArray.emplaceBack(x * delta, southY, southZ); 11042 for (let x = 0; x < meshSize; x++) { 11043 indexArray.emplaceBack(offsetBottomEdge + x, offsetBottom + x, offsetBottom + x + 1); 11044 indexArray.emplaceBack(offsetBottomEdge + x, offsetBottom + x + 1, offsetBottomEdge + x + 1); 11045 indexArray.emplaceBack(offsetTopEdge + x, offsetTop + x + 1, offsetTop + x); 11046 indexArray.emplaceBack(offsetTopEdge + x, offsetTopEdge + x + 1, offsetTop + x + 1); 11047 } 11048 const offsetLeft = vertexArray.length; 11049 const offsetRight = offsetLeft + (meshSize + 1) * 2; 11050 for (const x of [0, 1]) for (let y = 0; y <= meshSize; y++) for (const z of [0, 1]) vertexArray.emplaceBack(x * EXTENT, y * delta, z); 11051 for (let y = 0; y < meshSize * 2; y += 2) { 11052 indexArray.emplaceBack(offsetLeft + y, offsetLeft + y + 1, offsetLeft + y + 3); 11053 indexArray.emplaceBack(offsetLeft + y, offsetLeft + y + 3, offsetLeft + y + 2); 11054 indexArray.emplaceBack(offsetRight + y, offsetRight + y + 3, offsetRight + y + 1); 11055 indexArray.emplaceBack(offsetRight + y, offsetRight + y + 2, offsetRight + y + 3); 11056 } 11057 } 11058}; 11059const NOT_COVERED = { 11060 covered: false, 11061 demLoaded: false, 11062 elevation: 0 11063}; 11064/** 11065* Elevation of the rendered terrain surface at a mercator position, and whether it is covered at all. 11066* A covered tile whose DEM has not loaded yet is flat at zero, which is what the terrain mesh renders. 11067*/ 11068function sampleAt(index, exaggeration, mercatorX, mercatorY) { 11069 if (mercatorY < 0 || mercatorY >= 1) return NOT_COVERED; 11070 const wrap = Math.floor(mercatorX); 11071 const wrappedX = mercatorX - wrap; 11072 for (const z of index.zooms) { 11073 const scale = 1 << z; 11074 const scaledX = wrappedX * scale; 11075 const scaledY = mercatorY * scale; 11076 const tileX = Math.floor(scaledX); 11077 const tileY = Math.floor(scaledY); 11078 const key = `${wrap}/${z}/${tileX}/${tileY}`; 11079 if (!index.samplerPerTile.has(key)) continue; 11080 const sampler = index.samplerPerTile.get(key); 11081 if (!sampler) return { 11082 covered: true, 11083 demLoaded: false, 11084 elevation: 0 11085 }; 11086 return { 11087 covered: true, 11088 demLoaded: true, 11089 elevation: sampler(Math.min((scaledX - tileX) * EXTENT, MAX_TILE_COORD), Math.min((scaledY - tileY) * EXTENT, MAX_TILE_COORD), EXTENT) * exaggeration 11090 }; 11091 } 11092 return NOT_COVERED; 11093} 11094/** 11095* Whether a height in meters is at or below the sampled terrain surface. 11096* The epsilon absorbs rounding when a bracket endpoint lands exactly on the surface. 11097*/ 11098function isBelowTerrainSample(sample, height) { 11099 return sample.covered && height <= sample.elevation + HIT_EPSILON_M; 11100} 11101/** 11102* Narrows the bracket `[lo, hi]` around the surface crossing until it is shorter than `tolerance` in ray parameter units. 11103*/ 11104function bisect(ray, isBelowTerrain, lo, hi, tolerance) { 11105 for (let j = 0; j < MAX_BISECTIONS && hi - lo > tolerance; j++) { 11106 const mid = (lo + hi) / 2; 11107 if (isBelowTerrain(ray, mid)) hi = mid; 11108 else lo = mid; 11109 } 11110 return { 11111 lo, 11112 hi 11113 }; 11114} 11115//#endregion 11116//#region src/geo/projection/mercator_transform.ts 11117const TARGET_WORLD_STEP_PX = 4; 11118const MAX_SAMPLES = 512; 11119const MERCATOR_BISECT_EPSILON_WORLD_PX = .001; 11120var MercatorTransform = class MercatorTransform { 11121 get pixelsToClipSpaceMatrix() { 11122 return this._helper.pixelsToClipSpaceMatrix; 11123 } 11124 get clipSpaceToPixelsMatrix() { 11125 return this._helper.clipSpaceToPixelsMatrix; 11126 } 11127 get pixelsToGLUnits() { 11128 return this._helper.pixelsToGLUnits; 11129 } 11130 get centerOffset() { 11131 return this._helper.centerOffset; 11132 } 11133 get size() { 11134 return this._helper.size; 11135 } 11136 get rotationMatrix() { 11137 return this._helper.rotationMatrix; 11138 } 11139 get centerPoint() { 11140 return this._helper.centerPoint; 11141 } 11142 get pixelsPerMeter() { 11143 return this._helper.pixelsPerMeter; 11144 } 11145 setMinZoom(zoom) { 11146 this._helper.setMinZoom(zoom); 11147 } 11148 setMaxZoom(zoom) { 11149 this._helper.setMaxZoom(zoom); 11150 } 11151 setMinPitch(pitch) { 11152 this._helper.setMinPitch(pitch); 11153 } 11154 setMaxPitch(pitch) { 11155 this._helper.setMaxPitch(pitch); 11156 } 11157 setRenderWorldCopies(renderWorldCopies) { 11158 this._helper.setRenderWorldCopies(renderWorldCopies); 11159 } 11160 setBearing(bearing) { 11161 this._helper.setBearing(bearing); 11162 } 11163 setPitch(pitch) { 11164 this._helper.setPitch(pitch); 11165 } 11166 setRoll(roll) { 11167 this._helper.setRoll(roll); 11168 } 11169 setFov(fov) { 11170 this._helper.setFov(fov); 11171 } 11172 setZoom(zoom) { 11173 this._helper.setZoom(zoom); 11174 } 11175 setCenter(center) { 11176 this._helper.setCenter(center); 11177 } 11178 setElevation(elevation) { 11179 this._helper.setElevation(elevation); 11180 } 11181 setMinElevationForCurrentTile(elevation) { 11182 this._helper.setMinElevationForCurrentTile(elevation); 11183 } 11184 setPadding(padding) { 11185 this._helper.setPadding(padding); 11186 } 11187 interpolatePadding(start, target, t) { 11188 this._helper.interpolatePadding(start, target, t); 11189 } 11190 isPaddingEqual(padding) { 11191 return this._helper.isPaddingEqual(padding); 11192 } 11193 resize(width, height, constrain = true) { 11194 this._helper.resize(width, height, constrain); 11195 } 11196 getMaxBounds() { 11197 return this._helper.getMaxBounds(); 11198 } 11199 setMaxBounds(bounds) { 11200 this._helper.setMaxBounds(bounds); 11201 } 11202 setConstrainOverride(constrain) { 11203 this._helper.setConstrainOverride(constrain); 11204 } 11205 overrideNearFarZ(nearZ, farZ) { 11206 this._helper.overrideNearFarZ(nearZ, farZ); 11207 } 11208 clearNearFarZOverride() { 11209 this._helper.clearNearFarZOverride(); 11210 } 11211 getCameraQueryGeometry(queryGeometry) { 11212 return this._helper.getCameraQueryGeometry(this.getCameraPoint(), queryGeometry); 11213 } 11214 get tileSize() { 11215 return this._helper.tileSize; 11216 } 11217 get tileZoom() { 11218 return this._helper.tileZoom; 11219 } 11220 get scale() { 11221 return this._helper.scale; 11222 } 11223 get worldSize() { 11224 return this._helper.worldSize; 11225 } 11226 get width() { 11227 return this._helper.width; 11228 } 11229 get height() { 11230 return this._helper.height; 11231 } 11232 get lngRange() { 11233 return this._helper.lngRange; 11234 } 11235 get latRange() { 11236 return this._helper.latRange; 11237 } 11238 get minZoom() { 11239 return this._helper.minZoom; 11240 } 11241 get maxZoom() { 11242 return this._helper.maxZoom; 11243 } 11244 get zoom() { 11245 return this._helper.zoom; 11246 } 11247 get center() { 11248 return this._helper.center; 11249 } 11250 get minPitch() { 11251 return this._helper.minPitch; 11252 } 11253 get maxPitch() { 11254 return this._helper.maxPitch; 11255 } 11256 get pitch() { 11257 return this._helper.pitch; 11258 } 11259 get pitchInRadians() { 11260 return this._helper.pitchInRadians; 11261 } 11262 get roll() { 11263 return this._helper.roll; 11264 } 11265 get rollInRadians() { 11266 return this._helper.rollInRadians; 11267 } 11268 get bearing() { 11269 return this._helper.bearing; 11270 } 11271 get bearingInRadians() { 11272 return this._helper.bearingInRadians; 11273 } 11274 get fov() { 11275 return this._helper.fov; 11276 } 11277 get fovInRadians() { 11278 return this._helper.fovInRadians; 11279 } 11280 get elevation() { 11281 return this._helper.elevation; 11282 } 11283 get minElevationForCurrentTile() { 11284 return this._helper.minElevationForCurrentTile; 11285 } 11286 get padding() { 11287 return this._helper.padding; 11288 } 11289 get unmodified() { 11290 return this._helper.unmodified; 11291 } 11292 get renderWorldCopies() { 11293 return this._helper.renderWorldCopies; 11294 } 11295 get cameraToCenterDistance() { 11296 return this._helper.cameraToCenterDistance; 11297 } 11298 get constrainOverride() { 11299 return this._helper.constrainOverride; 11300 } 11301 get nearZ() { 11302 return this._helper.nearZ; 11303 } 11304 get farZ() { 11305 return this._helper.farZ; 11306 } 11307 get autoCalculateNearFarZ() { 11308 return this._helper.autoCalculateNearFarZ; 11309 } 11310 setTransitionState(_value) {} 11311 /** 11312 * @param options - Initial state. Ignored when `sharedHelper` is given, which already carries it. 11313 * @param sharedHelper - Camera to use instead of owning one, so that a composing transform such as 11314 * {@link GlobeTransform} keeps a single copy of the state rather than one per child. Its owner then drives 11315 * {@link _calcMatrices}, because a helper has only one `calcMatrices` callback. 11316 */ 11317 constructor(options, sharedHelper) { 11318 this._posMatrixCache = /* @__PURE__ */ new Map(); 11319 this._alignedPosMatrixCache = /* @__PURE__ */ new Map(); 11320 this._fogMatrixCacheF32 = /* @__PURE__ */ new Map(); 11321 this.defaultConstrain = (lngLat, zoom) => { 11322 zoom = clamp(+zoom, this.minZoom, this.maxZoom); 11323 const result = { 11324 center: new LngLat(lngLat.lng, lngLat.lat), 11325 zoom 11326 }; 11327 let lngRange = this._helper._lngRange; 11328 if (!this._helper._renderWorldCopies && lngRange === null) lngRange = [-179.9999999999, 180 - 1e-10]; 11329 const worldSize = this.tileSize * zoomScale(result.zoom); 11330 let minY = 0; 11331 let maxY = worldSize; 11332 let minX = 0; 11333 let maxX = worldSize; 11334 let scaleY = 0; 11335 let scaleX = 0; 11336 const { x: screenWidth, y: screenHeight } = this.size; 11337 if (this._helper._latRange) { 11338 const latRange = this._helper._latRange; 11339 minY = mercatorYfromLat(latRange[1]) * worldSize; 11340 maxY = mercatorYfromLat(latRange[0]) * worldSize; 11341 if (maxY - minY < screenHeight) scaleY = screenHeight / (maxY - minY); 11342 } 11343 if (lngRange) { 11344 minX = wrap(mercatorXfromLng(lngRange[0]) * worldSize, 0, worldSize); 11345 maxX = wrap(mercatorXfromLng(lngRange[1]) * worldSize, 0, worldSize); 11346 if (maxX < minX) maxX += worldSize; 11347 if (maxX - minX < screenWidth) scaleX = screenWidth / (maxX - minX); 11348 } 11349 const { x: originalX, y: originalY } = projectToWorldCoordinates(worldSize, lngLat); 11350 let modifiedX, modifiedY; 11351 const scale = Math.max(scaleX || 0, scaleY || 0); 11352 if (scale) { 11353 const newPoint = new Point(scaleX ? (maxX + minX) / 2 : originalX, scaleY ? (maxY + minY) / 2 : originalY); 11354 result.center = unprojectFromWorldCoordinates(worldSize, newPoint).wrap(); 11355 result.zoom += scaleZoom(scale); 11356 return result; 11357 } 11358 if (this._helper._latRange) { 11359 const h2 = screenHeight / 2; 11360 if (originalY - h2 < minY) modifiedY = minY + h2; 11361 if (originalY + h2 > maxY) modifiedY = maxY - h2; 11362 } 11363 if (lngRange) { 11364 const centerX = (minX + maxX) / 2; 11365 let wrappedX = originalX; 11366 if (this._helper._renderWorldCopies) wrappedX = wrap(originalX, centerX - worldSize / 2, centerX + worldSize / 2); 11367 const w2 = screenWidth / 2; 11368 if (wrappedX - w2 < minX) modifiedX = minX + w2; 11369 if (wrappedX + w2 > maxX) modifiedX = maxX - w2; 11370 } 11371 if (modifiedX !== void 0 || modifiedY !== void 0) { 11372 const newPoint = new Point(modifiedX ?? originalX, modifiedY ?? originalY); 11373 result.center = unprojectFromWorldCoordinates(worldSize, newPoint).wrap(); 11374 } 11375 return result; 11376 }; 11377 this.applyConstrain = (lngLat, zoom) => { 11378 return this._helper.applyConstrain(lngLat, zoom); 11379 }; 11380 this._helper = sharedHelper ?? new TransformHelper({ 11381 calcMatrices: () => this._calcMatrices(), 11382 defaultConstrain: (center, zoom) => { 11383 return this.defaultConstrain(center, zoom); 11384 } 11385 }, options); 11386 this._coveringTilesDetailsProvider = new MercatorCoveringTilesDetailsProvider(); 11387 } 11388 clone() { 11389 const clone = new MercatorTransform(); 11390 clone.apply(this, false); 11391 return clone; 11392 } 11393 apply(that, constrain) { 11394 this._helper.apply(that, constrain); 11395 } 11396 get cameraPosition() { 11397 return this._cameraPosition; 11398 } 11399 get projectionMatrix() { 11400 return this._projectionMatrix; 11401 } 11402 get modelViewProjectionMatrix() { 11403 return this._viewProjMatrix; 11404 } 11405 get inverseProjectionMatrix() { 11406 return this._invProjMatrix; 11407 } 11408 get mercatorMatrix() { 11409 return this._mercatorMatrix; 11410 } 11411 getVisibleUnwrappedCoordinates(tileID) { 11412 const result = [new UnwrappedTileID(0, tileID)]; 11413 if (this._helper._renderWorldCopies) { 11414 const utl = this.screenPointToMercatorCoordinate(new Point(0, 0)); 11415 const utr = this.screenPointToMercatorCoordinate(new Point(this._helper._width, 0)); 11416 const ubl = this.screenPointToMercatorCoordinate(new Point(this._helper._width, this._helper._height)); 11417 const ubr = this.screenPointToMercatorCoordinate(new Point(0, this._helper._height)); 11418 const w0 = Math.floor(Math.min(utl.x, utr.x, ubl.x, ubr.x)); 11419 const w1 = Math.floor(Math.max(utl.x, utr.x, ubl.x, ubr.x)); 11420 const extraWorldCopy = 1; 11421 for (let w = w0 - extraWorldCopy; w <= w1 + extraWorldCopy; w++) { 11422 if (w === 0) continue; 11423 result.push(new UnwrappedTileID(w, tileID)); 11424 } 11425 } 11426 return result; 11427 } 11428 getCameraFrustum() { 11429 return Frustum.fromInvProjectionMatrix(this._invViewProjMatrix, this.worldSize); 11430 } 11431 getClippingPlane() { 11432 return null; 11433 } 11434 getCoveringTilesDetailsProvider() { 11435 return this._coveringTilesDetailsProvider; 11436 } 11437 recalculateZoomAndCenter(terrain) { 11438 const center = this.screenPointToLocation(this.centerPoint, terrain); 11439 const elevation = terrain ? terrain.getElevationForLngLat(center, this) : 0; 11440 this._helper.recalculateZoomAndCenter(elevation); 11441 } 11442 /** 11443 * Moves the center so that `lnglat`, on the ground at `elevation` meters, renders 11444 * at the screen `point`. Both rays are cast through the same inverse pixel matrix 11445 * so its inversion error cancels out of their difference; the current-center ray 11446 * must be intersected at the center's own elevation (z=0) â intersecting it with 11447 * the elevated plane would land `(elevation - centerElevation)·tan(pitch)` away 11448 * from the center and make repeated calls drift. 11449 */ 11450 setLocationAtPoint(lnglat, point, elevation = this.elevation) { 11451 const z = elevation - this.elevation; 11452 const a = this.screenPointToMercatorCoordinateAtZ(point, z); 11453 const b = this.screenPointToMercatorCoordinateAtZ(this.centerPoint, 0); 11454 const loc = MercatorCoordinate.fromLngLat(lnglat); 11455 const newCenter = new MercatorCoordinate(loc.x - (a.x - b.x), loc.y - (a.y - b.y)); 11456 this.setCenter(newCenter?.toLngLat()); 11457 if (this._helper._renderWorldCopies) this.setCenter(this.center.wrap()); 11458 } 11459 locationToScreenPoint(lnglat, terrain) { 11460 return terrain ? this.coordinatePoint(MercatorCoordinate.fromLngLat(lnglat), terrain.getElevationForLngLat(lnglat, this), this._pixelMatrix3D) : this.coordinatePoint(MercatorCoordinate.fromLngLat(lnglat)); 11461 } 11462 screenPointToLocation(p, terrain) { 11463 return this.screenPointToMercatorCoordinate(p, terrain)?.toLngLat(); 11464 } 11465 screenPointToLocationAtElevation(p, elevation) { 11466 return this.screenPointToMercatorCoordinateAtZ(p, elevation - this.elevation)?.toLngLat(); 11467 } 11468 screenPointToMercatorCoordinate(p, terrain) { 11469 if (terrain) { 11470 const coordinate = this.screenTerrainPointToMercatorCoordinate(p, terrain); 11471 if (coordinate != null) return coordinate; 11472 } 11473 return this.screenPointToMercatorCoordinateAtZ(p); 11474 } 11475 /** {@inheritDoc ITransform.screenTerrainPointToMercatorCoordinate} */ 11476 screenTerrainPointToMercatorCoordinate(p, terrain) { 11477 const index = terrain.getCoverageIndex(); 11478 if (!index) return null; 11479 const { near, far } = this.getRaySegmentFromPixel(p); 11480 const worldSize = this.worldSize; 11481 const dx = far[0] - near[0]; 11482 const dy = far[1] - near[1]; 11483 const dz = far[2] - near[2]; 11484 const ray = { 11485 index, 11486 exaggeration: terrain.exaggeration, 11487 near, 11488 dx, 11489 dy, 11490 dz, 11491 worldSize 11492 }; 11493 let tStart = 0; 11494 let tEnd = 1; 11495 if (dz === 0) { 11496 if (near[2] > index.maxElevation || near[2] < index.minElevation) return null; 11497 } else { 11498 const tHigh = (index.maxElevation - near[2]) / dz; 11499 const tLow = (index.minElevation - near[2]) / dz; 11500 tStart = Math.max(tStart, Math.min(tHigh, tLow)); 11501 tEnd = Math.min(tEnd, Math.max(tHigh, tLow)); 11502 if (tStart > tEnd) return null; 11503 } 11504 const horizontalLength = Math.hypot(dx, dy); 11505 const samples = clamp(Math.ceil(horizontalLength * (tEnd - tStart) / TARGET_WORLD_STEP_PX), 1, MAX_SAMPLES); 11506 let previousT = 0; 11507 let aboveTerrain = !mercatorIsBelowTerrain(ray, 0); 11508 for (let i = 0; i <= samples; i++) { 11509 const t = tStart + (tEnd - tStart) * i / samples; 11510 if (!aboveTerrain) aboveTerrain = !mercatorIsBelowTerrain(ray, t); 11511 else if (mercatorIsBelowTerrain(ray, t)) { 11512 const { lo, hi } = bisect(ray, mercatorIsBelowTerrain, previousT, t, MERCATOR_BISECT_EPSILON_WORLD_PX / horizontalLength); 11513 const sampleLo = mercatorSampleAt(ray, lo); 11514 const sampleHi = mercatorSampleAt(ray, hi); 11515 const fLo = near[2] + lo * dz - sampleLo.elevation; 11516 const fHi = near[2] + hi * dz - sampleHi.elevation; 11517 const hit = sampleLo.covered && fLo > fHi ? clamp(lo + fLo * (hi - lo) / (fLo - fHi), lo, hi) : hi; 11518 return new MercatorCoordinate((near[0] + hit * dx) / worldSize, (near[1] + hit * dy) / worldSize, mercatorSampleAt(ray, hit).elevation); 11519 } 11520 previousT = t; 11521 } 11522 return null; 11523 } 11524 /** 11525 * Returns the segment of the ray through the given screen pixel that lies inside the view frustum. 11526 */ 11527 getRaySegmentFromPixel(p) { 11528 const coord0 = [ 11529 p.x, 11530 p.y, 11531 0, 11532 1 11533 ]; 11534 const coord1 = [ 11535 p.x, 11536 p.y, 11537 1, 11538 1 11539 ]; 11540 transformMat4(coord0, coord0, this._pixelMatrixInverse); 11541 transformMat4(coord1, coord1, this._pixelMatrixInverse); 11542 const w0 = coord0[3]; 11543 const w1 = coord1[3]; 11544 const elevation = this.elevation; 11545 return { 11546 near: [ 11547 coord0[0] / w0, 11548 coord0[1] / w0, 11549 coord0[2] / w0 + elevation 11550 ], 11551 far: [ 11552 coord1[0] / w1, 11553 coord1[1] / w1, 11554 coord1[2] / w1 + elevation 11555 ] 11556 }; 11557 } 11558 /** 11559 * Intersects the ray through a screen point with the horizontal plane at `z`, 11560 * given in meters relative to the plane at the center's elevation (not mercator units). 11561 */ 11562 screenPointToMercatorCoordinateAtZ(p, z) { 11563 const targetZ = z ? z : 0; 11564 const { near, far } = this.getRaySegmentFromPixel(p); 11565 const t = near[2] === far[2] ? 0 : (targetZ + this.elevation - near[2]) / (far[2] - near[2]); 11566 return new MercatorCoordinate(interpolateFactory.number(near[0], far[0], t) / this.worldSize, interpolateFactory.number(near[1], far[1], t) / this.worldSize, targetZ); 11567 } 11568 /** 11569 * Given a coordinate, return the screen point that corresponds to it 11570 * @param coord - the coordinates 11571 * @param elevation - the elevation 11572 * @param pixelMatrix - the pixel matrix 11573 * @returns screen point. Point will be outside the viewport if the coordinate is behind the camera. 11574 */ 11575 coordinatePoint(coord, elevation = 0, pixelMatrix = this._pixelMatrix) { 11576 const p = this._coordinateClipPoint(coord, elevation, pixelMatrix); 11577 const w = p[3]; 11578 if (w > 0) return new Point(p[0] / w, p[1] / w); 11579 return this._offScreenPointBehindCamera(p[0], p[1], w); 11580 } 11581 /** 11582 * The coordinate in the clip space of `pixelMatrix`: pixel x and y and NDC depth, each times `w`, which is positive 11583 * in front of the camera. 11584 */ 11585 _coordinateClipPoint(coord, elevation, pixelMatrix) { 11586 const p = [ 11587 coord.x * this.worldSize, 11588 coord.y * this.worldSize, 11589 elevation, 11590 1 11591 ]; 11592 return transformMat4(p, p, pixelMatrix); 11593 } 11594 /** 11595 * Returns a screen point outside the viewport for a coordinate that is behind the camera. 11596 * The point lies on the boundary of the viewport enlarged by one viewport size on each side, 11597 * on whichever edge the ray from the screen centre in the coordinate's direction reaches first. 11598 * A coordinate exactly behind the camera has no direction to leave through, so it is placed straight down. 11599 * @param x - the x component of the clip space position, before the division by w 11600 * @param y - the y component of the clip space position, before the division by w 11601 * @param w - the w component of the clip space position, zero or negative 11602 * @returns screen point outside the viewport 11603 */ 11604 _offScreenPointBehindCamera(x, y, w) { 11605 const cx = this.width / 2; 11606 const cy = this.height / 2; 11607 const dx = x - cx * w; 11608 let dy = y - cy * w; 11609 if (dx === 0 && dy === 0) dy = 1; 11610 const halfExtentX = cx + this.width;
vendor: 4,533 bytes, lines 11611-11684
11611 const halfExtentY = cy + this.height; 11612 const scale = Math.min(dx !== 0 ? halfExtentX / Math.abs(dx) : Infinity, dy !== 0 ? halfExtentY / Math.abs(dy) : Infinity); 11613 return new Point(cx + dx * scale, cy + dy * scale); 11614 } 11615 getBounds() { 11616 const top = Math.max(0, this._helper._height / 2 - getMercatorHorizon(this)); 11617 return new LngLatBounds().extend(this.screenPointToLocation(new Point(0, top))).extend(this.screenPointToLocation(new Point(this._helper._width, top))).extend(this.screenPointToLocation(new Point(this._helper._width, this._helper._height))).extend(this.screenPointToLocation(new Point(0, this._helper._height))); 11618 } 11619 isPointOnMapSurface(p, terrain) { 11620 if (terrain) return this.screenTerrainPointToMercatorCoordinate(p, terrain) != null; 11621 return p.y > this.height / 2 - getMercatorHorizon(this); 11622 } 11623 calculatePosMatrix(tileID, aligned = false, useFloat32 = false) { 11624 const posMatrixKey = tileID.key ?? calculateTileKey(tileID.wrap, tileID.canonical.z, tileID.canonical.z, tileID.canonical.x, tileID.canonical.y); 11625 const cache = aligned ? this._alignedPosMatrixCache : this._posMatrixCache; 11626 if (cache.has(posMatrixKey)) { 11627 const matrices = cache.get(posMatrixKey); 11628 return useFloat32 ? matrices.f32 : matrices.f64; 11629 } 11630 const tileMatrix = calculateTileMatrix(tileID, this.worldSize); 11631 multiply(tileMatrix, aligned ? this._alignedProjMatrix : this._viewProjMatrix, tileMatrix); 11632 const matrices = { 11633 f64: tileMatrix, 11634 f32: new Float32Array(tileMatrix) 11635 }; 11636 cache.set(posMatrixKey, matrices); 11637 return useFloat32 ? matrices.f32 : matrices.f64; 11638 } 11639 calculateFogMatrix(unwrappedTileID) { 11640 const posMatrixKey = unwrappedTileID.key; 11641 const cache = this._fogMatrixCacheF32; 11642 if (cache.has(posMatrixKey)) return cache.get(posMatrixKey); 11643 const fogMatrix = calculateTileMatrix(unwrappedTileID, this.worldSize); 11644 multiply(fogMatrix, this._fogMatrix, fogMatrix); 11645 cache.set(posMatrixKey, new Float32Array(fogMatrix)); 11646 return cache.get(posMatrixKey); 11647 } 11648 calculateCenterFromCameraLngLatAlt(lnglat, alt, bearing, pitch) { 11649 return this._helper.calculateCenterFromCameraLngLatAlt(lnglat, alt, bearing, pitch); 11650 } 11651 calculateCameraOptionsFromTo(from, altitudeFrom, to, altitudeTo) { 11652 const fromMercator = MercatorCoordinate.fromLngLat(from, altitudeFrom); 11653 const toMercator = MercatorCoordinate.fromLngLat(to, altitudeTo); 11654 const dx = toMercator.x - fromMercator.x; 11655 const dy = toMercator.y - fromMercator.y; 11656 const dz = toMercator.z - fromMercator.z; 11657 const distance3D = Math.hypot(dx, dy, dz); 11658 if (distance3D === 0) throw new Error("Can't calculate camera options with same From and To"); 11659 const groundDistance = Math.hypot(dx, dy); 11660 const zoom = scaleZoom(this.cameraToCenterDistance / distance3D / this.tileSize); 11661 const bearing = radiansToDegrees(Math.atan2(dx, -dy)); 11662 let pitch = radiansToDegrees(Math.acos(groundDistance / distance3D)); 11663 pitch = dz < 0 ? 90 - pitch : 90 + pitch; 11664 return { 11665 center: toMercator.toLngLat(), 11666 elevation: altitudeTo, 11667 zoom, 11668 pitch, 11669 bearing 11670 }; 11671 } 11672 _calculateNearFarZ(cameraToSeaLevelDistance, limitedPitchRadians, offset) { 11673 const minElevation = Math.min(this.elevation, this.minElevationForCurrentTile, this.getCameraAltitude() - 100); 11674 const cameraToLowestPointDistance = cameraToSeaLevelDistance - minElevation * this._helper._pixelPerMeter / Math.cos(limitedPitchRadians); 11675 const lowestPlane = minElevation < 0 ? cameraToLowestPointDistance : cameraToSeaLevelDistance; 11676 const groundAngle = Math.PI / 2 + this.pitchInRadians; 11677 const fovAboveCenter = degreesToRadians(this.fov) * (Math.abs(Math.cos(degreesToRadians(this.roll))) * this.height + Math.abs(Math.sin(degreesToRadians(this.roll))) * this.width) / this.height * (.5 + offset.y / this.height); 11678 const topHalfSurfaceDistance = Math.sin(fovAboveCenter) * lowestPlane / Math.sin(clamp(Math.PI - groundAngle - fovAboveCenter, .01, Math.PI - .01)); 11679 const horizon = getMercatorHorizon(this); 11680 const horizonAngle = Math.atan(horizon / this._helper.cameraToCenterDistance); 11681 const minFovCenterToHorizonRadians = degreesToRadians(90 - maxMercatorHorizonAngle); 11682 const fovCenterToHorizon = horizonAngle > minFovCenterToHorizonRadians ? 2 * horizonAngle * (.5 + offset.y / (horizon * 2)) : minFovCenterToHorizonRadians; 11683 const topHalfSurfaceDistanceHorizon = Math.sin(fovCenterToHorizon) * lowestPlane / Math.sin(clamp(Math.PI - groundAngle - fovCenterToHorizon, .01, Math.PI - .01)); 11684 const topHalfMinDistance = Math.min(topHalfSurfaceDistance, topHalfSurfaceDistanceHorizon);
vendor: 15,158 bytes, lines 11685-12084
11685 this._helper._farZ = (Math.cos(Math.PI / 2 - limitedPitchRadians) * topHalfMinDistance + lowestPlane) * 1.01; 11686 this._helper._nearZ = this._helper._height / 50; 11687 } 11688 /** 11689 * @param calculateNearFarZ - Whether to compute the near/far Z range, or leave the range the helper already 11690 * holds. Defaults to {@link autoCalculateNearFarZ}; a composing transform such as {@link GlobeTransform} 11691 * overrides it so that its two children share a single depth range. 11692 */ 11693 _calcMatrices(calculateNearFarZ = this._helper.autoCalculateNearFarZ) { 11694 const offset = this.centerOffset; 11695 const point = projectToWorldCoordinates(this.worldSize, this.center); 11696 const x = point.x, y = point.y; 11697 const limitedPitchRadians = degreesToRadians(Math.min(this.pitch, maxMercatorHorizonAngle)); 11698 const cameraToSeaLevelDistance = Math.max(this._helper.cameraToCenterDistance / 2, this._helper.cameraToCenterDistance + this._helper._elevation * this._helper._pixelPerMeter / Math.cos(limitedPitchRadians)); 11699 if (calculateNearFarZ) this._calculateNearFarZ(cameraToSeaLevelDistance, limitedPitchRadians, offset); 11700 let m; 11701 m = /* @__PURE__ */ new Float64Array(16); 11702 perspective(m, this.fovInRadians, this._helper._width / this._helper._height, this._helper._nearZ, this._helper._farZ); 11703 this._invProjMatrix = /* @__PURE__ */ new Float64Array(16); 11704 fastInvertProjMat4(this._invProjMatrix, m); 11705 m[8] = -offset.x * 2 / this._helper._width; 11706 m[9] = offset.y * 2 / this._helper._height; 11707 this._projectionMatrix = clone$1(m); 11708 scale(m, m, [ 11709 1, 11710 -1, 11711 1 11712 ]); 11713 translate(m, m, [ 11714 0, 11715 0, 11716 -this._helper.cameraToCenterDistance 11717 ]); 11718 rotateZ(m, m, -this.rollInRadians); 11719 rotateX(m, m, this.pitchInRadians); 11720 rotateZ(m, m, -this.bearingInRadians); 11721 translate(m, m, [ 11722 -x, 11723 -y, 11724 0 11725 ]); 11726 this._mercatorMatrix = scale([], m, [ 11727 this.worldSize, 11728 this.worldSize, 11729 this.worldSize 11730 ]); 11731 scale(m, m, [ 11732 1, 11733 1, 11734 this._helper._pixelPerMeter 11735 ]); 11736 this._pixelMatrix = multiply(/* @__PURE__ */ new Float64Array(16), this.clipSpaceToPixelsMatrix, m); 11737 translate(m, m, [ 11738 0, 11739 0, 11740 -this.elevation 11741 ]); 11742 this._viewProjMatrix = m; 11743 this._invViewProjMatrix = invert$1([], m); 11744 const cameraPos = [ 11745 0, 11746 0, 11747 -1, 11748 1 11749 ]; 11750 transformMat4(cameraPos, cameraPos, this._invViewProjMatrix); 11751 this._cameraPosition = [ 11752 cameraPos[0] / cameraPos[3], 11753 cameraPos[1] / cameraPos[3], 11754 cameraPos[2] / cameraPos[3] 11755 ]; 11756 this._fogMatrix = /* @__PURE__ */ new Float64Array(16); 11757 perspective(this._fogMatrix, this.fovInRadians, this.width / this.height, cameraToSeaLevelDistance, this._helper._farZ); 11758 this._fogMatrix[8] = -offset.x * 2 / this.width; 11759 this._fogMatrix[9] = offset.y * 2 / this.height; 11760 scale(this._fogMatrix, this._fogMatrix, [ 11761 1, 11762 -1, 11763 1 11764 ]); 11765 translate(this._fogMatrix, this._fogMatrix, [ 11766 0, 11767 0, 11768 -this.cameraToCenterDistance 11769 ]); 11770 rotateZ(this._fogMatrix, this._fogMatrix, -this.rollInRadians); 11771 rotateX(this._fogMatrix, this._fogMatrix, this.pitchInRadians); 11772 rotateZ(this._fogMatrix, this._fogMatrix, -this.bearingInRadians); 11773 translate(this._fogMatrix, this._fogMatrix, [ 11774 -x, 11775 -y, 11776 0 11777 ]); 11778 scale(this._fogMatrix, this._fogMatrix, [ 11779 1, 11780 1, 11781 this._helper._pixelPerMeter 11782 ]); 11783 translate(this._fogMatrix, this._fogMatrix, [ 11784 0, 11785 0, 11786 -this.elevation 11787 ]); 11788 this._pixelMatrix3D = multiply(/* @__PURE__ */ new Float64Array(16), this.clipSpaceToPixelsMatrix, m); 11789 const xShift = this._helper._width % 2 / 2, yShift = this._helper._height % 2 / 2, angleCos = Math.cos(this.bearingInRadians), angleSin = Math.sin(-this.bearingInRadians), dx = x - Math.round(x) + angleCos * xShift + angleSin * yShift, dy = y - Math.round(y) + angleCos * yShift + angleSin * xShift; 11790 const alignedM = new Float64Array(m); 11791 translate(alignedM, alignedM, [ 11792 dx > .5 ? dx - 1 : dx, 11793 dy > .5 ? dy - 1 : dy, 11794 0 11795 ]); 11796 this._alignedProjMatrix = alignedM; 11797 m = invert$1(/* @__PURE__ */ new Float64Array(16), this._pixelMatrix); 11798 if (!m) throw new Error("failed to invert matrix"); 11799 this._pixelMatrixInverse = m; 11800 this._clearMatrixCaches(); 11801 } 11802 _clearMatrixCaches() { 11803 this._posMatrixCache.clear(); 11804 this._alignedPosMatrixCache.clear(); 11805 this._fogMatrixCacheF32.clear(); 11806 } 11807 maxPitchScaleFactor() { 11808 if (!this._pixelMatrixInverse) return 1; 11809 const coord = this.screenPointToMercatorCoordinate(new Point(0, 0)); 11810 const p = [ 11811 coord.x * this.worldSize, 11812 coord.y * this.worldSize, 11813 0, 11814 1 11815 ]; 11816 return transformMat4(p, p, this._pixelMatrix)[3] / this._helper.cameraToCenterDistance; 11817 } 11818 getCameraPoint() { 11819 return this._helper.getCameraPoint(); 11820 } 11821 getCameraAltitude() { 11822 return Math.cos(this.pitchInRadians) * this.cameraToCenterDistance / this.pixelsPerMeter + this.elevation; 11823 } 11824 getCameraLngLat() { 11825 const pixelPerMeter = mercatorZfromAltitude(1, this.center.lat) * this.worldSize; 11826 const cameraToCenterDistanceMeters = this._helper.cameraToCenterDistance / pixelPerMeter; 11827 return cameraMercatorCoordinateFromCenterAndRotation(this.center, this.elevation, this.pitch, this.bearing, cameraToCenterDistanceMeters).toLngLat(); 11828 } 11829 getProjectionData(params) { 11830 const { overscaledTileID, aligned, applyTerrainMatrix } = params; 11831 const mercatorTileCoordinates = this._helper.getMercatorTileCoordinates(overscaledTileID); 11832 const tilePosMatrix = overscaledTileID ? this.calculatePosMatrix(overscaledTileID, aligned, true) : null; 11833 let mainMatrix; 11834 if (overscaledTileID?.terrainRttPosMatrix32f && applyTerrainMatrix) mainMatrix = overscaledTileID.terrainRttPosMatrix32f; 11835 else if (tilePosMatrix) mainMatrix = tilePosMatrix; 11836 else mainMatrix = createIdentityMat4f32(); 11837 return { 11838 mainMatrix, 11839 tileMercatorCoords: mercatorTileCoordinates, 11840 clippingPlane: [ 11841 0, 11842 0, 11843 0, 11844 0 11845 ], 11846 projectionTransition: 0, 11847 fallbackMatrix: mainMatrix, 11848 clipAntimeridian: false 11849 }; 11850 } 11851 /** {@inheritDoc ITransform.isLocationOccluded} */ 11852 isLocationOccluded(lngLat, terrain, elevation) { 11853 if (!terrain?.getCoverageIndex()) return false; 11854 const location = MercatorCoordinate.fromLngLat(lngLat); 11855 elevation ??= terrain.getElevationForLngLat(lngLat, this); 11856 const clip = this._coordinateClipPoint(location, elevation, this._pixelMatrix3D); 11857 const w = clip[3]; 11858 if (w <= 0 || clip[2] > w) return true; 11859 const p = new Point(clip[0] / w, clip[1] / w); 11860 const hit = this.screenTerrainPointToMercatorCoordinate(p, terrain); 11861 if (hit == null) return false; 11862 const segment = this.getRaySegmentFromPixel(p); 11863 const tLocation = raySegmentParameter(segment, location.x * this.worldSize, location.y * this.worldSize, elevation); 11864 return raySegmentParameter(segment, hit.x * this.worldSize, hit.y * this.worldSize, hit.z) < tLocation * .99; 11865 } 11866 getPixelScale() { 11867 return 1; 11868 } 11869 getCircleRadiusCorrection() { 11870 return 1; 11871 } 11872 getPitchedTextCorrection(_textAnchorX, _textAnchorY, _tileID) { 11873 return 1; 11874 } 11875 transformLightDirection(dir) { 11876 return clone(dir); 11877 } 11878 getRayDirectionFromPixel(_p) { 11879 throw new Error("Not implemented."); 11880 } 11881 projectTileCoordinates(x, y, unwrappedTileID, elevation) { 11882 const matrix = this.calculatePosMatrix(unwrappedTileID); 11883 let pos; 11884 if (elevation != null) { 11885 pos = [ 11886 x, 11887 y, 11888 elevation, 11889 1 11890 ]; 11891 transformMat4(pos, pos, matrix); 11892 } else { 11893 pos = [ 11894 x, 11895 y, 11896 0, 11897 1 11898 ]; 11899 xyTransformMat4(pos, pos, matrix); 11900 } 11901 const w = pos[3]; 11902 return { 11903 point: new Point(pos[0] / w, pos[1] / w), 11904 signedDistanceFromCamera: w, 11905 isOccluded: false 11906 }; 11907 } 11908 populateCache(coords) { 11909 for (const coord of coords) this.calculatePosMatrix(coord); 11910 } 11911 getProjectionDataForCustomLayer(applyGlobeMatrix = true) { 11912 const tileID = new OverscaledTileID(0, 0, 0, 0, 0); 11913 const rendererProjectionData = this.getProjectionData({ 11914 overscaledTileID: tileID, 11915 applyGlobeMatrix 11916 }); 11917 const tileMatrix = calculateTileMatrix(tileID, this.worldSize); 11918 multiply(tileMatrix, this._viewProjMatrix, tileMatrix); 11919 const scale$4 = [ 11920 EXTENT, 11921 EXTENT, 11922 this.worldSize / this._helper.pixelsPerMeter 11923 ]; 11924 const projectionMatrixScaled = createMat4f64(); 11925 scale(projectionMatrixScaled, tileMatrix, scale$4); 11926 return { 11927 ...rendererProjectionData, 11928 tileMercatorCoords: [ 11929 0, 11930 0, 11931 1, 11932 1 11933 ], 11934 fallbackMatrix: projectionMatrixScaled, 11935 mainMatrix: projectionMatrixScaled 11936 }; 11937 } 11938 getFastPathSimpleProjectionMatrix(tileID) { 11939 return this.calculatePosMatrix(tileID); 11940 } 11941}; 11942function mercatorSampleAt(ray, t) { 11943 return sampleAt(ray.index, ray.exaggeration, (ray.near[0] + t * ray.dx) / ray.worldSize, (ray.near[1] + t * ray.dy) / ray.worldSize); 11944} 11945function mercatorIsBelowTerrain(ray, t) { 11946 return isBelowTerrainSample(mercatorSampleAt(ray, t), ray.near[2] + t * ray.dz); 11947} 11948/** 11949* Where the point of `segment` closest to the given world pixel position and elevation lies along it, 11950* as the fraction from `near` (0) to `far` (1). 11951*/ 11952function raySegmentParameter({ near, far }, worldX, worldY, elevation) { 11953 const dx = far[0] - near[0]; 11954 const dy = far[1] - near[1]; 11955 const dz = far[2] - near[2]; 11956 return ((worldX - near[0]) * dx + (worldY - near[1]) * dy + (elevation - near[2]) * dz) / (dx * dx + dy * dy + dz * dz); 11957} 11958//#endregion 11959//#region src/geo/projection/camera_helper.ts 11960/** 11961* @internal 11962*/ 11963function cameraBoundsWarning() { 11964 warnOnce("Map cannot fit within canvas with the given bounds, padding, and/or offset."); 11965} 11966/** 11967* @internal 11968* Set a transform's rotation to a value interpolated between startEulerAngles and endEulerAngles 11969*/ 11970function updateRotation(args) { 11971 if (args.useSlerp) { 11972 if (args.k < 1) { 11973 const startRotation = rollPitchBearingToQuat(args.startEulerAngles.roll, args.startEulerAngles.pitch, args.startEulerAngles.bearing); 11974 const endRotation = rollPitchBearingToQuat(args.endEulerAngles.roll, args.endEulerAngles.pitch, args.endEulerAngles.bearing); 11975 const rotation = /* @__PURE__ */ new Float64Array(4); 11976 slerp(rotation, startRotation, endRotation, args.k); 11977 const eulerAngles = getRollPitchBearing(rotation); 11978 args.tr.setRoll(eulerAngles.roll); 11979 args.tr.setPitch(eulerAngles.pitch); 11980 args.tr.setBearing(eulerAngles.bearing); 11981 } else { 11982 args.tr.setRoll(args.endEulerAngles.roll); 11983 args.tr.setPitch(args.endEulerAngles.pitch); 11984 args.tr.setBearing(args.endEulerAngles.bearing); 11985 } 11986 } else { 11987 args.tr.setRoll(interpolateFactory.number(args.startEulerAngles.roll, args.endEulerAngles.roll, args.k)); 11988 args.tr.setPitch(interpolateFactory.number(args.startEulerAngles.pitch, args.endEulerAngles.pitch, args.k)); 11989 args.tr.setBearing(interpolateFactory.number(args.startEulerAngles.bearing, args.endEulerAngles.bearing, args.k)); 11990 } 11991} 11992function cameraForBoxAndBearing(options, padding, bounds, bearing, tr) { 11993 const edgePadding = tr.padding; 11994 const nwWorld = projectToWorldCoordinates(tr.worldSize, bounds.getNorthWest()); 11995 const neWorld = projectToWorldCoordinates(tr.worldSize, bounds.getNorthEast()); 11996 const seWorld = projectToWorldCoordinates(tr.worldSize, bounds.getSouthEast()); 11997 const swWorld = projectToWorldCoordinates(tr.worldSize, bounds.getSouthWest()); 11998 const bearingRadians = degreesToRadians(-bearing); 11999 const nwRotatedWorld = nwWorld.rotate(bearingRadians); 12000 const neRotatedWorld = neWorld.rotate(bearingRadians); 12001 const seRotatedWorld = seWorld.rotate(bearingRadians); 12002 const swRotatedWorld = swWorld.rotate(bearingRadians); 12003 const upperRight = new Point(Math.max(nwRotatedWorld.x, neRotatedWorld.x, swRotatedWorld.x, seRotatedWorld.x), Math.max(nwRotatedWorld.y, neRotatedWorld.y, swRotatedWorld.y, seRotatedWorld.y)); 12004 const lowerLeft = new Point(Math.min(nwRotatedWorld.x, neRotatedWorld.x, swRotatedWorld.x, seRotatedWorld.x), Math.min(nwRotatedWorld.y, neRotatedWorld.y, swRotatedWorld.y, seRotatedWorld.y)); 12005 const size = upperRight.sub(lowerLeft); 12006 const availableWidth = tr.width - (edgePadding.left + edgePadding.right + padding.left + padding.right); 12007 const availableHeight = tr.height - (edgePadding.top + edgePadding.bottom + padding.top + padding.bottom); 12008 const scaleX = availableWidth / size.x; 12009 const scaleY = availableHeight / size.y; 12010 if (scaleY < 0 || scaleX < 0) { 12011 cameraBoundsWarning(); 12012 return; 12013 } 12014 const zoom = Math.min(scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom); 12015 const offset = Point.convert(options.offset); 12016 const paddingOffsetX = (padding.left - padding.right) / 2; 12017 const paddingOffsetY = (padding.top - padding.bottom) / 2; 12018 const rotatedPaddingOffset = new Point(paddingOffsetX, paddingOffsetY).rotate(degreesToRadians(bearing)); 12019 const offsetAtFinalZoom = offset.add(rotatedPaddingOffset).mult(tr.scale / zoomScale(zoom)); 12020 return { 12021 center: unprojectFromWorldCoordinates(tr.worldSize, nwWorld.add(seWorld).div(2).sub(offsetAtFinalZoom)), 12022 zoom, 12023 bearing 12024 }; 12025} 12026//#endregion 12027//#region src/geo/projection/mercator_camera_helper.ts 12028/** 12029* @internal 12030*/ 12031var MercatorCameraHelper = class { 12032 get useGlobeControls() { 12033 return false; 12034 } 12035 handlePanInertia(pan, transform) { 12036 const offsetLength = pan.mag(); 12037 const pixelsToHorizon = Math.abs(getMercatorHorizon(transform)); 12038 return { 12039 easingOffset: pan.mult(Math.min(pixelsToHorizon * .75 / offsetLength, 1)), 12040 easingCenter: transform.center 12041 }; 12042 } 12043 handleMapControlsRollPitchBearingZoom(deltas, tr) { 12044 if (deltas.bearingDelta) tr.setBearing(tr.bearing + deltas.bearingDelta); 12045 if (deltas.pitchDelta) tr.setPitch(tr.pitch + deltas.pitchDelta); 12046 if (deltas.rollDelta) tr.setRoll(tr.roll + deltas.rollDelta); 12047 if (deltas.zoomDelta) tr.setZoom(tr.zoom + deltas.zoomDelta); 12048 } 12049 handleMapControlsPan(deltas, tr, preZoomAroundLoc) { 12050 if (deltas.around.distSqr(tr.centerPoint) < .01) return; 12051 tr.setLocationAtPoint(preZoomAroundLoc, deltas.around, deltas.aroundElevation); 12052 } 12053 cameraForBoxAndBearing(options, padding, bounds, bearing, tr) { 12054 return cameraForBoxAndBearing(options, padding, bounds, bearing, tr); 12055 } 12056 handleJumpToCenterZoom(tr, options) { 12057 const zoom = typeof options.zoom !== "undefined" ? +options.zoom : tr.zoom; 12058 if (tr.zoom !== zoom) tr.setZoom(+options.zoom); 12059 if (options.center !== void 0) tr.setCenter(LngLat.convert(options.center)); 12060 } 12061 handleEaseTo(tr, options) { 12062 const startZoom = tr.zoom; 12063 const startPadding = tr.padding; 12064 const startEulerAngles = { 12065 roll: tr.roll, 12066 pitch: tr.pitch, 12067 bearing: tr.bearing 12068 }; 12069 const endEulerAngles = { 12070 roll: options.roll === void 0 ? tr.roll : options.roll, 12071 pitch: options.pitch === void 0 ? tr.pitch : options.pitch, 12072 bearing: options.bearing === void 0 ? tr.bearing : options.bearing 12073 }; 12074 const optionsZoom = typeof options.zoom !== "undefined"; 12075 const doPadding = !tr.isPaddingEqual(options.padding); 12076 let isZooming = false; 12077 const zoom = optionsZoom ? +options.zoom : tr.zoom; 12078 let pointAtOffset = tr.centerPoint.add(options.offsetAsPoint); 12079 const locationAtOffset = tr.screenPointToLocation(pointAtOffset); 12080 const { center, zoom: endZoom } = tr.applyConstrain(LngLat.convert(options.center || locationAtOffset), zoom ?? startZoom); 12081 normalizeCenter(tr, center); 12082 const from = projectToWorldCoordinates(tr.worldSize, locationAtOffset); 12083 const delta = projectToWorldCoordinates(tr.worldSize, center).sub(from); 12084 const finalScale = zoomScale(endZoom - startZoom);
vendor: 11,027 bytes, lines 12085-12359
12085 isZooming = endZoom !== startZoom; 12086 const easeFunc = (k) => { 12087 if (isZooming) tr.setZoom(interpolateFactory.number(startZoom, endZoom, k)); 12088 if (!rollPitchBearingEqual(startEulerAngles, endEulerAngles)) updateRotation({ 12089 startEulerAngles, 12090 endEulerAngles, 12091 tr, 12092 k, 12093 useSlerp: startEulerAngles.roll != endEulerAngles.roll 12094 }); 12095 if (doPadding) { 12096 tr.interpolatePadding(startPadding, options.padding, k); 12097 pointAtOffset = tr.centerPoint.add(options.offsetAsPoint); 12098 } 12099 if (options.around) tr.setLocationAtPoint(options.around, options.aroundPoint); 12100 else { 12101 const scale = zoomScale(tr.zoom - startZoom); 12102 const speedup = Math.pow(endZoom > startZoom ? Math.min(2, finalScale) : Math.max(.5, finalScale), 1 - k); 12103 const newCenter = unprojectFromWorldCoordinates(tr.worldSize, from.add(delta.mult(k * speedup)).mult(scale)); 12104 tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, pointAtOffset); 12105 } 12106 }; 12107 return { 12108 easeFunc, 12109 isZooming, 12110 elevationCenter: center 12111 }; 12112 } 12113 handleFlyTo(tr, options) { 12114 const optionsZoom = typeof options.zoom !== "undefined"; 12115 const startZoom = tr.zoom; 12116 const constrained = tr.applyConstrain(LngLat.convert(options.center || options.locationAtOffset), optionsZoom ? +options.zoom : startZoom); 12117 const targetCenter = constrained.center; 12118 const targetZoom = constrained.zoom; 12119 normalizeCenter(tr, targetCenter); 12120 const startWorldSize = tr.worldSize; 12121 const from = projectToWorldCoordinates(startWorldSize, options.locationAtOffset); 12122 const delta = projectToWorldCoordinates(startWorldSize, targetCenter).sub(from); 12123 const pixelPathLength = delta.mag(); 12124 const scaleOfZoom = zoomScale(targetZoom - startZoom); 12125 const requestedMinZoom = typeof options.minZoom !== "undefined" ? +options.minZoom : tr.minZoom; 12126 const effectiveMinZoom = Math.max(requestedMinZoom, tr.minZoom); 12127 const minZoomPreConstrain = Math.min(effectiveMinZoom, startZoom, targetZoom); 12128 const minZoom = tr.applyConstrain(targetCenter, minZoomPreConstrain).zoom; 12129 const scaleOfMinZoom = zoomScale(minZoom - startZoom); 12130 const easeFunc = (k, scale, centerFactor, pointAtOffset) => { 12131 tr.setZoom(k === 1 ? targetZoom : startZoom + scaleZoom(scale)); 12132 const newCenter = k === 1 ? targetCenter : unprojectFromWorldCoordinates(startWorldSize, from.add(delta.mult(centerFactor))); 12133 tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, pointAtOffset); 12134 }; 12135 return { 12136 easeFunc, 12137 scaleOfZoom, 12138 targetCenter, 12139 scaleOfMinZoom, 12140 pixelPathLength 12141 }; 12142 } 12143}; 12144//#endregion 12145//#region src/style/projection_properties.g.ts 12146let properties; 12147const getProperties = () => properties = properties || new Properties({ "type": new DataConstantProperty(latest["projection"]["type"], "type") }); 12148//#endregion 12149//#region src/geo/projection/vertical_perspective_projection.ts 12150const VerticalPerspectiveShaderDefine = "#define GLOBE"; 12151const VerticalPerspectiveShaderVariantKey = "globe"; 12152const granularitySettingsGlobe = new SubdivisionGranularitySetting({ 12153 fill: new SubdivisionGranularityExpression(128, 2), 12154 line: new SubdivisionGranularityExpression(512, 0), 12155 tile: new SubdivisionGranularityExpression(128, 32), 12156 stencil: new SubdivisionGranularityExpression(128, 1), 12157 circle: 3 12158}); 12159var VerticalPerspectiveProjection = class { 12160 constructor() { 12161 this._tileMeshCache = {}; 12162 } 12163 get name() { 12164 return "vertical-perspective"; 12165 } 12166 get transitionState() { 12167 return 1; 12168 } 12169 get useSubdivision() { 12170 return true; 12171 } 12172 get shaderVariantName() { 12173 return VerticalPerspectiveShaderVariantKey; 12174 } 12175 get shaderDefine() { 12176 return VerticalPerspectiveShaderDefine; 12177 } 12178 get shaderPreludeCode() { 12179 return shaders.projectionGlobe; 12180 } 12181 get vertexShaderPreludeCode() { 12182 return shaders.projectionMercator.vertexSource; 12183 } 12184 get subdivisionGranularity() { 12185 return granularitySettingsGlobe; 12186 } 12187 get useGlobeControls() { 12188 return true; 12189 } 12190 destroy() {} 12191 _getMeshKey(options) { 12192 return `${options.granularity.toString(36)}_${options.generateBorders ? "b" : ""}${options.extendToNorthPole ? "n" : ""}${options.extendToSouthPole ? "s" : ""}`; 12193 } 12194 getMeshFromTileID(context, canonical, hasBorder, allowPoles, usage) { 12195 const granularity = (usage === "stencil" ? granularitySettingsGlobe.stencil : granularitySettingsGlobe.tile).getGranularityForZoomLevel(canonical.z); 12196 const north = canonical.y === 0 && allowPoles; 12197 const south = canonical.y === (1 << canonical.z) - 1 && allowPoles; 12198 return this._getMesh(context, { 12199 granularity, 12200 generateBorders: hasBorder, 12201 extendToNorthPole: north, 12202 extendToSouthPole: south 12203 }); 12204 } 12205 _getMesh(context, options) { 12206 const key = this._getMeshKey(options); 12207 if (key in this._tileMeshCache) return this._tileMeshCache[key]; 12208 const mesh = createTileMeshWithBuffers(context, options); 12209 this._tileMeshCache[key] = mesh; 12210 return mesh; 12211 } 12212 recalculate(_params) {} 12213 hasTransition() { 12214 return false; 12215 } 12216}; 12217//#endregion 12218//#region src/geo/projection/globe_projection.ts 12219var GlobeProjection = class extends Evented { 12220 constructor(projection, globalState) { 12221 super(); 12222 this._transitionable = new Transitionable(getProperties(), "projection", globalState); 12223 this.setProjection(projection); 12224 this._transitioning = this._transitionable.untransitioned(); 12225 this.recalculate(new EvaluationParameters(0)); 12226 this._mercatorProjection = new MercatorProjection(); 12227 this._verticalPerspectiveProjection = new VerticalPerspectiveProjection(); 12228 } 12229 get transitionState() { 12230 const currentProjectionSpecValue = this.properties.get("type"); 12231 if (typeof currentProjectionSpecValue === "string" && currentProjectionSpecValue === "mercator") return 0; 12232 if (typeof currentProjectionSpecValue === "string" && currentProjectionSpecValue === "vertical-perspective") return 1; 12233 if (currentProjectionSpecValue instanceof ProjectionDefinition) { 12234 if (currentProjectionSpecValue.from === currentProjectionSpecValue.to) return currentProjectionSpecValue.from === "mercator" ? 0 : 1; 12235 if (currentProjectionSpecValue.from === "vertical-perspective" && currentProjectionSpecValue.to === "mercator") return 1 - currentProjectionSpecValue.transition; 12236 if (currentProjectionSpecValue.from === "mercator" && currentProjectionSpecValue.to === "vertical-perspective") return currentProjectionSpecValue.transition; 12237 } 12238 return 1; 12239 } 12240 get useGlobeRendering() { 12241 return this.transitionState > 0; 12242 } 12243 get currentProjection() { 12244 return this.useGlobeRendering ? this._verticalPerspectiveProjection : this._mercatorProjection; 12245 } 12246 get name() { 12247 return "globe"; 12248 } 12249 get useSubdivision() { 12250 return this.currentProjection.useSubdivision; 12251 } 12252 get shaderVariantName() { 12253 return this.currentProjection.shaderVariantName; 12254 } 12255 get shaderDefine() { 12256 return this.currentProjection.shaderDefine; 12257 } 12258 get shaderPreludeCode() { 12259 return this.currentProjection.shaderPreludeCode; 12260 } 12261 get vertexShaderPreludeCode() { 12262 return this.currentProjection.vertexShaderPreludeCode; 12263 } 12264 get subdivisionGranularity() { 12265 return this.currentProjection.subdivisionGranularity; 12266 } 12267 get useGlobeControls() { 12268 return this.transitionState > 0; 12269 } 12270 destroy() { 12271 this._mercatorProjection.destroy(); 12272 this._verticalPerspectiveProjection.destroy(); 12273 } 12274 getMeshFromTileID(context, _tileID, _hasBorder, _allowPoles, _usage) { 12275 return this.currentProjection.getMeshFromTileID(context, _tileID, _hasBorder, _allowPoles, _usage); 12276 } 12277 setProjection(projection) { 12278 this._transitionable.setValue("type", projection?.type || "mercator"); 12279 } 12280 updateTransitions(parameters) { 12281 this._transitioning = this._transitionable.transitioned(parameters, this._transitioning); 12282 } 12283 hasTransition() { 12284 return this._transitioning.hasTransition() || this.currentProjection.hasTransition(); 12285 } 12286 recalculate(parameters) { 12287 this.properties = this._transitioning.possiblyEvaluate(parameters); 12288 } 12289}; 12290//#endregion 12291//#region src/geo/projection/globe_utils.ts 12292function getGlobeCircumferencePixels(transform) { 12293 const radius = getGlobeRadiusPixels(transform.worldSize, transform.center.lat); 12294 return 2 * Math.PI * radius; 12295} 12296function globeDistanceOfLocationsPixels(transform, a, b) { 12297 const vecA = angularCoordinatesToSurfaceVector(a); 12298 const vecB = angularCoordinatesToSurfaceVector(b); 12299 const dot$2 = dot(vecA, vecB); 12300 const radians = Math.acos(dot$2); 12301 const circumference = getGlobeCircumferencePixels(transform); 12302 return radians / (2 * Math.PI) * circumference; 12303} 12304/** 12305* For given mercator coordinates in range 0..1, returns the angular coordinates on the sphere's surface, in radians. 12306*/ 12307function mercatorCoordinatesToAngularCoordinatesRadians(mercatorX, mercatorY) { 12308 return [mod(mercatorX * Math.PI * 2 + Math.PI, Math.PI * 2), 2 * Math.atan(Math.exp(Math.PI - mercatorY * Math.PI * 2)) - Math.PI * .5]; 12309} 12310/** 12311* For a given longitude and latitude (note: in radians) returns the normalized vector from the planet center to the specified place on the surface. 12312* @param lngRadians - Longitude in radians. 12313* @param latRadians - Latitude in radians. 12314*/ 12315function angularCoordinatesRadiansToVector(lngRadians, latRadians) { 12316 const len = Math.cos(latRadians); 12317 const vec = /* @__PURE__ */ new Float64Array(3); 12318 vec[0] = Math.sin(lngRadians) * len; 12319 vec[1] = Math.sin(latRadians); 12320 vec[2] = Math.cos(lngRadians) * len; 12321 return vec; 12322} 12323/** 12324* Projects a point within a tile to the surface of the unit sphere globe. 12325* @param inTileX - X coordinate inside the tile in range [0 .. 8192]. 12326* @param inTileY - Y coordinate inside the tile in range [0 .. 8192]. 12327* @param tileIdX - Tile's X coordinate in range [0 .. 2^zoom - 1]. 12328* @param tileIdY - Tile's Y coordinate in range [0 .. 2^zoom - 1]. 12329* @param tileIdZ - Tile's zoom. 12330* @returns A 3D vector - coordinates of the projected point on a unit sphere. 12331*/ 12332function projectTileCoordinatesToSphere(inTileX, inTileY, tileIdX, tileIdY, tileIdZ) { 12333 const scale = 1 / (1 << tileIdZ); 12334 const mercatorX = inTileX / EXTENT * scale + tileIdX * scale; 12335 const mercatorY = inTileY / EXTENT * scale + tileIdY * scale; 12336 const sphericalX = mod(mercatorX * Math.PI * 2 + Math.PI, Math.PI * 2); 12337 const sphericalY = 2 * Math.atan(Math.exp(Math.PI - mercatorY * Math.PI * 2)) - Math.PI * .5; 12338 const len = Math.cos(sphericalY); 12339 const vec = /* @__PURE__ */ new Float64Array(3); 12340 vec[0] = Math.sin(sphericalX) * len; 12341 vec[1] = Math.sin(sphericalY); 12342 vec[2] = Math.cos(sphericalX) * len; 12343 return vec; 12344} 12345/** 12346* For a given longitude and latitude (note: in degrees) returns the normalized vector from the planet center to the specified place on the surface. 12347*/ 12348function angularCoordinatesToSurfaceVector(lngLat) { 12349 return angularCoordinatesRadiansToVector(lngLat.lng * Math.PI / 180, lngLat.lat * Math.PI / 180); 12350} 12351function getGlobeRadiusPixels(worldSize, latitudeDegrees) { 12352 return worldSize / (2 * Math.PI) / Math.cos(latitudeDegrees * Math.PI / 180); 12353} 12354/** 12355* Returns the globe center in view space, in pixels: the camera is at the origin and looks down the negative Z axis. 12356*/ 12357function getGlobeCenterInViewSpace(transform) { 12358 const position = transformMat4(createVec4f64(), [ 12359 0,
vendor: 12,885 bytes, lines 12360-12615
12360 0, 12361 0, 12362 1 12363 ], transform.modelViewProjectionMatrix); 12364 transformMat4(position, position, transform.inverseProjectionMatrix); 12365 return [ 12366 position[0] / position[3], 12367 position[1] / position[3], 12368 position[2] / position[3] 12369 ]; 12370} 12371/** 12372* Given a 3D point on the surface of a unit sphere, returns its angular coordinates in degrees. 12373* The input vector must be normalized. 12374*/ 12375function sphereSurfacePointToCoordinates(surface) { 12376 const latDegrees = Math.asin(surface[1]) / Math.PI * 180; 12377 const lengthXZ = Math.sqrt(surface[0] * surface[0] + surface[2] * surface[2]); 12378 if (lengthXZ > 1e-6) { 12379 const projX = surface[0] / lengthXZ; 12380 const projZ = surface[2] / lengthXZ; 12381 const acosZ = Math.acos(projZ); 12382 const lngDegrees = (projX > 0 ? acosZ : -acosZ) / Math.PI * 180; 12383 return new LngLat(wrap(lngDegrees, -180, 180), latDegrees); 12384 } else return new LngLat(0, latDegrees); 12385} 12386/** 12387* Returns the globe orientation quaternion for the given map center and bearing. 12388* The inverse of {@link lngLatBearingFromOrientation}. 12389*/ 12390function orientationFromLngLatBearing(lngLat, bearing) { 12391 return fromEuler(createVec4f64(), -lngLat.lng, -lngLat.lat, bearing); 12392} 12393/** 12394* Given a globe orientation quaternion, returns the corresponding map center and bearing. 12395* The inverse of {@link orientationFromLngLatBearing}. 12396*/ 12397function lngLatBearingFromOrientation(q) { 12398 const x = q[0], y = q[1], z = q[2], w = q[3]; 12399 return { 12400 lng: -Math.atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y)) * 180 / Math.PI, 12401 lat: -Math.asin(clamp(2 * (w * y - z * x), -1, 1)) * 180 / Math.PI, 12402 bearing: Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)) * 180 / Math.PI 12403 }; 12404} 12405/** Kept just below a half turn so the center cannot land exactly on the far side of the globe. */ 12406const PAN_MAX_ANGLE = Math.PI * .98; 12407/** 12408* Returns the point on the globe that a drag towards `point` should aim at. 12409* 12410* Exact tracking breaks down at the silhouette: the ray meets the globe at `asin(D sin a) - a`, 12411* whose slope runs away to infinity as the ray goes tangent, and past it there is no intersection 12412* at all. So the exact curve is left {@link PAN_FALLOFF_BAND} early and continued with a hyperbola 12413* matching it in value and slope, easing off and saturating short of the far side. Bell's virtual 12414* trackball takes the same idea of easing a sphere into a hyperbola before the rim, though it fits 12415* the curve differently: SGI's `trackball.c`, described in Henriksen, Sporring and Hornbæk, 12416* "Virtual Trackballs Revisited", IEEE TVCG 10(2):206-216, 2004. 12417* 12418* The angle comes from atan2, so a ray pointing away from the globe is a wide angle the falloff 12419* saturates rather than a case to reject. 12420* @param tr - The transform being dragged. 12421* @param point - The cursor position. 12422*/ 12423function panSurfaceLocation(tr, point) { 12424 const origin = tr.cameraPosition; 12425 const distance = length(origin); 12426 if (distance <= 1) return tr.screenPointToLocation(point); 12427 const u = createVec3f64(); 12428 normalize(u, origin); 12429 const direction = tr.getRayDirectionFromPixel(point); 12430 const c = -dot(direction, u); 12431 const lateral = createVec3f64(); 12432 scaleAndAdd(lateral, direction, u, c); 12433 const s = length(lateral); 12434 if (s < 1e-9) return tr.screenPointToLocation(point); 12435 const angle = Math.atan2(s, c); 12436 const handoverAngle = Math.asin(1 / distance) * .9; 12437 if (angle < handoverAngle) return tr.screenPointToLocation(point); 12438 const sinHandover = distance * Math.sin(handoverAngle); 12439 const targetAtHandover = Math.asin(clamp(sinHandover, -1, 1)) - handoverAngle; 12440 const slopeAtHandover = distance * Math.cos(handoverAngle) / Math.sqrt(Math.max(1 - sinHandover * sinHandover, 1e-12)) - 1; 12441 const room = PAN_MAX_ANGLE - targetAtHandover; 12442 const excess = angle - handoverAngle; 12443 const target = targetAtHandover + room * (slopeAtHandover * excess) / (room + slopeAtHandover * excess); 12444 const e = createVec3f64(); 12445 scale$1(e, lateral, 1 / s); 12446 const surface = createVec3f64(); 12447 scale$1(surface, u, Math.cos(clamp(target, 0, PAN_MAX_ANGLE))); 12448 scaleAndAdd(surface, surface, e, Math.sin(clamp(target, 0, PAN_MAX_ANGLE))); 12449 normalize(surface, surface); 12450 return sphereSurfacePointToCoordinates(surface); 12451} 12452/** 12453* Rotates the globe so that the given location appears at the given screen point, by composing 12454* versors, the unit quaternions that represent rotations. Unlike the bearing-preserving 12455* {@link ITransform.setLocationAtPoint}, this stays smooth near and across the poles, and keeps 12456* panning once the cursor leaves the globe. 12457* 12458* Note: the delta rotation's axis is in the surface-vector frame of 12459* {@link angularCoordinatesToSurfaceVector}, while the orientation quaternion uses the Euler frame 12460* of {@link orientationFromLngLatBearing}, hence the component swizzle where they combine. Zoom is 12461* adjusted to keep the planet the same size, as `setLocationAtPoint` does. 12462* @param tr - The transform to rotate. 12463* @param lnglat - The location to bring under `point`. 12464* @param point - The screen point that `lnglat` should appear at. 12465* @param panDelta - The drag's pixel delta. Used to re-derive the previous cursor location through 12466* {@link panSurfaceLocation}, since both ends of the rotation must come from the same mapping. 12467* @param fixedBearing - Applies the swing only, keeping the bearing fixed, which is what dragging 12468* the globe does. Pass `false` to apply the twist about the view axis as well, so that the grabbed 12469* location tracks the cursor exactly and the bearing drifts with it. 12470*/ 12471function versorSetLocationAtPoint(tr, lnglat, point, panDelta, fixedBearing = true) { 12472 const pointLngLat = panSurfaceLocation(tr, point); 12473 let sourceLngLat = lnglat; 12474 if (panDelta) sourceLngLat = panSurfaceLocation(tr, point.sub(panDelta)); 12475 else if (!tr.isPointOnMapSurface(point)) return; 12476 const vecToPixelCurrent = angularCoordinatesToSurfaceVector(pointLngLat); 12477 const vecToTarget = angularCoordinatesToSurfaceVector(sourceLngLat); 12478 const centerQuat = orientationFromLngLatBearing(tr.center, tr.bearing); 12479 const w = cross(createVec3f64(), vecToTarget, vecToPixelCurrent); 12480 const l = Math.sqrt(dot(w, w)); 12481 const t = Math.acos(clamp(dot(vecToTarget, vecToPixelCurrent), -1, 1)) / 2; 12482 const s = Math.sin(t); 12483 const delta = l ? fromValues(w[1] / l * s, -w[0] / l * s, w[2] / l * s, Math.cos(t)) : fromValues(0, 0, 0, 1); 12484 const { lng: newCenterLng, lat: newCenterLat, bearing: newBearing } = lngLatBearingFromOrientation(multiply$1(createVec4f64(), centerQuat, delta)); 12485 const oldLat = tr.center.lat; 12486 const oldZoom = tr.zoom; 12487 const finalLat = clamp(newCenterLat, -90, 90); 12488 const finalLng = fixedBearing ? fixedBearingLongitude(tr, point, panDelta, newCenterLng) : newCenterLng; 12489 tr.setCenter(new LngLat(wrap(finalLng, -180, 180), finalLat)); 12490 if (!fixedBearing) tr.setBearing(newBearing); 12491 tr.setZoom(oldZoom + getZoomAdjustment(oldLat, tr.center.lat)); 12492} 12493/** 12494* Returns the center longitude for a bearing-preserving drag. 12495* 12496* The swing longitude becomes ill-conditioned near the pole and the grabbed location slips away 12497* from the cursor, so within the last ~12 degrees of latitude the cursor is treated as turning a 12498* dial around the pole, blended in with a smoothstep anchored on {@link MAX_VALID_LATITUDE}, the 12499* highest latitude the center can reach. The sweep comes from the raw pixel delta rather than a 12500* round-tripped previous cursor position, which would lose its sign to cancellation at the pole, 12501* and is damped within {@link DIAL_MIN_RADIUS_PIXELS} so it eases to nothing there instead of 12502* being dropped, which would leave a spot where the drag could not move at all. 12503* @param tr - The transform being dragged. 12504* @param point - The cursor position. 12505* @param panDelta - The drag's pixel delta, or undefined to use the swing longitude only. 12506* @param newCenterLng - The swing target longitude. 12507* @returns the center longitude to apply. 12508*/ 12509function fixedBearingLongitude(tr, point, panDelta, newCenterLng) { 12510 const oldLng = tr.center.lng; 12511 const oldLat = tr.center.lat; 12512 const poleLat = oldLat >= 0 ? 90 : -90; 12513 const polePoint = tr.locationToScreenPoint(new LngLat(0, poleLat)); 12514 const rx = point.x - polePoint.x, ry = point.y - polePoint.y; 12515 const r2 = rx * rx + ry * ry; 12516 const tRamp = clamp(1 - (MAX_VALID_LATITUDE - Math.abs(oldLat)) / 12, 0, 1); 12517 const dial = tRamp * tRamp * (3 - 2 * tRamp); 12518 const dLngSwing = mod(newCenterLng - oldLng + 180, 360) - 180; 12519 let dLngDial = 0; 12520 if (dial > 0 && panDelta) { 12521 const dTheta = (rx * panDelta.y - ry * panDelta.x) / Math.max(r2, 400); 12522 dLngDial = (poleLat > 0 ? 1 : -1) * dTheta * 180 / Math.PI; 12523 } 12524 return oldLng + (1 - dial) * dLngSwing + dial * dLngDial; 12525} 12526/** 12527* Given a normalized horizon plane in Ax+By+Cz+D=0 format, compute the center and radius of 12528* the circle in that plain that contains the entire visible portion of the unit sphere from horizon 12529* to horizon. 12530* @param horizonPlane - The plane that passes through visible horizon in Ax + By + Cz + D = 0 format where mag(A,B,C)=1 12531* @returns the center point and radius of the disc that passes through the entire visible horizon 12532*/ 12533function horizonPlaneToCenterAndRadius(horizonPlane) { 12534 const center = createVec3f64(); 12535 center[0] = horizonPlane[0] * -horizonPlane[3]; 12536 center[1] = horizonPlane[1] * -horizonPlane[3]; 12537 center[2] = horizonPlane[2] * -horizonPlane[3]; 12538 return { 12539 center, 12540 radius: Math.sqrt(1 - horizonPlane[3] * horizonPlane[3]) 12541 }; 12542} 12543/** 12544* Computes the closest point on a sphere to `point`. 12545* @param center - Center of the sphere 12546* @param radius - Radius of the sphere 12547* @param point - Point inside or outside the sphere 12548* @returns A 3d vector of the point on the sphere closest to `point` 12549*/ 12550function clampToSphere(center, radius, point) { 12551 const relativeToCenter = createVec3f64(); 12552 sub(relativeToCenter, point, center); 12553 const clamped = createVec3f64(); 12554 scaleAndAdd(clamped, center, relativeToCenter, radius / len(relativeToCenter)); 12555 return clamped; 12556} 12557function planetScaleAtLatitude(latitudeDegrees) { 12558 return Math.cos(latitudeDegrees * Math.PI / 180); 12559} 12560/** 12561* Computes how much to modify zoom to keep the globe size constant when changing latitude. 12562* @param transform - An instance of any transform. Does not have any relation on the computed values. 12563* @param oldLat - Latitude before change, in degrees. 12564* @param newLat - Latitude after change, in degrees. 12565* @returns A value to add to zoom level used for old latitude to keep same planet radius at new latitude. 12566*/ 12567function getZoomAdjustment(oldLat, newLat) { 12568 const oldCircumference = planetScaleAtLatitude(oldLat); 12569 const newCircumference = planetScaleAtLatitude(newLat); 12570 return scaleZoom(newCircumference / oldCircumference); 12571} 12572function getDegreesPerPixel(worldSize, lat) { 12573 return 360 / getGlobeCircumferencePixels({ 12574 worldSize, 12575 center: { lat } 12576 }); 12577} 12578/** 12579* Returns transform's new center rotation after applying panning. 12580* @param panDelta - Panning delta, in same units as what is supplied to {@link HandlerManager}. 12581* @param tr - Current transform. This object is not modified by the function. 12582* @returns New center location to set to the map's transform to apply the specified panning. 12583*/ 12584function computeGlobePanCenter(panDelta, tr) { 12585 const rotatedPanDelta = panDelta.rotate(tr.bearingInRadians); 12586 const normalizedGlobeZoom = tr.zoom + getZoomAdjustment(tr.center.lat, 0); 12587 const lngSpeed = lerp(1 / planetScaleAtLatitude(tr.center.lat), 1 / planetScaleAtLatitude(Math.min(Math.abs(tr.center.lat), 60)), remapSaturate(normalizedGlobeZoom, 7, 3, 0, 1)); 12588 const panningDegreesPerPixel = getDegreesPerPixel(tr.worldSize, tr.center.lat); 12589 return new LngLat(tr.center.lng - rotatedPanDelta.x * panningDegreesPerPixel * lngSpeed, clamp(tr.center.lat + rotatedPanDelta.y * panningDegreesPerPixel, -MAX_VALID_LATITUDE, MAX_VALID_LATITUDE)); 12590} 12591/** 12592* Integration of `1 / cos(x)`. 12593*/ 12594function integrateSecX(x) { 12595 const xHalf = .5 * x; 12596 const sin = Math.sin(xHalf); 12597 const cos = Math.cos(xHalf); 12598 return Math.log(sin + cos) - Math.log(cos - sin); 12599} 12600/** 12601* Interpolates globe center between two locations while preserving apparent rotation speed during interpolation. 12602* @param start - The starting location of the interpolation. 12603* @param deltaLng - Longitude delta to the end of the interpolation. 12604* @param deltaLat - Latitude delta to the end of the interpolation. 12605* @param t - The interpolation point in [0..1], where 0 is starting location, 1 is end location and other values are in between. 12606* @returns The interpolated location. 12607*/ 12608function interpolateLngLatForGlobe(start, deltaLng, deltaLat, t) { 12609 const interpolatedLat = start.lat + deltaLat * t; 12610 if (Math.abs(deltaLat) > 1) { 12611 const endLat = start.lat + deltaLat; 12612 const samplePointStart = (Math.sign(endLat) !== Math.sign(start.lat) ? -Math.abs(start.lat) : Math.abs(start.lat)) * Math.PI / 180; 12613 const samplePointEnd = Math.abs(start.lat + deltaLat) * Math.PI / 180; 12614 const valueT = integrateSecX(samplePointStart + t * (samplePointEnd - samplePointStart)); 12615 const valueStart = integrateSecX(samplePointStart);
vendor: 5,022 bytes, lines 12616-12740
12616 const valueEnd = integrateSecX(samplePointEnd); 12617 const newT = (valueT - valueStart) / (valueEnd - valueStart); 12618 const interpolatedLng = start.lng + deltaLng * newT; 12619 return new LngLat(interpolatedLng, interpolatedLat); 12620 } else { 12621 const interpolatedLng = start.lng + deltaLng * t; 12622 return new LngLat(interpolatedLng, interpolatedLat); 12623 } 12624} 12625/** 12626* Returns the two intersection points of the ray and a sphere centered at the origin, 12627* or null if no intersection occurs. 12628* The intersections are encoded as the parameter for parametric ray equation, 12629* with `tMin` being the first intersection and `tMax` being the second. 12630* The quadratic is solved as suggested in Ray Tracing Gems, chapter 7, since the 12631* schoolbook approach leads to floating point precision issues: 12632* https://www.realtimerendering.com/raytracinggems/rtg/index.html 12633* @param origin - The ray origin. 12634* @param direction - The normalized ray direction. 12635* @param radius - The sphere radius, defaults to the unit sphere of the planet. 12636*/ 12637function raySphereIntersection(origin, direction, radius = 1) { 12638 const originDotDirection = dot(origin, direction); 12639 const radiusSquared = radius * radius; 12640 const inner = createVec3f64(); 12641 const scaledDir = createVec3f64(); 12642 scale$1(scaledDir, direction, originDotDirection); 12643 sub(inner, origin, scaledDir); 12644 const discriminant = radiusSquared - dot(inner, inner); 12645 if (discriminant < 0) return null; 12646 const c = dot(origin, origin) - radiusSquared; 12647 const q = -originDotDirection + (originDotDirection < 0 ? 1 : -1) * Math.sqrt(discriminant); 12648 const t0 = c / q; 12649 const t1 = q; 12650 return { 12651 tMin: Math.min(t0, t1), 12652 tMax: Math.max(t0, t1) 12653 }; 12654} 12655/** Height of the atmosphere relative to the globe radius, 100 km for Earth. */ 12656const ATMOSPHERE_HEIGHT_TO_GLOBE_RADIUS = 1e5 / 6371e3; 12657/** 12658* Returns how much of the atmosphere to draw for a camera at the given altitude above the globe. 12659* It is 0 inside the atmosphere and reaches 1 at ten times the atmosphere height. The sky shader 12660* fades out along the same curve. 12661*/ 12662function getAtmosphereAltitudeBlend(altitude, globeRadius) { 12663 const atmosphereHeight = globeRadius * ATMOSPHERE_HEIGHT_TO_GLOBE_RADIUS; 12664 const x = clamp((altitude - atmosphereHeight) / (9 * atmosphereHeight), 0, 1); 12665 return x * x * (3 - 2 * x); 12666} 12667//#endregion 12668//#region src/util/primitives/bounding_volume_cache.ts 12669var BoundingVolumeCache = class { 12670 constructor(boundingVolumeFactory) { 12671 this._cachePrevious = /* @__PURE__ */ new Map(); 12672 this._cache = /* @__PURE__ */ new Map(); 12673 this._hadAnyChanges = false; 12674 this._boundingVolumeFactory = boundingVolumeFactory; 12675 } 12676 /** 12677 * Prepares bounding volume cache for next frame. Call at the beginning of a frame. 12678 * Bounding volume of any tile accesses in the last frame is kept in the cache, other (unaccessed) bounding volumes are deleted. 12679 */ 12680 swapBuffers() { 12681 if (!this._hadAnyChanges) return; 12682 const oldCache = this._cachePrevious; 12683 this._cachePrevious = this._cache; 12684 this._cache = oldCache; 12685 this._cache.clear(); 12686 this._hadAnyChanges = false; 12687 } 12688 /** 12689 * Returns the bounding volume of the specified tile, fetching it from cache or creating it using the factory function if needed. 12690 * @param tileID - Tile x, y and z for zoom. 12691 */ 12692 getTileBoundingVolume(tileID, wrap, elevation, options) { 12693 const key = `${tileID.z}_${tileID.x}_${tileID.y}_${options?.terrain ? "t" : ""}_${Math.round(elevation)}`; 12694 const cached = this._cache.get(key); 12695 if (cached) return cached; 12696 const cachedPrevious = this._cachePrevious.get(key); 12697 if (cachedPrevious) { 12698 this._cache.set(key, cachedPrevious); 12699 return cachedPrevious; 12700 } 12701 const boundingVolume = this._boundingVolumeFactory(tileID, wrap, elevation, options); 12702 this._cache.set(key, boundingVolume); 12703 this._hadAnyChanges = true; 12704 return boundingVolume; 12705 } 12706}; 12707//#endregion 12708//#region src/util/primitives/convex_volume.ts 12709/** 12710* A general convex bounding volume, defined by a set of points. 12711*/ 12712var ConvexVolume = class ConvexVolume { 12713 /** 12714 * Creates an instance of a general convex bounding volume. 12715 * Note that the provided points array is used *as is*, its contents are not copied! 12716 * 12717 * Additionally, an AABB must be provided for rejecting frustum intersections. 12718 * This AABB does not need to bound this convex volume (it may be smaller), 12719 * but it *must* accurately bound the actual shape this volume is approximating. 12720 * @param points - Points forming the convex shape. Note that this array reference is used *as is*, its contents are not copied! 12721 * @param min - The bounding AABB's min point. 12722 * @param max - The bounding AABB's min point. 12723 */ 12724 constructor(points, planes, min, max) { 12725 this.min = min; 12726 this.max = max; 12727 this.points = points; 12728 this.planes = planes; 12729 } 12730 /** 12731 * Creates a convex BV equivalent to the specified AABB. 12732 * @param min - The AABB's min point. 12733 * @param max - The AABB's max point. 12734 */ 12735 static fromAabb(min, max) { 12736 const points = []; 12737 for (let i = 0; i < 8; i++) points.push([ 12738 (i >> 0 & 1) === 1 ? max[0] : min[0], 12739 (i >> 1 & 1) === 1 ? max[1] : min[1], 12740 (i >>
vendor: 58,668 bytes, lines 12740-14435
12740 2 & 1) === 1 ? max[2] : min[2] 12741 ]); 12742 return new ConvexVolume(points, [ 12743 [ 12744 -1, 12745 0, 12746 0, 12747 max[0] 12748 ], 12749 [ 12750 1, 12751 0, 12752 0, 12753 -min[0] 12754 ], 12755 [ 12756 0, 12757 -1, 12758 0, 12759 max[1] 12760 ], 12761 [ 12762 0, 12763 1, 12764 0, 12765 -min[1] 12766 ], 12767 [ 12768 0, 12769 0, 12770 -1, 12771 max[2] 12772 ], 12773 [ 12774 0, 12775 0, 12776 1, 12777 -min[2] 12778 ] 12779 ], min, max); 12780 } 12781 /** 12782 * Creates a convex bounding volume that is actually an oriented bounding box created from the specified center, half-size and rotation angles. 12783 * @param center - Center of the OBB. 12784 * @param halfSize - The half-size of the OBB in each axis. The box will extend by this value in each direction for the given axis. 12785 * @param angles - The rotation of the box. Euler angles, in degrees. 12786 */ 12787 static fromCenterSizeAngles(center, halfSize, angles) { 12788 const q = fromEuler([], angles[0], angles[1], angles[2]); 12789 const axisX = transformQuat([], [ 12790 halfSize[0], 12791 0, 12792 0 12793 ], q); 12794 const axisY = transformQuat([], [ 12795 0, 12796 halfSize[1], 12797 0 12798 ], q); 12799 const axisZ = transformQuat([], [ 12800 0, 12801 0, 12802 halfSize[2] 12803 ], q); 12804 const min = [...center]; 12805 const max = [...center]; 12806 for (let i = 0; i < 8; i++) for (let axis = 0; axis < 3; axis++) { 12807 const point = center[axis] + axisX[axis] * ((i >> 0 & 1) === 1 ? 1 : -1) + axisY[axis] * ((i >> 1 & 1) === 1 ? 1 : -1) + axisZ[axis] * ((i >> 2 & 1) === 1 ? 1 : -1); 12808 min[axis] = Math.min(min[axis], point); 12809 max[axis] = Math.max(max[axis], point); 12810 } 12811 const points = []; 12812 for (let i = 0; i < 8; i++) { 12813 const p = [...center]; 12814 add(p, p, scale$1([], axisX, (i >> 0 & 1) === 1 ? 1 : -1)); 12815 add(p, p, scale$1([], axisY, (i >> 1 & 1) === 1 ? 1 : -1)); 12816 add(p, p, scale$1([], axisZ, (i >> 2 & 1) === 1 ? 1 : -1)); 12817 points.push(p); 12818 } 12819 return new ConvexVolume(points, [ 12820 [...axisX, -dot(axisX, points[0])], 12821 [...axisY, -dot(axisY, points[0])], 12822 [...axisZ, -dot(axisZ, points[0])], 12823 [ 12824 -axisX[0], 12825 -axisX[1], 12826 -axisX[2], 12827 -dot(axisX, points[7]) 12828 ], 12829 [ 12830 -axisY[0], 12831 -axisY[1], 12832 -axisY[2], 12833 -dot(axisY, points[7]) 12834 ], 12835 [ 12836 -axisZ[0], 12837 -axisZ[1], 12838 -axisZ[2], 12839 -dot(axisZ, points[7]) 12840 ] 12841 ], min, max); 12842 } 12843 /** 12844 * Performs an approximate frustum-obb intersection test. 12845 */ 12846 intersectsFrustum(frustum) { 12847 let fullyInside = true; 12848 const boxPointCount = this.points.length; 12849 const boxPlaneCount = this.planes.length; 12850 const frustumPlaneCount = frustum.planes.length; 12851 const frustumPointCount = frustum.points.length; 12852 for (let i = 0; i < frustumPlaneCount; i++) { 12853 const plane = frustum.planes[i]; 12854 let boxPointsPassed = 0; 12855 for (let j = 0; j < boxPointCount; j++) { 12856 const point = this.points[j]; 12857 if (plane[0] * point[0] + plane[1] * point[1] + plane[2] * point[2] + plane[3] >= 0) boxPointsPassed++; 12858 } 12859 if (boxPointsPassed === 0) return 0; 12860 if (boxPointsPassed < boxPointCount) fullyInside = false; 12861 } 12862 if (fullyInside) return 2; 12863 for (let i = 0; i < boxPlaneCount; i++) { 12864 const plane = this.planes[i]; 12865 let frustumPointsPassed = 0; 12866 for (let j = 0; j < frustumPointCount; j++) { 12867 const point = frustum.points[j]; 12868 if (plane[0] * point[0] + plane[1] * point[1] + plane[2] * point[2] + plane[3] >= 0) frustumPointsPassed++; 12869 } 12870 if (frustumPointsPassed === 0) return 0; 12871 } 12872 return 1; 12873 } 12874 /** 12875 * Performs an intersection test with a halfspace. 12876 */ 12877 intersectsPlane(plane) { 12878 const pointCount = this.points.length; 12879 let positivePoints = 0; 12880 for (let i = 0; i < pointCount; i++) { 12881 const point = this.points[i]; 12882 if (plane[0] * point[0] + plane[1] * point[1] + plane[2] * point[2] + plane[3] >= 0) positivePoints++; 12883 } 12884 if (positivePoints === pointCount) return 2; 12885 if (positivePoints === 0) return 0; 12886 return 1; 12887 } 12888}; 12889//#endregion 12890//#region src/geo/projection/globe_covering_tiles_details_provider.ts 12891/** 12892* Computes distance of a point to a tile in an arbitrary axis. 12893* World is assumed to have size 1, distance returned is to the nearer tile edge. 12894* @param point - Point position. 12895* @param tile - Tile position. 12896* @param tileSize - Tile size. 12897*/ 12898function distanceToTileSimple(point, tile, tileSize) { 12899 const delta = point - tile; 12900 return delta < 0 ? -delta : Math.max(0, delta - tileSize); 12901} 12902function distanceToTileWrapX(pointX, pointY, tileCornerX, tileCornerY, tileSize) { 12903 const tileCornerToPointX = pointX - tileCornerX; 12904 let distanceX; 12905 if (tileCornerToPointX < 0) distanceX = Math.min(-tileCornerToPointX, 1 + tileCornerToPointX - tileSize); 12906 else if (tileCornerToPointX > tileSize) distanceX = Math.min(Math.max(tileCornerToPointX - tileSize, 0), 1 - tileCornerToPointX); 12907 else distanceX = 0; 12908 return Math.max(distanceX, distanceToTileSimple(pointY, tileCornerY, tileSize)); 12909} 12910var GlobeCoveringTilesDetailsProvider = class { 12911 constructor() { 12912 this._boundingVolumeCache = new BoundingVolumeCache(this._computeTileBoundingVolume); 12913 } 12914 /** 12915 * Prepares the internal bounding volume cache for the next frame. 12916 */ 12917 prepareNextFrame() { 12918 this._boundingVolumeCache.swapBuffers(); 12919 } 12920 /** 12921 * Returns the distance of a point to a square tile. If the point is inside the tile, returns 0. 12922 * Assumes the world to be of size 1. 12923 * Handles distances on a sphere correctly: X is wrapped when crossing the antimeridian, 12924 * when crossing the poles Y is mirrored and X is shifted by half world size. 12925 */ 12926 distanceToTile2d(pointX, pointY, tileID, _bv) { 12927 const scale = 1 << tileID.z; 12928 const tileMercatorSize = 1 / scale; 12929 const tileCornerX = tileID.x / scale; 12930 const tileCornerY = tileID.y / scale; 12931 const halfWorld = .5; 12932 let smallestDistance = 2; 12933 smallestDistance = Math.min(smallestDistance, distanceToTileWrapX(pointX, pointY, tileCornerX, tileCornerY, tileMercatorSize)); 12934 smallestDistance = Math.min(smallestDistance, distanceToTileWrapX(pointX, pointY, tileCornerX + halfWorld, -tileCornerY - tileMercatorSize, tileMercatorSize)); 12935 smallestDistance = Math.min(smallestDistance, distanceToTileWrapX(pointX, pointY, tileCornerX + halfWorld, 2 - tileCornerY - tileMercatorSize, tileMercatorSize)); 12936 return smallestDistance; 12937 } 12938 /** 12939 * Returns the wrap value for a given tile, computed so that tiles will remain loaded when crossing the antimeridian. 12940 */ 12941 getWrap(centerCoord, tileID, _parentWrap) { 12942 const scale = 1 << tileID.z; 12943 const tileMercatorSize = 1 / scale; 12944 const tileX = tileID.x / scale; 12945 const distanceCurrent = distanceToTileSimple(centerCoord.x, tileX, tileMercatorSize); 12946 const distanceLeft = distanceToTileSimple(centerCoord.x, tileX - 1, tileMercatorSize); 12947 const distanceRight = distanceToTileSimple(centerCoord.x, tileX + 1, tileMercatorSize); 12948 const distanceSmallest = Math.min(distanceCurrent, distanceLeft, distanceRight); 12949 if (distanceSmallest === distanceRight) return 1; 12950 if (distanceSmallest === distanceLeft) return -1; 12951 return 0; 12952 } 12953 allowVariableZoom(transform, options) { 12954 return coveringZoomLevel(transform, options) > 4; 12955 } 12956 allowWorldCopies() { 12957 return false; 12958 } 12959 getTileBoundingVolume(tileID, wrap, elevation, options) { 12960 return this._boundingVolumeCache.getTileBoundingVolume(tileID, wrap, elevation, options); 12961 } 12962 /** 12963 * Computes the bounding volume of a tile for culling. The volume reaches up to `elevation`, 12964 * which carries the content elevation allowance (e.g. elevated symbols); terrain elevations 12965 * widen the volume but never shrink that allowance. 12966 */ 12967 _computeTileBoundingVolume(tileID, wrap, elevation, options) { 12968 let minElevation = Math.min(0, elevation); 12969 let maxElevation = Math.max(0, elevation); 12970 if (options?.terrain) { 12971 const overscaledTileID = new OverscaledTileID(tileID.z, wrap, tileID.z, tileID.x, tileID.y); 12972 const minMax = options.terrain.getMinMaxElevation(overscaledTileID); 12973 minElevation = minMax.minElevation ?? minElevation; 12974 maxElevation = Math.max(minMax.maxElevation ?? maxElevation, maxElevation); 12975 } 12976 minElevation /= earthRadius; 12977 maxElevation /= earthRadius; 12978 minElevation += 1; 12979 maxElevation += 1; 12980 if (tileID.z <= 0) return ConvexVolume.fromAabb([ 12981 -maxElevation, 12982 -maxElevation, 12983 -maxElevation 12984 ], [ 12985 maxElevation, 12986 maxElevation, 12987 maxElevation 12988 ]); 12989 else if (tileID.z === 1) return ConvexVolume.fromAabb([ 12990 tileID.x === 0 ? -maxElevation : 0, 12991 tileID.y === 0 ? 0 : -maxElevation, 12992 -maxElevation 12993 ], [ 12994 tileID.x === 0 ? 0 : maxElevation, 12995 tileID.y === 0 ? maxElevation : 0, 12996 maxElevation 12997 ]); 12998 else { 12999 const corners = [ 13000 projectTileCoordinatesToSphere(0, 0, tileID.x, tileID.y, tileID.z), 13001 projectTileCoordinatesToSphere(EXTENT, 0, tileID.x, tileID.y, tileID.z), 13002 projectTileCoordinatesToSphere(EXTENT, EXTENT, tileID.x, tileID.y, tileID.z), 13003 projectTileCoordinatesToSphere(0, EXTENT, tileID.x, tileID.y, tileID.z) 13004 ]; 13005 const extremesPoints = []; 13006 for (const c of corners) extremesPoints.push(scale$1([], c, maxElevation)); 13007 if (maxElevation !== minElevation) for (const c of corners) extremesPoints.push(scale$1([], c, minElevation)); 13008 if (tileID.y === 0) extremesPoints.push([ 13009 0, 13010 1, 13011 0 13012 ]); 13013 if (tileID.y === (1 << tileID.z) - 1) extremesPoints.push([ 13014 0, 13015 -1, 13016 0 13017 ]); 13018 const aabbMin = [ 13019 1, 13020 1, 13021 1 13022 ]; 13023 const aabbMax = [ 13024 -1, 13025 -1, 13026 -1 13027 ]; 13028 for (const c of extremesPoints) for (let i = 0; i < 3; i++) { 13029 aabbMin[i] = Math.min(aabbMin[i], c[i]); 13030 aabbMax[i] = Math.max(aabbMax[i], c[i]); 13031 } 13032 const center = projectTileCoordinatesToSphere(EXTENT / 2, EXTENT / 2, tileID.x, tileID.y, tileID.z); 13033 const centerEast = cross([], [ 13034 0, 13035 1, 13036 0 13037 ], center); 13038 normalize(centerEast, centerEast); 13039 const north = cross([], center, centerEast); 13040 normalize(north, north); 13041 const axisEast = cross([], corners[2], corners[1]); 13042 normalize(axisEast, axisEast); 13043 const axisWest = cross([], corners[0], corners[3]); 13044 normalize(axisWest, axisWest); 13045 extremesPoints.push(scale$1([], center, maxElevation)); 13046 if (tileID.y >= (1 << tileID.z) / 2) extremesPoints.push(scale$1([], projectTileCoordinatesToSphere(EXTENT / 2, 0, tileID.x, tileID.y, tileID.z), maxElevation)); 13047 if (tileID.y < (1 << tileID.z) / 2) extremesPoints.push(scale$1([], projectTileCoordinatesToSphere(EXTENT / 2, EXTENT, tileID.x, tileID.y, tileID.z), maxElevation)); 13048 const upDownMinMax = findAxisMinMax(center, extremesPoints); 13049 const northSouthMinMax = findAxisMinMax(north, extremesPoints); 13050 const planeUp = [ 13051 -center[0], 13052 -center[1], 13053 -center[2], 13054 upDownMinMax.max 13055 ]; 13056 const planeDown = [ 13057 center[0], 13058 center[1], 13059 center[2], 13060 -upDownMinMax.min 13061 ]; 13062 const planeNorth = [ 13063 -north[0], 13064 -north[1], 13065 -north[2], 13066 northSouthMinMax.max 13067 ]; 13068 const planeSouth = [ 13069 north[0], 13070 north[1], 13071 north[2], 13072 -northSouthMinMax.min 13073 ]; 13074 const planeEast = [...axisEast, 0]; 13075 const planeWest = [...axisWest, 0]; 13076 const points = []; 13077 if (tileID.y === 0) points.push(threePlaneIntersection(planeWest, planeEast, planeUp), threePlaneIntersection(planeWest, planeEast, planeDown)); 13078 else points.push(threePlaneIntersection(planeNorth, planeEast, planeUp), threePlaneIntersection(planeNorth, planeEast, planeDown), threePlaneIntersection(planeNorth, planeWest, planeUp), threePlaneIntersection(planeNorth, planeWest, planeDown)); 13079 if (tileID.y === (1 << tileID.z) - 1) points.push(threePlaneIntersection(planeWest, planeEast, planeUp), threePlaneIntersection(planeWest, planeEast, planeDown)); 13080 else points.push(threePlaneIntersection(planeSouth, planeEast, planeUp), threePlaneIntersection(planeSouth, planeEast, planeDown), threePlaneIntersection(planeSouth, planeWest, planeUp), threePlaneIntersection(planeSouth, planeWest, planeDown)); 13081 return new ConvexVolume(points, [ 13082 planeUp, 13083 planeDown, 13084 planeNorth, 13085 planeSouth, 13086 planeEast, 13087 planeWest 13088 ], aabbMin, aabbMax); 13089 } 13090 } 13091}; 13092function findAxisMinMax(axis, points) { 13093 let min = Infinity; 13094 let max = -Infinity; 13095 for (const c of points) { 13096 const dot$1 = dot(axis, c); 13097 min = Math.min(min, dot$1); 13098 max = Math.max(max, dot$1); 13099 } 13100 return { 13101 min, 13102 max 13103 }; 13104} 13105//#endregion 13106//#region src/geo/projection/vertical_perspective_transform.ts 13107const GLOBE_SAMPLES = 256; 13108const GLOBE_BISECT_EPSILON_T = 1e-12; 13109/** Latitudes outside the mercator range project past the world edge; the globe mesh still covers them. */ 13110const MAX_MERCATOR_Y = 1 - 1e-9; 13111/** Two points on the unit globe closer than this, a micrometre, are the same point. */ 13112const SAME_POINT_DISTANCE = 1e-12; 13113/** A camera whose horizontal offset is this small relative to its distance is straight above the center. */ 13114const STRAIGHT_ABOVE_RATIO = 1e-9; 13115var VerticalPerspectiveTransform = class VerticalPerspectiveTransform { 13116 get pixelsToClipSpaceMatrix() { 13117 return this._helper.pixelsToClipSpaceMatrix; 13118 } 13119 get clipSpaceToPixelsMatrix() { 13120 return this._helper.clipSpaceToPixelsMatrix; 13121 } 13122 get pixelsToGLUnits() { 13123 return this._helper.pixelsToGLUnits; 13124 } 13125 get centerOffset() { 13126 return this._helper.centerOffset; 13127 } 13128 get size() { 13129 return this._helper.size; 13130 } 13131 get rotationMatrix() { 13132 return this._helper.rotationMatrix; 13133 } 13134 get centerPoint() { 13135 return this._helper.centerPoint; 13136 } 13137 get pixelsPerMeter() { 13138 return this._helper.pixelsPerMeter; 13139 } 13140 setMinZoom(zoom) { 13141 this._helper.setMinZoom(zoom); 13142 } 13143 setMaxZoom(zoom) { 13144 this._helper.setMaxZoom(zoom); 13145 } 13146 setMinPitch(pitch) { 13147 this._helper.setMinPitch(pitch); 13148 } 13149 setMaxPitch(pitch) { 13150 this._helper.setMaxPitch(pitch); 13151 } 13152 setRenderWorldCopies(renderWorldCopies) { 13153 this._helper.setRenderWorldCopies(renderWorldCopies); 13154 } 13155 setBearing(bearing) { 13156 this._helper.setBearing(bearing); 13157 } 13158 setPitch(pitch) { 13159 this._helper.setPitch(pitch); 13160 } 13161 setRoll(roll) { 13162 this._helper.setRoll(roll); 13163 } 13164 setFov(fov) { 13165 this._helper.setFov(fov); 13166 } 13167 setZoom(zoom) { 13168 this._helper.setZoom(zoom); 13169 } 13170 setCenter(center) { 13171 this._helper.setCenter(center); 13172 } 13173 setElevation(elevation) { 13174 this._helper.setElevation(elevation); 13175 } 13176 setMinElevationForCurrentTile(elevation) { 13177 this._helper.setMinElevationForCurrentTile(elevation); 13178 } 13179 setPadding(padding) { 13180 this._helper.setPadding(padding); 13181 } 13182 interpolatePadding(start, target, t) { 13183 this._helper.interpolatePadding(start, target, t); 13184 } 13185 isPaddingEqual(padding) { 13186 return this._helper.isPaddingEqual(padding); 13187 } 13188 resize(width, height) { 13189 this._helper.resize(width, height); 13190 } 13191 getMaxBounds() { 13192 return this._helper.getMaxBounds(); 13193 } 13194 setMaxBounds(bounds) { 13195 this._helper.setMaxBounds(bounds); 13196 } 13197 setConstrainOverride(constrain) { 13198 this._helper.setConstrainOverride(constrain); 13199 } 13200 overrideNearFarZ(nearZ, farZ) { 13201 this._helper.overrideNearFarZ(nearZ, farZ); 13202 } 13203 clearNearFarZOverride() { 13204 this._helper.clearNearFarZOverride(); 13205 } 13206 getCameraQueryGeometry(queryGeometry) { 13207 return this._helper.getCameraQueryGeometry(this.getCameraPoint(), queryGeometry); 13208 } 13209 get tileSize() { 13210 return this._helper.tileSize; 13211 } 13212 get tileZoom() { 13213 return this._helper.tileZoom; 13214 } 13215 get scale() { 13216 return this._helper.scale; 13217 } 13218 get worldSize() { 13219 return this._helper.worldSize; 13220 } 13221 get width() { 13222 return this._helper.width; 13223 } 13224 get height() { 13225 return this._helper.height; 13226 } 13227 get lngRange() { 13228 return this._helper.lngRange; 13229 } 13230 get latRange() { 13231 return this._helper.latRange; 13232 } 13233 get minZoom() { 13234 return this._helper.minZoom; 13235 } 13236 get maxZoom() { 13237 return this._helper.maxZoom; 13238 } 13239 get zoom() { 13240 return this._helper.zoom; 13241 } 13242 get center() { 13243 return this._helper.center; 13244 } 13245 get minPitch() { 13246 return this._helper.minPitch; 13247 } 13248 get maxPitch() { 13249 return this._helper.maxPitch; 13250 } 13251 get pitch() { 13252 return this._helper.pitch; 13253 } 13254 get pitchInRadians() { 13255 return this._helper.pitchInRadians; 13256 } 13257 get roll() { 13258 return this._helper.roll; 13259 } 13260 get rollInRadians() { 13261 return this._helper.rollInRadians; 13262 } 13263 get bearing() { 13264 return this._helper.bearing; 13265 } 13266 get bearingInRadians() { 13267 return this._helper.bearingInRadians; 13268 } 13269 get fov() { 13270 return this._helper.fov; 13271 } 13272 get fovInRadians() { 13273 return this._helper.fovInRadians; 13274 } 13275 get elevation() { 13276 return this._helper.elevation; 13277 } 13278 get minElevationForCurrentTile() { 13279 return this._helper.minElevationForCurrentTile; 13280 } 13281 get padding() { 13282 return this._helper.padding; 13283 } 13284 get unmodified() { 13285 return this._helper.unmodified; 13286 } 13287 get renderWorldCopies() { 13288 return this._helper.renderWorldCopies; 13289 } 13290 get constrainOverride() { 13291 return this._helper.constrainOverride; 13292 } 13293 get nearZ() { 13294 return this._helper.nearZ; 13295 } 13296 get farZ() { 13297 return this._helper.farZ; 13298 } 13299 get autoCalculateNearFarZ() { 13300 return this._helper.autoCalculateNearFarZ; 13301 } 13302 setTransitionState(_value) {} 13303 /** 13304 * @param options - Initial state. Ignored when `sharedHelper` is given, which already carries it. 13305 * @param sharedHelper - Camera to use instead of owning one, so that a composing transform such as 13306 * {@link GlobeTransform} keeps a single copy of the state rather than one per child. Its owner then drives 13307 * {@link _calcMatrices}, because a helper has only one `calcMatrices` callback. 13308 */ 13309 constructor(options, sharedHelper) { 13310 this._cachedClippingPlane = createVec4f64(); 13311 this._projectionMatrix = createIdentityMat4f64(); 13312 this._globeViewProjMatrix32f = createIdentityMat4f32(); 13313 this._globeViewProjMatrixF64 = createIdentityMat4f64(); 13314 this._globeViewProjMatrixF64Inverted = createIdentityMat4f64(); 13315 this._globeProjMatrixInverted = createIdentityMat4f64(); 13316 this._cameraPosition = createVec3f64(); 13317 this.defaultConstrain = (lngLat, zoom) => { 13318 const constrainedLat = clamp(lngLat.lat, -MAX_VALID_LATITUDE, MAX_VALID_LATITUDE); 13319 const constrainedZoom = clamp(+zoom, this.minZoom + getZoomAdjustment(0, constrainedLat), this.maxZoom); 13320 return { 13321 center: new LngLat(lngLat.lng, constrainedLat), 13322 zoom: constrainedZoom 13323 }; 13324 }; 13325 this.applyConstrain = (lngLat, zoom) => { 13326 return this._helper.applyConstrain(lngLat, zoom); 13327 }; 13328 this._helper = sharedHelper ?? new TransformHelper({ 13329 calcMatrices: () => this._calcMatrices(), 13330 defaultConstrain: (center, zoom) => { 13331 return this.defaultConstrain(center, zoom); 13332 } 13333 }, options); 13334 this._coveringTilesDetailsProvider = new GlobeCoveringTilesDetailsProvider(); 13335 } 13336 clone() { 13337 const clone = new VerticalPerspectiveTransform(); 13338 clone.apply(this, false); 13339 return clone; 13340 } 13341 apply(that, constrain) { 13342 this._helper.apply(that, constrain); 13343 } 13344 get projectionMatrix() { 13345 return this._projectionMatrix; 13346 } 13347 get modelViewProjectionMatrix() { 13348 return this._globeViewProjMatrixF64; 13349 } 13350 get inverseProjectionMatrix() { 13351 return this._globeProjMatrixInverted; 13352 } 13353 get cameraPosition() { 13354 const copy = createVec3f64(); 13355 copy[0] = this._cameraPosition[0]; 13356 copy[1] = this._cameraPosition[1]; 13357 copy[2] = this._cameraPosition[2]; 13358 return copy; 13359 } 13360 get cameraToCenterDistance() { 13361 return this._helper.cameraToCenterDistance; 13362 } 13363 getProjectionData(params) { 13364 const { overscaledTileID, applyGlobeMatrix } = params; 13365 const mercatorTileCoordinates = this._helper.getMercatorTileCoordinates(overscaledTileID); 13366 return { 13367 mainMatrix: this._globeViewProjMatrix32f, 13368 tileMercatorCoords: mercatorTileCoordinates, 13369 clippingPlane: this._cachedClippingPlane, 13370 projectionTransition: applyGlobeMatrix ? 1 : 0, 13371 fallbackMatrix: this._globeViewProjMatrix32f, 13372 clipAntimeridian: overscaledTileID?.canonical.z === 0 13373 }; 13374 } 13375 _computeClippingPlane(globeRadiusPixels) { 13376 const pitch = this.pitchInRadians; 13377 const distanceCameraToB = this.cameraToCenterDistance / globeRadiusPixels; 13378 const radius = 1; 13379 const distanceCameraToA = Math.sin(pitch) * distanceCameraToB; 13380 const distanceAtoC = Math.cos(pitch) * distanceCameraToB + radius; 13381 const tangentPlaneDistanceToC = radius / Math.sqrt(distanceCameraToA * distanceCameraToA + distanceAtoC * distanceAtoC) * radius; 13382 let vectorCtoCamX = -distanceCameraToA; 13383 let vectorCtoCamY = distanceAtoC; 13384 const vectorCtoCamLength = Math.sqrt(vectorCtoCamX * vectorCtoCamX + vectorCtoCamY * vectorCtoCamY); 13385 vectorCtoCamX /= vectorCtoCamLength; 13386 vectorCtoCamY /= vectorCtoCamLength; 13387 const planeVector = [ 13388 0, 13389 vectorCtoCamX, 13390 vectorCtoCamY 13391 ]; 13392 rotateZ$1(planeVector, planeVector, [ 13393 0, 13394 0, 13395 0 13396 ], -this.bearingInRadians); 13397 rotateX$1(planeVector, planeVector, [ 13398 0, 13399 0, 13400 0 13401 ], -1 * this.center.lat * Math.PI / 180); 13402 rotateY(planeVector, planeVector, [ 13403 0, 13404 0, 13405 0 13406 ], this.center.lng * Math.PI / 180); 13407 const scale = 1 / length(planeVector); 13408 scale$1(planeVector, planeVector, scale); 13409 return [...planeVector, -tangentPlaneDistanceToC * scale]; 13410 } 13411 /** {@inheritDoc ITransform.isLocationOccluded} */ 13412 isLocationOccluded(lngLat, terrain, elevation) { 13413 const coverage = terrain?.getCoverageIndex(); 13414 elevation ??= coverage ? terrain.getElevationForLngLat(lngLat, this) : 0; 13415 const location = raisedSurfaceVector(lngLat, elevation); 13416 if (!this.isSurfacePointVisible(location)) return true; 13417 if (!coverage) return false; 13418 const p = this._projectSurfacePointToScreen(location); 13419 const origin = this.cameraPosition; 13420 const direction = this.getRayDirectionFromPixel(p); 13421 const tLocation = rayParameter(origin, direction, location); 13422 if (tLocation <= 0) return true; 13423 const hit = this.screenTerrainPointToMercatorCoordinate(p, terrain); 13424 return hit != null && rayParameter(origin, direction, raisedSurfaceVector(hit.toLngLat(), hit.z)) < tLocation * .99; 13425 } 13426 transformLightDirection(dir) { 13427 const sphereX = this._helper._center.lng * Math.PI / 180; 13428 const sphereY = this._helper._center.lat * Math.PI / 180; 13429 const len = Math.cos(sphereY); 13430 const spherePos = [ 13431 Math.sin(sphereX) * len, 13432 Math.sin(sphereY), 13433 Math.cos(sphereX) * len 13434 ]; 13435 const axisRight = [ 13436 spherePos[2], 13437 0, 13438 -spherePos[0] 13439 ]; 13440 const axisDown = [ 13441 0, 13442 0, 13443 0 13444 ]; 13445 cross(axisDown, axisRight, spherePos); 13446 normalize(axisRight, axisRight); 13447 normalize(axisDown, axisDown); 13448 const transformed = [ 13449 axisRight[0] * dir[0] + axisDown[0] * dir[1] + spherePos[0] * dir[2], 13450 axisRight[1] * dir[0] + axisDown[1] * dir[1] + spherePos[1] * dir[2], 13451 axisRight[2] * dir[0] + axisDown[2] * dir[1] + spherePos[2] * dir[2] 13452 ]; 13453 const normalized = [ 13454 0, 13455 0, 13456 0 13457 ]; 13458 normalize(normalized, transformed); 13459 return normalized; 13460 } 13461 getPixelScale() { 13462 return 1 / Math.cos(this._helper._center.lat * Math.PI / 180); 13463 } 13464 getCircleRadiusCorrection() { 13465 return Math.cos(this._helper._center.lat * Math.PI / 180); 13466 } 13467 getPitchedTextCorrection(textAnchorX, textAnchorY, tileID) { 13468 const mercator = tileCoordinatesToMercatorCoordinates(textAnchorX, textAnchorY, tileID.canonical); 13469 const angular = mercatorCoordinatesToAngularCoordinatesRadians(mercator.x, mercator.y); 13470 return this.getCircleRadiusCorrection() / Math.cos(angular[1]); 13471 } 13472 projectTileCoordinates(x, y, unwrappedTileID, elevation) { 13473 const canonical = unwrappedTileID.canonical; 13474 const spherePos = projectTileCoordinatesToSphere(x, y, canonical.x, canonical.y, canonical.z); 13475 const vectorMultiplier = 1 + (elevation ?? 0) / earthRadius; 13476 const elevatedX = spherePos[0] * vectorMultiplier; 13477 const elevatedY = spherePos[1] * vectorMultiplier; 13478 const elevatedZ = spherePos[2] * vectorMultiplier; 13479 const pos = [ 13480 elevatedX, 13481 elevatedY, 13482 elevatedZ, 13483 1 13484 ]; 13485 transformMat4(pos, pos, this._globeViewProjMatrixF64); 13486 let isOccluded; 13487 if (vectorMultiplier <= 1) { 13488 const plane = this._cachedClippingPlane; 13489 isOccluded = plane[0] * spherePos[0] + plane[1] * spherePos[1] + plane[2] * spherePos[2] + plane[3] < 0; 13490 } else isOccluded = this._isLineOfSightBlocked(elevatedX, elevatedY, elevatedZ); 13491 return { 13492 point: new Point(pos[0] / pos[3], pos[1] / pos[3]), 13493 signedDistanceFromCamera: pos[3], 13494 isOccluded 13495 }; 13496 } 13497 /** 13498 * True when the segment from the camera to the given point, both in unit-globe coordinates, passes through the planet. 13499 */ 13500 _isLineOfSightBlocked(x, y, z) { 13501 const cam = this._cameraPosition; 13502 const dx = x - cam[0]; 13503 const dy = y - cam[1]; 13504 const dz = z - cam[2]; 13505 const lengthSq = dx * dx + dy * dy + dz * dz; 13506 if (lengthSq === 0) return false; 13507 const t = clamp(-(cam[0] * dx + cam[1] * dy + cam[2] * dz) / lengthSq, 0, 1); 13508 const cx = cam[0] + t * dx; 13509 const cy = cam[1] + t * dy; 13510 const cz = cam[2] + t * dz; 13511 return cx * cx + cy * cy + cz * cz < 1; 13512 } 13513 /** 13514 * @param calculateNearFarZ - Whether to compute the near/far Z range, or leave the range the helper already 13515 * holds. Defaults to {@link autoCalculateNearFarZ}; a composing transform such as {@link GlobeTransform} 13516 * overrides it so that its two children share a single depth range. 13517 */ 13518 _calcMatrices(calculateNearFarZ = this._helper.autoCalculateNearFarZ) { 13519 const globeRadiusPixels = getGlobeRadiusPixels(this.worldSize, this.center.lat); 13520 const globeMatrix = createMat4f64(); 13521 if (calculateNearFarZ) { 13522 this._helper._nearZ = .5; 13523 this._helper._farZ = this.cameraToCenterDistance + globeRadiusPixels * 2; 13524 } 13525 perspective(globeMatrix, this.fovInRadians, this.width / this.height, this._helper._nearZ, this._helper._farZ); 13526 const offset = this.centerOffset; 13527 globeMatrix[8] = -offset.x * 2 / this._helper._width; 13528 globeMatrix[9] = offset.y * 2 / this._helper._height; 13529 this._projectionMatrix = clone$1(globeMatrix); 13530 this._globeProjMatrixInverted = createMat4f64(); 13531 invert$1(this._globeProjMatrixInverted, globeMatrix); 13532 translate(globeMatrix, globeMatrix, [ 13533 0, 13534 0, 13535 -this.cameraToCenterDistance 13536 ]); 13537 rotateZ(globeMatrix, globeMatrix, this.rollInRadians); 13538 rotateX(globeMatrix, globeMatrix, -this.pitchInRadians); 13539 rotateZ(globeMatrix, globeMatrix, this.bearingInRadians); 13540 translate(globeMatrix, globeMatrix, [ 13541 0, 13542 0, 13543 -globeRadiusPixels 13544 ]); 13545 const scaleVec = createVec3f64(); 13546 scaleVec[0] = globeRadiusPixels; 13547 scaleVec[1] = globeRadiusPixels; 13548 scaleVec[2] = globeRadiusPixels; 13549 rotateX(globeMatrix, globeMatrix, this.center.lat * Math.PI / 180); 13550 rotateY$1(globeMatrix, globeMatrix, -this.center.lng * Math.PI / 180); 13551 scale(globeMatrix, globeMatrix, scaleVec); 13552 this._globeViewProjMatrixF64 = globeMatrix; 13553 this._globeViewProjMatrix32f = new Float32Array(globeMatrix); 13554 this._globeViewProjMatrixF64Inverted = createMat4f64(); 13555 invert$1(this._globeViewProjMatrixF64Inverted, globeMatrix); 13556 const zero = createVec3f64(); 13557 this._cameraPosition = createVec3f64(); 13558 this._cameraPosition[2] = this.cameraToCenterDistance / globeRadiusPixels; 13559 rotateZ$1(this._cameraPosition, this._cameraPosition, zero, -this.rollInRadians); 13560 rotateX$1(this._cameraPosition, this._cameraPosition, zero, this.pitchInRadians); 13561 rotateZ$1(this._cameraPosition, this._cameraPosition, zero, -this.bearingInRadians); 13562 add(this._cameraPosition, this._cameraPosition, [ 13563 0, 13564 0, 13565 1 13566 ]); 13567 rotateX$1(this._cameraPosition, this._cameraPosition, zero, -this.center.lat * Math.PI / 180); 13568 rotateY(this._cameraPosition, this._cameraPosition, zero, this.center.lng * Math.PI / 180); 13569 this._cachedClippingPlane = this._computeClippingPlane(globeRadiusPixels); 13570 const matrix = clone$1(this._globeViewProjMatrixF64Inverted); 13571 scale(matrix, matrix, [ 13572 1, 13573 1, 13574 -1 13575 ]); 13576 this._cachedFrustum = Frustum.fromInvProjectionMatrix(matrix, 1, 0, this._cachedClippingPlane, true); 13577 } 13578 calculateFogMatrix(_unwrappedTileID) { 13579 warnOnce("calculateFogMatrix is not supported on globe projection."); 13580 const m = createMat4f64(); 13581 identity(m); 13582 return m; 13583 } 13584 getVisibleUnwrappedCoordinates(tileID) { 13585 return [new UnwrappedTileID(0, tileID)]; 13586 } 13587 getCameraFrustum() { 13588 return this._cachedFrustum; 13589 } 13590 getClippingPlane() { 13591 return this._cachedClippingPlane; 13592 } 13593 getCoveringTilesDetailsProvider() { 13594 return this._coveringTilesDetailsProvider; 13595 } 13596 recalculateZoomAndCenter(terrain) { 13597 if (terrain) { 13598 warnOnce("terrain is not fully supported on vertical perspective projection."); 13599 return; 13600 } 13601 this._helper.recalculateZoomAndCenter(0); 13602 } 13603 maxPitchScaleFactor() { 13604 return 1; 13605 } 13606 getCameraPoint() { 13607 return this._helper.getCameraPoint(); 13608 } 13609 /** 13610 * The altitude of the rendered camera above sea level. The sphere keeps the center point at sea level whatever its 13611 * elevation (`_calcMatrices` does not apply it), so unlike on mercator the center elevation does not lift the camera. 13612 * {@link calculateCameraOptionsFromTo} is the inverse. 13613 */ 13614 getCameraAltitude() { 13615 return (length(this._cameraPosition) - 1) * earthRadius; 13616 } 13617 getCameraLngLat() { 13618 const surface = createVec3f64(); 13619 normalize(surface, this._cameraPosition); 13620 return sphereSurfacePointToCoordinates(surface); 13621 } 13622 populateCache(_coords) {} 13623 getBounds() { 13624 const xMid = this.width * .5; 13625 const yMid = this.height * .5; 13626 const testPoints = [ 13627 new Point(0, 0), 13628 new Point(xMid, 0), 13629 new Point(this.width, 0), 13630 new Point(this.width, yMid), 13631 new Point(this.width, this.height), 13632 new Point(xMid, this.height), 13633 new Point(0, this.height), 13634 new Point(0, yMid) 13635 ]; 13636 const projectedPoints = []; 13637 for (const p of testPoints) projectedPoints.push(this.unprojectScreenPoint(p)); 13638 let mostEast = 0, mostWest = 0, mostNorth = 0, mostSouth = 0; 13639 const center = this.center; 13640 for (const p of projectedPoints) { 13641 const dLng = differenceOfAnglesDegrees(center.lng, p.lng); 13642 const dLat = differenceOfAnglesDegrees(center.lat, p.lat); 13643 if (dLng < mostWest) mostWest = dLng; 13644 if (dLng > mostEast) mostEast = dLng; 13645 if (dLat < mostSouth) mostSouth = dLat; 13646 if (dLat > mostNorth) mostNorth = dLat; 13647 } 13648 const boundsArray = [ 13649 center.lng + mostWest, 13650 center.lat + mostSouth, 13651 center.lng + mostEast, 13652 center.lat + mostNorth 13653 ]; 13654 if (this.isSurfacePointOnScreen([ 13655 0, 13656 1, 13657 0 13658 ])) { 13659 boundsArray[3] = 90; 13660 boundsArray[0] = -180; 13661 boundsArray[2] = 180; 13662 } 13663 if (this.isSurfacePointOnScreen([ 13664 0, 13665 -1, 13666 0 13667 ])) { 13668 boundsArray[1] = -90; 13669 boundsArray[0] = -180; 13670 boundsArray[2] = 180; 13671 } 13672 return new LngLatBounds(boundsArray); 13673 } 13674 calculateCenterFromCameraLngLatAlt(lngLat, alt, bearing, pitch) { 13675 return this._helper.calculateCenterFromCameraLngLatAlt(lngLat, alt, bearing, pitch); 13676 } 13677 /** 13678 * Inverts the camera placement of `_calcMatrices` in unit-globe coordinates: the camera sits at radius 13679 * `1 + altitudeFrom / earthRadius` and looks at the center on the sea-level sphere, the target altitude only becoming 13680 * the center elevation (the inverse of {@link getCameraAltitude}). Pitch and bearing are read in the center's local 13681 * frame, +z up, +y north, +x east. A camera straight above the center keeps the transform's bearing. 13682 */ 13683 calculateCameraOptionsFromTo(from, altitudeFrom, to, altitudeTo) { 13684 const center = LngLat.convert(to); 13685 const camera = angularCoordinatesToSurfaceVector(LngLat.convert(from)); 13686 scale$1(camera, camera, 1 + altitudeFrom / earthRadius); 13687 const target = angularCoordinatesToSurfaceVector(center); 13688 const targetAtAltitude = scale$1(createVec3f64(), target, 1 + altitudeTo / earthRadius); 13689 const toCamera = subtract(createVec3f64(), camera, target); 13690 const distance$1 = length(toCamera); 13691 if (distance$1 < SAME_POINT_DISTANCE || distance(camera, targetAtAltitude) < SAME_POINT_DISTANCE) throw new Error("Can't calculate camera options with same From and To"); 13692 const zero = createVec3f64(); 13693 rotateY(toCamera, toCamera, zero, -degreesToRadians(center.lng)); 13694 rotateX$1(toCamera, toCamera, zero, degreesToRadians(center.lat)); 13695 const pitch = radiansToDegrees(Math.acos(clamp(toCamera[2] / distance$1, -1, 1))); 13696 const bearing = Math.hypot(toCamera[0], toCamera[1]) < distance$1 * STRAIGHT_ABOVE_RATIO ? this.bearing : radiansToDegrees(Math.atan2(-toCamera[0], -toCamera[1])); 13697 return { 13698 center, 13699 elevation: altitudeTo, 13700 zoom: scaleZoom(this.cameraToCenterDistance / distance$1 / getGlobeRadiusPixels(this.tileSize, center.lat)), 13701 pitch, 13702 bearing 13703 }; 13704 } 13705 /** 13706 * Note: automatically adjusts zoom to keep planet size consistent 13707 * (same size before and after a {@link setLocationAtPoint} call). 13708 */ 13709 setLocationAtPoint(lnglat, point, _elevation) { 13710 const vecToPixelCurrent = angularCoordinatesToSurfaceVector(this.unprojectScreenPoint(point)); 13711 const vecToTarget = angularCoordinatesToSurfaceVector(lnglat); 13712 const zero = createVec3f64(); 13713 zero$1(zero); 13714 const rotatedPixelVector = createVec3f64(); 13715 rotateY(rotatedPixelVector, vecToPixelCurrent, zero, -this.center.lng * Math.PI / 180); 13716 rotateX$1(rotatedPixelVector, rotatedPixelVector, zero, this.center.lat * Math.PI / 180); 13717 const vecToTargetXZLengthSquared = vecToTarget[0] * vecToTarget[0] + vecToTarget[2] * vecToTarget[2]; 13718 const targetXSquared = rotatedPixelVector[0] * rotatedPixelVector[0]; 13719 if (vecToTargetXZLengthSquared < targetXSquared) return; 13720 const intersectionA = Math.sqrt(vecToTargetXZLengthSquared - targetXSquared); 13721 const intersectionB = -intersectionA; 13722 const lngA = angleToRotateBetweenVectors2D(vecToTarget[0], vecToTarget[2], rotatedPixelVector[0], intersectionA); 13723 const lngB = angleToRotateBetweenVectors2D(vecToTarget[0], vecToTarget[2], rotatedPixelVector[0], intersectionB); 13724 const vecToTargetLngA = createVec3f64(); 13725 rotateY(vecToTargetLngA, vecToTarget, zero, -lngA); 13726 const latA = angleToRotateBetweenVectors2D(vecToTargetLngA[1], vecToTargetLngA[2], rotatedPixelVector[1], rotatedPixelVector[2]); 13727 const vecToTargetLngB = createVec3f64(); 13728 rotateY(vecToTargetLngB, vecToTarget, zero, -lngB); 13729 const latB = angleToRotateBetweenVectors2D(vecToTargetLngB[1], vecToTargetLngB[2], rotatedPixelVector[1], rotatedPixelVector[2]); 13730 const limit = Math.PI * .5; 13731 const isValidA = latA >= -limit && latA <= limit; 13732 const isValidB = latB >= -limit && latB <= limit; 13733 let validLng; 13734 let validLat; 13735 if (isValidA && isValidB) { 13736 const centerLngRadians = this.center.lng * Math.PI / 180; 13737 const centerLatRadians = this.center.lat * Math.PI / 180; 13738 const lngDistA = distanceOfAnglesRadians(lngA, centerLngRadians); 13739 const latDistA = distanceOfAnglesRadians(latA, centerLatRadians); 13740 const lngDistB = distanceOfAnglesRadians(lngB, centerLngRadians); 13741 const latDistB = distanceOfAnglesRadians(latB, centerLatRadians); 13742 if (lngDistA + latDistA < lngDistB + latDistB) { 13743 validLng = lngA; 13744 validLat = latA; 13745 } else { 13746 validLng = lngB; 13747 validLat = latB; 13748 } 13749 } else if (isValidA) { 13750 validLng = lngA; 13751 validLat = latA; 13752 } else if (isValidB) { 13753 validLng = lngB; 13754 validLat = latB; 13755 } else return; 13756 const newLng = validLng / Math.PI * 180; 13757 const newLat = validLat / Math.PI * 180; 13758 const oldLat = this.center.lat; 13759 this.setCenter(new LngLat(newLng, clamp(newLat, -90, 90))); 13760 this.setZoom(this.zoom + getZoomAdjustment(oldLat, this.center.lat)); 13761 } 13762 locationToScreenPoint(lnglat, terrain) { 13763 const pos = angularCoordinatesToSurfaceVector(lnglat); 13764 if (terrain) { 13765 const elevation = terrain.getElevationForLngLatZoom(lnglat, this._helper._tileZoom); 13766 scale$1(pos, pos, 1 + elevation / earthRadius); 13767 } 13768 return this._projectSurfacePointToScreen(pos); 13769 } 13770 /** 13771 * Projects a given vector on the surface of a unit sphere (or possible above the surface) 13772 * and returns its coordinates on screen in pixels. 13773 */ 13774 _projectSurfacePointToScreen(pos) { 13775 const projected = createVec4f64(); 13776 transformMat4(projected, [...pos, 1], this._globeViewProjMatrixF64); 13777 projected[0] /= projected[3]; 13778 projected[1] /= projected[3]; 13779 return new Point((projected[0] * .5 + .5) * this.width, (-projected[1] * .5 + .5) * this.height); 13780 } 13781 screenPointToMercatorCoordinate(p, terrain) { 13782 if (terrain) { 13783 const coordinate = this.screenTerrainPointToMercatorCoordinate(p, terrain); 13784 if (coordinate) return coordinate; 13785 } 13786 return MercatorCoordinate.fromLngLat(this.unprojectScreenPoint(p)); 13787 } 13788 /** {@inheritDoc ITransform.screenTerrainPointToMercatorCoordinate} */ 13789 screenTerrainPointToMercatorCoordinate(p, terrain) { 13790 const index = terrain.getCoverageIndex(); 13791 if (!index) return null; 13792 const origin = this.cameraPosition; 13793 const direction = this.getRayDirectionFromPixel(p); 13794 const outer = raySphereIntersection(origin, direction, 1 + index.maxElevation / earthRadius); 13795 if (!outer) return null; 13796 const inner = raySphereIntersection(origin, direction, 1 + index.minElevation / earthRadius); 13797 const tStart = Math.max(outer.tMin, 0); 13798 const tEnd = inner ? inner.tMin : outer.tMax; 13799 if (tEnd <= tStart) return null; 13800 const ray = { 13801 index, 13802 exaggeration: terrain.exaggeration, 13803 origin, 13804 direction 13805 }; 13806 let previousT = 0; 13807 for (let i = 0; i <= GLOBE_SAMPLES; i++) { 13808 const t = tStart + (tEnd - tStart) * i / GLOBE_SAMPLES; 13809 if (globeIsBelowTerrain(ray, t)) { 13810 const { hi } = bisect(ray, globeIsBelowTerrain, previousT, t, GLOBE_BISECT_EPSILON_T); 13811 const { sample, mercator } = globeSampleAt(ray, hi); 13812 return new MercatorCoordinate(mercator.x, mercator.y, sample.elevation); 13813 } 13814 previousT = t; 13815 } 13816 return null; 13817 } 13818 screenPointToLocation(p, terrain) { 13819 return this.screenPointToMercatorCoordinate(p, terrain)?.toLngLat(); 13820 } 13821 screenPointToLocationAtElevation(p, _elevation) { 13822 return this.screenPointToLocation(p); 13823 } 13824 isPointOnMapSurface(p, _terrain) { 13825 const rayOrigin = this._cameraPosition; 13826 return !!raySphereIntersection(rayOrigin, this.getRayDirectionFromPixel(p)); 13827 } 13828 /** 13829 * Computes normalized direction of a ray from the camera to the given screen pixel. 13830 */ 13831 getRayDirectionFromPixel(p) { 13832 const pos = createVec4f64(); 13833 pos[0] = p.x / this.width * 2 - 1; 13834 pos[1] = (p.y / this.height * 2 - 1) * -1; 13835 pos[2] = 1; 13836 pos[3] = 1; 13837 transformMat4(pos, pos, this._globeViewProjMatrixF64Inverted); 13838 pos[0] /= pos[3]; 13839 pos[1] /= pos[3]; 13840 pos[2] /= pos[3]; 13841 const ray = createVec3f64(); 13842 ray[0] = pos[0] - this._cameraPosition[0]; 13843 ray[1] = pos[1] - this._cameraPosition[1]; 13844 ray[2] = pos[2] - this._cameraPosition[2]; 13845 const rayNormalized = createVec3f64(); 13846 normalize(rayNormalized, ray); 13847 return rayNormalized; 13848 } 13849 /** 13850 * For a given point on the unit sphere of the planet, or raised above it, returns whether it lies on the camera's 13851 * side of the horizon plane, the plane the globe shaders clip with (not taking into account camera rotation at all). 13852 */ 13853 isSurfacePointVisible(p) { 13854 const plane = this._cachedClippingPlane; 13855 return plane[0] * p[0] + plane[1] * p[1] + plane[2] * p[2] + plane[3] >= 0; 13856 } 13857 /** 13858 * Returns whether surface point is visible on screen. 13859 * It must both project to a pixel in screen bounds and not be occluded by the planet. 13860 */ 13861 isSurfacePointOnScreen(vec) { 13862 if (!this.isSurfacePointVisible(vec)) return false; 13863 const projected = createVec4f64(); 13864 transformMat4(projected, [...vec, 1], this._globeViewProjMatrixF64); 13865 projected[0] /= projected[3]; 13866 projected[1] /= projected[3]; 13867 projected[2] /= projected[3]; 13868 return projected[0] > -1 && projected[0] < 1 && projected[1] > -1 && projected[1] < 1 && projected[2] > -1 && projected[2] < 1; 13869 } 13870 /** 13871 * @internal 13872 * Returns a {@link LngLat} representing geographical coordinates that correspond to the specified pixel coordinates. 13873 * Note: if the point does not lie on the globe, returns a location on the visible globe horizon (edge) that is 13874 * as close to the point as possible. 13875 * @param p - Screen point in pixels to unproject. 13876 * @param terrain - Optional terrain. 13877 */ 13878 unprojectScreenPoint(p) { 13879 const rayOrigin = this._cameraPosition; 13880 const rayDirection = this.getRayDirectionFromPixel(p); 13881 const intersection = raySphereIntersection(rayOrigin, rayDirection); 13882 if (intersection) { 13883 const intersectionPoint = createVec3f64(); 13884 add(intersectionPoint, rayOrigin, [ 13885 rayDirection[0] * intersection.tMin, 13886 rayDirection[1] * intersection.tMin, 13887 rayDirection[2] * intersection.tMin 13888 ]); 13889 const sphereSurface = createVec3f64(); 13890 normalize(sphereSurface, intersectionPoint); 13891 return sphereSurfacePointToCoordinates(sphereSurface); 13892 } 13893 const horizonPlane = this._cachedClippingPlane; 13894 const directionDotPlaneXyz = horizonPlane[0] * rayDirection[0] + horizonPlane[1] * rayDirection[1] + horizonPlane[2] * rayDirection[2]; 13895 const distanceToIntersection = -pointPlaneSignedDistance(horizonPlane, rayOrigin) / directionDotPlaneXyz; 13896 const maxRayLength = 2; 13897 const planeIntersection = createVec3f64(); 13898 if (distanceToIntersection > 0) add(planeIntersection, rayOrigin, [ 13899 rayDirection[0] * distanceToIntersection, 13900 rayDirection[1] * distanceToIntersection, 13901 rayDirection[2] * distanceToIntersection 13902 ]); 13903 else { 13904 const distantPoint = createVec3f64(); 13905 add(distantPoint, rayOrigin, [ 13906 rayDirection[0] * maxRayLength, 13907 rayDirection[1] * maxRayLength, 13908 rayDirection[2] * maxRayLength 13909 ]); 13910 const distanceFromPlane = pointPlaneSignedDistance(this._cachedClippingPlane, distantPoint); 13911 sub(planeIntersection, distantPoint, [ 13912 this._cachedClippingPlane[0] * distanceFromPlane, 13913 this._cachedClippingPlane[1] * distanceFromPlane, 13914 this._cachedClippingPlane[2] * distanceFromPlane 13915 ]); 13916 } 13917 const horizonDisk = horizonPlaneToCenterAndRadius(horizonPlane); 13918 return sphereSurfacePointToCoordinates(clampToSphere(horizonDisk.center, horizonDisk.radius, planeIntersection)); 13919 } 13920 getProjectionDataForCustomLayer(applyGlobeMatrix = true) { 13921 const globeData = this.getProjectionData({ 13922 overscaledTileID: new OverscaledTileID(0, 0, 0, 0, 0), 13923 applyGlobeMatrix 13924 }); 13925 globeData.tileMercatorCoords = [ 13926 0, 13927 0, 13928 1, 13929 1 13930 ]; 13931 return globeData; 13932 } 13933 getFastPathSimpleProjectionMatrix(_tileID) {} 13934}; 13935function globeSampleAt(ray, t) { 13936 const position = createVec3f64(); 13937 scaleAndAdd(position, ray.origin, ray.direction, t); 13938 const radius = length(position); 13939 const surface = createVec3f64(); 13940 scale$1(surface, position, 1 / radius); 13941 const lngLat = sphereSurfacePointToCoordinates(surface); 13942 const projected = MercatorCoordinate.fromLngLat(lngLat); 13943 const mercator = new MercatorCoordinate(projected.x, clamp(projected.y, 0, MAX_MERCATOR_Y)); 13944 const sample = sampleAt(ray.index, ray.exaggeration, mercator.x, mercator.y); 13945 const elevation = Math.abs(lngLat.lat) > 85.051129 ? 0 : sample.elevation; 13946 return { 13947 sample: { 13948 ...sample, 13949 elevation 13950 }, 13951 radius, 13952 mercator 13953 }; 13954} 13955function globeIsBelowTerrain(ray, t) { 13956 const { sample, radius } = globeSampleAt(ray, t); 13957 return isBelowTerrainSample(sample, (radius - 1) * earthRadius); 13958} 13959/** 13960* The location as a vector from the globe's center in globe radii, raised `elevation` meters above the surface. 13961*/ 13962function raisedSurfaceVector(lngLat, elevation) { 13963 const vector = angularCoordinatesToSurfaceVector(lngLat); 13964 return scale$1(vector, vector, 1 + elevation / earthRadius); 13965} 13966/** 13967* Where the point of the ray closest to `point` lies along it, in units of `direction` from `origin`. 13968*/ 13969function rayParameter(origin, direction, point) { 13970 const offset = subtract(createVec3f64(), point, origin); 13971 return dot(offset, direction); 13972} 13973//#endregion 13974//#region src/geo/projection/globe_transform.ts 13975/** 13976* Globe transform is a transform that moves between vertical perspective and mercator projections. 13977* 13978* The two child transforms share this transform's {@link TransformHelper}, so the camera exists exactly once: 13979* a child that moves itself - as {@link setLocationAtPoint} has it do, since only the child knows its own 13980* projection's geometry - has moved this transform too, with no copying back. The children own only what they 13981* derive from that shared camera, such as their matrices, and this transform drives their `_calcMatrices` 13982* because a helper has a single `calcMatrices` callback. 13983*/ 13984var GlobeTransform = class GlobeTransform { 13985 get pixelsToClipSpaceMatrix() { 13986 return this._helper.pixelsToClipSpaceMatrix; 13987 } 13988 get clipSpaceToPixelsMatrix() { 13989 return this._helper.clipSpaceToPixelsMatrix; 13990 } 13991 get pixelsToGLUnits() { 13992 return this._helper.pixelsToGLUnits; 13993 } 13994 get centerOffset() { 13995 return this._helper.centerOffset; 13996 } 13997 get size() { 13998 return this._helper.size; 13999 } 14000 get rotationMatrix() { 14001 return this._helper.rotationMatrix; 14002 } 14003 get centerPoint() { 14004 return this._helper.centerPoint; 14005 } 14006 get pixelsPerMeter() { 14007 return this._helper.pixelsPerMeter; 14008 } 14009 setMinZoom(zoom) { 14010 this._helper.setMinZoom(zoom); 14011 } 14012 setMaxZoom(zoom) { 14013 this._helper.setMaxZoom(zoom); 14014 } 14015 setMinPitch(pitch) { 14016 this._helper.setMinPitch(pitch); 14017 } 14018 setMaxPitch(pitch) { 14019 this._helper.setMaxPitch(pitch); 14020 } 14021 setRenderWorldCopies(renderWorldCopies) { 14022 this._helper.setRenderWorldCopies(renderWorldCopies); 14023 } 14024 setBearing(bearing) { 14025 this._helper.setBearing(bearing); 14026 } 14027 setPitch(pitch) { 14028 this._helper.setPitch(pitch); 14029 } 14030 setRoll(roll) { 14031 this._helper.setRoll(roll); 14032 } 14033 setFov(fov) { 14034 this._helper.setFov(fov); 14035 } 14036 setZoom(zoom) { 14037 this._helper.setZoom(zoom); 14038 } 14039 setCenter(center) { 14040 this._helper.setCenter(center); 14041 } 14042 setElevation(elevation) { 14043 this._helper.setElevation(elevation); 14044 } 14045 setMinElevationForCurrentTile(elevation) { 14046 this._helper.setMinElevationForCurrentTile(elevation); 14047 } 14048 setPadding(padding) { 14049 this._helper.setPadding(padding); 14050 } 14051 interpolatePadding(start, target, t) { 14052 this._helper.interpolatePadding(start, target, t); 14053 } 14054 isPaddingEqual(padding) { 14055 return this._helper.isPaddingEqual(padding); 14056 } 14057 resize(width, height, constrainTransform = true) { 14058 this._helper.resize(width, height, constrainTransform); 14059 } 14060 getMaxBounds() { 14061 return this._helper.getMaxBounds(); 14062 } 14063 setMaxBounds(bounds) { 14064 this._helper.setMaxBounds(bounds); 14065 } 14066 setConstrainOverride(constrain) { 14067 this._helper.setConstrainOverride(constrain); 14068 } 14069 overrideNearFarZ(nearZ, farZ) { 14070 this._helper.overrideNearFarZ(nearZ, farZ); 14071 } 14072 clearNearFarZOverride() { 14073 this._helper.clearNearFarZOverride(); 14074 } 14075 getCameraQueryGeometry(queryGeometry) { 14076 return this._helper.getCameraQueryGeometry(this.getCameraPoint(), queryGeometry); 14077 } 14078 get tileSize() { 14079 return this._helper.tileSize; 14080 } 14081 get tileZoom() { 14082 return this._helper.tileZoom; 14083 } 14084 get scale() { 14085 return this._helper.scale; 14086 } 14087 get worldSize() { 14088 return this._helper.worldSize; 14089 } 14090 get width() { 14091 return this._helper.width; 14092 } 14093 get height() { 14094 return this._helper.height; 14095 } 14096 get lngRange() { 14097 return this._helper.lngRange; 14098 } 14099 get latRange() { 14100 return this._helper.latRange; 14101 } 14102 get minZoom() { 14103 return this._helper.minZoom; 14104 } 14105 get maxZoom() { 14106 return this._helper.maxZoom; 14107 } 14108 get zoom() { 14109 return this._helper.zoom; 14110 } 14111 get center() { 14112 return this._helper.center; 14113 } 14114 get minPitch() { 14115 return this._helper.minPitch; 14116 } 14117 get maxPitch() { 14118 return this._helper.maxPitch; 14119 } 14120 get pitch() { 14121 return this._helper.pitch; 14122 } 14123 get pitchInRadians() { 14124 return this._helper.pitchInRadians; 14125 } 14126 get roll() { 14127 return this._helper.roll; 14128 } 14129 get rollInRadians() { 14130 return this._helper.rollInRadians; 14131 } 14132 get bearing() { 14133 return this._helper.bearing; 14134 } 14135 get bearingInRadians() { 14136 return this._helper.bearingInRadians; 14137 } 14138 get fov() { 14139 return this._helper.fov; 14140 } 14141 get fovInRadians() { 14142 return this._helper.fovInRadians; 14143 } 14144 get elevation() { 14145 return this._helper.elevation; 14146 } 14147 get minElevationForCurrentTile() { 14148 return this._helper.minElevationForCurrentTile; 14149 } 14150 get padding() { 14151 return this._helper.padding; 14152 } 14153 get unmodified() { 14154 return this._helper.unmodified; 14155 } 14156 get renderWorldCopies() { 14157 return this._helper.renderWorldCopies; 14158 } 14159 get cameraToCenterDistance() { 14160 return this._helper.cameraToCenterDistance; 14161 } 14162 get constrainOverride() { 14163 return this._helper.constrainOverride; 14164 } 14165 get nearZ() { 14166 return this._helper.nearZ; 14167 } 14168 get farZ() { 14169 return this._helper.farZ; 14170 } 14171 get autoCalculateNearFarZ() { 14172 return this._helper.autoCalculateNearFarZ; 14173 } 14174 /** 14175 * True when globe render path should be used instead of the old but simpler mercator rendering. 14176 * Globe automatically transitions to mercator at high zoom levels, which causes a switch from 14177 * globe to mercator render path. 14178 */ 14179 get isGlobeRendering() { 14180 return this._globeness > 0; 14181 } 14182 setTransitionState(globeness) { 14183 this._globeness = globeness; 14184 this._calcMatrices(); 14185 this._verticalPerspectiveTransform.getCoveringTilesDetailsProvider().prepareNextFrame(); 14186 this._mercatorTransform.getCoveringTilesDetailsProvider().prepareNextFrame(); 14187 } 14188 get currentTransform() { 14189 return this.isGlobeRendering ? this._verticalPerspectiveTransform : this._mercatorTransform; 14190 } 14191 constructor(options) { 14192 this._globeness = 1; 14193 this.defaultConstrain = (lngLat, zoom) => { 14194 return this.currentTransform.defaultConstrain(lngLat, zoom); 14195 }; 14196 this.applyConstrain = (lngLat, zoom) => { 14197 return this._helper.applyConstrain(lngLat, zoom); 14198 }; 14199 this._helper = new TransformHelper({ 14200 calcMatrices: () => this._calcMatrices(), 14201 defaultConstrain: (center, zoom) => { 14202 return this.defaultConstrain(center, zoom); 14203 } 14204 }, options); 14205 this._globeness = 1; 14206 this._mercatorTransform = new MercatorTransform(void 0, this._helper); 14207 this._verticalPerspectiveTransform = new VerticalPerspectiveTransform(void 0, this._helper); 14208 } 14209 clone() { 14210 const clone = new GlobeTransform(); 14211 clone._globeness = this._globeness; 14212 clone.apply(this, false); 14213 return clone; 14214 } 14215 apply(that, constrain) { 14216 this._helper.apply(that, constrain); 14217 } 14218 get projectionMatrix() { 14219 return this.currentTransform.projectionMatrix; 14220 } 14221 get modelViewProjectionMatrix() { 14222 return this.currentTransform.modelViewProjectionMatrix; 14223 } 14224 get inverseProjectionMatrix() { 14225 return this.currentTransform.inverseProjectionMatrix; 14226 } 14227 get cameraPosition() { 14228 return this.currentTransform.cameraPosition; 14229 } 14230 getProjectionData(params) { 14231 const mercatorProjectionData = this._mercatorTransform.getProjectionData(params); 14232 const verticalPerspectiveProjectionData = this._verticalPerspectiveTransform.getProjectionData(params); 14233 return { 14234 mainMatrix: this.isGlobeRendering ? verticalPerspectiveProjectionData.mainMatrix : mercatorProjectionData.mainMatrix, 14235 clippingPlane: verticalPerspectiveProjectionData.clippingPlane, 14236 tileMercatorCoords: verticalPerspectiveProjectionData.tileMercatorCoords, 14237 projectionTransition: params.applyGlobeMatrix ? this._globeness : 0, 14238 fallbackMatrix: mercatorProjectionData.fallbackMatrix, 14239 clipAntimeridian: verticalPerspectiveProjectionData.clipAntimeridian 14240 }; 14241 } 14242 /** {@inheritDoc ITransform.isLocationOccluded} */ 14243 isLocationOccluded(lngLat, terrain, elevation) { 14244 return this.currentTransform.isLocationOccluded(lngLat, terrain, elevation); 14245 } 14246 transformLightDirection(dir) { 14247 return this.currentTransform.transformLightDirection(dir); 14248 } 14249 getPixelScale() { 14250 return lerp(this._mercatorTransform.getPixelScale(), this._verticalPerspectiveTransform.getPixelScale(), this._globeness); 14251 } 14252 getCircleRadiusCorrection() { 14253 return lerp(this._mercatorTransform.getCircleRadiusCorrection(), this._verticalPerspectiveTransform.getCircleRadiusCorrection(), this._globeness); 14254 } 14255 getPitchedTextCorrection(textAnchorX, textAnchorY, tileID) { 14256 const mercatorCorrection = this._mercatorTransform.getPitchedTextCorrection(textAnchorX, textAnchorY, tileID); 14257 const verticalCorrection = this._verticalPerspectiveTransform.getPitchedTextCorrection(textAnchorX, textAnchorY, tileID); 14258 return lerp(mercatorCorrection, verticalCorrection, this._globeness); 14259 } 14260 projectTileCoordinates(x, y, unwrappedTileID, elevation) { 14261 return this.currentTransform.projectTileCoordinates(x, y, unwrappedTileID, elevation); 14262 } 14263 /** 14264 * Both children write their near/far Z into the shared helper, so the order here is what keeps the two render 14265 * paths at the same depth across the globe-to-mercator transition: vertical perspective computes the globe's Z 14266 * first, and while the globe is rendering mercator is made to reuse that result instead of computing its own. 14267 */ 14268 _calcMatrices() { 14269 if (!this._helper._width || !this._helper._height) return; 14270 this._verticalPerspectiveTransform._calcMatrices(); 14271 this._mercatorTransform._calcMatrices(this.autoCalculateNearFarZ && !this.isGlobeRendering); 14272 } 14273 calculateFogMatrix(unwrappedTileID) { 14274 return this.currentTransform.calculateFogMatrix(unwrappedTileID); 14275 } 14276 getVisibleUnwrappedCoordinates(tileID) { 14277 return this.currentTransform.getVisibleUnwrappedCoordinates(tileID); 14278 } 14279 getCameraFrustum() { 14280 return this.currentTransform.getCameraFrustum(); 14281 } 14282 getClippingPlane() { 14283 return this.currentTransform.getClippingPlane(); 14284 } 14285 getCoveringTilesDetailsProvider() { 14286 return this.currentTransform.getCoveringTilesDetailsProvider(); 14287 } 14288 recalculateZoomAndCenter(terrain) { 14289 this.currentTransform.recalculateZoomAndCenter(terrain); 14290 } 14291 maxPitchScaleFactor() { 14292 return this._mercatorTransform.maxPitchScaleFactor(); 14293 } 14294 getCameraPoint() { 14295 return this._helper.getCameraPoint(); 14296 } 14297 /** The camera of the child that renders the current frame. */ 14298 getCameraAltitude() { 14299 return this.currentTransform.getCameraAltitude(); 14300 } 14301 /** See {@link getCameraAltitude}. */ 14302 getCameraLngLat() { 14303 return this.currentTransform.getCameraLngLat(); 14304 } 14305 populateCache(coords) { 14306 this._mercatorTransform.populateCache(coords); 14307 this._verticalPerspectiveTransform.populateCache(coords); 14308 } 14309 getBounds() { 14310 return this.currentTransform.getBounds(); 14311 } 14312 calculateCenterFromCameraLngLatAlt(lngLat, alt, bearing, pitch) { 14313 return this._helper.calculateCenterFromCameraLngLatAlt(lngLat, alt, bearing, pitch); 14314 } 14315 /** See {@link getCameraAltitude}. */ 14316 calculateCameraOptionsFromTo(from, altitudeFrom, to, altitudeTo) { 14317 return this.currentTransform.calculateCameraOptionsFromTo(from, altitudeFrom, to, altitudeTo); 14318 } 14319 /** 14320 * Note: automatically adjusts zoom to keep planet size consistent 14321 * (same size before and after a {@link setLocationAtPoint} call). 14322 */ 14323 setLocationAtPoint(lnglat, point, elevation) { 14324 this.currentTransform.setLocationAtPoint(lnglat, point, elevation); 14325 } 14326 locationToScreenPoint(lnglat, terrain) { 14327 return this.currentTransform.locationToScreenPoint(lnglat, terrain); 14328 } 14329 screenPointToMercatorCoordinate(p, terrain) { 14330 return this.currentTransform.screenPointToMercatorCoordinate(p, terrain); 14331 } 14332 /** {@inheritDoc ITransform.screenTerrainPointToMercatorCoordinate} */ 14333 screenTerrainPointToMercatorCoordinate(p, terrain) { 14334 return this.currentTransform.screenTerrainPointToMercatorCoordinate(p, terrain); 14335 } 14336 screenPointToLocation(p, terrain) { 14337 return this.currentTransform.screenPointToLocation(p, terrain); 14338 } 14339 screenPointToLocationAtElevation(p, elevation) { 14340 return this.currentTransform.screenPointToLocationAtElevation(p, elevation); 14341 } 14342 isPointOnMapSurface(p, terrain) { 14343 return this.currentTransform.isPointOnMapSurface(p, terrain); 14344 } 14345 /** 14346 * Computes normalized direction of a ray from the camera to the given screen pixel. 14347 * 14348 * Always answered by the vertical perspective child: the ray is in unit-sphere space, so it is only meaningful 14349 * to the globe camera controls that ask for it, and the mercator transform does not implement it at all. 14350 */ 14351 getRayDirectionFromPixel(p) { 14352 return this._verticalPerspectiveTransform.getRayDirectionFromPixel(p); 14353 } 14354 getProjectionDataForCustomLayer(applyGlobeMatrix = true) { 14355 const mercatorData = this._mercatorTransform.getProjectionDataForCustomLayer(applyGlobeMatrix); 14356 if (!this.isGlobeRendering) return mercatorData; 14357 const globeData = this._verticalPerspectiveTransform.getProjectionDataForCustomLayer(applyGlobeMatrix); 14358 globeData.fallbackMatrix = mercatorData.mainMatrix; 14359 globeData.projectionTransition = this._globeness; 14360 return globeData; 14361 } 14362 getFastPathSimpleProjectionMatrix(tileID) { 14363 return this.currentTransform.getFastPathSimpleProjectionMatrix(tileID); 14364 } 14365}; 14366//#endregion 14367//#region src/geo/projection/vertical_perspective_camera_helper.ts 14368/** 14369* Zoom movement slowing starts when the mouse ray passes further than this above the planet surface, 14370* so a cursor off the globe does not move the map unnaturally. Globe radius is 1, so 0.3 is ~2000 km. 14371*/ 14372const RAY_SURFACE_DISTANCE_FOR_SLOWING_START = .3; 14373/** How sharply zoom movement slows as the mouse ray rises above the planet surface. Lower is more gradual. */ 14374const SLOWING_MULTIPLIER = .5; 14375/** Longitude difference between zoom location and map center at which the blend from exact to heuristic zooming starts, in degrees. */ 14376const INTERPOLATE_TO_HEURISTIC_START_LNG = 45; 14377/** Longitude difference at which the blend to heuristic zooming is complete, in degrees. */ 14378const INTERPOLATE_TO_HEURISTIC_END_LNG = 85; 14379/** Exponent applied to the blend factor: below 1, so the blend leans towards heuristic zooming and flattens as it completes. */ 14380const INTERPOLATE_TO_HEURISTIC_EXPONENT = .25; 14381/** 14382* Distance of the mouse ray from the globe center at which the blend from exact to heuristic zooming 14383* starts. Globe radius is 1, so 1 is a ray grazing the horizon. 14384*/ 14385const INTERPOLATE_TO_HEURISTIC_START_HORIZON = .95; 14386/** Ray distance at which the blend to heuristic zooming is complete. */ 14387const INTERPOLATE_TO_HEURISTIC_END_HORIZON = .999; 14388/** 14389* Globe radius relative to the smaller viewport dimension below which zoom movement near the horizon 14390* starts being inhibited, avoiding unnatural movements when the map is zoomed out a lot. 14391*/ 14392const SLOWING_RADIUS_START = .9; 14393/** Globe radius relative to the smaller viewport dimension at which that inhibition is at full strength. */ 14394const SLOWING_RADIUS_STOP = .5; 14395/** Fraction of the zoom movement that remains once the globe has shrunk to `SLOWING_RADIUS_STOP`. */ 14396const SLOWING_RADIUS_SLOW_FACTOR = .25; 14397/** 14398* @internal 14399*/ 14400var VerticalPerspectiveCameraHelper = class VerticalPerspectiveCameraHelper { 14401 get useGlobeControls() { 14402 return true; 14403 } 14404 handlePanInertia(pan, transform) { 14405 const panCenter = computeGlobePanCenter(pan, transform); 14406 if (Math.abs(panCenter.lng - transform.center.lng) > 180) panCenter.lng = transform.center.lng + 179.5 * Math.sign(panCenter.lng - transform.center.lng); 14407 return { 14408 easingCenter: panCenter, 14409 easingOffset: new Point(0, 0) 14410 }; 14411 } 14412 /** 14413 * Zooms around the pointer. 14414 * 14415 * `setLocationAtPoint` is exact but degenerates when called repeatedly for a 14416 * location whose longitude is far from the center's, or one near the horizon, 14417 * so those cases blend towards a heuristic. 14418 */ 14419 handleMapControlsRollPitchBearingZoom(deltas, tr) { 14420 const zoomPixel = deltas.around; 14421 const zoomLoc = tr.screenPointToLocation(zoomPixel); 14422 if (deltas.bearingDelta) tr.setBearing(tr.bearing + deltas.bearingDelta); 14423 if (deltas.pitchDelta) tr.setPitch(tr.pitch + deltas.pitchDelta); 14424 if (deltas.rollDelta) tr.setRoll(tr.roll + deltas.rollDelta); 14425 const oldZoomPreZoomDelta = tr.zoom; 14426 if (deltas.zoomDelta) tr.setZoom(tr.zoom + deltas.zoomDelta); 14427 const actualZoomDelta = tr.zoom - oldZoomPreZoomDelta; 14428 if (actualZoomDelta === 0) return; 14429 const dLngRaw = differenceOfAnglesDegrees(tr.center.lng, zoomLoc.lng); 14430 const dLng = dLngRaw / (Math.abs(dLngRaw / 180) + 1); 14431 const dLat = differenceOfAnglesDegrees(tr.center.lat, zoomLoc.lat); 14432 const rayDirection = tr.getRayDirectionFromPixel(zoomPixel); 14433 const rayOrigin = tr.cameraPosition; 14434 const distanceToClosestPoint = dot(rayOrigin, rayDirection) * -1; 14435 const closestPoint = createVec3f64();
vendor: 3,919 bytes, lines 14436-14495
14436 add(closestPoint, rayOrigin, [ 14437 rayDirection[0] * distanceToClosestPoint, 14438 rayDirection[1] * distanceToClosestPoint, 14439 rayDirection[2] * distanceToClosestPoint 14440 ]); 14441 const rayDistanceFromGlobeCenter = length(closestPoint); 14442 const distanceFromSurface = rayDistanceFromGlobeCenter - 1; 14443 const distanceFactor = Math.exp(-Math.max(distanceFromSurface - RAY_SURFACE_DISTANCE_FOR_SLOWING_START, 0) * SLOWING_MULTIPLIER); 14444 const interpolationFactorHorizon = remapSaturate(rayDistanceFromGlobeCenter, INTERPOLATE_TO_HEURISTIC_START_HORIZON, INTERPOLATE_TO_HEURISTIC_END_HORIZON, 0, 1); 14445 const radius = getGlobeRadiusPixels(tr.worldSize, tr.center.lat) / Math.min(tr.width, tr.height); 14446 const radiusFactor = remapSaturate(radius, SLOWING_RADIUS_START, SLOWING_RADIUS_STOP, 1, SLOWING_RADIUS_SLOW_FACTOR); 14447 const slowingFactor = Math.min(distanceFactor, lerp(1, radiusFactor, interpolationFactorHorizon)); 14448 const factor = (1 - zoomScale(-actualZoomDelta)) * slowingFactor; 14449 const oldCenterLat = tr.center.lat; 14450 const oldZoom = tr.zoom; 14451 const heuristicCenter = new LngLat(tr.center.lng + dLng * factor, clamp(tr.center.lat + dLat * factor, -MAX_VALID_LATITUDE, MAX_VALID_LATITUDE)); 14452 tr.setLocationAtPoint(zoomLoc, zoomPixel); 14453 const exactCenter = tr.center; 14454 const interpolationFactorLongitude = remapSaturate(Math.abs(dLngRaw), INTERPOLATE_TO_HEURISTIC_START_LNG, INTERPOLATE_TO_HEURISTIC_END_LNG, 0, 1); 14455 const heuristicFactor = Math.pow(Math.max(interpolationFactorLongitude, interpolationFactorHorizon), INTERPOLATE_TO_HEURISTIC_EXPONENT); 14456 const lngExactToHeuristic = differenceOfAnglesDegrees(exactCenter.lng, heuristicCenter.lng); 14457 const latExactToHeuristic = differenceOfAnglesDegrees(exactCenter.lat, heuristicCenter.lat); 14458 tr.setCenter(new LngLat(exactCenter.lng + lngExactToHeuristic * heuristicFactor, exactCenter.lat + latExactToHeuristic * heuristicFactor).wrap()); 14459 tr.setZoom(oldZoom + getZoomAdjustment(oldCenterLat, tr.center.lat)); 14460 } 14461 /** 14462 * Pans the globe by rotating it with a single quaternion that brings the grabbed location back 14463 * under the cursor. This stays consistent near and across the poles, where the previous 14464 * bearing-preserving mapping inverted and stalled (#5296). 14465 * @param deltas - The deltas accumulated for this frame. 14466 * @param tr - The transform to pan. 14467 * @param preZoomAroundLoc - The location that was under the cursor before this frame. 14468 */ 14469 handleMapControlsPan(deltas, tr, preZoomAroundLoc) { 14470 if (!deltas.panDelta) return; 14471 versorSetLocationAtPoint(tr, preZoomAroundLoc, tr.isPointOnMapSurface(deltas.around) ? deltas.around : tr.centerPoint, deltas.panDelta); 14472 } 14473 cameraForBoxAndBearing(options, padding, bounds, bearing, tr) { 14474 const result = cameraForBoxAndBearing(options, padding, bounds, bearing, tr); 14475 const xLeft = padding.left / tr.width * 2 - 1; 14476 const xRight = (tr.width - padding.right) / tr.width * 2 - 1; 14477 const yTop = padding.top / tr.height * -2 + 1; 14478 const yBottom = (tr.height - padding.bottom) / tr.height * -2 + 1; 14479 const flipEastWest = differenceOfAnglesDegrees(bounds.getWest(), bounds.getEast()) < 0; 14480 const lngWest = flipEastWest ? bounds.getEast() : bounds.getWest(); 14481 const lngEast = flipEastWest ? bounds.getWest() : bounds.getEast(); 14482 const latNorth = Math.max(bounds.getNorth(), bounds.getSouth()); 14483 const latSouth = Math.min(bounds.getNorth(), bounds.getSouth()); 14484 const lngMid = lngWest + differenceOfAnglesDegrees(lngWest, lngEast) * .5; 14485 const latMid = latNorth + differenceOfAnglesDegrees(latNorth, latSouth) * .5; 14486 const clonedTr = tr.clone(); 14487 clonedTr.setCenter(result.center); 14488 clonedTr.setBearing(result.bearing); 14489 clonedTr.setPitch(0); 14490 clonedTr.setRoll(0); 14491 clonedTr.setZoom(result.zoom); 14492 const matrix = clonedTr.modelViewProjectionMatrix; 14493 const testVectors = [ 14494 angularCoordinatesToSurfaceVector(bounds.getNorthWest()), 14495 angularCoordinatesToSurfaceVector(bounds.getNorthEast()),
vendor: 70,575 bytes, lines 14496-16261
14496 angularCoordinatesToSurfaceVector(bounds.getSouthWest()), 14497 angularCoordinatesToSurfaceVector(bounds.getSouthEast()), 14498 angularCoordinatesToSurfaceVector(new LngLat(lngEast, latMid)), 14499 angularCoordinatesToSurfaceVector(new LngLat(lngWest, latMid)), 14500 angularCoordinatesToSurfaceVector(new LngLat(lngMid, latNorth)), 14501 angularCoordinatesToSurfaceVector(new LngLat(lngMid, latSouth)) 14502 ]; 14503 const vecToCenter = angularCoordinatesToSurfaceVector(result.center); 14504 let smallestNeededScale = Number.POSITIVE_INFINITY; 14505 for (const vec of testVectors) { 14506 if (xLeft < 0) smallestNeededScale = VerticalPerspectiveCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, VerticalPerspectiveCameraHelper.solveVectorScale(vec, vecToCenter, matrix, "x", xLeft)); 14507 if (xRight > 0) smallestNeededScale = VerticalPerspectiveCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, VerticalPerspectiveCameraHelper.solveVectorScale(vec, vecToCenter, matrix, "x", xRight)); 14508 if (yTop > 0) smallestNeededScale = VerticalPerspectiveCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, VerticalPerspectiveCameraHelper.solveVectorScale(vec, vecToCenter, matrix, "y", yTop)); 14509 if (yBottom < 0) smallestNeededScale = VerticalPerspectiveCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, VerticalPerspectiveCameraHelper.solveVectorScale(vec, vecToCenter, matrix, "y", yBottom)); 14510 } 14511 if (!Number.isFinite(smallestNeededScale) || smallestNeededScale === 0) { 14512 cameraBoundsWarning(); 14513 return; 14514 } 14515 result.zoom = Math.min(clonedTr.zoom + scaleZoom(smallestNeededScale), options.maxZoom); 14516 return result; 14517 } 14518 /** 14519 * Handles the zoom and center change during camera jumpTo. 14520 */ 14521 handleJumpToCenterZoom(tr, options) { 14522 const startingLat = tr.center.lat; 14523 const constrainedCenter = tr.applyConstrain(options.center ? LngLat.convert(options.center) : tr.center, tr.zoom).center; 14524 tr.setCenter(constrainedCenter.wrap()); 14525 const targetZoom = typeof options.zoom !== "undefined" ? +options.zoom : tr.zoom + getZoomAdjustment(startingLat, constrainedCenter.lat); 14526 if (tr.zoom !== targetZoom) tr.setZoom(targetZoom); 14527 } 14528 handleEaseTo(tr, options) { 14529 const startZoom = tr.zoom; 14530 const startCenter = tr.center; 14531 const startPadding = tr.padding; 14532 const startEulerAngles = { 14533 roll: tr.roll, 14534 pitch: tr.pitch, 14535 bearing: tr.bearing 14536 }; 14537 const endEulerAngles = { 14538 roll: options.roll === void 0 ? tr.roll : options.roll, 14539 pitch: options.pitch === void 0 ? tr.pitch : options.pitch, 14540 bearing: options.bearing === void 0 ? tr.bearing : options.bearing 14541 }; 14542 const optionsZoom = typeof options.zoom !== "undefined"; 14543 const doPadding = !tr.isPaddingEqual(options.padding); 14544 let isZooming = false; 14545 const preConstrainCenter = options.center ? LngLat.convert(options.center) : startCenter; 14546 const constrainedCenter = tr.applyConstrain(preConstrainCenter, startZoom).center; 14547 normalizeCenter(tr, constrainedCenter); 14548 const clonedTr = tr.clone(); 14549 clonedTr.setCenter(constrainedCenter); 14550 clonedTr.setZoom(optionsZoom ? +options.zoom : startZoom + getZoomAdjustment(startCenter.lat, preConstrainCenter.lat)); 14551 clonedTr.setBearing(options.bearing); 14552 const clampedPoint = new Point(clamp(tr.centerPoint.x + options.offsetAsPoint.x, 0, tr.width), clamp(tr.centerPoint.y + options.offsetAsPoint.y, 0, tr.height)); 14553 clonedTr.setLocationAtPoint(constrainedCenter, clampedPoint); 14554 const endCenterWithShift = (options.offset && options.offsetAsPoint.mag()) > 0 ? clonedTr.center : constrainedCenter; 14555 const endZoomWithShift = optionsZoom ? +options.zoom : startZoom + getZoomAdjustment(startCenter.lat, endCenterWithShift.lat); 14556 const normalizedStartZoom = startZoom + getZoomAdjustment(startCenter.lat, 0); 14557 const normalizedEndZoom = endZoomWithShift + getZoomAdjustment(endCenterWithShift.lat, 0); 14558 const deltaLng = differenceOfAnglesDegrees(startCenter.lng, endCenterWithShift.lng); 14559 const deltaLat = differenceOfAnglesDegrees(startCenter.lat, endCenterWithShift.lat); 14560 const finalScale = zoomScale(normalizedEndZoom - normalizedStartZoom); 14561 isZooming = endZoomWithShift !== startZoom; 14562 const easeFunc = (k) => { 14563 if (!rollPitchBearingEqual(startEulerAngles, endEulerAngles)) updateRotation({ 14564 startEulerAngles, 14565 endEulerAngles, 14566 tr, 14567 k, 14568 useSlerp: startEulerAngles.roll != endEulerAngles.roll 14569 }); 14570 if (doPadding) tr.interpolatePadding(startPadding, options.padding, k); 14571 if (options.around) { 14572 warnOnce("Easing around a point is not supported under globe projection."); 14573 tr.setLocationAtPoint(options.around, options.aroundPoint); 14574 } else { 14575 const factor = k * Math.pow(normalizedEndZoom > normalizedStartZoom ? Math.min(2, finalScale) : Math.max(.5, finalScale), 1 - k); 14576 const newCenter = interpolateLngLatForGlobe(startCenter, deltaLng, deltaLat, factor); 14577 tr.setCenter(newCenter.wrap()); 14578 } 14579 if (isZooming) { 14580 const interpolatedZoom = interpolateFactory.number(normalizedStartZoom, normalizedEndZoom, k) + getZoomAdjustment(0, tr.center.lat); 14581 tr.setZoom(interpolatedZoom); 14582 } 14583 }; 14584 return { 14585 easeFunc, 14586 isZooming, 14587 elevationCenter: endCenterWithShift 14588 }; 14589 } 14590 handleFlyTo(tr, options) { 14591 const optionsZoom = typeof options.zoom !== "undefined"; 14592 const startCenter = tr.center; 14593 const startZoom = tr.zoom; 14594 const startPadding = tr.padding; 14595 const doPadding = !tr.isPaddingEqual(options.padding); 14596 const constrainedCenter = tr.applyConstrain(LngLat.convert(options.center || options.locationAtOffset), startZoom).center; 14597 const targetZoom = optionsZoom ? +options.zoom : tr.zoom + getZoomAdjustment(tr.center.lat, constrainedCenter.lat); 14598 const clonedTr = tr.clone(); 14599 clonedTr.setCenter(constrainedCenter); 14600 clonedTr.setZoom(targetZoom); 14601 clonedTr.setBearing(options.bearing); 14602 const clampedPoint = new Point(clamp(tr.centerPoint.x + options.offsetAsPoint.x, 0, tr.width), clamp(tr.centerPoint.y + options.offsetAsPoint.y, 0, tr.height)); 14603 clonedTr.setLocationAtPoint(constrainedCenter, clampedPoint); 14604 const targetCenter = clonedTr.center; 14605 normalizeCenter(tr, targetCenter); 14606 const pixelPathLength = globeDistanceOfLocationsPixels(tr, startCenter, targetCenter); 14607 const normalizedStartZoom = startZoom + getZoomAdjustment(startCenter.lat, 0); 14608 const normalizedTargetZoom = targetZoom + getZoomAdjustment(targetCenter.lat, 0); 14609 const scaleOfZoom = zoomScale(normalizedTargetZoom - normalizedStartZoom); 14610 const requestedMinZoom = typeof options.minZoom === "number" ? +options.minZoom : tr.minZoom; 14611 const normalizedEffectiveMinZoom = Math.max(requestedMinZoom, tr.minZoom) + getZoomAdjustment(targetCenter.lat, 0); 14612 const minZoomPreConstrain = Math.min(normalizedEffectiveMinZoom, normalizedStartZoom, normalizedTargetZoom) + getZoomAdjustment(0, targetCenter.lat); 14613 const normalizedMinZoom = tr.applyConstrain(targetCenter, minZoomPreConstrain).zoom + getZoomAdjustment(targetCenter.lat, 0); 14614 const scaleOfMinZoom = zoomScale(normalizedMinZoom - normalizedStartZoom); 14615 const deltaLng = differenceOfAnglesDegrees(startCenter.lng, targetCenter.lng); 14616 const deltaLat = differenceOfAnglesDegrees(startCenter.lat, targetCenter.lat); 14617 const easeFunc = (k, scale, centerFactor, _pointAtOffset) => { 14618 const interpolatedCenter = interpolateLngLatForGlobe(startCenter, deltaLng, deltaLat, centerFactor); 14619 if (doPadding) tr.interpolatePadding(startPadding, options.padding, k); 14620 const newCenter = k === 1 ? targetCenter : interpolatedCenter; 14621 tr.setCenter(newCenter.wrap()); 14622 const interpolatedZoom = normalizedStartZoom + scaleZoom(scale); 14623 tr.setZoom(k === 1 ? targetZoom : interpolatedZoom + getZoomAdjustment(0, newCenter.lat)); 14624 }; 14625 return { 14626 easeFunc, 14627 scaleOfZoom, 14628 targetCenter, 14629 scaleOfMinZoom, 14630 pixelPathLength 14631 }; 14632 } 14633 /** 14634 * Computes how much to scale the globe in order for a given point on its surface (a location) to project to a given clip space coordinate in either the X or the Y axis. 14635 * @param vector - Position of the queried location on the surface of the unit sphere globe. 14636 * @param toCenter - Position of current transform center on the surface of the unit sphere globe. 14637 * This is needed because zooming the globe not only changes its scale, 14638 * but also moves the camera closer or further away along this vector (pitch is disregarded). 14639 * @param projection - The globe projection matrix. 14640 * @param targetDimension - The dimension in which the scaled vector must match the target value in clip space. 14641 * @param targetValue - The target clip space value in the specified dimension to which the queried vector must project. 14642 * @returns How much to scale the globe. 14643 */ 14644 static solveVectorScale(vector, toCenter, projection, targetDimension, targetValue) { 14645 const k = targetValue; 14646 const columnXorY = targetDimension === "x" ? [ 14647 projection[0], 14648 projection[4], 14649 projection[8], 14650 projection[12] 14651 ] : [ 14652 projection[1], 14653 projection[5], 14654 projection[9], 14655 projection[13] 14656 ]; 14657 const columnZ = [ 14658 projection[3], 14659 projection[7], 14660 projection[11], 14661 projection[15] 14662 ]; 14663 const vecDotXY = vector[0] * columnXorY[0] + vector[1] * columnXorY[1] + vector[2] * columnXorY[2]; 14664 const vecDotZ = vector[0] * columnZ[0] + vector[1] * columnZ[1] + vector[2] * columnZ[2]; 14665 const toCenterDotXY = toCenter[0] * columnXorY[0] + toCenter[1] * columnXorY[1] + toCenter[2] * columnXorY[2]; 14666 const toCenterDotZ = toCenter[0] * columnZ[0] + toCenter[1] * columnZ[1] + toCenter[2] * columnZ[2]; 14667 const t = (toCenterDotXY + columnXorY[3] - k * toCenterDotZ - k * columnZ[3]) / (toCenterDotXY - vecDotXY - k * toCenterDotZ + k * vecDotZ); 14668 if (toCenterDotXY + k * vecDotZ === vecDotXY + k * toCenterDotZ || columnZ[3] * (vecDotXY - toCenterDotXY) + columnXorY[3] * (toCenterDotZ - vecDotZ) + vecDotXY * toCenterDotZ === toCenterDotXY * vecDotZ) return null; 14669 return t; 14670 } 14671 /** 14672 * Returns `newValue` if it is: 14673 * 14674 * - not null AND 14675 * - not negative AND 14676 * - smaller than `newValue`, 14677 * 14678 * ...otherwise returns `oldValue`. 14679 */ 14680 static getLesserNonNegativeNonNull(oldValue, newValue) { 14681 if (newValue !== null && newValue >= 0 && newValue < oldValue) return newValue; 14682 else return oldValue; 14683 } 14684}; 14685//#endregion 14686//#region src/geo/projection/globe_camera_helper.ts 14687/** 14688* @internal 14689*/ 14690var GlobeCameraHelper = class { 14691 constructor(globe) { 14692 this._globe = globe; 14693 this._mercatorCameraHelper = new MercatorCameraHelper(); 14694 this._verticalPerspectiveCameraHelper = new VerticalPerspectiveCameraHelper(); 14695 } 14696 get useGlobeControls() { 14697 return this._globe.useGlobeRendering; 14698 } 14699 get currentHelper() { 14700 return this.useGlobeControls ? this._verticalPerspectiveCameraHelper : this._mercatorCameraHelper; 14701 } 14702 handlePanInertia(pan, transform) { 14703 return this.currentHelper.handlePanInertia(pan, transform); 14704 } 14705 handleMapControlsRollPitchBearingZoom(deltas, tr) { 14706 this.currentHelper.handleMapControlsRollPitchBearingZoom(deltas, tr); 14707 } 14708 handleMapControlsPan(deltas, tr, preZoomAroundLoc) { 14709 this.currentHelper.handleMapControlsPan(deltas, tr, preZoomAroundLoc); 14710 } 14711 cameraForBoxAndBearing(options, padding, bounds, bearing, tr) { 14712 return this.currentHelper.cameraForBoxAndBearing(options, padding, bounds, bearing, tr); 14713 } 14714 /** 14715 * Handles the zoom and center change during camera jumpTo. 14716 */ 14717 handleJumpToCenterZoom(tr, options) { 14718 this.currentHelper.handleJumpToCenterZoom(tr, options); 14719 } 14720 handleEaseTo(tr, options) { 14721 return this.currentHelper.handleEaseTo(tr, options); 14722 } 14723 handleFlyTo(tr, options) { 14724 return this.currentHelper.handleFlyTo(tr, options); 14725 } 14726}; 14727//#endregion 14728//#region src/geo/projection/projection_factory.ts 14729function createProjectionFromName(name, transformConstrain, globalState) { 14730 const transformOptions = { constrainOverride: transformConstrain }; 14731 if (Array.isArray(name)) { 14732 const globeProjection = new GlobeProjection({ type: name }, globalState); 14733 return { 14734 projection: globeProjection, 14735 transform: new GlobeTransform(transformOptions), 14736 cameraHelper: new GlobeCameraHelper(globeProjection) 14737 }; 14738 } 14739 switch (name) { 14740 case "mercator": return { 14741 projection: new MercatorProjection(), 14742 transform: new MercatorTransform(transformOptions), 14743 cameraHelper: new MercatorCameraHelper() 14744 }; 14745 case "globe": { 14746 const globeProjection = new GlobeProjection({ type: [ 14747 "interpolate", 14748 ["linear"], 14749 ["zoom"], 14750 11, 14751 "vertical-perspective", 14752 12, 14753 "mercator" 14754 ] }, {}); 14755 return { 14756 projection: globeProjection, 14757 transform: new GlobeTransform(transformOptions), 14758 cameraHelper: new GlobeCameraHelper(globeProjection) 14759 }; 14760 } 14761 case "vertical-perspective": return { 14762 projection: new VerticalPerspectiveProjection(), 14763 transform: new VerticalPerspectiveTransform(transformOptions), 14764 cameraHelper: new VerticalPerspectiveCameraHelper() 14765 }; 14766 default: 14767 warnOnce(`Unknown projection name: ${name}. Falling back to mercator projection.`); 14768 return { 14769 projection: new MercatorProjection(), 14770 transform: new MercatorTransform(transformOptions), 14771 cameraHelper: new MercatorCameraHelper() 14772 }; 14773 } 14774} 14775//#endregion 14776//#region src/style/style.ts 14777const empty = emptyStyle(); 14778/** 14779* The Style base class 14780*/ 14781var Style = class extends Evented { 14782 constructor(map, options = {}) { 14783 super(); 14784 this._rtlPluginLoaded = () => { 14785 for (const id in this.tileManagers) { 14786 const sourceType = this.tileManagers[id].getSource().type; 14787 if (sourceType === "vector" || sourceType === "geojson") this.tileManagers[id].reload(); 14788 } 14789 }; 14790 this.map = map; 14791 this.dispatcher = new Dispatcher(getGlobalWorkerPool(), map._getMapId()).setEventedParent(this); 14792 this.dispatcher.registerMessageHandler("GG", (mapId, params) => { 14793 return this.getGlyphs(mapId, params); 14794 }); 14795 this.dispatcher.registerMessageHandler("GI", (mapId, params) => { 14796 return this.getImages(mapId, params); 14797 }); 14798 this.dispatcher.registerMessageHandler("GDA", (mapId, params) => { 14799 return this.getDashes(mapId, params); 14800 }); 14801 this.imageManager = new ImageManager(); 14802 this.imageManager.setEventedParent(this); 14803 this.imageManager.setMissingImageResolver(map._missingStyleImageResolver); 14804 this.patternAtlas = new PatternAtlas(this.imageManager); 14805 const glyphLang = map._container?.lang || typeof document !== "undefined" && document.documentElement?.lang || void 0; 14806 this.glyphManager = new GlyphManager(map._requestManager, options.localIdeographFontFamily, glyphLang); 14807 this.lineAtlas = new LineAtlas(256, 512); 14808 this.crossTileSymbolIndex = new CrossTileSymbolIndex(); 14809 this._setInitialValues(); 14810 this._resetUpdates(); 14811 this.dispatcher.broadcast("SR", getReferrer()); 14812 rtlMainThreadPluginFactory().on(RTLPluginLoadedEventName, this._rtlPluginLoaded); 14813 this.on("data", (event) => { 14814 if (event.dataType !== "source" || event.sourceDataType !== "metadata") return; 14815 const tileManager = this.tileManagers[event.sourceId]; 14816 if (!tileManager) return; 14817 const source = tileManager.getSource(); 14818 if (!source?.vectorLayerIds) return; 14819 for (const layerId in this._layers) { 14820 const layer = this._layers[layerId]; 14821 if (layer.source === source.id) this._validateLayer(layer); 14822 } 14823 }); 14824 } 14825 _setInitialValues() { 14826 this._layers = {}; 14827 this._order = []; 14828 this.tileManagers = {}; 14829 this.zoomHistory = new ZoomHistory(); 14830 this._imagesListDirty = false; 14831 this._globalState = {}; 14832 this._serializedLayers = {}; 14833 this.stylesheet = null; 14834 this.light = null; 14835 this.sky = null; 14836 if (this.projection) { 14837 this.projection.destroy(); 14838 delete this.projection; 14839 } 14840 this._loaded = false; 14841 this._changed = false; 14842 this._updatedLayers = {}; 14843 this._updatedSources = {}; 14844 this._changedImages = {}; 14845 this._glyphsDidChange = false; 14846 this._updatedPaintProps = {}; 14847 this._layerOrderChanged = false; 14848 this._symbolPlacementTriggered = false; 14849 this._placedProjectionTransition = void 0; 14850 this.crossTileSymbolIndex = new ((this.crossTileSymbolIndex?.constructor) || Object)(); 14851 this.pauseablePlacement = void 0; 14852 this.placement = void 0; 14853 this.z = 0; 14854 } 14855 setGlobalStateProperty(name, value) { 14856 this._checkLoaded(); 14857 const newValue = value === null ? this.stylesheet.state?.[name]?.default ?? null : value; 14858 this._setGlobalStateValues({ [name]: newValue }); 14859 return this; 14860 } 14861 getGlobalState() { 14862 return this._globalState; 14863 } 14864 setGlobalState(newStylesheetState) { 14865 this._checkLoaded(); 14866 const values = {}; 14867 for (const propertyName in newStylesheetState) values[propertyName] = newStylesheetState[propertyName].default; 14868 this._setGlobalStateValues(values); 14869 } 14870 /** Sets the keys in `values` that differ, keeping a copy of the state from before so readers transition from it. */ 14871 _setGlobalStateValues(values) { 14872 const changedGlobalStateRefs = []; 14873 for (const ref in values) if (!deepEqual(values[ref], this._globalState[ref])) changedGlobalStateRefs.push(ref); 14874 if (changedGlobalStateRefs.length === 0) return; 14875 const priorGlobalState = { ...this._globalState }; 14876 for (const ref of changedGlobalStateRefs) this._globalState[ref] = values[ref]; 14877 this._applyGlobalStateChanges(changedGlobalStateRefs, priorGlobalState); 14878 } 14879 /** 14880 * @internal 14881 * Find all sources that are affected by the global state changes and reload them. 14882 * Find all paint, light and sky properties that are affected and transition them from `priorGlobalState`, the state before the change. 14883 * For example, if a layer filter uses global-state expression, this function will find the source id of that layer. 14884 */ 14885 _applyGlobalStateChanges(globalStateRefs, priorGlobalState) { 14886 const sourceIdsToReload = /* @__PURE__ */ new Set(); 14887 const globalStateChange = {}; 14888 for (const ref of globalStateRefs) globalStateChange[ref] = this._globalState[ref]; 14889 for (const layerId in this._layers) { 14890 const layer = this._layers[layerId]; 14891 layer.retainPriorGlobalState(globalStateRefs, priorGlobalState); 14892 const layoutAffectingGlobalStateRefs = layer.getLayoutAffectingGlobalStateRefs(); 14893 const paintAffectingGlobalStateRefs = layer.getPaintAffectingGlobalStateRefs(); 14894 const visibilityAffectingGlobalStateRefs = layer.getVisibilityAffectingGlobalStateRefs(); 14895 for (const ref of globalStateRefs) { 14896 if (layoutAffectingGlobalStateRefs.has(ref)) sourceIdsToReload.add(layer.source); 14897 if (paintAffectingGlobalStateRefs.has(ref)) for (const { name, value } of paintAffectingGlobalStateRefs.get(ref)) this._updatePaintProperty(layer, name, value, { validate: false }); 14898 if (visibilityAffectingGlobalStateRefs?.has(ref)) { 14899 layer.recalculateVisibility(); 14900 this._updateLayer(layer); 14901 } 14902 } 14903 } 14904 const parameters = { 14905 now: now(), 14906 transition: this.getTransition() 14907 }; 14908 this.light?.applyGlobalStateChange(globalStateRefs, priorGlobalState, parameters); 14909 this.sky?.applyGlobalStateChange(globalStateRefs, priorGlobalState, parameters); 14910 this.dispatcher.broadcast("UGS", globalStateChange); 14911 for (const id in this.tileManagers) if (sourceIdsToReload.has(id)) { 14912 this._reloadSource(id); 14913 this._changed = true; 14914 } 14915 } 14916 async loadURL(url, options = {}, previousStyle) { 14917 this.fire(new MapStyleDataEvent("dataloading")); 14918 options.validate = typeof options.validate === "boolean" ? options.validate : true; 14919 this._loadStyleRequest = new AbortController(); 14920 const abortController = this._loadStyleRequest; 14921 try { 14922 const request = await this.map._requestManager.transformRequest(url, "Style"); 14923 throwIfAborted(abortController.signal); 14924 const response = await getJSON(request, abortController); 14925 if (this._loadStyleRequest === abortController) this._loadStyleRequest = null; 14926 this._load(response.data, options, previousStyle); 14927 } catch (error) { 14928 if (this._loadStyleRequest === abortController) this._loadStyleRequest = null; 14929 if (error && !abortController.signal.aborted) this.fire(new ErrorEvent(ensureError(error))); 14930 } 14931 } 14932 loadJSON(json, options = {}, previousStyle) { 14933 this.fire(new MapStyleDataEvent("dataloading")); 14934 this._frameRequest = new AbortController(); 14935 browser.frameAsync(this._frameRequest, this.map._ownerWindow).then(() => { 14936 this._frameRequest = null; 14937 options.validate = options.validate !== false; 14938 this._load(json, options, previousStyle); 14939 }).catch(() => {}); 14940 } 14941 loadEmpty() { 14942 this.fire(new MapStyleDataEvent("dataloading")); 14943 this._load(empty, { validate: false }); 14944 } 14945 _load(json, options, previousStyle) { 14946 let nextState = options.transformStyle ? options.transformStyle(previousStyle, json) : json; 14947 if (options.validate && validateStyleAndEmit(this, nextState)) return; 14948 nextState = { ...nextState }; 14949 this._loaded = true; 14950 this.stylesheet = nextState; 14951 for (const id in nextState.sources) this.addSource(id, nextState.sources[id], { validate: false }); 14952 if (nextState.sprite) this._loadSprite(nextState.sprite); 14953 else this.imageManager.setLoaded(true); 14954 this.glyphManager.setURL(nextState.glyphs); 14955 this.glyphManager.setFontFaces(nextState["font-faces"]); 14956 this._createLayers(); 14957 this.light = new Light(this.stylesheet.light ?? {}, this._globalState); 14958 this._setProjectionInternal(this.stylesheet.projection?.type || "mercator"); 14959 this.sky = new Sky(this.stylesheet.sky, this._globalState); 14960 this.sky.setEventedParent(this); 14961 this.map.setTerrain(this.stylesheet.terrain ?? null, { validate: false }); 14962 this.fire(new MapStyleDataEvent("data")); 14963 this.fire(new MapStyleLoadEvent()); 14964 } 14965 _createLayers() { 14966 const dereferencedLayers = derefLayers(this.stylesheet.layers); 14967 this.setGlobalState(this.stylesheet.state ?? null); 14968 this.dispatcher.broadcast("SL", dereferencedLayers); 14969 this._order = dereferencedLayers.map((layer) => layer.id); 14970 this._layers = {}; 14971 this._serializedLayers = null; 14972 for (const layer of dereferencedLayers) { 14973 const styledLayer = createStyleLayer(layer, this._globalState); 14974 styledLayer.setEventedParent(this, { layer: { id: layer.id } }); 14975 this._layers[layer.id] = styledLayer; 14976 if (isRasterStyleLayer(styledLayer) && this.tileManagers[styledLayer.source]) { 14977 const rasterFadeDuration = layer.paint?.["raster-fade-duration"] ?? styledLayer.paint.get("raster-fade-duration"); 14978 this.tileManagers[styledLayer.source].setRasterFadeDuration(rasterFadeDuration); 14979 } 14980 } 14981 } 14982 async _loadSprite(sprite, isUpdate = false, completion = void 0) { 14983 this.imageManager.setLoaded(false); 14984 const abortController = new AbortController(); 14985 this._spriteRequest = abortController; 14986 let err; 14987 try { 14988 const images = await loadSprite(sprite, this.map._requestManager, this.map.getPixelRatio(), abortController); 14989 if (!images) return; 14990 for (const spriteId in images) { 14991 const { loaded, removed } = this.imageManager.setSpriteImages(spriteId, images[spriteId]); 14992 this._markImagesChanged(removed); 14993 if (isUpdate) this._markImagesChanged(loaded); 14994 } 14995 } catch (error) { 14996 err = error; 14997 if (!abortController.signal.aborted) this.fire(new ErrorEvent(err)); 14998 } finally { 14999 this._spriteRequest = null; 15000 this.imageManager.setLoaded(true); 15001 if (isUpdate) this._changed = true; 15002 this.dispatcher.broadcast("SI", this.imageManager.listImages()); 15003 this.fire(new MapStyleDataEvent("data")); 15004 completion?.(err); 15005 } 15006 } 15007 _unloadSprite() { 15008 this._markImagesChanged(this.imageManager.removeAllSpriteImages()); 15009 this._imagesListDirty = true; 15010 this._changed = true; 15011 this.fire(new MapStyleDataEvent("data")); 15012 } 15013 _validateLayer(layer) { 15014 const tileManager = this.tileManagers[layer.source]; 15015 if (!tileManager) return; 15016 const sourceLayer = layer.sourceLayer; 15017 if (!sourceLayer) return; 15018 const source = tileManager.getSource(); 15019 if (source.type === "geojson" || source.vectorLayerIds && !source.vectorLayerIds.includes(sourceLayer)) this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Source layer "${sourceLayer}" does not exist on source "${source.id}" as specified by style layer "${layer.id}".`))); 15020 } 15021 loaded() { 15022 if (!this._loaded) return false; 15023 if (Object.keys(this._updatedSources).length) return false; 15024 for (const id in this.tileManagers) if (!this.tileManagers[id].loaded()) return false; 15025 return this.imageManager.isLoaded(); 15026 } 15027 /** 15028 * @hidden 15029 * take an array of string IDs, and based on this._layers, generate an array of LayerSpecification 15030 * @param ids - an array of string IDs, for which serialized layers will be generated. If omitted, all serialized layers will be returned 15031 * @param returnClone - if true, return a clone of the layer object 15032 * @returns generated result 15033 */ 15034 _serializeByIds(ids, returnClone = false) { 15035 const serializedLayersDictionary = this._serializedAllLayers(); 15036 if (!ids || ids.length === 0) return returnClone ? Object.values(clone$2(serializedLayersDictionary)) : Object.values(serializedLayersDictionary); 15037 const serializedLayers = []; 15038 for (const id of ids) if (serializedLayersDictionary[id]) { 15039 const toPush = returnClone ? clone$2(serializedLayersDictionary[id]) : serializedLayersDictionary[id]; 15040 serializedLayers.push(toPush); 15041 } 15042 return serializedLayers; 15043 } 15044 /** 15045 * @hidden 15046 * Lazy initialization of this._serializedLayers dictionary and return it 15047 * @returns this._serializedLayers dictionary 15048 */ 15049 _serializedAllLayers() { 15050 let serializedLayers = this._serializedLayers; 15051 if (serializedLayers) return serializedLayers; 15052 serializedLayers = this._serializedLayers = {}; 15053 const allLayerIds = Object.keys(this._layers); 15054 for (const layerId of allLayerIds) { 15055 const layer = this._layers[layerId]; 15056 if (layer.type !== "custom") serializedLayers[layerId] = layer.serialize(); 15057 } 15058 return serializedLayers; 15059 } 15060 hasTransitions() { 15061 if (this.light?.hasTransition()) return true; 15062 if (this.sky?.hasTransition()) return true; 15063 if (this.projection?.hasTransition()) return true; 15064 for (const id in this.tileManagers) if (this.tileManagers[id].hasTransition()) return true; 15065 for (const id in this._layers) if (this._layers[id].hasTransition()) return true; 15066 return false; 15067 } 15068 _checkLoaded() { 15069 if (!this._loaded) throw new Error("Style is not done loading."); 15070 } 15071 /** 15072 * @internal 15073 * Apply queued style updates in a batch and recalculate zoom-dependent paint properties. 15074 */ 15075 update(parameters) { 15076 if (!this._loaded) return; 15077 const changed = this._changed; 15078 if (changed) { 15079 if (this._imagesListDirty) { 15080 this.dispatcher.broadcast("SI", this.imageManager.listImages()); 15081 this._imagesListDirty = false; 15082 } 15083 const updatedIds = Object.keys(this._updatedLayers); 15084 const removedIds = Object.keys(this._removedLayers); 15085 if (updatedIds.length || removedIds.length) this._updateWorkerLayers(updatedIds, removedIds); 15086 for (const id in this._updatedSources) { 15087 const action = this._updatedSources[id]; 15088 if (action === "reload") this._reloadSource(id); 15089 else if (action === "clear") this._clearSource(id); 15090 else throw new Error(`Invalid action ${action}`); 15091 } 15092 this._updateTilesForChangedImages(); 15093 this._updateTilesForChangedGlyphs(); 15094 for (const id in this._updatedPaintProps) this._layers[id].updateTransitions(parameters); 15095 this._resetUpdates(); 15096 } 15097 const managersUsedBefore = {}; 15098 for (const id in this.tileManagers) { 15099 const tileManager = this.tileManagers[id]; 15100 managersUsedBefore[id] = tileManager.used; 15101 tileManager.used = false; 15102 } 15103 const availableImages = this.imageManager.listImages(); 15104 for (const layerId of this._order) { 15105 const layer = this._layers[layerId]; 15106 layer.recalculate(parameters, availableImages); 15107 if (!layer.isHidden(parameters.zoom) && layer.source) this.tileManagers[layer.source].used = true; 15108 } 15109 for (const id in managersUsedBefore) { 15110 const tileManager = this.tileManagers[id]; 15111 if (!!managersUsedBefore[id] !== !!tileManager.used) tileManager.fire(new MapSourceDataEvent("data", { 15112 sourceDataType: "visibility", 15113 sourceId: id 15114 })); 15115 } 15116 this.light.recalculate(parameters); 15117 this.sky.recalculate(parameters); 15118 this.projection.recalculate(parameters); 15119 this.z = parameters.zoom; 15120 if (changed) this.fire(new MapStyleDataEvent("data")); 15121 } 15122 _updateTilesForChangedImages() { 15123 const changedImages = Object.keys(this._changedImages); 15124 if (changedImages.length) { 15125 for (const name in this.tileManagers) this.tileManagers[name].reloadTilesForDependencies(["icons", "patterns"], changedImages); 15126 this._changedImages = {}; 15127 } 15128 } 15129 _updateTilesForChangedGlyphs() { 15130 if (this._glyphsDidChange) { 15131 for (const name in this.tileManagers) this.tileManagers[name].reloadTilesForDependencies(["glyphs"], [""]); 15132 this._glyphsDidChange = false; 15133 } 15134 } 15135 _updateWorkerLayers(updatedIds, removedIds) { 15136 this.dispatcher.broadcast("UL", { 15137 layers: this._serializeByIds(updatedIds, false), 15138 removedIds 15139 }); 15140 } 15141 _resetUpdates() { 15142 this._changed = false; 15143 this._updatedLayers = {}; 15144 this._removedLayers = {}; 15145 this._updatedSources = {}; 15146 this._updatedPaintProps = {}; 15147 this._changedImages = {}; 15148 this._glyphsDidChange = false; 15149 } 15150 /** 15151 * Update this style's state to match the given style JSON, performing only 15152 * the necessary mutations. 15153 * 15154 * May throw an Error ('Unimplemented: METHOD') if the maplibre-gl-style-spec 15155 * diff algorithm produces an operation that is not supported. 15156 * 15157 * @returns true if any changes were made; false otherwise 15158 */ 15159 setState(nextState, options = {}) { 15160 this._checkLoaded(); 15161 const serializedStyle = this.serialize(); 15162 nextState = options.transformStyle ? options.transformStyle(serializedStyle, nextState) : nextState; 15163 if ((options.validate ?? true) && validateStyleAndEmit(this, nextState)) return false; 15164 nextState = clone$2(nextState); 15165 nextState.layers = derefLayers(nextState.layers); 15166 const changes = diff(serializedStyle, nextState); 15167 const operations = this._getOperationsToPerform(changes); 15168 if (operations.unimplemented.length > 0) throw new Error(`Unimplemented: ${operations.unimplemented.join(", ")}.`); 15169 if (operations.operations.length === 0) return false; 15170 for (const styleChangeOperation of operations.operations) styleChangeOperation(); 15171 this.stylesheet = nextState; 15172 this._serializedLayers = null; 15173 this.fire(new MapStyleLoadEvent({ style: this })); 15174 return true; 15175 } 15176 /** 15177 * Translates a style diff into the calls that apply it to this style. 15178 * @param diff - the operations produced by the style-spec diff algorithm 15179 * @returns the operations to run, and the names of the commands that are not supported 15180 */ 15181 _getOperationsToPerform(diff) { 15182 const operations = []; 15183 const unimplemented = []; 15184 for (const op of diff) switch (op.command) { 15185 case "setCenter": 15186 case "setZoom": 15187 case "setBearing": 15188 case "setPitch": 15189 case "setRoll": continue; 15190 case "addLayer": { 15191 const [layer, before] = op.args; 15192 operations.push(() => this.addLayer(layer, before)); 15193 break; 15194 } 15195 case "removeLayer": { 15196 const [layerId] = op.args; 15197 operations.push(() => this.removeLayer(layerId)); 15198 break; 15199 } 15200 case "setPaintProperty": { 15201 const [layerId, name, value] = op.args; 15202 operations.push(() => this.setPaintProperty(layerId, name, value)); 15203 break; 15204 } 15205 case "setLayoutProperty": { 15206 const [layerId, name, value] = op.args; 15207 operations.push(() => this.setLayoutProperty(layerId, name, value)); 15208 break; 15209 } 15210 case "setFilter": { 15211 const [layerId, filter] = op.args; 15212 operations.push(() => this.setFilter(layerId, filter)); 15213 break; 15214 } 15215 case "addSource": { 15216 const [id, source] = op.args; 15217 operations.push(() => this.addSource(id, source)); 15218 break; 15219 } 15220 case "removeSource": { 15221 const [id] = op.args; 15222 operations.push(() => this.removeSource(id)); 15223 break; 15224 } 15225 case "setLayerZoomRange": { 15226 const [layerId, minzoom, maxzoom] = op.args; 15227 operations.push(() => this.setLayerZoomRange(layerId, minzoom, maxzoom)); 15228 break; 15229 } 15230 case "setLight": { 15231 const [light] = op.args; 15232 operations.push(() => this.setLight(light)); 15233 break; 15234 } 15235 case "setGeoJSONSourceData": { 15236 const [id, data] = op.args; 15237 operations.push(() => this.setGeoJSONSourceData(id, data)); 15238 break; 15239 } 15240 case "setGlyphs": { 15241 const [glyphsUrl] = op.args; 15242 operations.push(() => this.setGlyphs(glyphsUrl)); 15243 break; 15244 } 15245 case "setFontFaces": { 15246 const [fontFaces] = op.args; 15247 operations.push(() => this.setFontFaces(fontFaces)); 15248 break; 15249 } 15250 case "setSprite": { 15251 const [sprite] = op.args; 15252 operations.push(() => this.setSprite(sprite)); 15253 break; 15254 } 15255 case "setTerrain": { 15256 const [terrain] = op.args; 15257 operations.push(() => this.map.setTerrain(terrain)); 15258 break; 15259 } 15260 case "setSky": { 15261 const [sky] = op.args; 15262 operations.push(() => this.setSky(sky)); 15263 break; 15264 } 15265 case "setProjection": { 15266 const [projection] = op.args; 15267 operations.push(() => this.setProjection(projection)); 15268 break; 15269 } 15270 case "setGlobalState": { 15271 const [state] = op.args; 15272 operations.push(() => this.setGlobalState(state)); 15273 break; 15274 } 15275 case "setTransition": 15276 operations.push(() => {}); 15277 break; 15278 default: unimplemented.push(op.command); 15279 } 15280 return { 15281 operations, 15282 unimplemented 15283 }; 15284 } 15285 addImage(id, image) { 15286 if (this.getImage(id)) { 15287 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`An image named "${id}" already exists.`))); 15288 return; 15289 } 15290 this.imageManager.addImage(id, image); 15291 this._afterImageUpdated(id); 15292 } 15293 updateImage(id, image) { 15294 this.imageManager.updateImage(id, image); 15295 } 15296 getImage(id) { 15297 return this.imageManager.getImage(id); 15298 } 15299 setMissingImageResolver(resolver) { 15300 this.imageManager.setMissingImageResolver(resolver); 15301 } 15302 removeImage(id) { 15303 if (!this.getImage(id)) { 15304 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`An image named "${id}" does not exist.`))); 15305 return; 15306 } 15307 this.imageManager.removeImage(id); 15308 this._afterImageUpdated(id); 15309 } 15310 /** 15311 * Queues the tiles that depend on these images to be reloaded on the next update, which is 15312 * needed whenever an image appears, disappears or changes size. 15313 */ 15314 _markImagesChanged(ids) { 15315 for (const id of ids) this._changedImages[id] = true; 15316 } 15317 _afterImageUpdated(id) { 15318 this._changedImages[id] = true; 15319 this._imagesListDirty = true; 15320 this._changed = true; 15321 this.fire(new MapStyleDataEvent("data")); 15322 } 15323 listImages() { 15324 this._checkLoaded(); 15325 return this.imageManager.listImages(); 15326 } 15327 addSource(id, source, options = {}) { 15328 this._checkLoaded(); 15329 if (this.tileManagers[id] !== void 0) throw new Error(`Source "${id}" already exists.`); 15330 if (!source.type) throw new Error(`The type property must be defined, but only the following properties were given: ${Object.keys(source).join(", ")}.`); 15331 if (SPEC_SOURCE_TYPES.has(source.type) && this._validate(validateStyle.source, `sources.${id}`, source, null, options)) return; 15332 if (this.map?._collectResourceTiming) source.collectResourceTiming = true; 15333 const tileManager = this.tileManagers[id] = new TileManager(id, source, this.dispatcher); 15334 tileManager.style = this; 15335 tileManager.setEventedParent(this, () => ({ 15336 isSourceLoaded: tileManager.loaded(), 15337 source: tileManager.serialize(), 15338 sourceId: id 15339 })); 15340 tileManager.onAdd(this.map); 15341 this._changed = true; 15342 } 15343 /** 15344 * Remove a source from this stylesheet, given its id. 15345 * @param id - id of the source to remove 15346 * @throws if no source is found with the given ID 15347 */ 15348 removeSource(id) { 15349 this._checkLoaded(); 15350 if (this.tileManagers[id] === void 0) throw new Error(`There is no source with this ID=${id}`); 15351 for (const layerId in this._layers) if (this._layers[layerId].source === id) return this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Source "${id}" cannot be removed while layer "${layerId}" is using it.`))); 15352 const tileManager = this.tileManagers[id]; 15353 delete this.tileManagers[id]; 15354 delete this._updatedSources[id]; 15355 tileManager.fire(new MapSourceDataEvent("data", { 15356 sourceDataType: "metadata", 15357 sourceId: id 15358 })); 15359 tileManager.setEventedParent(null); 15360 tileManager.onRemove(this.map); 15361 this._changed = true; 15362 } 15363 /** 15364 * Set the data of a GeoJSON source, given its id. 15365 * @param id - id of the source 15366 * @param data - GeoJSON source 15367 */ 15368 setGeoJSONSourceData(id, data) { 15369 this._checkLoaded(); 15370 if (this.tileManagers[id] === void 0) throw new Error(`There is no source with this ID=${id}`); 15371 const geojsonSource = this.tileManagers[id].getSource(); 15372 if (geojsonSource.type !== "geojson") throw new Error(`geojsonSource.type is ${geojsonSource.type}, which is !== 'geojson`); 15373 geojsonSource.setData(data); 15374 this._changed = true; 15375 } 15376 /** 15377 * Get a source by ID. 15378 * @param id - ID of the desired source 15379 * @returns source 15380 */ 15381 getSource(id) { 15382 return this.tileManagers[id]?.getSource(); 15383 } 15384 /** 15385 * Add a layer to the map style. The layer will be inserted before the layer with 15386 * ID `before`, or appended if `before` is omitted. 15387 * @param layerObject - The style layer to add. 15388 * @param before - ID of an existing layer to insert before 15389 * @param options - Style setter options. 15390 */ 15391 addLayer(layerObject, before, options = {}) { 15392 this._checkLoaded(); 15393 const id = layerObject.id; 15394 if (this.getLayer(id)) { 15395 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Layer "${id}" already exists on this map.`))); 15396 return; 15397 } 15398 let layer; 15399 if (layerObject.type === "custom") { 15400 if (emitValidationErrors(this, validateCustomStyleLayer(layerObject))) return; 15401 layer = createStyleLayer(layerObject, this._globalState); 15402 } else { 15403 if ("source" in layerObject && typeof layerObject.source === "object") { 15404 this.addSource(id, layerObject.source); 15405 layerObject = clone$2(layerObject); 15406 layerObject = extend(layerObject, { source: id }); 15407 } 15408 if (this._validate(validateStyle.layer, `layers.${id}`, layerObject, { arrayIndex: -1 }, options)) return; 15409 layer = createStyleLayer(layerObject, this._globalState); 15410 this._validateLayer(layer); 15411 layer.setEventedParent(this, { layer: { id } }); 15412 } 15413 const index = before ? this._order.indexOf(before) : this._order.length; 15414 if (before && index === -1) { 15415 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Cannot add layer "${id}" before non-existing layer "${before}".`))); 15416 return; 15417 } 15418 this._order.splice(index, 0, id); 15419 this._layerOrderChanged = true; 15420 this._layers[id] = layer; 15421 if (this._removedLayers[id] && layer.source && layer.type !== "custom") { 15422 const removed = this._removedLayers[id]; 15423 delete this._removedLayers[id]; 15424 if (removed.type !== layer.type) this._updatedSources[layer.source] = "clear"; 15425 else { 15426 this._updatedSources[layer.source] = "reload"; 15427 this.tileManagers[layer.source].pause(); 15428 } 15429 } 15430 this._updateLayer(layer); 15431 if (layer.onAdd) layer.onAdd(this.map); 15432 } 15433 /** 15434 * Moves a layer to a different z-position. The layer will be inserted before the layer with 15435 * ID `before`, or appended if `before` is omitted. 15436 * @param id - ID of the layer to move 15437 * @param before - ID of an existing layer to insert before 15438 */ 15439 moveLayer(id, before) { 15440 this._checkLoaded(); 15441 this._changed = true; 15442 if (!this._layers[id]) { 15443 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`The layer '${id}' does not exist in the map's style and cannot be moved.`))); 15444 return; 15445 } 15446 if (id === before) return; 15447 if (before && !this._order.includes(before)) { 15448 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Cannot move layer "${id}" before non-existing layer "${before}".`))); 15449 return; 15450 } 15451 const index = this._order.indexOf(id); 15452 this._order.splice(index, 1); 15453 const newIndex = before ? this._order.indexOf(before) : this._order.length; 15454 this._order.splice(newIndex, 0, id); 15455 this._layerOrderChanged = true; 15456 } 15457 /** 15458 * Remove the layer with the given id from the style. 15459 * A {@link ErrorEvent} event will be fired if no such layer exists. 15460 * 15461 * @param id - id of the layer to remove 15462 */ 15463 removeLayer(id) { 15464 this._checkLoaded(); 15465 const layer = this._layers[id]; 15466 if (!layer) { 15467 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Cannot remove non-existing layer "${id}".`))); 15468 return; 15469 } 15470 layer.setEventedParent(null); 15471 const index = this._order.indexOf(id); 15472 this._order.splice(index, 1); 15473 this._layerOrderChanged = true; 15474 this._changed = true; 15475 this._removedLayers[id] = layer; 15476 delete this._layers[id]; 15477 if (this._serializedLayers) delete this._serializedLayers[id]; 15478 delete this._updatedLayers[id]; 15479 delete this._updatedPaintProps[id]; 15480 if (layer.type === "symbol" && layer.source) this.tileManagers[layer.source]?.resetMaxContentElevation(); 15481 if (layer.onRemove) layer.onRemove(this.map); 15482 } 15483 /** 15484 * Return the style layer object with the given `id`. 15485 * 15486 * @param id - id of the desired layer 15487 * @returns a layer, if one with the given `id` exists 15488 */ 15489 getLayer(id) { 15490 return this._layers[id]; 15491 } 15492 /** 15493 * Return the ids of all layers currently in the style, including custom layers, in order. 15494 * 15495 * @returns ids of layers, in order 15496 */ 15497 getLayersOrder() { 15498 return [...this._order]; 15499 } 15500 /** 15501 * Checks if a specific layer is present within the style. 15502 * 15503 * @param id - the id of the desired layer 15504 * @returns a boolean specifying if the given layer is present 15505 */ 15506 hasLayer(id) { 15507 return id in this._layers; 15508 } 15509 setLayerZoomRange(layerId, minzoom, maxzoom) { 15510 this._checkLoaded(); 15511 const layer = this.getLayer(layerId); 15512 if (!layer) { 15513 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Cannot set the zoom range of non-existing layer "${layerId}".`))); 15514 return; 15515 } 15516 if (layer.minzoom === minzoom && layer.maxzoom === maxzoom) return; 15517 if (minzoom != null) layer.minzoom = minzoom; 15518 if (maxzoom != null) layer.maxzoom = maxzoom; 15519 this._updateLayer(layer); 15520 } 15521 setFilter(layerId, filter, options = {}) { 15522 this._checkLoaded(); 15523 const layer = this.getLayer(layerId); 15524 if (!layer) { 15525 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Cannot filter non-existing layer "${layerId}".`))); 15526 return; 15527 } 15528 if (deepEqual(layer.filter, filter)) return; 15529 if (filter === null || filter === void 0) { 15530 layer.setFilter(void 0); 15531 this._updateLayer(layer); 15532 return; 15533 } 15534 if (this._validate(validateStyle.filter, `layers.${layer.id}.filter`, filter, null, options)) return; 15535 layer.setFilter(clone$2(filter)); 15536 this._updateLayer(layer); 15537 } 15538 /** 15539 * Get a layer's filter object 15540 * @param layer - the layer to inspect 15541 * @returns the layer's filter, if any 15542 */ 15543 getFilter(layer) { 15544 return clone$2(this.getLayer(layer).filter); 15545 } 15546 setLayoutProperty(layerId, name, value, options = {}) { 15547 this._checkLoaded(); 15548 const layer = this.getLayer(layerId); 15549 if (!layer) { 15550 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Cannot style non-existing layer "${layerId}".`))); 15551 return; 15552 } 15553 if (deepEqual(layer.getLayoutProperty(name), value)) return; 15554 layer.setLayoutProperty(name, value, options); 15555 this._updateLayer(layer); 15556 } 15557 /** 15558 * Get a layout property's value from a given layer 15559 * @param layerId - the layer to inspect 15560 * @param name - the name of the layout property 15561 * @returns the property value 15562 */ 15563 getLayoutProperty(layerId, name) { 15564 const layer = this.getLayer(layerId); 15565 if (!layer) { 15566 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Cannot get style of non-existing layer "${layerId}".`))); 15567 return; 15568 } 15569 return layer.getLayoutProperty(name); 15570 } 15571 setPaintProperty(layerId, name, value, options = {}) { 15572 this._checkLoaded(); 15573 const layer = this.getLayer(layerId); 15574 if (!layer) { 15575 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Cannot style non-existing layer "${layerId}".`))); 15576 return; 15577 } 15578 if (deepEqual(layer.getPaintProperty(name), value)) return; 15579 this._updatePaintProperty(layer, name, value, options); 15580 } 15581 _updatePaintProperty(layer, name, value, options = {}) { 15582 if (layer.setPaintProperty(name, value, options)) this._updateLayer(layer); 15583 if (isRasterStyleLayer(layer) && name === "raster-fade-duration") this.tileManagers[layer.source].setRasterFadeDuration(value); 15584 this._changed = true; 15585 this._updatedPaintProps[layer.id] = true; 15586 if (layer.type === "symbol") this.triggerSymbolPlacement(); 15587 this._serializedLayers = null; 15588 } 15589 getPaintProperty(layer, name) { 15590 return this.getLayer(layer).getPaintProperty(name); 15591 } 15592 setFeatureState(target, state) { 15593 this._checkLoaded(); 15594 const sourceId = target.source; 15595 const sourceLayer = target.sourceLayer; 15596 const tileManager = this.tileManagers[sourceId]; 15597 if (tileManager === void 0) { 15598 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`The source '${sourceId}' does not exist in the map's style.`))); 15599 return; 15600 } 15601 const sourceType = tileManager.getSource().type; 15602 if (sourceType === "geojson" && sourceLayer) { 15603 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("GeoJSON sources cannot have a sourceLayer parameter."))); 15604 return; 15605 } 15606 if (sourceType === "vector" && !sourceLayer) { 15607 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("The sourceLayer parameter must be provided for vector source types."))); 15608 return; 15609 } 15610 if (target.id === void 0) { 15611 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("The feature id parameter must be provided."))); 15612 return; 15613 } 15614 const forbiddenStateKeys = [ 15615 "__proto__", 15616 "constructor", 15617 "prototype" 15618 ]; 15619 if (state && Object.keys(state).some((stateKey) => forbiddenStateKeys.includes(stateKey))) { 15620 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`The feature state should not include one of the following keys: ${forbiddenStateKeys}`))); 15621 return; 15622 } 15623 tileManager.setFeatureState(sourceLayer, target.id, state); 15624 } 15625 removeFeatureState(target, key) { 15626 this._checkLoaded(); 15627 const sourceId = target.source; 15628 const tileManager = this.tileManagers[sourceId]; 15629 if (tileManager === void 0) { 15630 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`The source '${sourceId}' does not exist in the map's style.`))); 15631 return; 15632 } 15633 const sourceType = tileManager.getSource().type; 15634 const sourceLayer = sourceType === "vector" ? target.sourceLayer : void 0; 15635 if (sourceType === "vector" && !sourceLayer) { 15636 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("The sourceLayer parameter must be provided for vector source types."))); 15637 return; 15638 } 15639 if (key && typeof target.id !== "string" && typeof target.id !== "number") { 15640 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("A feature id is required to remove its specific state property."))); 15641 return; 15642 } 15643 tileManager.removeFeatureState(sourceLayer, target.id, key); 15644 } 15645 getFeatureState(target) { 15646 this._checkLoaded(); 15647 const sourceId = target.source; 15648 const sourceLayer = target.sourceLayer; 15649 const tileManager = this.tileManagers[sourceId]; 15650 if (tileManager === void 0) { 15651 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`The source '${sourceId}' does not exist in the map's style.`))); 15652 return; 15653 } 15654 if (tileManager.getSource().type === "vector" && !sourceLayer) { 15655 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("The sourceLayer parameter must be provided for vector source types."))); 15656 return; 15657 } 15658 if (target.id === void 0) this.fire(new ErrorEvent(/* @__PURE__ */ new Error("The feature id parameter must be provided."))); 15659 return tileManager.getFeatureState(sourceLayer, target.id); 15660 } 15661 getTransition() { 15662 return extend({ 15663 duration: 300, 15664 delay: 0 15665 }, this.stylesheet?.transition); 15666 } 15667 serialize() { 15668 if (!this._loaded) return; 15669 const sources = mapObject(this.tileManagers, (source) => source.serialize()); 15670 const layers = this._serializeByIds(this._order, true); 15671 const terrain = this.map.getTerrain() || void 0; 15672 const myStyleSheet = this.stylesheet; 15673 return filterObject({ 15674 version: myStyleSheet.version, 15675 name: myStyleSheet.name, 15676 metadata: myStyleSheet.metadata, 15677 light: myStyleSheet.light, 15678 sky: myStyleSheet.sky, 15679 center: myStyleSheet.center, 15680 zoom: myStyleSheet.zoom, 15681 bearing: myStyleSheet.bearing, 15682 pitch: myStyleSheet.pitch, 15683 sprite: myStyleSheet.sprite, 15684 glyphs: myStyleSheet.glyphs, 15685 "font-faces": myStyleSheet["font-faces"], 15686 transition: myStyleSheet.transition, 15687 projection: myStyleSheet.projection, 15688 state: myStyleSheet.state, 15689 sources, 15690 layers, 15691 terrain 15692 }, (value) => value !== void 0); 15693 } 15694 _updateLayer(layer) { 15695 this._updatedLayers[layer.id] = true; 15696 if (layer.source && !this._updatedSources[layer.source] && this.tileManagers[layer.source].getSource().type !== "raster") { 15697 this._updatedSources[layer.source] = "reload"; 15698 this.tileManagers[layer.source].pause(); 15699 } 15700 this._serializedLayers = null; 15701 this._changed = true; 15702 } 15703 _flattenAndSortRenderedFeatures(sourceResults) { 15704 const isLayer3D = (layerId) => this._layers[layerId].type === "fill-extrusion"; 15705 const layerIndex = {}; 15706 const features3D = []; 15707 for (let l = this._order.length - 1; l >= 0; l--) { 15708 const layerId = this._order[l]; 15709 if (isLayer3D(layerId)) { 15710 layerIndex[layerId] = l; 15711 for (const sourceResult of sourceResults) { 15712 const layerFeatures = sourceResult[layerId]; 15713 if (layerFeatures) for (const featureWrapper of layerFeatures) features3D.push(featureWrapper); 15714 } 15715 } 15716 } 15717 features3D.sort((a, b) => { 15718 return b.intersectionZ - a.intersectionZ; 15719 }); 15720 const features = []; 15721 for (let l = this._order.length - 1; l >= 0; l--) { 15722 const layerId = this._order[l]; 15723 if (isLayer3D(layerId)) for (let i = features3D.length - 1; i >= 0; i--) { 15724 const topmost3D = features3D[i].feature; 15725 if (layerIndex[topmost3D.layer.id] < l) break; 15726 features.push(topmost3D); 15727 features3D.pop(); 15728 } 15729 else for (const sourceResult of sourceResults) { 15730 const layerFeatures = sourceResult[layerId]; 15731 if (layerFeatures) for (const featureWrapper of layerFeatures) features.push(featureWrapper.feature); 15732 } 15733 } 15734 return features; 15735 } 15736 queryRenderedFeatures(queryGeometry, params, transform) { 15737 if (params?.filter) this._validate(validateStyle.filter, "queryRenderedFeatures.filter", params.filter, null, params); 15738 const includedSources = {}; 15739 if (params?.layers) { 15740 if (!(Array.isArray(params.layers) || params.layers instanceof Set)) { 15741 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("parameters.layers must be an Array or a Set of strings"))); 15742 return []; 15743 } 15744 for (const layerId of params.layers) { 15745 const layer = this._layers[layerId]; 15746 if (!layer) { 15747 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`The layer '${layerId}' does not exist in the map's style and cannot be queried for features.`))); 15748 return []; 15749 } 15750 includedSources[layer.source] = true; 15751 } 15752 } 15753 const sourceResults = []; 15754 params.availableImages = this.imageManager.listImages(); 15755 const serializedLayers = this._serializedAllLayers(); 15756 const layersAsSet = params.layers instanceof Set ? params.layers : Array.isArray(params.layers) ? new Set(params.layers) : null; 15757 const paramsStrict = { 15758 ...params, 15759 layers: layersAsSet, 15760 globalState: this._globalState 15761 }; 15762 for (const id in this.tileManagers) { 15763 if (params.layers && !includedSources[id]) continue; 15764 sourceResults.push(queryRenderedFeatures(this.tileManagers[id], this._layers, serializedLayers, queryGeometry, paramsStrict, transform, this.map.terrain ? (id, x, y) => this.map.terrain.getElevation(id, x, y) : void 0)); 15765 } 15766 if (this.placement) sourceResults.push(queryRenderedSymbols(this._layers, serializedLayers, this.tileManagers, queryGeometry, paramsStrict, this.placement.collisionIndex, this.placement.retainedQueryData)); 15767 return this._flattenAndSortRenderedFeatures(sourceResults); 15768 } 15769 querySourceFeatures(sourceID, params) { 15770 if (params?.filter) this._validate(validateStyle.filter, "querySourceFeatures.filter", params.filter, null, params); 15771 const tileManager = this.tileManagers[sourceID]; 15772 return tileManager ? querySourceFeatures(tileManager, params ? { 15773 ...params, 15774 globalState: this._globalState 15775 } : { globalState: this._globalState }) : []; 15776 } 15777 getLight() { 15778 return this.light.getLight(); 15779 } 15780 setLight(lightOptions, options = {}) { 15781 this._checkLoaded(); 15782 const light = this.light.getLight(); 15783 let _update = false; 15784 for (const key in lightOptions) if (!deepEqual(lightOptions[key], light[key])) { 15785 _update = true; 15786 break; 15787 } 15788 if (!_update) return; 15789 const parameters = { 15790 now: now(), 15791 transition: extend({ 15792 duration: 300, 15793 delay: 0 15794 }, this.stylesheet.transition) 15795 }; 15796 this.light.setLight(lightOptions, options); 15797 this.light.updateTransitions(parameters); 15798 } 15799 getProjection() { 15800 return this.stylesheet?.projection; 15801 } 15802 setProjection(projection) { 15803 this._checkLoaded(); 15804 const resolvedProjection = projection ?? { type: "mercator" }; 15805 this.stylesheet.projection = projection; 15806 if (this.projection) { 15807 if (this.projection.name === resolvedProjection.type) return; 15808 this.projection.destroy(); 15809 delete this.projection; 15810 } 15811 this._setProjectionInternal(resolvedProjection.type); 15812 } 15813 getSky() { 15814 return this.stylesheet?.sky; 15815 } 15816 setSky(skyOptions, options = {}) { 15817 this._checkLoaded(); 15818 const sky = this.getSky(); 15819 let update = false; 15820 if (!skyOptions && !sky) return; 15821 if (skyOptions && !sky) update = true; 15822 else if (!skyOptions && sky) update = true; 15823 else for (const key in skyOptions) if (!deepEqual(skyOptions[key], sky[key])) { 15824 update = true; 15825 break; 15826 } 15827 if (!update) return; 15828 const parameters = { 15829 now: now(), 15830 transition: extend({ 15831 duration: 300, 15832 delay: 0 15833 }, this.stylesheet.transition) 15834 }; 15835 if (!this.sky.setSky(skyOptions, options)) return; 15836 this.stylesheet.sky = skyOptions; 15837 this.sky.updateTransitions(parameters); 15838 } 15839 _setProjectionInternal(name) { 15840 const projectionObjects = createProjectionFromName(name, this.map._camera?.transform.constrainOverride, this._globalState); 15841 this.projection = projectionObjects.projection; 15842 this.map.migrateProjection(projectionObjects.transform, projectionObjects.cameraHelper); 15843 for (const key in this.tileManagers) this.tileManagers[key].reload(); 15844 } 15845 _validate(validate, key, value, props, options = {}) { 15846 if (options.validate === false) return false; 15847 return validateAndEmit(this, validate, { 15848 key, 15849 style: this.serialize(), 15850 value, 15851 ...props 15852 }, options); 15853 } 15854 _remove(mapRemoved = true) { 15855 if (this._frameRequest) { 15856 this._frameRequest.abort(); 15857 this._frameRequest = null; 15858 } 15859 if (this._loadStyleRequest) { 15860 this._loadStyleRequest.abort(); 15861 this._loadStyleRequest = null; 15862 } 15863 if (this._spriteRequest) { 15864 this._spriteRequest.abort(); 15865 this._spriteRequest = null; 15866 } 15867 rtlMainThreadPluginFactory().off(RTLPluginLoadedEventName, this._rtlPluginLoaded); 15868 for (const layerId in this._layers) this._layers[layerId].setEventedParent(null); 15869 for (const id in this.tileManagers) { 15870 const tileManager = this.tileManagers[id]; 15871 tileManager.setEventedParent(null); 15872 tileManager.onRemove(this.map); 15873 } 15874 this.imageManager.setEventedParent(null); 15875 this.setEventedParent(null); 15876 if (mapRemoved) this.dispatcher.broadcast("RM", void 0); 15877 this.dispatcher.remove(mapRemoved); 15878 } 15879 _clearSource(id) { 15880 this.tileManagers[id].clearTiles(); 15881 } 15882 _reloadSource(id) { 15883 this.tileManagers[id].resume(); 15884 this.tileManagers[id].reload(); 15885 } 15886 _updateSources(transform) { 15887 for (const id in this.tileManagers) this.tileManagers[id].update(transform, this.map.terrain); 15888 } 15889 _generateCollisionBoxes() { 15890 for (const id in this.tileManagers) this._reloadSource(id); 15891 } 15892 /** 15893 * Re-places symbols on the next frame. 15894 * Placement is skipped while its inputs look unchanged, so call this when something 15895 * the map cannot see for itself has moved symbols. 15896 */ 15897 triggerSymbolPlacement() { 15898 this._symbolPlacementTriggered = true; 15899 } 15900 /** 15901 * Whether re-running symbol placement could produce a different result than the last run. 15902 * Every camera and viewport parameter placement can observe perturbs the model-view-projection 15903 * matrix, so comparing the matrix covers them all. Inputs the matrix cannot see arrive as 15904 * {@link triggerSymbolPlacement} calls. 15905 * 15906 * @internal 15907 */ 15908 _placementInputsChanged(transform, showCollisionBoxes, crossSourceCollisions) { 15909 const lastPlacement = this.pauseablePlacement; 15910 return !lastPlacement || this._symbolPlacementTriggered || this._placedProjectionTransition !== this.projection?.transitionState || lastPlacement._showCollisionBoxes !== showCollisionBoxes || lastPlacement.placement.collisionGroups.crossSourceCollisions !== crossSourceCollisions || lastPlacement.placement.transform.renderWorldCopies !== transform.renderWorldCopies || !exactEquals(lastPlacement.placement.transform.modelViewProjectionMatrix, transform.modelViewProjectionMatrix); 15911 } 15912 _updatePlacement(transform, showCollisionBoxes, fadeDuration, crossSourceCollisions, forceFullPlacement = false) { 15913 let symbolBucketsChanged = false; 15914 let placementCommitted = false; 15915 const layerTiles = {}; 15916 for (const layerID of this._order) { 15917 const styleLayer = this._layers[layerID]; 15918 if (styleLayer.type !== "symbol") continue; 15919 if (!layerTiles[styleLayer.source]) { 15920 const tileManager = this.tileManagers[styleLayer.source]; 15921 layerTiles[styleLayer.source] = tileManager.getRenderableIds(true).map((id) => tileManager.getTileByID(id)).sort((a, b) => b.tileID.overscaledZ - a.tileID.overscaledZ || (a.tileID.isLessThan(b.tileID) ? -1 : 1)); 15922 } 15923 const layerBucketsChanged = this.crossTileSymbolIndex.addLayer(styleLayer, layerTiles[styleLayer.source], transform.center.lng); 15924 symbolBucketsChanged ||= layerBucketsChanged; 15925 } 15926 this.crossTileSymbolIndex.pruneUnusedLayers(this._order); 15927 forceFullPlacement ||= this._layerOrderChanged || fadeDuration === 0; 15928 const placementInputsChanged = symbolBucketsChanged || this._placementInputsChanged(transform, showCollisionBoxes, crossSourceCollisions); 15929 const placementSettled = this.pauseablePlacement?.isDone() && !this.placement.stillRecent(now(), transform.zoom); 15930 if (forceFullPlacement || !this.pauseablePlacement || placementSettled && (placementInputsChanged || this.placement.stale)) { 15931 this._symbolPlacementTriggered = false; 15932 this._placedProjectionTransition = this.projection?.transitionState; 15933 this.pauseablePlacement = new PauseablePlacement(transform, this.map.terrain, this._order, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions, this.placement); 15934 this._layerOrderChanged = false; 15935 } 15936 if (this.pauseablePlacement.isDone()) { 15937 if (placementInputsChanged) this.placement.setStale(); 15938 } else { 15939 this.pauseablePlacement.continuePlacement(this._order, this._layers, layerTiles); 15940 if (this.pauseablePlacement.isDone()) { 15941 this.placement = this.pauseablePlacement.commit(now()); 15942 placementCommitted = true; 15943 } 15944 if (symbolBucketsChanged) this.pauseablePlacement.placement.setStale(); 15945 } 15946 if (placementCommitted || symbolBucketsChanged) for (const layerID of this._order) { 15947 const styleLayer = this._layers[layerID]; 15948 if (styleLayer.type !== "symbol") continue; 15949 this.placement.updateLayerOpacities(styleLayer, layerTiles[styleLayer.source]); 15950 } 15951 return !this.pauseablePlacement.isDone() || this.placement.hasTransitions(now()); 15952 } 15953 _releaseSymbolFadeTiles() { 15954 for (const id in this.tileManagers) this.tileManagers[id].releaseSymbolFadeTiles(); 15955 } 15956 async getImages(mapId, params) { 15957 const images = await this.imageManager.getImages(params.icons); 15958 this._updateTilesForChangedImages(); 15959 const tileManager = this.tileManagers[params.source]; 15960 if (tileManager) tileManager.setDependencies(params.tileID.key, params.type, params.icons); 15961 return images; 15962 } 15963 async getGlyphs(mapId, params) { 15964 const glyphs = await this.glyphManager.getGlyphs(params.stacks); 15965 const tileManager = this.tileManagers[params.source]; 15966 if (tileManager) tileManager.setDependencies(params.tileID.key, params.type, [""]); 15967 return glyphs; 15968 } 15969 getGlyphsUrl() { 15970 return this.stylesheet.glyphs || null; 15971 } 15972 setGlyphs(glyphsUrl, options = {}) { 15973 this._checkLoaded(); 15974 if (glyphsUrl && this._validate(validateStyle.glyphs, "glyphs", glyphsUrl, null, options)) return; 15975 this._changed = true; 15976 this._glyphsDidChange = true; 15977 this.stylesheet.glyphs = glyphsUrl; 15978 this.glyphManager.entries = {}; 15979 this.glyphManager.setURL(glyphsUrl); 15980 } 15981 getFontFaces() { 15982 return this.stylesheet["font-faces"] || null; 15983 } 15984 setFontFaces(fontFaces) { 15985 this._checkLoaded(); 15986 this._changed = true; 15987 this._glyphsDidChange = true; 15988 this.stylesheet["font-faces"] = fontFaces; 15989 this.glyphManager.setFontFaces(fontFaces); 15990 } 15991 async getDashes(mapId, params) { 15992 const result = {}; 15993 for (const [key, dash] of Object.entries(params.dashes)) result[key] = this.lineAtlas.getDash(dash.dasharray, dash.round); 15994 return result; 15995 } 15996 /** 15997 * Add a sprite. 15998 * 15999 * @param id - The id of the desired sprite 16000 * @param url - The url to load the desired sprite from 16001 * @param options - The style setter options 16002 * @param completion - The completion handler 16003 */ 16004 addSprite(id, url, options = {}, completion) { 16005 this._checkLoaded(); 16006 const spriteToAdd = [{ 16007 id, 16008 url 16009 }]; 16010 const updatedSprite = [...coerceSpriteToArray(this.stylesheet.sprite), ...spriteToAdd]; 16011 if (this._validate(validateStyle.sprite, "sprite", updatedSprite, null, options)) return; 16012 this.stylesheet.sprite = updatedSprite; 16013 this._loadSprite(spriteToAdd, true, completion); 16014 } 16015 /** 16016 * Remove a sprite by its id. When the last sprite is removed, the whole `this.stylesheet.sprite` object becomes 16017 * `undefined`. This falsy `undefined` value later prevents attempts to load the sprite when it's absent. 16018 * 16019 * @param id - the id of the sprite to remove 16020 */ 16021 removeSprite(id) { 16022 this._checkLoaded(); 16023 const internalSpriteRepresentation = coerceSpriteToArray(this.stylesheet.sprite); 16024 if (!internalSpriteRepresentation.find((sprite) => sprite.id === id)) { 16025 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Sprite "${id}" doesn't exists on this map.`))); 16026 return; 16027 } 16028 const changedImages = this.imageManager.removeSpriteImages(id); 16029 this._markImagesChanged(changedImages); 16030 internalSpriteRepresentation.splice(internalSpriteRepresentation.findIndex((sprite) => sprite.id === id), 1); 16031 this.stylesheet.sprite = internalSpriteRepresentation.length > 0 ? internalSpriteRepresentation : void 0; 16032 this._imagesListDirty = true; 16033 this._changed = true; 16034 this.fire(new MapStyleDataEvent("data")); 16035 } 16036 /** 16037 * Get the current sprite value. 16038 * 16039 * @returns empty array when no sprite is set; id-url pairs otherwise 16040 */ 16041 getSprite() { 16042 return coerceSpriteToArray(this.stylesheet.sprite); 16043 } 16044 /** 16045 * Set a new value for the style's sprite. 16046 * 16047 * @param sprite - new sprite value 16048 * @param options - style setter options 16049 * @param completion - the completion handler 16050 */ 16051 setSprite(sprite, options = {}, completion) { 16052 this._checkLoaded(); 16053 if (sprite && this._validate(validateStyle.sprite, "sprite", sprite, null, options)) return; 16054 this.stylesheet.sprite = sprite; 16055 if (sprite) this._loadSprite(sprite, true, completion); 16056 else { 16057 this._unloadSprite(); 16058 if (completion) completion(null); 16059 } 16060 } 16061 /** 16062 * Destroys all internal resources of the style (sources, images, layers, etc.) 16063 */ 16064 destroy() { 16065 if (this._frameRequest) { 16066 this._frameRequest.abort(); 16067 this._frameRequest = null; 16068 } 16069 if (this._loadStyleRequest) { 16070 this._loadStyleRequest.abort(); 16071 this._loadStyleRequest = null; 16072 } 16073 if (this._spriteRequest) { 16074 this._spriteRequest.abort(); 16075 this._spriteRequest = null; 16076 } 16077 for (const id in this.tileManagers) { 16078 const tileManager = this.tileManagers[id]; 16079 tileManager.setEventedParent(null); 16080 tileManager.onRemove(this.map); 16081 } 16082 this.tileManagers = {}; 16083 if (this.imageManager) { 16084 this.imageManager.setEventedParent(null); 16085 this.imageManager.destroy(); 16086 this.patternAtlas.destroy(); 16087 } 16088 if (this.glyphManager) this.glyphManager.destroy(); 16089 for (const layerId in this._layers) { 16090 const layer = this._layers[layerId]; 16091 layer.setEventedParent(null); 16092 if (layer.onRemove) layer.onRemove(this.map); 16093 } 16094 this._setInitialValues(); 16095 this.setEventedParent(null); 16096 this.dispatcher.unregisterMessageHandler("GG"); 16097 this.dispatcher.unregisterMessageHandler("GI"); 16098 this.dispatcher.unregisterMessageHandler("GDA"); 16099 this.dispatcher.remove(true); 16100 this._listeners = {}; 16101 this._oneTimeListeners = {}; 16102 } 16103}; 16104//#endregion 16105//#region src/data/raster_bounds_attributes.ts 16106const rasterBoundsAttributes = createLayout([{ 16107 name: "a_pos", 16108 type: "Int16", 16109 components: 2 16110}, { 16111 name: "a_texture_pos", 16112 type: "Int16", 16113 components: 2 16114}]); 16115//#endregion 16116//#region src/webgl/vertex_array_object.ts 16117/** 16118* @internal 16119* A vertex array object used to pass data to the webgl code 16120*/ 16121var VertexArrayObject = class { 16122 constructor() { 16123 this.boundProgram = null; 16124 this.boundLayoutVertexBuffer = null; 16125 this.boundPaintVertexBuffers = []; 16126 this.boundIndexBuffer = null; 16127 this.boundVertexOffset = null; 16128 this.boundDynamicVertexBuffer = null; 16129 this.vao = null; 16130 } 16131 bind(context, program, layoutVertexBuffer, paintVertexBuffers, indexBuffer, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2, dynamicVertexBuffer3) { 16132 this.context = context; 16133 let paintBuffersDiffer = this.boundPaintVertexBuffers.length !== paintVertexBuffers.length; 16134 for (let i = 0; !paintBuffersDiffer && i < paintVertexBuffers.length; i++) if (this.boundPaintVertexBuffers[i] !== paintVertexBuffers[i]) paintBuffersDiffer = true; 16135 if (!this.vao || this.boundProgram !== program || this.boundLayoutVertexBuffer !== layoutVertexBuffer || paintBuffersDiffer || this.boundIndexBuffer !== indexBuffer || this.boundVertexOffset !== vertexOffset || this.boundDynamicVertexBuffer !== dynamicVertexBuffer || this.boundDynamicVertexBuffer2 !== dynamicVertexBuffer2 || this.boundDynamicVertexBuffer3 !== dynamicVertexBuffer3) this.freshBind(program, layoutVertexBuffer, paintVertexBuffers, indexBuffer, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2, dynamicVertexBuffer3); 16136 else context.bindVertexArray.set(this.vao); 16137 } 16138 freshBind(program, layoutVertexBuffer, paintVertexBuffers, indexBuffer, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2, dynamicVertexBuffer3) { 16139 const numNextAttributes = program.numAttributes; 16140 const context = this.context; 16141 const gl = context.gl; 16142 if (this.vao) this.destroy(); 16143 this.vao = context.createVertexArray(); 16144 context.bindVertexArray.set(this.vao); 16145 this.boundProgram = program; 16146 this.boundLayoutVertexBuffer = layoutVertexBuffer; 16147 this.boundPaintVertexBuffers = paintVertexBuffers; 16148 this.boundIndexBuffer = indexBuffer; 16149 this.boundVertexOffset = vertexOffset; 16150 this.boundDynamicVertexBuffer = dynamicVertexBuffer; 16151 this.boundDynamicVertexBuffer2 = dynamicVertexBuffer2; 16152 this.boundDynamicVertexBuffer3 = dynamicVertexBuffer3; 16153 layoutVertexBuffer.enableAttributes(gl, program); 16154 for (const vertexBuffer of paintVertexBuffers) vertexBuffer.enableAttributes(gl, program); 16155 if (dynamicVertexBuffer) dynamicVertexBuffer.enableAttributes(gl, program); 16156 if (dynamicVertexBuffer2) dynamicVertexBuffer2.enableAttributes(gl, program); 16157 if (dynamicVertexBuffer3) dynamicVertexBuffer3.enableAttributes(gl, program); 16158 layoutVertexBuffer.bind(); 16159 layoutVertexBuffer.setVertexAttribPointers(gl, program, vertexOffset); 16160 for (const vertexBuffer of paintVertexBuffers) { 16161 vertexBuffer.bind(); 16162 vertexBuffer.setVertexAttribPointers(gl, program, vertexOffset); 16163 } 16164 if (dynamicVertexBuffer) { 16165 dynamicVertexBuffer.bind(); 16166 dynamicVertexBuffer.setVertexAttribPointers(gl, program, vertexOffset); 16167 } 16168 if (indexBuffer) indexBuffer.bind(); 16169 if (dynamicVertexBuffer2) { 16170 dynamicVertexBuffer2.bind(); 16171 dynamicVertexBuffer2.setVertexAttribPointers(gl, program, vertexOffset); 16172 } 16173 if (dynamicVertexBuffer3) { 16174 dynamicVertexBuffer3.bind(); 16175 dynamicVertexBuffer3.setVertexAttribPointers(gl, program, vertexOffset); 16176 } 16177 context.currentNumAttributes = numNextAttributes; 16178 } 16179 destroy() { 16180 if (this.vao) { 16181 this.context.deleteVertexArray(this.vao); 16182 this.vao = null; 16183 } 16184 } 16185}; 16186//#endregion 16187//#region src/webgl/program/terrain_program.ts 16188const terrainPreludeUniforms = (context, locations) => ({ 16189 "u_depth": new Uniform1i(context, locations.u_depth), 16190 "u_terrain": new Uniform1i(context, locations.u_terrain) 16191}); 16192const terrainUniforms = (context, locations) => ({ 16193 "u_texture": new Uniform1i(context, locations.u_texture), 16194 "u_ele_delta": new Uniform1f(context, locations.u_ele_delta), 16195 "u_fog_matrix": new UniformMatrix4f(context, locations.u_fog_matrix), 16196 "u_fog_color": new UniformColor(context, locations.u_fog_color), 16197 "u_fog_ground_blend": new Uniform1f(context, locations.u_fog_ground_blend), 16198 "u_fog_ground_blend_opacity": new Uniform1f(context, locations.u_fog_ground_blend_opacity), 16199 "u_horizon_color": new UniformColor(context, locations.u_horizon_color), 16200 "u_horizon_fog_blend": new Uniform1f(context, locations.u_horizon_fog_blend), 16201 "u_is_globe_mode": new Uniform1f(context, locations.u_is_globe_mode) 16202}); 16203const terrainDepthUniforms = (context, locations) => ({ "u_ele_delta": new Uniform1f(context, locations.u_ele_delta) }); 16204const terrainUniformValues = (eleDelta, fogMatrix, sky, pitch, isGlobeMode) => ({ 16205 "u_texture": 0, 16206 "u_ele_delta": eleDelta, 16207 "u_fog_matrix": fogMatrix, 16208 "u_fog_color": sky ? sky.properties.get("fog-color") : Color.white, 16209 "u_fog_ground_blend": sky ? sky.properties.get("fog-ground-blend") : 1, 16210 "u_fog_ground_blend_opacity": isGlobeMode ? 0 : sky ? sky.calculateFogBlendOpacity(pitch) : 0, 16211 "u_horizon_color": sky ? sky.properties.get("horizon-color") : Color.white, 16212 "u_horizon_fog_blend": sky ? sky.properties.get("horizon-fog-blend") : 1, 16213 "u_is_globe_mode": isGlobeMode ? 1 : 0 16214}); 16215const terrainDepthUniformValues = (eleDelta) => ({ "u_ele_delta": eleDelta }); 16216//#endregion 16217//#region src/webgl/uniform_buffer.ts 16218const UBO_BINDINGS = { 16219 ProjectionUBO: 0, 16220 TerrainUBO: 1, 16221 FrameUBO: 2 16222}; 16223function applyUBOBindings(gl, program) { 16224 for (const [name, binding] of Object.entries(UBO_BINDINGS)) { 16225 const index = gl.getUniformBlockIndex(program, name); 16226 if (index !== gl.INVALID_INDEX) gl.uniformBlockBinding(program, index, binding); 16227 } 16228} 16229const STD140_SIZE_AND_ALIGNMENT_WORDS = { 16230 float: [1, 1], 16231 int: [1, 1], 16232 vec2: [2, 2], 16233 vec4: [4, 4], 16234 mat4: [16, 4] 16235}; 16236function std140Layout(members) { 16237 const offsets = {}; 16238 let words = 0; 16239 for (const { name, type } of members) { 16240 const [size, alignment] = STD140_SIZE_AND_ALIGNMENT_WORDS[type]; 16241 words = Math.ceil(words / alignment) * alignment; 16242 offsets[name] = words; 16243 words += size; 16244 } 16245 return { 16246 offsets, 16247 contentWords: words, 16248 sizeWords: Math.ceil(words / 4) * 4 16249 }; 16250} 16251/** 16252* @internal 16253* The buffer behind one std140 uniform block, bound to a fixed binding point. Callers write members into 16254* `pending` at the layout's offsets and call `upload`, which skips the GPU write when nothing changed. Uploads 16255* use `bufferData` because Apple's OpenGL driver stalls a `bufferSubData` into a buffer a pending draw still reads (#8468). 16256*/ 16257var UniformBuffer = class { 16258 constructor(context, binding, layout) { 16259 this.context = context; 16260 this.binding = binding; 16261 this.contentWords = layout.contentWords;
vendor: 10,176 bytes, lines 16262-16528
16262 const gl = context.gl; 16263 this.buffer = gl.createBuffer(); 16264 gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer); 16265 gl.bufferData(gl.UNIFORM_BUFFER, layout.sizeWords * 4, gl.DYNAMIC_DRAW); 16266 gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, this.buffer); 16267 this.uploaded = new Float32Array(layout.sizeWords); 16268 this.pending = new Float32Array(layout.sizeWords); 16269 this.uploadedWords = new Uint32Array(this.uploaded.buffer); 16270 this.pendingWords = new Uint32Array(this.pending.buffer); 16271 this.hasData = false; 16272 this.bindingDirty = false; 16273 } 16274 upload() { 16275 const gl = this.context.gl; 16276 let changed = !this.hasData; 16277 if (!changed) { 16278 const words = this.pendingWords; 16279 const uploadedWords = this.uploadedWords; 16280 for (let i = 0; i < this.contentWords; i++) if (words[i] !== uploadedWords[i]) { 16281 changed = true; 16282 break; 16283 } 16284 } 16285 this.bind(); 16286 if (!changed) return; 16287 gl.bindBufferBase(gl.UNIFORM_BUFFER, this.binding, this.buffer); 16288 gl.bufferData(gl.UNIFORM_BUFFER, this.pending, gl.DYNAMIC_DRAW); 16289 this.uploaded.set(this.pending); 16290 this.hasData = true; 16291 } 16292 /** Restores the indexed binding after external rendering without uploading unchanged data. */ 16293 bind() { 16294 if (!this.bindingDirty) return; 16295 const gl = this.context.gl; 16296 gl.bindBufferBase(gl.UNIFORM_BUFFER, this.binding, this.buffer); 16297 this.bindingDirty = false; 16298 } 16299 destroy() { 16300 const gl = this.context.gl; 16301 if (this.buffer) { 16302 gl.deleteBuffer(this.buffer); 16303 delete this.buffer; 16304 } 16305 } 16306}; 16307//#endregion 16308//#region src/webgl/projection_uniform_buffer.ts 16309const layout$2 = std140Layout([ 16310 { 16311 name: "u_projection_matrix", 16312 type: "mat4" 16313 }, 16314 { 16315 name: "u_projection_fallback_matrix", 16316 type: "mat4" 16317 }, 16318 { 16319 name: "u_projection_tile_mercator_coords", 16320 type: "vec4" 16321 }, 16322 { 16323 name: "u_projection_clipping_plane", 16324 type: "vec4" 16325 }, 16326 { 16327 name: "u_projection_transition", 16328 type: "float" 16329 }, 16330 { 16331 name: "u_projection_clip_antimeridian", 16332 type: "int" 16333 } 16334]); 16335const offsets$2 = layout$2.offsets; 16336/** 16337* @internal 16338* The buffer behind the `ProjectionUBO` block in the shader preludes, written by `Program.draw` before each draw. 16339*/ 16340function createProjectionUniformBuffer(context) { 16341 return new UniformBuffer(context, UBO_BINDINGS.ProjectionUBO, layout$2); 16342} 16343function updateProjectionUniformBuffer(buffer, projectionData) { 16344 const f32 = buffer.pending; 16345 f32.set(projectionData.mainMatrix, offsets$2.u_projection_matrix); 16346 f32.set(projectionData.fallbackMatrix, offsets$2.u_projection_fallback_matrix); 16347 f32.set(projectionData.tileMercatorCoords, offsets$2.u_projection_tile_mercator_coords); 16348 f32.set(projectionData.clippingPlane, offsets$2.u_projection_clipping_plane); 16349 f32[offsets$2.u_projection_transition] = projectionData.projectionTransition; 16350 buffer.pendingWords[offsets$2.u_projection_clip_antimeridian] = projectionData.clipAntimeridian ? 1 : 0; 16351 buffer.upload(); 16352} 16353//#endregion 16354//#region src/webgl/terrain_uniform_buffer.ts 16355const layout$1 = std140Layout([ 16356 { 16357 name: "u_terrain_matrix", 16358 type: "mat4" 16359 }, 16360 { 16361 name: "u_terrain_unpack", 16362 type: "vec4" 16363 }, 16364 { 16365 name: "u_terrain_dim", 16366 type: "float" 16367 }, 16368 { 16369 name: "u_terrain_exaggeration", 16370 type: "float" 16371 } 16372]); 16373const offsets$1 = layout$1.offsets; 16374/** 16375* @internal 16376* The buffer behind the `TerrainUBO` block in the vertex prelude, written by `Program.draw` for every draw with terrain. 16377*/ 16378function createTerrainUniformBuffer(context) { 16379 return new UniformBuffer(context, UBO_BINDINGS.TerrainUBO, layout$1); 16380} 16381function updateTerrainUniformBuffer(buffer, terrain) { 16382 const f32 = buffer.pending; 16383 f32.set(terrain.u_terrain_matrix, offsets$1.u_terrain_matrix); 16384 f32.set(terrain.u_terrain_unpack, offsets$1.u_terrain_unpack); 16385 f32[offsets$1.u_terrain_dim] = terrain.u_terrain_dim; 16386 f32[offsets$1.u_terrain_exaggeration] = terrain.u_terrain_exaggeration; 16387 buffer.upload(); 16388} 16389//#endregion 16390//#region src/webgl/program.ts 16391function getTokenizedAttributesAndUniforms(array) { 16392 const result = []; 16393 for (const entry of array) { 16394 if (entry === null) continue; 16395 const token = entry.split(" "); 16396 result.push(token.pop()); 16397 } 16398 return result; 16399} 16400function getIntegerAttributeNames(gl, program) { 16401 const integerTypes = /* @__PURE__ */ new Set([ 16402 gl.INT, 16403 gl.INT_VEC2, 16404 gl.INT_VEC3, 16405 gl.INT_VEC4, 16406 gl.UNSIGNED_INT, 16407 gl.UNSIGNED_INT_VEC2, 16408 gl.UNSIGNED_INT_VEC3, 16409 gl.UNSIGNED_INT_VEC4 16410 ]); 16411 const names = /* @__PURE__ */ new Set(); 16412 const numActiveAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); 16413 for (let i = 0; i < numActiveAttributes; i++) { 16414 const attribute = gl.getActiveAttrib(program, i); 16415 if (attribute && integerTypes.has(attribute.type)) names.add(attribute.name); 16416 } 16417 return names; 16418} 16419/** 16420* @internal 16421* A webgl program to execute in the GPU space 16422*/ 16423var Program = class { 16424 constructor(context, source, configuration, fixedUniforms, showOverdrawInspector, useTerrain, projectionPrelude, projectionDefine, extraDefines = []) { 16425 const gl = context.gl; 16426 this.program = gl.createProgram(); 16427 const staticAttrInfo = getTokenizedAttributesAndUniforms(source.staticAttributes); 16428 const dynamicAttrInfo = configuration ? configuration.getBinderAttributes() : []; 16429 const allAttrInfo = staticAttrInfo.concat(dynamicAttrInfo); 16430 const preludeUniformsInfo = shaders.prelude.staticUniforms ? getTokenizedAttributesAndUniforms(shaders.prelude.staticUniforms) : []; 16431 const staticUniformsInfo = source.staticUniforms ? getTokenizedAttributesAndUniforms(source.staticUniforms) : []; 16432 const dynamicUniformsInfo = configuration ? configuration.getBinderUniforms() : []; 16433 const uniformList = preludeUniformsInfo.concat(staticUniformsInfo).concat(dynamicUniformsInfo); 16434 const allUniformsInfo = []; 16435 for (const uniform of uniformList) if (!allUniformsInfo.includes(uniform)) allUniformsInfo.push(uniform); 16436 const defines = configuration ? configuration.defines() : []; 16437 defines.unshift("#version 300 es"); 16438 if (showOverdrawInspector) defines.push("#define OVERDRAW_INSPECTOR;"); 16439 if (useTerrain) defines.push("#define TERRAIN3D;"); 16440 if (projectionDefine) defines.push(projectionDefine); 16441 if (extraDefines) defines.push(...extraDefines); 16442 const fragmentSource = defines.concat(shaders.prelude.fragmentSource, projectionPrelude.fragmentSource, source.fragmentSource).join("\n"); 16443 const vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join("\n"); 16444 const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 16445 if (gl.isContextLost()) { 16446 this.failedToCreate = true; 16447 return; 16448 } 16449 gl.shaderSource(fragmentShader, fragmentSource); 16450 gl.compileShader(fragmentShader); 16451 gl.attachShader(this.program, fragmentShader); 16452 const vertexShader = gl.createShader(gl.VERTEX_SHADER); 16453 if (gl.isContextLost()) { 16454 this.failedToCreate = true; 16455 return; 16456 } 16457 gl.shaderSource(vertexShader, vertexSource); 16458 gl.compileShader(vertexShader); 16459 gl.attachShader(this.program, vertexShader); 16460 this.attributes = {}; 16461 const uniformLocations = {}; 16462 this.numAttributes = allAttrInfo.length; 16463 gl.linkProgram(this.program); 16464 if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { 16465 if (gl.isContextLost()) { 16466 this.failedToCreate = true; 16467 return; 16468 } 16469 if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) throw new Error(`Could not compile fragment shader: ${gl.getShaderInfoLog(fragmentShader)}`); 16470 if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) throw new Error(`Could not compile vertex shader: ${gl.getShaderInfoLog(vertexShader)}`); 16471 throw new Error(`Program failed to link: ${gl.getProgramInfoLog(this.program)}`); 16472 } 16473 applyUBOBindings(gl, this.program); 16474 const integerAttributeNames = getIntegerAttributeNames(gl, this.program); 16475 for (const name of allAttrInfo) { 16476 if (!name) continue; 16477 const location = gl.getAttribLocation(this.program, name); 16478 if (location >= 0) this.attributes[name] = { 16479 location, 16480 isInteger: integerAttributeNames.has(name) 16481 }; 16482 } 16483 gl.deleteShader(vertexShader); 16484 gl.deleteShader(fragmentShader); 16485 for (const uniform of allUniformsInfo) if (uniform && !uniformLocations[uniform]) { 16486 const uniformLocation = gl.getUniformLocation(this.program, uniform); 16487 if (uniformLocation) uniformLocations[uniform] = uniformLocation; 16488 } 16489 this.fixedUniforms = fixedUniforms(context, uniformLocations); 16490 this.terrainUniforms = terrainPreludeUniforms(context, uniformLocations); 16491 this.binderUniforms = configuration ? configuration.getUniforms(context, uniformLocations) : []; 16492 } 16493 draw(context, drawMode, depthMode, stencilMode, colorMode, cullFaceMode, uniformValues, terrain, projectionData, layerID, layoutVertexBuffer, indexBuffer, segments, currentProperties, zoom, configuration, dynamicLayoutBuffer, dynamicLayoutBuffer2, dynamicLayoutBuffer3) { 16494 const gl = context.gl; 16495 if (this.failedToCreate) return; 16496 context.program.set(this.program); 16497 context.projectionUniformBuffer.bind(); 16498 context.terrainUniformBuffer.bind(); 16499 context.frameUniformBuffer.bind(); 16500 context.setDepthMode(depthMode); 16501 context.setStencilMode(stencilMode); 16502 context.setColorMode(colorMode); 16503 context.setCullFace(cullFaceMode); 16504 if (terrain) { 16505 context.activeTexture.set(gl.TEXTURE2); 16506 gl.bindTexture(gl.TEXTURE_2D, terrain.depthTexture); 16507 context.activeTexture.set(gl.TEXTURE3); 16508 gl.bindTexture(gl.TEXTURE_2D, terrain.texture); 16509 for (const name in this.terrainUniforms) this.terrainUniforms[name].set(terrain[name]); 16510 updateTerrainUniformBuffer(context.terrainUniformBuffer, terrain); 16511 } 16512 if (projectionData) updateProjectionUniformBuffer(context.projectionUniformBuffer, projectionData); 16513 if (uniformValues) for (const name in this.fixedUniforms) this.fixedUniforms[name].set(uniformValues[name]); 16514 if (configuration) configuration.setUniforms(context, this.binderUniforms, currentProperties, { zoom }); 16515 let primitiveSize = 0; 16516 switch (drawMode) { 16517 case gl.LINES: 16518 primitiveSize = 2; 16519 break; 16520 case gl.TRIANGLES: 16521 primitiveSize = 3; 16522 break; 16523 case gl.LINE_STRIP: primitiveSize = 1; 16524 } 16525 for (const segment of segments.get()) { 16526 segment.vaos ||= {}; 16527 segment.vaos[layerID] ||= new VertexArrayObject(); 16528 segment.vaos[layerID].bind(context, this, layoutVertexBuffer, configuration ? c
vendor: 46,275 bytes, lines 16528-17813
16528onfiguration.getPaintVertexBuffers() : [], indexBuffer, segment.vertexOffset, dynamicLayoutBuffer, dynamicLayoutBuffer2, dynamicLayoutBuffer3); 16529 gl.drawElements(drawMode, segment.primitiveLength * primitiveSize, gl.UNSIGNED_SHORT, segment.primitiveOffset * primitiveSize * 2); 16530 } 16531 } 16532}; 16533//#endregion 16534//#region src/webgl/program/pattern.ts 16535function patternUniformValues(crossfade, painter, tile) { 16536 const tileRatio = 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom); 16537 const numTiles = Math.pow(2, tile.tileID.overscaledZ); 16538 const tileSizeAtNearestZoom = tile.tileSize * Math.pow(2, painter.transform.tileZoom) / numTiles; 16539 const pixelX = tileSizeAtNearestZoom * (tile.tileID.canonical.x + tile.tileID.wrap * numTiles); 16540 const pixelY = tileSizeAtNearestZoom * tile.tileID.canonical.y; 16541 return { 16542 "u_image": 0, 16543 "u_texsize": tile.imageAtlasTexture.size, 16544 "u_scale": [ 16545 tileRatio, 16546 crossfade.fromScale, 16547 crossfade.toScale 16548 ], 16549 "u_fade": crossfade.t, 16550 "u_pixel_coord_upper": [pixelX >> 16, pixelY >> 16], 16551 "u_pixel_coord_lower": [pixelX & 65535, pixelY & 65535] 16552 }; 16553} 16554function bgPatternUniformValues(image, crossfade, painter, tile) { 16555 const imagePosA = painter.patternAtlas.getPattern(image.from.toString()); 16556 const imagePosB = painter.patternAtlas.getPattern(image.to.toString()); 16557 const { width, height } = painter.patternAtlas.getPixelSize(); 16558 const numTiles = Math.pow(2, tile.tileID.overscaledZ); 16559 const tileSizeAtNearestZoom = tile.tileSize * Math.pow(2, painter.transform.tileZoom) / numTiles; 16560 const pixelX = tileSizeAtNearestZoom * (tile.tileID.canonical.x + tile.tileID.wrap * numTiles); 16561 const pixelY = tileSizeAtNearestZoom * tile.tileID.canonical.y; 16562 return { 16563 "u_image": 0, 16564 "u_pattern_tl_a": imagePosA.tl, 16565 "u_pattern_br_a": imagePosA.br, 16566 "u_pattern_tl_b": imagePosB.tl, 16567 "u_pattern_br_b": imagePosB.br, 16568 "u_texsize": [width, height], 16569 "u_mix": crossfade.t, 16570 "u_pattern_size_a": imagePosA.displaySize, 16571 "u_pattern_size_b": imagePosB.displaySize, 16572 "u_scale_a": crossfade.fromScale, 16573 "u_scale_b": crossfade.toScale, 16574 "u_tile_units_to_pixels": 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom), 16575 "u_pixel_coord_upper": [pixelX >> 16, pixelY >> 16], 16576 "u_pixel_coord_lower": [pixelX & 65535, pixelY & 65535] 16577 }; 16578} 16579//#endregion 16580//#region src/webgl/program/fill_extrusion_program.ts 16581const fillExtrusionUniforms = (context, locations) => ({ 16582 "u_lightpos": new Uniform3f(context, locations.u_lightpos), 16583 "u_lightpos_globe": new Uniform3f(context, locations.u_lightpos_globe), 16584 "u_lightintensity": new Uniform1f(context, locations.u_lightintensity), 16585 "u_lightcolor": new Uniform3f(context, locations.u_lightcolor), 16586 "u_vertical_gradient": new Uniform1f(context, locations.u_vertical_gradient), 16587 "u_opacity": new Uniform1f(context, locations.u_opacity), 16588 "u_fill_translate": new Uniform2f(context, locations.u_fill_translate) 16589}); 16590const fillExtrusionPatternUniforms = (context, locations) => ({ 16591 "u_lightpos": new Uniform3f(context, locations.u_lightpos), 16592 "u_lightpos_globe": new Uniform3f(context, locations.u_lightpos_globe), 16593 "u_lightintensity": new Uniform1f(context, locations.u_lightintensity), 16594 "u_lightcolor": new Uniform3f(context, locations.u_lightcolor), 16595 "u_vertical_gradient": new Uniform1f(context, locations.u_vertical_gradient), 16596 "u_height_factor": new Uniform1f(context, locations.u_height_factor), 16597 "u_opacity": new Uniform1f(context, locations.u_opacity), 16598 "u_fill_translate": new Uniform2f(context, locations.u_fill_translate), 16599 "u_image": new Uniform1i(context, locations.u_image), 16600 "u_texsize": new Uniform2f(context, locations.u_texsize), 16601 "u_pixel_coord_upper": new Uniform2f(context, locations.u_pixel_coord_upper), 16602 "u_pixel_coord_lower": new Uniform2f(context, locations.u_pixel_coord_lower), 16603 "u_scale": new Uniform3f(context, locations.u_scale), 16604 "u_fade": new Uniform1f(context, locations.u_fade) 16605}); 16606const fillExtrusionUniformValues = (painter, shouldUseVerticalGradient, opacity, translate) => { 16607 const light = painter.style.light; 16608 const lightPos = light.getCartesianPosition(); 16609 const lightMat = create$4(); 16610 if (light.properties.get("anchor") === "viewport") fromRotation(lightMat, painter.transform.bearingInRadians); 16611 transformMat3(lightPos, lightPos, lightMat); 16612 const transformedLightPos = painter.transform.transformLightDirection(lightPos); 16613 const lightColor = light.properties.get("color"); 16614 return { 16615 "u_lightpos": lightPos, 16616 "u_lightpos_globe": transformedLightPos, 16617 "u_lightintensity": light.properties.get("intensity"), 16618 "u_lightcolor": [ 16619 lightColor.r, 16620 lightColor.g, 16621 lightColor.b 16622 ], 16623 "u_vertical_gradient": +shouldUseVerticalGradient, 16624 "u_opacity": opacity, 16625 "u_fill_translate": translate 16626 }; 16627}; 16628const fillExtrusionPatternUniformValues = (painter, shouldUseVerticalGradient, opacity, translate, coord, crossfade, tile) => { 16629 return extend(fillExtrusionUniformValues(painter, shouldUseVerticalGradient, opacity, translate), patternUniformValues(crossfade, painter, tile), { "u_height_factor": -Math.pow(2, coord.overscaledZ) / tile.tileSize / 8 }); 16630}; 16631//#endregion 16632//#region src/webgl/program/fill_program.ts 16633const fillUniforms = (context, locations) => ({ "u_fill_translate": new Uniform2f(context, locations.u_fill_translate) }); 16634const fillPatternUniforms = (context, locations) => ({ 16635 "u_image": new Uniform1i(context, locations.u_image), 16636 "u_texsize": new Uniform2f(context, locations.u_texsize), 16637 "u_pixel_coord_upper": new Uniform2f(context, locations.u_pixel_coord_upper), 16638 "u_pixel_coord_lower": new Uniform2f(context, locations.u_pixel_coord_lower), 16639 "u_scale": new Uniform3f(context, locations.u_scale), 16640 "u_fade": new Uniform1f(context, locations.u_fade), 16641 "u_sdf_pattern": new Uniform1i(context, locations.u_sdf_pattern), 16642 "u_fill_translate": new Uniform2f(context, locations.u_fill_translate) 16643}); 16644const fillOutlineUniforms = (context, locations) => ({ "u_fill_translate": new Uniform2f(context, locations.u_fill_translate) }); 16645const fillOutlinePatternUniforms = (context, locations) => ({ 16646 "u_image": new Uniform1i(context, locations.u_image), 16647 "u_texsize": new Uniform2f(context, locations.u_texsize), 16648 "u_pixel_coord_upper": new Uniform2f(context, locations.u_pixel_coord_upper), 16649 "u_pixel_coord_lower": new Uniform2f(context, locations.u_pixel_coord_lower), 16650 "u_scale": new Uniform3f(context, locations.u_scale), 16651 "u_fade": new Uniform1f(context, locations.u_fade), 16652 "u_sdf_pattern": new Uniform1i(context, locations.u_sdf_pattern), 16653 "u_fill_translate": new Uniform2f(context, locations.u_fill_translate) 16654}); 16655const fillPatternUniformValues = (painter, crossfade, tile, translate, isSdfPattern) => extend(patternUniformValues(crossfade, painter, tile), { 16656 "u_fill_translate": translate, 16657 "u_sdf_pattern": isSdfPattern ? 1 : 0 16658}); 16659const fillUniformValues = (translate) => ({ "u_fill_translate": translate }); 16660const fillOutlineUniformValues = (translate) => ({ "u_fill_translate": translate }); 16661const fillOutlinePatternUniformValues = (painter, crossfade, tile, translate, isSdfPattern) => fillPatternUniformValues(painter, crossfade, tile, translate, isSdfPattern); 16662//#endregion 16663//#region src/webgl/program/circle_program.ts 16664const circleUniforms = (context, locations) => ({ 16665 "u_scale_with_map": new Uniform1i(context, locations.u_scale_with_map), 16666 "u_pitch_with_map": new Uniform1i(context, locations.u_pitch_with_map), 16667 "u_extrude_scale": new Uniform2f(context, locations.u_extrude_scale), 16668 "u_globe_extrude_scale": new Uniform1f(context, locations.u_globe_extrude_scale), 16669 "u_translate": new Uniform2f(context, locations.u_translate) 16670}); 16671const circleUniformValues = (painter, tile, layer, translate, radiusCorrectionFactor) => { 16672 const transform = painter.transform; 16673 let pitchWithMap, extrudeScale; 16674 let globeExtrudeScale = 0; 16675 if (layer.paint.get("circle-pitch-alignment") === "map") { 16676 const pixelRatio = pixelsToTileUnits(tile, 1, transform.zoom); 16677 pitchWithMap = true; 16678 extrudeScale = [pixelRatio, pixelRatio]; 16679 globeExtrudeScale = pixelRatio / (EXTENT * Math.pow(2, tile.tileID.overscaledZ)) * 2 * Math.PI * radiusCorrectionFactor; 16680 } else { 16681 pitchWithMap = false; 16682 extrudeScale = transform.pixelsToGLUnits; 16683 } 16684 return { 16685 "u_scale_with_map": +(layer.paint.get("circle-pitch-scale") === "map"), 16686 "u_pitch_with_map": +pitchWithMap, 16687 "u_extrude_scale": extrudeScale, 16688 "u_globe_extrude_scale": globeExtrudeScale, 16689 "u_translate": translate 16690 }; 16691}; 16692//#endregion 16693//#region src/webgl/program/debug_program.ts 16694const debugUniforms = (context, locations) => ({ 16695 "u_color": new UniformColor(context, locations.u_color), 16696 "u_overlay": new Uniform1i(context, locations.u_overlay), 16697 "u_overlay_scale": new Uniform1f(context, locations.u_overlay_scale) 16698}); 16699const debugUniformValues = (color, scaleRatio = 1) => ({ 16700 "u_color": color, 16701 "u_overlay": 0, 16702 "u_overlay_scale": scaleRatio 16703}); 16704//#endregion 16705//#region src/webgl/program/heatmap_program.ts 16706const heatmapUniforms = (context, locations) => ({ 16707 "u_extrude_scale": new Uniform1f(context, locations.u_extrude_scale), 16708 "u_intensity": new Uniform1f(context, locations.u_intensity), 16709 "u_globe_extrude_scale": new Uniform1f(context, locations.u_globe_extrude_scale) 16710}); 16711const heatmapTextureUniforms = (context, locations) => ({ 16712 "u_matrix": new UniformMatrix4f(context, locations.u_matrix), 16713 "u_image": new Uniform1i(context, locations.u_image), 16714 "u_color_ramp": new Uniform1i(context, locations.u_color_ramp), 16715 "u_opacity": new Uniform1f(context, locations.u_opacity) 16716}); 16717const heatmapUniformValues = (tile, zoom, intensity, radiusCorrectionFactor) => { 16718 const globeExtrudeScale = pixelsToTileUnits(tile, 1, zoom) / (EXTENT * Math.pow(2, tile.tileID.overscaledZ)) * 2 * Math.PI * radiusCorrectionFactor; 16719 return { 16720 "u_extrude_scale": pixelsToTileUnits(tile, 1, zoom), 16721 "u_intensity": intensity, 16722 "u_globe_extrude_scale": globeExtrudeScale 16723 }; 16724}; 16725const heatmapTextureUniformValues = (painter, layer, textureUnit, colorRampUnit) => { 16726 const matrix = create$1(); 16727 ortho(matrix, 0, painter.width, painter.height, 0, 0, 1); 16728 return { 16729 "u_matrix": matrix, 16730 "u_image": textureUnit, 16731 "u_color_ramp": colorRampUnit, 16732 "u_opacity": layer.paint.get("heatmap-opacity") 16733 }; 16734}; 16735//#endregion 16736//#region src/webgl/program/hillshade_program.ts 16737const hillshadeUniforms = (context, locations) => ({ 16738 "u_image": new Uniform1i(context, locations.u_image), 16739 "u_latrange": new Uniform2f(context, locations.u_latrange), 16740 "u_exaggeration": new Uniform1f(context, locations.u_exaggeration), 16741 "u_altitudes": new UniformFloatArray(context, locations.u_altitudes), 16742 "u_azimuths": new UniformFloatArray(context, locations.u_azimuths), 16743 "u_accent": new UniformColor(context, locations.u_accent), 16744 "u_method": new Uniform1i(context, locations.u_method), 16745 "u_shadows": new UniformColorArray(context, locations.u_shadows), 16746 "u_highlights": new UniformColorArray(context, locations.u_highlights) 16747}); 16748const hillshadePrepareUniforms = (context, locations) => ({ 16749 "u_matrix": new UniformMatrix4f(context, locations.u_matrix), 16750 "u_image": new Uniform1i(context, locations.u_image), 16751 "u_dimension": new Uniform2f(context, locations.u_dimension), 16752 "u_zoom": new Uniform1f(context, locations.u_zoom), 16753 "u_unpack": new Uniform4f(context, locations.u_unpack) 16754}); 16755const hillshadeUniformValues = (painter, tile, layer) => { 16756 const accent = layer.paint.get("hillshade-accent-color"); 16757 let method; 16758 switch (layer.paint.get("hillshade-method")) { 16759 case "basic": 16760 method = 4; 16761 break; 16762 case "combined": 16763 method = 1; 16764 break; 16765 case "igor": 16766 method = 2; 16767 break; 16768 case "multidirectional": 16769 method = 3; 16770 break; 16771 default: method = 0; 16772 } 16773 const illumination = layer.getIlluminationProperties(); 16774 for (let i = 0; i < illumination.directionRadians.length; i++) if (layer.paint.get("hillshade-illumination-anchor") === "viewport") illumination.directionRadians[i] += painter.transform.bearingInRadians; 16775 return { 16776 "u_image": 0, 16777 "u_latrange": getTileLatRange(painter, tile.tileID), 16778 "u_exaggeration": layer.paint.get("hillshade-exaggeration"), 16779 "u_altitudes": illumination.altitudeRadians, 16780 "u_azimuths": illumination.directionRadians, 16781 "u_accent": accent, 16782 "u_method": method, 16783 "u_highlights": illumination.highlightColor, 16784 "u_shadows": illumination.shadowColor 16785 }; 16786}; 16787const hillshadeUniformPrepareValues = (tileID, dem) => { 16788 const stride = dem.stride; 16789 const matrix = create$1(); 16790 ortho(matrix, 0, EXTENT, -EXTENT, 0, 0, 1); 16791 translate(matrix, matrix, [ 16792 0, 16793 -EXTENT, 16794 0 16795 ]); 16796 return { 16797 "u_matrix": matrix, 16798 "u_image": 1, 16799 "u_dimension": [stride, stride], 16800 "u_zoom": tileID.overscaledZ, 16801 "u_unpack": dem.getUnpackVector() 16802 }; 16803}; 16804function getTileLatRange(painter, tileID) { 16805 const tilesAtZoom = Math.pow(2, tileID.canonical.z); 16806 const y = tileID.canonical.y; 16807 return [new MercatorCoordinate(0, y / tilesAtZoom).toLngLat().lat, new MercatorCoordinate(0, (y + 1) / tilesAtZoom).toLngLat().lat]; 16808} 16809//#endregion 16810//#region src/webgl/program/color_relief_program.ts 16811const colorReliefUniforms = (context, locations) => ({ 16812 "u_image": new Uniform1i(context, locations.u_image), 16813 "u_unpack": new Uniform4f(context, locations.u_unpack), 16814 "u_dimension": new Uniform2f(context, locations.u_dimension), 16815 "u_elevation_stops": new Uniform1i(context, locations.u_elevation_stops), 16816 "u_color_stops": new Uniform1i(context, locations.u_color_stops), 16817 "u_color_ramp_size": new Uniform1i(context, locations.u_color_ramp_size), 16818 "u_opacity": new Uniform1f(context, locations.u_opacity) 16819}); 16820const colorReliefUniformValues = (layer, dem, colorRampSize = 0) => { 16821 return { 16822 "u_image": 0, 16823 "u_unpack": dem.getUnpackVector(), 16824 "u_dimension": [dem.stride, dem.stride], 16825 "u_elevation_stops": 1, 16826 "u_color_stops": 4, 16827 "u_color_ramp_size": colorRampSize, 16828 "u_opacity": layer.paint.get("color-relief-opacity") 16829 }; 16830}; 16831//#endregion 16832//#region src/webgl/program/line_program.ts 16833const lineUniforms = (context, locations) => ({ 16834 "u_translation": new Uniform2f(context, locations.u_translation), 16835 "u_ratio": new Uniform1f(context, locations.u_ratio) 16836}); 16837const lineGradientUniforms = (context, locations) => ({ 16838 "u_translation": new Uniform2f(context, locations.u_translation), 16839 "u_ratio": new Uniform1f(context, locations.u_ratio), 16840 "u_image": new Uniform1i(context, locations.u_image), 16841 "u_image_height": new Uniform1f(context, locations.u_image_height) 16842}); 16843const linePatternUniforms = (context, locations) => ({ 16844 "u_translation": new Uniform2f(context, locations.u_translation), 16845 "u_texsize": new Uniform2f(context, locations.u_texsize), 16846 "u_ratio": new Uniform1f(context, locations.u_ratio), 16847 "u_image": new Uniform1i(context, locations.u_image), 16848 "u_scale": new Uniform3f(context, locations.u_scale), 16849 "u_fade": new Uniform1f(context, locations.u_fade) 16850}); 16851const lineSDFUniforms = (context, locations) => ({ 16852 "u_translation": new Uniform2f(context, locations.u_translation), 16853 "u_ratio": new Uniform1f(context, locations.u_ratio), 16854 "u_image": new Uniform1i(context, locations.u_image), 16855 "u_mix": new Uniform1f(context, locations.u_mix), 16856 "u_tileratio": new Uniform1f(context, locations.u_tileratio), 16857 "u_crossfade_from": new Uniform1f(context, locations.u_crossfade_from), 16858 "u_crossfade_to": new Uniform1f(context, locations.u_crossfade_to), 16859 "u_lineatlas_width": new Uniform1f(context, locations.u_lineatlas_width), 16860 "u_lineatlas_height": new Uniform1f(context, locations.u_lineatlas_height) 16861}); 16862const lineGradientSDFUniforms = (context, locations) => ({ 16863 "u_translation": new Uniform2f(context, locations.u_translation), 16864 "u_ratio": new Uniform1f(context, locations.u_ratio), 16865 "u_image": new Uniform1i(context, locations.u_image), 16866 "u_image_height": new Uniform1f(context, locations.u_image_height), 16867 "u_tileratio": new Uniform1f(context, locations.u_tileratio), 16868 "u_crossfade_from": new Uniform1f(context, locations.u_crossfade_from), 16869 "u_crossfade_to": new Uniform1f(context, locations.u_crossfade_to), 16870 "u_image_dash": new Uniform1i(context, locations.u_image_dash), 16871 "u_mix": new Uniform1f(context, locations.u_mix), 16872 "u_lineatlas_width": new Uniform1f(context, locations.u_lineatlas_width), 16873 "u_lineatlas_height": new Uniform1f(context, locations.u_lineatlas_height) 16874}); 16875const lineUniformValues = (painter, tile, layer, ratioScale) => { 16876 const transform = painter.transform; 16877 return { 16878 "u_translation": calculateTranslation(painter, tile, layer), 16879 "u_ratio": ratioScale / pixelsToTileUnits(tile, 1, transform.zoom) 16880 }; 16881}; 16882const lineGradientUniformValues = (painter, tile, layer, ratioScale, imageHeight) => { 16883 return extend(lineUniformValues(painter, tile, layer, ratioScale), { 16884 "u_image": 0, 16885 "u_image_height": imageHeight 16886 }); 16887}; 16888const linePatternUniformValues = (painter, tile, layer, ratioScale, crossfade) => { 16889 const transform = painter.transform; 16890 const tileZoomRatio = calculateTileRatio(tile, transform); 16891 return { 16892 "u_translation": calculateTranslation(painter, tile, layer), 16893 "u_texsize": tile.imageAtlasTexture.size, 16894 "u_ratio": ratioScale / pixelsToTileUnits(tile, 1, transform.zoom), 16895 "u_image": 0, 16896 "u_scale": [ 16897 tileZoomRatio, 16898 crossfade.fromScale, 16899 crossfade.toScale 16900 ], 16901 "u_fade": crossfade.t 16902 }; 16903}; 16904const lineSDFUniformValues = (painter, tile, layer, ratioScale, crossfade) => { 16905 const transform = painter.transform; 16906 const tileRatio = calculateTileRatio(tile, transform); 16907 return extend(lineUniformValues(painter, tile, layer, ratioScale), { 16908 "u_tileratio": tileRatio, 16909 "u_crossfade_from": crossfade.fromScale, 16910 "u_crossfade_to": crossfade.toScale, 16911 "u_image": 0, 16912 "u_mix": crossfade.t, 16913 "u_lineatlas_width": painter.lineAtlas.width, 16914 "u_lineatlas_height": painter.lineAtlas.height 16915 }); 16916}; 16917const lineGradientSDFUniformValues = (painter, tile, layer, ratioScale, crossfade, imageHeight) => { 16918 const transform = painter.transform; 16919 const tileRatio = calculateTileRatio(tile, transform); 16920 return extend(lineUniformValues(painter, tile, layer, ratioScale), { 16921 "u_image": 0, 16922 "u_image_height": imageHeight, 16923 "u_tileratio": tileRatio, 16924 "u_crossfade_from": crossfade.fromScale, 16925 "u_crossfade_to": crossfade.toScale, 16926 "u_image_dash": 1, 16927 "u_mix": crossfade.t, 16928 "u_lineatlas_width": painter.lineAtlas.width, 16929 "u_lineatlas_height": painter.lineAtlas.height 16930 }); 16931}; 16932function calculateTileRatio(tile, transform) { 16933 return 1 / pixelsToTileUnits(tile, 1, transform.tileZoom); 16934} 16935function calculateTranslation(painter, tile, layer) { 16936 return translatePosition(painter.transform, tile, layer.paint.get("line-translate"), layer.paint.get("line-translate-anchor")); 16937} 16938//#endregion 16939//#region src/webgl/program/layer_opacity_program.ts 16940const layerOpacityUniforms = (context, locations) => ({ 16941 "u_image": new Uniform1i(context, locations.u_image), 16942 "u_opacity": new Uniform1f(context, locations.u_opacity) 16943}); 16944const layerOpacityUniformValues = (opacity, textureUnit) => ({ 16945 "u_image": textureUnit, 16946 "u_opacity": opacity 16947}); 16948//#endregion 16949//#region src/webgl/program/symbol_program.ts 16950const symbolIconUniforms = (context, locations) => ({ 16951 "u_is_size_zoom_constant": new Uniform1i(context, locations.u_is_size_zoom_constant), 16952 "u_is_size_feature_constant": new Uniform1i(context, locations.u_is_size_feature_constant), 16953 "u_size_t": new Uniform1f(context, locations.u_size_t), 16954 "u_size": new Uniform1f(context, locations.u_size), 16955 "u_rotate_symbol": new Uniform1i(context, locations.u_rotate_symbol), 16956 "u_label_plane_matrix": new UniformMatrix4f(context, locations.u_label_plane_matrix), 16957 "u_coord_matrix": new UniformMatrix4f(context, locations.u_coord_matrix), 16958 "u_is_text": new Uniform1i(context, locations.u_is_text), 16959 "u_pitch_with_map": new Uniform1i(context, locations.u_pitch_with_map), 16960 "u_is_along_line": new Uniform1i(context, locations.u_is_along_line), 16961 "u_is_variable_anchor": new Uniform1i(context, locations.u_is_variable_anchor), 16962 "u_texsize": new Uniform2f(context, locations.u_texsize), 16963 "u_texture": new Uniform1i(context, locations.u_texture), 16964 "u_translation": new Uniform2f(context, locations.u_translation), 16965 "u_pitched_scale": new Uniform1f(context, locations.u_pitched_scale), 16966 "u_is_offset": new Uniform1i(context, locations.u_is_offset), 16967 "u_height_anchor_ground": new Uniform1i(context, locations.u_height_anchor_ground) 16968}); 16969const symbolSDFUniforms = (context, locations) => ({ 16970 "u_is_size_zoom_constant": new Uniform1i(context, locations.u_is_size_zoom_constant), 16971 "u_is_size_feature_constant": new Uniform1i(context, locations.u_is_size_feature_constant), 16972 "u_size_t": new Uniform1f(context, locations.u_size_t), 16973 "u_size": new Uniform1f(context, locations.u_size), 16974 "u_rotate_symbol": new Uniform1i(context, locations.u_rotate_symbol), 16975 "u_label_plane_matrix": new UniformMatrix4f(context, locations.u_label_plane_matrix), 16976 "u_coord_matrix": new UniformMatrix4f(context, locations.u_coord_matrix), 16977 "u_is_text": new Uniform1i(context, locations.u_is_text), 16978 "u_pitch_with_map": new Uniform1i(context, locations.u_pitch_with_map), 16979 "u_is_along_line": new Uniform1i(context, locations.u_is_along_line), 16980 "u_is_variable_anchor": new Uniform1i(context, locations.u_is_variable_anchor), 16981 "u_texsize": new Uniform2f(context, locations.u_texsize), 16982 "u_texture": new Uniform1i(context, locations.u_texture), 16983 "u_gamma_scale": new Uniform1f(context, locations.u_gamma_scale), 16984 "u_is_halo": new Uniform1i(context, locations.u_is_halo), 16985 "u_is_plain": new Uniform1i(context, locations.u_is_plain), 16986 "u_translation": new Uniform2f(context, locations.u_translation), 16987 "u_pitched_scale": new Uniform1f(context, locations.u_pitched_scale), 16988 "u_is_offset": new Uniform1i(context, locations.u_is_offset), 16989 "u_height_anchor_ground": new Uniform1i(context, locations.u_height_anchor_ground) 16990}); 16991const symbolTextAndIconUniforms = (context, locations) => ({ 16992 "u_is_size_zoom_constant": new Uniform1i(context, locations.u_is_size_zoom_constant), 16993 "u_is_size_feature_constant": new Uniform1i(context, locations.u_is_size_feature_constant), 16994 "u_size_t": new Uniform1f(context, locations.u_size_t), 16995 "u_size": new Uniform1f(context, locations.u_size), 16996 "u_rotate_symbol": new Uniform1i(context, locations.u_rotate_symbol), 16997 "u_label_plane_matrix": new UniformMatrix4f(context, locations.u_label_plane_matrix), 16998 "u_coord_matrix": new UniformMatrix4f(context, locations.u_coord_matrix), 16999 "u_is_text": new Uniform1i(context, locations.u_is_text), 17000 "u_pitch_with_map": new Uniform1i(context, locations.u_pitch_with_map), 17001 "u_is_along_line": new Uniform1i(context, locations.u_is_along_line), 17002 "u_is_variable_anchor": new Uniform1i(context, locations.u_is_variable_anchor), 17003 "u_texsize": new Uniform2f(context, locations.u_texsize), 17004 "u_texsize_icon": new Uniform2f(context, locations.u_texsize_icon), 17005 "u_texture": new Uniform1i(context, locations.u_texture), 17006 "u_texture_icon": new Uniform1i(context, locations.u_texture_icon), 17007 "u_gamma_scale": new Uniform1f(context, locations.u_gamma_scale), 17008 "u_is_halo": new Uniform1i(context, locations.u_is_halo), 17009 "u_translation": new Uniform2f(context, locations.u_translation), 17010 "u_pitched_scale": new Uniform1f(context, locations.u_pitched_scale), 17011 "u_is_offset": new Uniform1i(context, locations.u_is_offset), 17012 "u_height_anchor_ground": new Uniform1i(context, locations.u_height_anchor_ground) 17013}); 17014const symbolIconUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, pitchedScale, isOffset, heightAnchorGround) => { 17015 return { 17016 "u_is_size_zoom_constant": +(functionType === "constant" || functionType === "source"), 17017 "u_is_size_feature_constant": +(functionType === "constant" || functionType === "camera"), 17018 "u_size_t": size ? size.uSizeT : 0, 17019 "u_size": size ? size.uSize : 0, 17020 "u_rotate_symbol": +rotateInShader, 17021 "u_label_plane_matrix": labelPlaneMatrix, 17022 "u_coord_matrix": glCoordMatrix, 17023 "u_is_text": +isText, 17024 "u_pitch_with_map": +pitchWithMap, 17025 "u_is_along_line": isAlongLine, 17026 "u_is_variable_anchor": isVariableAnchor, 17027 "u_texsize": texSize, 17028 "u_texture": 0, 17029 "u_translation": translation, 17030 "u_pitched_scale": pitchedScale, 17031 "u_is_offset": isOffset, 17032 "u_height_anchor_ground": +heightAnchorGround 17033 }; 17034}; 17035const symbolSDFUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, isHalo, pitchedScale, isOffset, heightAnchorGround) => { 17036 const transform = painter.transform; 17037 return extend(symbolIconUniformValues(functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, isText, texSize, pitchedScale, isOffset, heightAnchorGround), { 17038 "u_gamma_scale": pitchWithMap ? Math.cos(transform.pitch * Math.PI / 180) * transform.cameraToCenterDistance : 1, 17039 "u_is_halo": isHalo ? 1 : 0, 17040 "u_is_plain": 1 17041 }); 17042}; 17043const symbolTextAndIconUniformValues = (functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, texSizeSDF, texSizeIcon, pitchedScale, isOffset, heightAnchorGround) => { 17044 return extend(symbolSDFUniformValues(functionType, size, rotateInShader, pitchWithMap, isAlongLine, isVariableAnchor, painter, labelPlaneMatrix, glCoordMatrix, translation, true, texSizeSDF, true, pitchedScale, isOffset, heightAnchorGround), { 17045 "u_texsize_icon": texSizeIcon, 17046 "u_texture_icon": 1 17047 }); 17048}; 17049//#endregion 17050//#region src/webgl/program/background_program.ts 17051const backgroundUniforms = (context, locations) => ({ 17052 "u_opacity": new Uniform1f(context, locations.u_opacity), 17053 "u_color": new UniformColor(context, locations.u_color) 17054}); 17055const backgroundPatternUniforms = (context, locations) => ({ 17056 "u_opacity": new Uniform1f(context, locations.u_opacity), 17057 "u_image": new Uniform1i(context, locations.u_image), 17058 "u_pattern_tl_a": new Uniform2f(context, locations.u_pattern_tl_a), 17059 "u_pattern_br_a": new Uniform2f(context, locations.u_pattern_br_a), 17060 "u_pattern_tl_b": new Uniform2f(context, locations.u_pattern_tl_b), 17061 "u_pattern_br_b": new Uniform2f(context, locations.u_pattern_br_b), 17062 "u_texsize": new Uniform2f(context, locations.u_texsize), 17063 "u_mix": new Uniform1f(context, locations.u_mix), 17064 "u_pattern_size_a": new Uniform2f(context, locations.u_pattern_size_a), 17065 "u_pattern_size_b": new Uniform2f(context, locations.u_pattern_size_b), 17066 "u_scale_a": new Uniform1f(context, locations.u_scale_a), 17067 "u_scale_b": new Uniform1f(context, locations.u_scale_b), 17068 "u_pixel_coord_upper": new Uniform2f(context, locations.u_pixel_coord_upper), 17069 "u_pixel_coord_lower": new Uniform2f(context, locations.u_pixel_coord_lower), 17070 "u_tile_units_to_pixels": new Uniform1f(context, locations.u_tile_units_to_pixels) 17071}); 17072const backgroundUniformValues = (opacity, color) => ({ 17073 "u_opacity": opacity, 17074 "u_color": color 17075}); 17076const backgroundPatternUniformValues = (opacity, painter, image, tile, crossfade) => extend(bgPatternUniformValues(image, crossfade, painter, tile), { "u_opacity": opacity }); 17077//#endregion 17078//#region src/webgl/program/atmosphere_program.ts 17079const atmosphereUniforms = (context, locations) => ({ 17080 "u_sun_pos": new Uniform3f(context, locations.u_sun_pos), 17081 "u_atmosphere_blend": new Uniform1f(context, locations.u_atmosphere_blend), 17082 "u_globe_position": new Uniform3f(context, locations.u_globe_position), 17083 "u_globe_radius": new Uniform1f(context, locations.u_globe_radius), 17084 "u_inv_proj_matrix": new UniformMatrix4f(context, locations.u_inv_proj_matrix) 17085}); 17086const atmosphereUniformValues = (sunPos, atmosphereBlend, globePosition, globeRadius, invProjMatrix) => ({ 17087 "u_sun_pos": sunPos, 17088 "u_atmosphere_blend": atmosphereBlend, 17089 "u_globe_position": globePosition, 17090 "u_globe_radius": globeRadius, 17091 "u_inv_proj_matrix": invProjMatrix 17092}); 17093//#endregion 17094//#region src/webgl/program/sky_program.ts 17095const skyUniforms = (context, locations) => ({ 17096 "u_sky_color": new UniformColor(context, locations.u_sky_color), 17097 "u_horizon_color": new UniformColor(context, locations.u_horizon_color), 17098 "u_horizon": new Uniform2f(context, locations.u_horizon), 17099 "u_horizon_normal": new Uniform2f(context, locations.u_horizon_normal), 17100 "u_sky_horizon_blend": new Uniform1f(context, locations.u_sky_horizon_blend), 17101 "u_sky_blend": new Uniform1f(context, locations.u_sky_blend), 17102 "u_inv_proj_matrix": new UniformMatrix4f(context, locations.u_inv_proj_matrix), 17103 "u_globe_position": new Uniform3f(context, locations.u_globe_position), 17104 "u_globe_radius": new Uniform1f(context, locations.u_globe_radius), 17105 "u_atmosphere_blend": new Uniform1f(context, locations.u_atmosphere_blend) 17106}); 17107const skyUniformValues = (sky, transform, pixelRatio) => { 17108 const cosRoll = Math.cos(transform.rollInRadians); 17109 const sinRoll = Math.sin(transform.rollInRadians); 17110 const mercatorHorizon = getMercatorHorizon(transform); 17111 const skyBlend = transform.getProjectionData({ 17112 overscaledTileID: null, 17113 applyGlobeMatrix: true, 17114 applyTerrainMatrix: true 17115 }).projectionTransition; 17116 const globePosition = getGlobeCenterInViewSpace(transform); 17117 const globeRadius = getGlobeRadiusPixels(transform.worldSize, transform.center.lat); 17118 return { 17119 "u_sky_color": sky.properties.get("sky-color"), 17120 "u_horizon_color": sky.properties.get("horizon-color"), 17121 "u_horizon": [(transform.width / 2 - mercatorHorizon * sinRoll) * pixelRatio, (transform.height / 2 + mercatorHorizon * cosRoll) * pixelRatio], 17122 "u_horizon_normal": [-sinRoll, cosRoll], 17123 "u_sky_horizon_blend": sky.properties.get("sky-horizon-blend") * transform.height / 2 * pixelRatio, 17124 "u_sky_blend": skyBlend, 17125 "u_inv_proj_matrix": transform.inverseProjectionMatrix, 17126 "u_globe_position": globePosition, 17127 "u_globe_radius": globeRadius, 17128 "u_atmosphere_blend": sky.properties.get("atmosphere-blend") * getAtmosphereAltitudeBlend(length(globePosition) - globeRadius, globeRadius) 17129 }; 17130}; 17131//#endregion 17132//#region src/webgl/program/program_uniforms.ts 17133const emptyUniforms = (_, __) => ({}); 17134const programUniforms = { 17135 fillExtrusion: fillExtrusionUniforms, 17136 fillExtrusionPattern: fillExtrusionPatternUniforms, 17137 fill: fillUniforms, 17138 fillPattern: fillPatternUniforms, 17139 fillOutline: fillOutlineUniforms, 17140 fillOutlinePattern: fillOutlinePatternUniforms, 17141 circle: circleUniforms, 17142 collisionBox: emptyUniforms, 17143 collisionCircle: emptyUniforms, 17144 debug: debugUniforms, 17145 depth: emptyUniforms, 17146 clippingMask: emptyUniforms, 17147 heatmap: heatmapUniforms, 17148 heatmapTexture: heatmapTextureUniforms, 17149 hillshade: hillshadeUniforms, 17150 hillshadePrepare: hillshadePrepareUniforms, 17151 colorRelief: colorReliefUniforms, 17152 line: lineUniforms, 17153 lineGradient: lineGradientUniforms, 17154 linePattern: linePatternUniforms, 17155 lineSDF: lineSDFUniforms, 17156 lineGradientSDF: lineGradientSDFUniforms, 17157 layerOpacity: layerOpacityUniforms, 17158 raster: rasterUniforms, 17159 symbolIcon: symbolIconUniforms, 17160 symbolSDF: symbolSDFUniforms, 17161 symbolTextAndIcon: symbolTextAndIconUniforms, 17162 background: backgroundUniforms, 17163 backgroundPattern: backgroundPatternUniforms, 17164 terrain: terrainUniforms, 17165 terrainDepth: terrainDepthUniforms, 17166 atmosphere: atmosphereUniforms, 17167 sky: skyUniforms 17168}; 17169//#endregion 17170//#region src/webgl/index_buffer.ts 17171/** 17172* @internal 17173* an index buffer class 17174*/ 17175var IndexBuffer = class { 17176 constructor(context, array, dynamicDraw) { 17177 this.context = context; 17178 const gl = context.gl; 17179 this.buffer = gl.createBuffer(); 17180 this.dynamicDraw = Boolean(dynamicDraw); 17181 this.context.unbindVAO(); 17182 context.bindElementBuffer.set(this.buffer); 17183 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, array.arrayBuffer, this.dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); 17184 if (!this.dynamicDraw) array.freeBufferAfterUpload(); 17185 } 17186 bind() { 17187 this.context.bindElementBuffer.set(this.buffer); 17188 } 17189 updateData(array) { 17190 const gl = this.context.gl; 17191 if (!this.dynamicDraw) throw new Error("Attempted to update data while not in dynamic mode."); 17192 this.context.unbindVAO(); 17193 this.bind(); 17194 gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, array.arrayBuffer); 17195 } 17196 destroy() { 17197 const gl = this.context.gl; 17198 if (this.buffer) { 17199 gl.deleteBuffer(this.buffer); 17200 delete this.buffer; 17201 } 17202 } 17203}; 17204//#endregion 17205//#region src/webgl/vertex_buffer.ts 17206/** 17207* An Enum for AttributeType 17208*/ 17209const AttributeType = { 17210 Int8: "BYTE", 17211 Uint8: "UNSIGNED_BYTE", 17212 Int16: "SHORT", 17213 Uint16: "UNSIGNED_SHORT", 17214 Int32: "INT", 17215 Uint32: "UNSIGNED_INT", 17216 Float32: "FLOAT" 17217}; 17218/** 17219* @internal 17220* The `VertexBuffer` class turns a `StructArray` into a WebGL buffer. Each member of the StructArray's 17221* Struct type is converted to a WebGL attribute. 17222*/ 17223var VertexBuffer = class { 17224 /** 17225 * @param dynamicDraw - Whether this buffer will be repeatedly updated. 17226 */ 17227 constructor(context, array, attributes, dynamicDraw) { 17228 this.length = array.length; 17229 this.attributes = attributes; 17230 this.itemSize = array.bytesPerElement; 17231 this.dynamicDraw = dynamicDraw; 17232 this.context = context; 17233 const gl = context.gl; 17234 this.buffer = gl.createBuffer(); 17235 context.bindVertexBuffer.set(this.buffer); 17236 gl.bufferData(gl.ARRAY_BUFFER, array.arrayBuffer, this.dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); 17237 if (!this.dynamicDraw) array.freeBufferAfterUpload(); 17238 } 17239 bind() { 17240 this.context.bindVertexBuffer.set(this.buffer); 17241 } 17242 updateData(array) { 17243 if (array.length !== this.length) throw new Error(`Length of new data is ${array.length}, which doesn't match current length of ${this.length}`); 17244 const gl = this.context.gl; 17245 this.bind(); 17246 gl.bufferSubData(gl.ARRAY_BUFFER, 0, array.arrayBuffer); 17247 } 17248 enableAttributes(gl, program) { 17249 for (const member of this.attributes) { 17250 const attribute = program.attributes[member.name]; 17251 if (attribute !== void 0) gl.enableVertexAttribArray(attribute.location); 17252 } 17253 } 17254 /** 17255 * Set the attribute pointers in a WebGL context 17256 * @param gl - The WebGL context 17257 * @param program - The active WebGL program 17258 * @param vertexOffset - Index of the starting vertex of the segment 17259 */ 17260 setVertexAttribPointers(gl, program, vertexOffset) { 17261 for (const member of this.attributes) { 17262 const attribute = program.attributes[member.name]; 17263 if (attribute !== void 0) { 17264 const offset = member.offset + this.itemSize * (vertexOffset || 0); 17265 if (attribute.isInteger) gl.vertexAttribIPointer(attribute.location, member.components, gl[AttributeType[member.type]], this.itemSize, offset); 17266 else gl.vertexAttribPointer(attribute.location, member.components, gl[AttributeType[member.type]], false, this.itemSize, offset); 17267 } 17268 } 17269 } 17270 /** 17271 * Destroy the GL buffer bound to the given WebGL context 17272 */ 17273 destroy() { 17274 const gl = this.context.gl; 17275 if (this.buffer) { 17276 gl.deleteBuffer(this.buffer); 17277 delete this.buffer; 17278 } 17279 } 17280}; 17281//#endregion 17282//#region src/webgl/value.ts 17283var BaseValue = class { 17284 constructor(context) { 17285 this.gl = context.gl; 17286 this.default = this.getDefault(); 17287 this.current = this.default; 17288 this.dirty = false; 17289 } 17290 get() { 17291 return this.current; 17292 } 17293 set(value) {} 17294 getDefault() { 17295 return this.default; 17296 } 17297 setDefault() { 17298 this.set(this.default); 17299 } 17300}; 17301var ClearColor = class extends BaseValue { 17302 getDefault() { 17303 return Color.transparent; 17304 } 17305 set(v) { 17306 const c = this.current; 17307 if (v.r === c.r && v.g === c.g && v.b === c.b && v.a === c.a && !this.dirty) return; 17308 this.gl.clearColor(v.r, v.g, v.b, v.a); 17309 this.current = v; 17310 this.dirty = false; 17311 } 17312}; 17313var ClearDepth = class extends BaseValue { 17314 getDefault() { 17315 return 1; 17316 } 17317 set(v) { 17318 if (v === this.current && !this.dirty) return; 17319 this.gl.clearDepth(v); 17320 this.current = v; 17321 this.dirty = false; 17322 } 17323}; 17324var ClearStencil = class extends BaseValue { 17325 getDefault() { 17326 return 0; 17327 } 17328 set(v) { 17329 if (v === this.current && !this.dirty) return; 17330 this.gl.clearStencil(v); 17331 this.current = v; 17332 this.dirty = false; 17333 } 17334}; 17335var ColorMask = class extends BaseValue { 17336 getDefault() { 17337 return [ 17338 true, 17339 true, 17340 true, 17341 true 17342 ]; 17343 } 17344 set(v) { 17345 const c = this.current; 17346 if (v[0] === c[0] && v[1] === c[1] && v[2] === c[2] && v[3] === c[3] && !this.dirty) return; 17347 this.gl.colorMask(v[0], v[1], v[2], v[3]); 17348 this.current = v; 17349 this.dirty = false; 17350 } 17351}; 17352var DepthMask = class extends BaseValue { 17353 getDefault() { 17354 return true; 17355 } 17356 set(v) { 17357 if (v === this.current && !this.dirty) return; 17358 this.gl.depthMask(v); 17359 this.current = v; 17360 this.dirty = false; 17361 } 17362}; 17363var StencilMask = class extends BaseValue { 17364 getDefault() { 17365 return 255; 17366 } 17367 set(v) { 17368 if (v === this.current && !this.dirty) return; 17369 this.gl.stencilMask(v); 17370 this.current = v; 17371 this.dirty = false; 17372 } 17373}; 17374var StencilFunc = class extends BaseValue { 17375 getDefault() { 17376 return { 17377 func: this.gl.ALWAYS, 17378 ref: 0, 17379 mask: 255 17380 }; 17381 } 17382 set(v) { 17383 const c = this.current; 17384 if (v.func === c.func && v.ref === c.ref && v.mask === c.mask && !this.dirty) return; 17385 this.gl.stencilFunc(v.func, v.ref, v.mask); 17386 this.current = v; 17387 this.dirty = false; 17388 } 17389}; 17390var StencilOp = class extends BaseValue { 17391 getDefault() { 17392 const gl = this.gl; 17393 return [ 17394 gl.KEEP, 17395 gl.KEEP, 17396 gl.KEEP 17397 ]; 17398 } 17399 set(v) { 17400 const c = this.current; 17401 if (v[0] === c[0] && v[1] === c[1] && v[2] === c[2] && !this.dirty) return; 17402 this.gl.stencilOp(v[0], v[1], v[2]); 17403 this.current = v; 17404 this.dirty = false; 17405 } 17406}; 17407var StencilTest = class extends BaseValue { 17408 getDefault() { 17409 return false; 17410 } 17411 set(v) { 17412 if (v === this.current && !this.dirty) return; 17413 const gl = this.gl; 17414 if (v) gl.enable(gl.STENCIL_TEST); 17415 else gl.disable(gl.STENCIL_TEST); 17416 this.current = v; 17417 this.dirty = false; 17418 } 17419}; 17420var DepthRange = class extends BaseValue { 17421 getDefault() { 17422 return [0, 1]; 17423 } 17424 set(v) { 17425 const c = this.current; 17426 if (v[0] === c[0] && v[1] === c[1] && !this.dirty) return; 17427 this.gl.depthRange(v[0], v[1]); 17428 this.current = v; 17429 this.dirty = false; 17430 } 17431}; 17432var DepthTest = class extends BaseValue { 17433 getDefault() { 17434 return false; 17435 } 17436 set(v) { 17437 if (v === this.current && !this.dirty) return; 17438 const gl = this.gl; 17439 if (v) gl.enable(gl.DEPTH_TEST); 17440 else gl.disable(gl.DEPTH_TEST); 17441 this.current = v; 17442 this.dirty = false; 17443 } 17444}; 17445var DepthFunc = class extends BaseValue { 17446 getDefault() { 17447 return this.gl.LESS; 17448 } 17449 set(v) { 17450 if (v === this.current && !this.dirty) return; 17451 this.gl.depthFunc(v); 17452 this.current = v; 17453 this.dirty = false; 17454 } 17455}; 17456var Blend = class extends BaseValue { 17457 getDefault() { 17458 return false; 17459 } 17460 set(v) { 17461 if (v === this.current && !this.dirty) return; 17462 const gl = this.gl; 17463 if (v) gl.enable(gl.BLEND); 17464 else gl.disable(gl.BLEND); 17465 this.current = v; 17466 this.dirty = false; 17467 } 17468}; 17469var BlendFunc = class extends BaseValue { 17470 getDefault() { 17471 const gl = this.gl; 17472 return [gl.ONE, gl.ZERO]; 17473 } 17474 set(v) { 17475 const c = this.current; 17476 if (v[0] === c[0] && v[1] === c[1] && !this.dirty) return; 17477 this.gl.blendFunc(v[0], v[1]); 17478 this.current = v; 17479 this.dirty = false; 17480 } 17481}; 17482var BlendColor = class extends BaseValue { 17483 getDefault() { 17484 return Color.transparent; 17485 } 17486 set(v) { 17487 const c = this.current; 17488 if (v.r === c.r && v.g === c.g && v.b === c.b && v.a === c.a && !this.dirty) return; 17489 this.gl.blendColor(v.r, v.g, v.b, v.a); 17490 this.current = v; 17491 this.dirty = false; 17492 } 17493}; 17494var BlendEquation = class extends BaseValue { 17495 getDefault() { 17496 return this.gl.FUNC_ADD; 17497 } 17498 set(v) { 17499 if (v === this.current && !this.dirty) return; 17500 this.gl.blendEquation(v); 17501 this.current = v; 17502 this.dirty = false; 17503 } 17504}; 17505var CullFace = class extends BaseValue { 17506 getDefault() { 17507 return false; 17508 } 17509 set(v) { 17510 if (v === this.current && !this.dirty) return; 17511 const gl = this.gl; 17512 if (v) gl.enable(gl.CULL_FACE); 17513 else gl.disable(gl.CULL_FACE); 17514 this.current = v; 17515 this.dirty = false; 17516 } 17517}; 17518var CullFaceSide = class extends BaseValue { 17519 getDefault() { 17520 return this.gl.BACK; 17521 } 17522 set(v) { 17523 if (v === this.current && !this.dirty) return; 17524 this.gl.cullFace(v); 17525 this.current = v; 17526 this.dirty = false; 17527 } 17528}; 17529var FrontFace = class extends BaseValue { 17530 getDefault() { 17531 return this.gl.CCW; 17532 } 17533 set(v) { 17534 if (v === this.current && !this.dirty) return; 17535 this.gl.frontFace(v); 17536 this.current = v; 17537 this.dirty = false; 17538 } 17539}; 17540var ProgramValue = class extends BaseValue { 17541 getDefault() { 17542 return null; 17543 } 17544 set(v) { 17545 if (v === this.current && !this.dirty) return; 17546 this.gl.useProgram(v); 17547 this.current = v; 17548 this.dirty = false; 17549 } 17550}; 17551var ActiveTextureUnit = class extends BaseValue { 17552 getDefault() { 17553 return this.gl.TEXTURE0; 17554 } 17555 set(v) { 17556 if (v === this.current && !this.dirty) return; 17557 this.gl.activeTexture(v); 17558 this.current = v; 17559 this.dirty = false; 17560 } 17561}; 17562var Viewport = class extends BaseValue { 17563 getDefault() { 17564 const gl = this.gl; 17565 return [ 17566 0, 17567 0, 17568 gl.drawingBufferWidth, 17569 gl.drawingBufferHeight 17570 ]; 17571 } 17572 set(v) { 17573 const c = this.current; 17574 if (v[0] === c[0] && v[1] === c[1] && v[2] === c[2] && v[3] === c[3] && !this.dirty) return; 17575 this.gl.viewport(v[0], v[1], v[2], v[3]); 17576 this.current = v; 17577 this.dirty = false; 17578 } 17579}; 17580var BindFramebuffer = class extends BaseValue { 17581 getDefault() { 17582 return null; 17583 } 17584 set(v) { 17585 if (v === this.current && !this.dirty) return; 17586 const gl = this.gl; 17587 gl.bindFramebuffer(gl.FRAMEBUFFER, v); 17588 this.current = v; 17589 this.dirty = false; 17590 } 17591}; 17592var BindRenderbuffer = class extends BaseValue { 17593 getDefault() { 17594 return null; 17595 } 17596 set(v) { 17597 if (v === this.current && !this.dirty) return; 17598 const gl = this.gl; 17599 gl.bindRenderbuffer(gl.RENDERBUFFER, v); 17600 this.current = v; 17601 this.dirty = false; 17602 } 17603}; 17604var BindTexture = class extends BaseValue { 17605 getDefault() { 17606 return null; 17607 } 17608 set(v) { 17609 if (v === this.current && !this.dirty) return; 17610 const gl = this.gl; 17611 gl.bindTexture(gl.TEXTURE_2D, v); 17612 this.current = v; 17613 this.dirty = false; 17614 } 17615}; 17616var BindVertexBuffer = class extends BaseValue { 17617 getDefault() { 17618 return null; 17619 } 17620 set(v) { 17621 if (v === this.current && !this.dirty) return; 17622 const gl = this.gl; 17623 gl.bindBuffer(gl.ARRAY_BUFFER, v); 17624 this.current = v; 17625 this.dirty = false; 17626 } 17627}; 17628var BindElementBuffer = class extends BaseValue { 17629 getDefault() { 17630 return null; 17631 } 17632 set(v) { 17633 const gl = this.gl; 17634 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, v); 17635 this.current = v; 17636 this.dirty = false; 17637 } 17638}; 17639var BindVertexArray = class extends BaseValue { 17640 getDefault() { 17641 return null; 17642 } 17643 set(v) { 17644 if (v === this.current && !this.dirty) return; 17645 this.gl.bindVertexArray(v); 17646 this.current = v; 17647 this.dirty = false; 17648 } 17649}; 17650var PixelStoreUnpack = class extends BaseValue { 17651 getDefault() { 17652 return 4; 17653 } 17654 set(v) { 17655 if (v === this.current && !this.dirty) return; 17656 const gl = this.gl; 17657 gl.pixelStorei(gl.UNPACK_ALIGNMENT, v); 17658 this.current = v; 17659 this.dirty = false; 17660 } 17661}; 17662var PixelStoreUnpackPremultiplyAlpha = class extends BaseValue { 17663 getDefault() { 17664 return false; 17665 } 17666 set(v) { 17667 if (v === this.current && !this.dirty) return; 17668 const gl = this.gl; 17669 gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, v); 17670 this.current = v; 17671 this.dirty = false; 17672 } 17673}; 17674var PixelStoreUnpackFlipY = class extends BaseValue { 17675 getDefault() { 17676 return false; 17677 } 17678 set(v) { 17679 if (v === this.current && !this.dirty) return; 17680 const gl = this.gl; 17681 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, v); 17682 this.current = v; 17683 this.dirty = false; 17684 } 17685}; 17686var FramebufferAttachment = class extends BaseValue { 17687 constructor(context, parent) { 17688 super(context); 17689 this.context = context; 17690 this.parent = parent; 17691 } 17692 getDefault() { 17693 return null; 17694 } 17695}; 17696var ColorAttachment = class extends FramebufferAttachment { 17697 setDirty() { 17698 this.dirty = true; 17699 } 17700 set(v) { 17701 if (v === this.current && !this.dirty) return; 17702 this.context.bindFramebuffer.set(this.parent); 17703 const gl = this.gl; 17704 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, v, 0); 17705 this.current = v; 17706 this.dirty = false; 17707 } 17708}; 17709var DepthAttachment = class extends FramebufferAttachment { 17710 set(v) { 17711 if (v === this.current && !this.dirty) return; 17712 this.context.bindFramebuffer.set(this.parent); 17713 const gl = this.gl; 17714 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, v); 17715 this.current = v; 17716 this.dirty = false; 17717 } 17718}; 17719var DepthStencilAttachment = class extends FramebufferAttachment { 17720 set(v) { 17721 if (v === this.current && !this.dirty) return; 17722 this.context.bindFramebuffer.set(this.parent); 17723 const gl = this.gl; 17724 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, v); 17725 this.current = v; 17726 this.dirty = false; 17727 } 17728}; 17729//#endregion 17730//#region src/webgl/framebuffer.ts 17731/** 17732* @internal 17733* A framebuffer holder object 17734*/ 17735var Framebuffer = class { 17736 constructor(context, width, height, hasDepth, hasStencil) { 17737 this.context = context; 17738 this.width = width; 17739 this.height = height; 17740 const gl = context.gl; 17741 const fbo = this.framebuffer = gl.createFramebuffer(); 17742 this.colorAttachment = new ColorAttachment(context, fbo); 17743 if (hasDepth) this.depthAttachment = hasStencil ? new DepthStencilAttachment(context, fbo) : new DepthAttachment(context, fbo); 17744 else if (hasStencil) throw new Error("Stencil cannot be set without depth"); 17745 } 17746 destroy() { 17747 const gl = this.context.gl; 17748 const texture = this.colorAttachment.get(); 17749 if (texture) gl.deleteTexture(texture); 17750 if (this.depthAttachment) { 17751 const renderbuffer = this.depthAttachment.get(); 17752 if (renderbuffer) gl.deleteRenderbuffer(renderbuffer); 17753 } 17754 gl.deleteFramebuffer(this.framebuffer); 17755 } 17756}; 17757//#endregion 17758//#region src/webgl/frame_uniform_buffer.ts 17759const layout = std140Layout([ 17760 { 17761 name: "u_units_to_pixels", 17762 type: "vec2" 17763 }, 17764 { 17765 name: "u_world_size", 17766 type: "vec2" 17767 }, 17768 { 17769 name: "u_camera_to_center_distance", 17770 type: "float" 17771 }, 17772 { 17773 name: "u_symbol_fade_change", 17774 type: "float" 17775 }, 17776 { 17777 name: "u_aspect_ratio", 17778 type: "float" 17779 }, 17780 { 17781 name: "u_device_pixel_ratio", 17782 type: "float" 17783 }, 17784 { 17785 name: "u_viewport_size", 17786 type: "vec2" 17787 }, 17788 { 17789 name: "u_pixel_extrude_scale", 17790 type: "vec2" 17791 }, 17792 { 17793 name: "u_pitch", 17794 type: "float" 17795 } 17796]); 17797const offsets = layout.offsets; 17798/** 17799* @internal 17800* The buffer behind the `FrameUBO` block in the shader preludes, written once per frame by `Painter.render`. 17801*/ 17802function createFrameUniformBuffer(context) { 17803 return new UniformBuffer(context, UBO_BINDINGS.FrameUBO, layout); 17804} 17805function updateFrameUniformBuffer(buffer, painter) { 17806 const f32 = buffer.pending; 17807 const { transform } = painter; 17808 const gl = painter.context.gl; 17809 f32[offsets.u_units_to_pixels] = 1 / transform.pixelsToGLUnits[0]; 17810 f32[offsets.u_units_to_pixels + 1] = 1 / transform.pixelsToGLUnits[1]; 17811 f32[offsets.u_world_size] = gl.drawingBufferWidth; 17812 f32[offsets.u_world_size + 1] = gl.drawingBufferHeight; 17813 f32[offsets.u_camera_to_center_distance] = transform.cameraToCenterDistance;
vendor: 17,207 bytes, lines 17814-18259
17814 f32[offsets.u_symbol_fade_change] = painter.options.fadeDuration ? painter.symbolFadeChange : 1; 17815 f32[offsets.u_aspect_ratio] = transform.width / transform.height; 17816 f32[offsets.u_device_pixel_ratio] = painter.pixelRatio; 17817 f32[offsets.u_viewport_size] = transform.width; 17818 f32[offsets.u_viewport_size + 1] = transform.height; 17819 f32[offsets.u_pixel_extrude_scale] = 1 / transform.width; 17820 f32[offsets.u_pixel_extrude_scale + 1] = 1 / transform.height; 17821 f32[offsets.u_pitch] = transform.pitch / 360 * 2 * Math.PI; 17822 buffer.upload(); 17823} 17824//#endregion 17825//#region src/webgl/color_mode.ts 17826const ZERO = 0; 17827const ONE = 1; 17828const ONE_MINUS_SRC_ALPHA = 771; 17829var ColorMode = class { 17830 constructor(blendFunction, blendColor, mask) { 17831 this.blendFunction = blendFunction; 17832 this.blendColor = blendColor; 17833 this.mask = mask; 17834 } 17835}; 17836ColorMode.Replace = [ONE, ZERO]; 17837ColorMode.disabled = new ColorMode(ColorMode.Replace, Color.transparent, [ 17838 false, 17839 false, 17840 false, 17841 false 17842]); 17843ColorMode.unblended = new ColorMode(ColorMode.Replace, Color.transparent, [ 17844 true, 17845 true, 17846 true, 17847 true 17848]); 17849ColorMode.alphaBlended = new ColorMode([ONE, ONE_MINUS_SRC_ALPHA], Color.transparent, [ 17850 true, 17851 true, 17852 true, 17853 true 17854]); 17855//#endregion 17856//#region src/webgl/context.ts 17857/** 17858* @internal 17859* A webgl wrapper class to allow injection, mocking and abstraction 17860*/ 17861var Context = class { 17862 constructor(gl) { 17863 this.gl = gl; 17864 this.clearColor = new ClearColor(this); 17865 this.clearDepth = new ClearDepth(this); 17866 this.clearStencil = new ClearStencil(this); 17867 this.colorMask = new ColorMask(this); 17868 this.depthMask = new DepthMask(this); 17869 this.stencilMask = new StencilMask(this); 17870 this.stencilFunc = new StencilFunc(this); 17871 this.stencilOp = new StencilOp(this); 17872 this.stencilTest = new StencilTest(this); 17873 this.depthRange = new DepthRange(this); 17874 this.depthTest = new DepthTest(this); 17875 this.depthFunc = new DepthFunc(this); 17876 this.blend = new Blend(this); 17877 this.blendFunc = new BlendFunc(this); 17878 this.blendColor = new BlendColor(this); 17879 this.blendEquation = new BlendEquation(this); 17880 this.cullFace = new CullFace(this); 17881 this.cullFaceSide = new CullFaceSide(this); 17882 this.frontFace = new FrontFace(this); 17883 this.program = new ProgramValue(this); 17884 this.activeTexture = new ActiveTextureUnit(this); 17885 this.viewport = new Viewport(this); 17886 this.bindFramebuffer = new BindFramebuffer(this); 17887 this.bindRenderbuffer = new BindRenderbuffer(this); 17888 this.bindTexture = new BindTexture(this); 17889 this.bindVertexBuffer = new BindVertexBuffer(this); 17890 this.bindElementBuffer = new BindElementBuffer(this); 17891 this.bindVertexArray = new BindVertexArray(this); 17892 this.pixelStoreUnpack = new PixelStoreUnpack(this); 17893 this.pixelStoreUnpackPremultiplyAlpha = new PixelStoreUnpackPremultiplyAlpha(this); 17894 this.pixelStoreUnpackFlipY = new PixelStoreUnpackFlipY(this); 17895 this.extTextureFilterAnisotropic = gl.getExtension("EXT_texture_filter_anisotropic"); 17896 if (this.extTextureFilterAnisotropic) this.extTextureFilterAnisotropicMax = gl.getParameter(this.extTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT); 17897 this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); 17898 gl.getExtension("EXT_color_buffer_half_float"); 17899 gl.getExtension("EXT_color_buffer_float"); 17900 this.projectionUniformBuffer = createProjectionUniformBuffer(this); 17901 this.terrainUniformBuffer = createTerrainUniformBuffer(this); 17902 this.frameUniformBuffer = createFrameUniformBuffer(this); 17903 } 17904 setDefault() { 17905 this.unbindVAO(); 17906 this.clearColor.setDefault(); 17907 this.clearDepth.setDefault(); 17908 this.clearStencil.setDefault(); 17909 this.colorMask.setDefault(); 17910 this.depthMask.setDefault(); 17911 this.stencilMask.setDefault(); 17912 this.stencilFunc.setDefault(); 17913 this.stencilOp.setDefault(); 17914 this.stencilTest.setDefault(); 17915 this.depthRange.setDefault(); 17916 this.depthTest.setDefault(); 17917 this.depthFunc.setDefault(); 17918 this.blend.setDefault(); 17919 this.blendFunc.setDefault(); 17920 this.blendColor.setDefault(); 17921 this.blendEquation.setDefault(); 17922 this.cullFace.setDefault(); 17923 this.cullFaceSide.setDefault(); 17924 this.frontFace.setDefault(); 17925 this.program.setDefault(); 17926 this.activeTexture.setDefault(); 17927 this.bindFramebuffer.setDefault(); 17928 this.pixelStoreUnpack.setDefault(); 17929 this.pixelStoreUnpackPremultiplyAlpha.setDefault(); 17930 this.pixelStoreUnpackFlipY.setDefault(); 17931 } 17932 setDirty() { 17933 this.clearColor.dirty = true; 17934 this.clearDepth.dirty = true; 17935 this.clearStencil.dirty = true; 17936 this.colorMask.dirty = true; 17937 this.depthMask.dirty = true; 17938 this.stencilMask.dirty = true; 17939 this.stencilFunc.dirty = true; 17940 this.stencilOp.dirty = true; 17941 this.stencilTest.dirty = true; 17942 this.depthRange.dirty = true; 17943 this.depthTest.dirty = true; 17944 this.depthFunc.dirty = true; 17945 this.blend.dirty = true; 17946 this.blendFunc.dirty = true; 17947 this.blendColor.dirty = true; 17948 this.blendEquation.dirty = true; 17949 this.cullFace.dirty = true; 17950 this.cullFaceSide.dirty = true; 17951 this.frontFace.dirty = true; 17952 this.program.dirty = true; 17953 this.activeTexture.dirty = true; 17954 this.viewport.dirty = true; 17955 this.bindFramebuffer.dirty = true; 17956 this.bindRenderbuffer.dirty = true; 17957 this.bindTexture.dirty = true; 17958 this.bindVertexBuffer.dirty = true; 17959 this.bindElementBuffer.dirty = true; 17960 this.bindVertexArray.dirty = true; 17961 this.pixelStoreUnpack.dirty = true; 17962 this.pixelStoreUnpackPremultiplyAlpha.dirty = true; 17963 this.pixelStoreUnpackFlipY.dirty = true; 17964 this.projectionUniformBuffer.bindingDirty = true; 17965 this.terrainUniformBuffer.bindingDirty = true; 17966 this.frameUniformBuffer.bindingDirty = true; 17967 } 17968 /** 17969 * Reset some GL state to default values before handing users the raw context, as we do for 17970 * custom layers and WebGL style images, to avoid hard-to-debug bugs in their code. 17971 * 17972 * MapLibre restores all of its own state afterwards, so the only state worth resetting first 17973 * is state users would be surprised to find dirty: `CULL_FACE`, `TEXTURE0` and the three 17974 * `UNPACK_` settings, whose defaults are meaningful enough that most code assumes them. 17975 * The vertex array is unbound rather than reset, so that MapLibre never has to track it and 17976 * a user's `vertexAttribPointer` calls cannot land on one of ours. 17977 */ 17978 setCustomLayerDefaults() { 17979 this.unbindVAO(); 17980 this.cullFace.setDefault(); 17981 this.activeTexture.setDefault(); 17982 this.pixelStoreUnpack.setDefault(); 17983 this.pixelStoreUnpackPremultiplyAlpha.setDefault(); 17984 this.pixelStoreUnpackFlipY.setDefault(); 17985 } 17986 createIndexBuffer(array, dynamicDraw) { 17987 return new IndexBuffer(this, array, dynamicDraw); 17988 } 17989 createVertexBuffer(array, attributes, dynamicDraw) { 17990 return new VertexBuffer(this, array, attributes, dynamicDraw); 17991 } 17992 createRenderbuffer(storageFormat, width, height) { 17993 const gl = this.gl; 17994 const rbo = gl.createRenderbuffer(); 17995 this.bindRenderbuffer.set(rbo); 17996 gl.renderbufferStorage(gl.RENDERBUFFER, storageFormat, width, height); 17997 this.bindRenderbuffer.set(null); 17998 return rbo; 17999 } 18000 createFramebuffer(width, height, hasDepth, hasStencil) { 18001 return new Framebuffer(this, width, height, hasDepth, hasStencil); 18002 } 18003 clear({ color, depth, stencil }) { 18004 const gl = this.gl; 18005 let mask = 0; 18006 if (color) { 18007 mask |= gl.COLOR_BUFFER_BIT; 18008 this.clearColor.set(color); 18009 this.colorMask.set([ 18010 true, 18011 true, 18012 true, 18013 true 18014 ]); 18015 } 18016 if (typeof depth !== "undefined") { 18017 mask |= gl.DEPTH_BUFFER_BIT; 18018 this.depthRange.set([0, 1]); 18019 this.clearDepth.set(depth); 18020 this.depthMask.set(true); 18021 } 18022 if (typeof stencil !== "undefined") { 18023 mask |= gl.STENCIL_BUFFER_BIT; 18024 this.clearStencil.set(stencil); 18025 this.stencilMask.set(255); 18026 } 18027 gl.clear(mask); 18028 } 18029 setCullFace(cullFaceMode) { 18030 if (cullFaceMode.enable === false) this.cullFace.set(false); 18031 else { 18032 this.cullFace.set(true); 18033 this.cullFaceSide.set(cullFaceMode.mode); 18034 this.frontFace.set(cullFaceMode.frontFace); 18035 } 18036 } 18037 setDepthMode(depthMode) { 18038 if (depthMode.func === this.gl.ALWAYS && !depthMode.mask) this.depthTest.set(false); 18039 else { 18040 this.depthTest.set(true); 18041 this.depthFunc.set(depthMode.func); 18042 this.depthMask.set(depthMode.mask); 18043 this.depthRange.set(depthMode.range); 18044 } 18045 } 18046 setStencilMode(stencilMode) { 18047 if (stencilMode.test.func === this.gl.ALWAYS && !stencilMode.mask) this.stencilTest.set(false); 18048 else { 18049 this.stencilTest.set(true); 18050 this.stencilMask.set(stencilMode.mask); 18051 this.stencilOp.set([ 18052 stencilMode.fail, 18053 stencilMode.depthFail, 18054 stencilMode.pass 18055 ]); 18056 this.stencilFunc.set({ 18057 func: stencilMode.test.func, 18058 ref: stencilMode.ref, 18059 mask: stencilMode.test.mask 18060 }); 18061 } 18062 } 18063 setColorMode(colorMode) { 18064 if (deepEqual(colorMode.blendFunction, ColorMode.Replace)) this.blend.set(false); 18065 else { 18066 this.blend.set(true); 18067 this.blendFunc.set(colorMode.blendFunction); 18068 this.blendColor.set(colorMode.blendColor); 18069 } 18070 this.colorMask.set(colorMode.mask); 18071 } 18072 createVertexArray() { 18073 return this.gl.createVertexArray(); 18074 } 18075 deleteVertexArray(x) { 18076 this.gl.deleteVertexArray(x); 18077 } 18078 unbindVAO() { 18079 this.bindVertexArray.set(null); 18080 } 18081}; 18082//#endregion 18083//#region src/webgl/depth_mode.ts 18084const ALWAYS$1 = 519; 18085var DepthMode = class { 18086 constructor(depthFunc, depthMask, depthRange) { 18087 this.func = depthFunc; 18088 this.mask = depthMask; 18089 this.range = depthRange; 18090 } 18091}; 18092DepthMode.ReadOnly = false; 18093DepthMode.ReadWrite = true; 18094DepthMode.disabled = new DepthMode(ALWAYS$1, DepthMode.ReadOnly, [0, 1]); 18095//#endregion 18096//#region src/webgl/stencil_mode.ts 18097const ALWAYS = 519; 18098const KEEP = 7680; 18099var StencilMode = class { 18100 constructor(test, ref, mask, fail, depthFail, pass) { 18101 this.test = test; 18102 this.ref = ref; 18103 this.mask = mask; 18104 this.fail = fail; 18105 this.depthFail = depthFail; 18106 this.pass = pass; 18107 } 18108}; 18109StencilMode.disabled = new StencilMode({ 18110 func: ALWAYS, 18111 mask: 0 18112}, 0, 0, KEEP, KEEP, KEEP); 18113//#endregion 18114//#region src/webgl/cull_face_mode.ts 18115const FRONT = 1028; 18116const BACK = 1029; 18117const CCW = 2305; 18118var CullFaceMode = class { 18119 constructor(enable, mode, frontFace) { 18120 this.enable = enable; 18121 this.mode = mode; 18122 this.frontFace = frontFace; 18123 } 18124}; 18125CullFaceMode.disabled = new CullFaceMode(false, BACK, CCW); 18126CullFaceMode.backCCW = new CullFaceMode(true, BACK, CCW); 18127CullFaceMode.frontCCW = new CullFaceMode(true, FRONT, CCW); 18128//#endregion 18129//#region src/render/render_context.ts 18130function createRenderContext(transform, projection, terrain) { 18131 const projectionTransition = projection?.transitionState ?? 0; 18132 return { 18133 currentPass: "offscreen", 18134 currentLayer: 0, 18135 opaquePassCutoff: Infinity, 18136 depthRangeFor3D: [0, 1], 18137 isRenderingToTexture: false, 18138 transform, 18139 terrain, 18140 projectionTransition, 18141 isRenderingGlobe: projectionTransition > 0 18142 }; 18143} 18144function getProjectionDataForTile(renderContext, tileID, options = {}) { 18145 return renderContext.transform.getProjectionData({ 18146 overscaledTileID: tileID, 18147 aligned: options.aligned, 18148 applyGlobeMatrix: !renderContext.isRenderingToTexture, 18149 applyTerrainMatrix: options.applyTerrainMatrix ?? true 18150 }); 18151} 18152/** 18153* Returns terrain data for a tile. 18154* Returns null if terrain is not configured or tiles are being rendered to a texture. 18155*/ 18156function getTerrainDataForTile(renderContext, tileID) { 18157 if (renderContext.isRenderingToTexture) return null; 18158 return renderContext.terrain?.getTerrainData(tileID) ?? null; 18159} 18160//#endregion 18161//#region src/webgl/draw/draw_collision_debug.ts 18162let quadTriangles; 18163function drawCollisionDebug(painter, tileManager, layer, coords, isText, renderContext) { 18164 const context = painter.context; 18165 const gl = context.gl; 18166 const program = painter.useProgram("collisionBox"); 18167 const tileBatches = []; 18168 let circleCount = 0; 18169 let circleOffset = 0; 18170 for (const coord of coords) { 18171 const bucket = tileManager.getTile(coord).getBucket(layer); 18172 if (!bucket) continue; 18173 const buffers = isText ? bucket.textCollisionBox : bucket.iconCollisionBox; 18174 const circleArray = bucket.collisionCircleArray; 18175 if (circleArray.length > 0) { 18176 tileBatches.push({ 18177 circleArray, 18178 circleOffset, 18179 coord 18180 }); 18181 circleCount += circleArray.length / 4; 18182 circleOffset = circleCount; 18183 } 18184 if (!buffers) continue; 18185 program.draw(context, gl.LINES, DepthMode.disabled, StencilMode.disabled, painter.colorModeForRenderPass(), CullFaceMode.disabled, null, getTerrainDataForTile(renderContext, coord), getProjectionDataForTile(renderContext, coord), layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer, buffers.segments, null, painter.transform.zoom, null, null, buffers.collisionVertexBuffer); 18186 } 18187 if (!isText || !tileBatches.length) return; 18188 const circleProgram = painter.useProgram("collisionCircle"); 18189 const vertexData = new CollisionCircleLayoutArray(); 18190 vertexData.resize(circleCount * 4); 18191 vertexData._trim(); 18192 let vertexOffset = 0; 18193 for (const batch of tileBatches) for (let i = 0; i < batch.circleArray.length / 4; i++) { 18194 const circleIdx = i * 4; 18195 const x = batch.circleArray[circleIdx + 0]; 18196 const y = batch.circleArray[circleIdx + 1]; 18197 const radius = batch.circleArray[circleIdx + 2]; 18198 const collision = batch.circleArray[circleIdx + 3]; 18199 vertexData.emplace(vertexOffset++, x, y, radius, collision, 0); 18200 vertexData.emplace(vertexOffset++, x, y, radius, collision, 1); 18201 vertexData.emplace(vertexOffset++, x, y, radius, collision, 2); 18202 vertexData.emplace(vertexOffset++, x, y, radius, collision, 3); 18203 } 18204 if (!quadTriangles || quadTriangles.length < circleCount * 2) quadTriangles = createQuadTriangles(circleCount); 18205 const indexBuffer = context.createIndexBuffer(quadTriangles, true); 18206 const vertexBuffer = context.createVertexBuffer(vertexData, collisionCircleLayout.members, true); 18207 for (const batch of tileBatches) circleProgram.draw(context, gl.TRIANGLES, DepthMode.disabled, StencilMode.disabled, painter.colorModeForRenderPass(), CullFaceMode.disabled, null, getTerrainDataForTile(renderContext, batch.coord), null, layer.id, vertexBuffer, indexBuffer, SegmentVector.simpleSegment(0, batch.circleOffset * 2, batch.circleArray.length, batch.circleArray.length / 2), null, painter.transform.zoom, null, null, null); 18208 vertexBuffer.destroy(); 18209 indexBuffer.destroy(); 18210} 18211function createQuadTriangles(quadCount) { 18212 const triCount = quadCount * 2; 18213 const array = new QuadTriangleArray(); 18214 array.resize(triCount); 18215 array._trim(); 18216 for (let i = 0; i < triCount; i++) { 18217 const idx = i * 6; 18218 array.uint16[idx + 0] = i * 4 + 0; 18219 array.uint16[idx + 1] = i * 4 + 1; 18220 array.uint16[idx + 2] = i * 4 + 2; 18221 array.uint16[idx + 3] = i * 4 + 2; 18222 array.uint16[idx + 4] = i * 4 + 3; 18223 array.uint16[idx + 5] = i * 4 + 0; 18224 } 18225 return array; 18226} 18227//#endregion 18228//#region src/webgl/draw/draw_symbol.ts 18229const identityMat4 = identity(/* @__PURE__ */ new Float32Array(16)); 18230function drawSymbols(painter, tileManager, layer, coords, variableOffsets, renderContext) { 18231 if (renderContext.currentPass !== "translucent") return; 18232 const stencilMode = StencilMode.disabled; 18233 const colorMode = painter.colorModeForRenderPass(); 18234 if (layer._unevaluatedLayout.hasValue("text-variable-anchor") || layer._unevaluatedLayout.hasValue("text-variable-anchor-offset")) updateVariableAnchors(coords, painter, layer, tileManager, layer.layout.get("text-rotation-alignment"), layer.layout.get("text-pitch-alignment"), layer.paint.get("text-translate"), layer.paint.get("text-translate-anchor"), variableOffsets); 18235 if (layer.paint.get("icon-opacity").constantOr(1) !== 0) drawLayerSymbols(painter, tileManager, layer, coords, false, layer.paint.get("icon-translate"), layer.paint.get("icon-translate-anchor"), layer.layout.get("icon-rotation-alignment").constantOr("viewport"), layer.layout.get("icon-pitch-alignment"), layer.layout.get("icon-keep-upright"), stencilMode, colorMode, renderContext); 18236 if (layer.paint.get("text-opacity").constantOr(1) !== 0) drawLayerSymbols(painter, tileManager, 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, renderContext); 18237 if (tileManager.map.showCollisionBoxes) { 18238 drawCollisionDebug(painter, tileManager, layer, coords, true, renderContext); 18239 drawCollisionDebug(painter, tileManager, layer, coords, false, renderContext); 18240 } 18241} 18242function calculateVariableRenderShift(anchor, width, height, textOffset, textBoxScale, renderTextSize) { 18243 const { horizontalAlign, verticalAlign } = getAnchorAlignment(anchor); 18244 const shiftX = -(horizontalAlign - .5) * width; 18245 const shiftY = -(verticalAlign - .5) * height; 18246 return new Point((shiftX / textBoxScale + textOffset[0]) * renderTextSize, (shiftY / textBoxScale + textOffset[1]) * renderTextSize); 18247} 18248function updateVariableAnchors(coords, painter, layer, tileManager, rotationAlignment, pitchAlignment, translate, translateAnchor, variableOffsets) { 18249 const transform = painter.transform; 18250 const terrain = painter.style.map.terrain; 18251 const rotateWithMap = rotationAlignment === "map"; 18252 const pitchWithMap = pitchAlignment === "map"; 18253 for (const coord of coords) { 18254 const tile = tileManager.getTile(coord); 18255 const bucket = tile.getBucket(layer); 18256 if (!bucket?.text?.segments.get().length) continue; 18257 const sizeData = bucket.textSizeData; 18258 const size = evaluateSizeForZoom(sizeData, transform.zoom); 18259 const pixelToTileScale = pixelsToTileUnits(tile, 1, painter.transform.zoom);
vendor: 72,018 bytes, lines 18260-19659
18260 const pitchedLabelPlaneMatrix = getPitchedLabelPlaneMatrix(rotateWithMap, painter.transform, pixelToTileScale); 18261 const updateTextFitIcon = layer.layout.get("icon-text-fit") !== "none" && bucket.hasIconData(); 18262 if (size) { 18263 const tileScale = Math.pow(2, transform.zoom - tile.tileID.overscaledZ); 18264 const getElevation = terrain ? (x, y) => terrain.getElevation(coord, x, y) : void 0; 18265 updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, transform, pitchedLabelPlaneMatrix, tileScale, size, updateTextFitIcon, translatePosition(transform, tile, translate, translateAnchor), coord.toUnwrapped(), getElevation, layer.layout.get("symbol-height-anchor") === "ground"); 18266 } 18267 } 18268} 18269function getShiftedAnchor(projectedAnchorPoint, projectionContext, rotateWithMap, shift, transformAngle, pitchedTextShiftCorrection) { 18270 const translatedAnchor = projectionContext.tileAnchorPoint.add(new Point(projectionContext.translation[0], projectionContext.translation[1])); 18271 if (projectionContext.pitchWithMap) { 18272 let adjustedShift = shift.mult(pitchedTextShiftCorrection); 18273 if (!rotateWithMap) adjustedShift = adjustedShift.rotate(-transformAngle); 18274 const tileAnchorShifted = translatedAnchor.add(adjustedShift); 18275 return projectWithMatrix(tileAnchorShifted.x, tileAnchorShifted.y, projectionContext.pitchedLabelPlaneMatrix, elevationAt(projectionContext, tileAnchorShifted.x, tileAnchorShifted.y)).point; 18276 } else if (rotateWithMap) { 18277 const east = projectTileCoordinatesToLabelPlane(projectionContext.tileAnchorPoint.x + 1, projectionContext.tileAnchorPoint.y, projectionContext).point.sub(projectedAnchorPoint); 18278 const angle = Math.atan(east.y / east.x) + (east.x < 0 ? Math.PI : 0); 18279 return projectedAnchorPoint.add(shift.rotate(angle)); 18280 } else return projectedAnchorPoint.add(shift); 18281} 18282function updateVariableAnchorsForBucket(bucket, rotateWithMap, pitchWithMap, variableOffsets, transform, pitchedLabelPlaneMatrix, tileScale, size, updateTextFitIcon, translation, unwrappedTileID, getElevation, heightAnchorGround) { 18283 const placedSymbols = bucket.text.placedSymbolArray; 18284 const dynamicTextLayoutVertexArray = bucket.text.dynamicLayoutVertexArray; 18285 const dynamicIconLayoutVertexArray = bucket.icon.dynamicLayoutVertexArray; 18286 const placedTextShifts = {}; 18287 dynamicTextLayoutVertexArray.clear(); 18288 for (let s = 0; s < placedSymbols.length; s++) { 18289 const symbol = placedSymbols.get(s); 18290 const skipOrientation = bucket.allowVerticalPlacement && !symbol.placedOrientation; 18291 const variableOffset = !symbol.hidden && symbol.crossTileID && !skipOrientation ? variableOffsets[symbol.crossTileID] : null; 18292 if (!variableOffset) hideGlyphs(symbol.numGlyphs, dynamicTextLayoutVertexArray); 18293 else { 18294 const tileAnchor = new Point(symbol.anchorX, symbol.anchorY); 18295 const projectionContext = { 18296 getElevation, 18297 heightOffset: symbol.heightOffset, 18298 heightAnchorGround, 18299 width: transform.width, 18300 height: transform.height, 18301 pitchedLabelPlaneMatrix, 18302 lineVertexArray: null, 18303 pitchWithMap, 18304 transform, 18305 projectionCache: null, 18306 tileAnchorPoint: tileAnchor, 18307 translation, 18308 unwrappedTileID 18309 }; 18310 const projectedAnchor = pitchWithMap ? projectTileCoordinatesToClipSpace(tileAnchor.x, tileAnchor.y, projectionContext) : projectTileCoordinatesToLabelPlane(tileAnchor.x, tileAnchor.y, projectionContext); 18311 const perspectiveRatio = getPerspectiveRatio(transform.cameraToCenterDistance, projectedAnchor.signedDistanceFromCamera); 18312 let renderTextSize = evaluateSizeForFeature(bucket.textSizeData, size, symbol) * perspectiveRatio / 24; 18313 if (pitchWithMap) renderTextSize *= bucket.tilePixelRatio / tileScale; 18314 const { width, height, anchor, textOffset, textBoxScale } = variableOffset; 18315 const shift = calculateVariableRenderShift(anchor, width, height, textOffset, textBoxScale, renderTextSize); 18316 const pitchedTextCorrection = transform.getPitchedTextCorrection(tileAnchor.x + translation[0], tileAnchor.y + translation[1], unwrappedTileID); 18317 const shiftedAnchor = getShiftedAnchor(projectedAnchor.point, projectionContext, rotateWithMap, shift, -transform.bearingInRadians, pitchedTextCorrection); 18318 const angle = bucket.allowVerticalPlacement && symbol.placedOrientation === 2 ? Math.PI / 2 : 0; 18319 for (let g = 0; g < symbol.numGlyphs; g++) addDynamicAttributes(dynamicTextLayoutVertexArray, shiftedAnchor, angle); 18320 if (updateTextFitIcon && symbol.associatedIconIndex >= 0) placedTextShifts[symbol.associatedIconIndex] = { 18321 shiftedAnchor, 18322 angle 18323 }; 18324 } 18325 } 18326 if (updateTextFitIcon) { 18327 dynamicIconLayoutVertexArray.clear(); 18328 const placedIcons = bucket.icon.placedSymbolArray; 18329 for (let i = 0; i < placedIcons.length; i++) { 18330 const placedIcon = placedIcons.get(i); 18331 if (placedIcon.hidden) hideGlyphs(placedIcon.numGlyphs, dynamicIconLayoutVertexArray); 18332 else { 18333 const shift = placedTextShifts[i]; 18334 if (!shift) hideGlyphs(placedIcon.numGlyphs, dynamicIconLayoutVertexArray); 18335 else for (let g = 0; g < placedIcon.numGlyphs; g++) addDynamicAttributes(dynamicIconLayoutVertexArray, shift.shiftedAnchor, shift.angle); 18336 } 18337 } 18338 bucket.icon.dynamicLayoutVertexBuffer.updateData(dynamicIconLayoutVertexArray); 18339 } 18340 bucket.text.dynamicLayoutVertexBuffer.updateData(dynamicTextLayoutVertexArray); 18341} 18342function getSymbolProgramName(isSDF, isText, bucket) { 18343 if (bucket.iconsInText && isText) return "symbolTextAndIcon"; 18344 else if (isSDF) return "symbolSDF"; 18345 else return "symbolIcon"; 18346} 18347function drawLayerSymbols(painter, tileManager, layer, coords, isText, translate, translateAnchor, rotationAlignment, pitchAlignment, keepUpright, stencilMode, colorMode, renderContext) { 18348 const context = painter.context; 18349 const gl = context.gl; 18350 const transform = painter.transform; 18351 const rotateWithMap = rotationAlignment === "map"; 18352 const pitchWithMap = pitchAlignment === "map"; 18353 const alongLine = rotationAlignment !== "viewport" && layer.layout.get("symbol-placement") !== "point"; 18354 const rotateInShader = rotateWithMap && !pitchWithMap && !alongLine; 18355 const hasSortKey = !layer.layout.get("symbol-sort-key").isConstant(); 18356 let sortFeaturesByKey = false; 18357 const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); 18358 const hasVariablePlacement = layer._unevaluatedLayout.hasValue("text-variable-anchor") || layer._unevaluatedLayout.hasValue("text-variable-anchor-offset"); 18359 const tileRenderState = []; 18360 const pitchedTextRescaling = transform.getCircleRadiusCorrection(); 18361 for (const coord of coords) { 18362 const tile = tileManager.getTile(coord); 18363 const bucket = tile.getBucket(layer); 18364 if (!bucket) continue; 18365 const buffers = isText ? bucket.text : bucket.icon; 18366 if (!buffers?.segments.get().length || !buffers.hasVisibleVertices) continue; 18367 const programConfiguration = buffers.programConfigurations.get(layer.id); 18368 const isSDF = isText || bucket.sdfIcons; 18369 const sizeData = isText ? bucket.textSizeData : bucket.iconSizeData; 18370 const transformed = pitchWithMap || transform.pitch !== 0; 18371 const program = painter.useProgram(getSymbolProgramName(isSDF, isText, bucket), programConfiguration); 18372 const size = evaluateSizeForZoom(sizeData, transform.zoom); 18373 const terrainData = getTerrainDataForTile(renderContext, coord); 18374 let texSize; 18375 let texSizeIcon = [0, 0]; 18376 let atlasTexture; 18377 let atlasInterpolation; 18378 let atlasTextureIcon = null; 18379 let atlasInterpolationIcon; 18380 if (isText) { 18381 atlasTexture = tile.glyphAtlasTexture; 18382 atlasInterpolation = gl.LINEAR; 18383 texSize = tile.glyphAtlasTexture.size; 18384 if (bucket.iconsInText) { 18385 texSizeIcon = tile.imageAtlasTexture.size; 18386 atlasTextureIcon = tile.imageAtlasTexture; 18387 const zoomDependentSize = sizeData.kind === "composite" || sizeData.kind === "camera"; 18388 atlasInterpolationIcon = transformed || painter.options.rotating || painter.options.zooming || zoomDependentSize ? gl.LINEAR : gl.NEAREST; 18389 } 18390 } else { 18391 const iconScaled = layer.layout.get("icon-size").constantOr(0) !== 1 || bucket.iconsNeedLinear; 18392 atlasTexture = tile.imageAtlasTexture; 18393 atlasInterpolation = isSDF || painter.options.rotating || painter.options.zooming || iconScaled || transformed ? gl.LINEAR : gl.NEAREST; 18394 texSize = tile.imageAtlasTexture.size; 18395 } 18396 const s = pixelsToTileUnits(tile, 1, painter.transform.zoom); 18397 const pitchedLabelPlaneMatrix = getPitchedLabelPlaneMatrix(rotateWithMap, painter.transform, s); 18398 const glCoordMatrixForShader = getGlCoordMatrix(pitchWithMap, rotateWithMap, painter.transform, s); 18399 const translation = translatePosition(transform, tile, translate, translateAnchor); 18400 const projectionData = getProjectionDataForTile(renderContext, coord); 18401 const hasVariableAnchors = hasVariablePlacement && bucket.hasTextData(); 18402 const updateTextFitIcon = layer.layout.get("icon-text-fit") !== "none" && hasVariableAnchors && bucket.hasIconData(); 18403 const isOffset = layer._unevaluatedLayout.hasValue("icon-offset"); 18404 const heightAnchorGround = layer.layout.get("symbol-height-anchor") === "ground"; 18405 if (alongLine) { 18406 const pitchedLabelPlaneMatrixInverse = create$1(); 18407 fastInvertTransformMat4(pitchedLabelPlaneMatrixInverse, pitchedLabelPlaneMatrix); 18408 const getElevation = painter.style.map.terrain ? (x, y) => painter.style.map.terrain.getElevation(coord, x, y) : void 0; 18409 updateLineLabels(bucket, painter, isText, pitchedLabelPlaneMatrix, pitchedLabelPlaneMatrixInverse, pitchWithMap, keepUpright, layer.layout.get("text-rotation-alignment") === "map", coord.toUnwrapped(), transform.width, transform.height, translation, getElevation); 18410 } 18411 const shaderVariableAnchor = isText && hasVariablePlacement || updateTextFitIcon; 18412 const combinedLabelPlaneMatrix = pitchWithMap ? pitchedLabelPlaneMatrix : painter.transform.clipSpaceToPixelsMatrix; 18413 const uLabelPlaneMatrix = alongLine || shaderVariableAnchor ? identityMat4 : combinedLabelPlaneMatrix; 18414 const hasHalo = isSDF && layer.paint.get(isText ? "text-halo-width" : "icon-halo-width").constantOr(1) !== 0; 18415 let uniformValues; 18416 if (isSDF) { 18417 if (!bucket.iconsInText) uniformValues = symbolSDFUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, isText, texSize, hasHalo, pitchedTextRescaling, isOffset, heightAnchorGround); 18418 else uniformValues = symbolTextAndIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, texSize, texSizeIcon, pitchedTextRescaling, isOffset, heightAnchorGround); 18419 } else uniformValues = symbolIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, alongLine, shaderVariableAnchor, painter, uLabelPlaneMatrix, glCoordMatrixForShader, translation, isText, texSize, pitchedTextRescaling, isOffset, heightAnchorGround); 18420 const state = { 18421 program, 18422 buffers, 18423 uniformValues, 18424 projectionData, 18425 atlasTexture, 18426 atlasTextureIcon, 18427 atlasInterpolation, 18428 atlasInterpolationIcon, 18429 isSDF, 18430 hasHalo 18431 }; 18432 if (hasSortKey && bucket.canOverlap) { 18433 sortFeaturesByKey = true; 18434 const oldSegments = buffers.segments.get(); 18435 for (const segment of oldSegments) tileRenderState.push({ 18436 segments: new SegmentVector([segment]), 18437 sortKey: segment.sortKey, 18438 state, 18439 terrainData 18440 }); 18441 } else tileRenderState.push({ 18442 segments: buffers.segments, 18443 sortKey: 0, 18444 state, 18445 terrainData 18446 }); 18447 } 18448 if (sortFeaturesByKey) tileRenderState.sort((a, b) => a.sortKey - b.sortKey); 18449 const haloWidth = layer.paint.get(isText ? "text-halo-width" : "icon-halo-width").constantOr(null) ?? Infinity; 18450 const isGlyphOverlap = layer.layout.get("text-letter-spacing").constantOr(0) * 24 < 0 || haloWidth > 1; 18451 for (const segmentState of tileRenderState) { 18452 const state = segmentState.state; 18453 context.activeTexture.set(gl.TEXTURE0); 18454 state.atlasTexture.bind(state.atlasInterpolation, gl.CLAMP_TO_EDGE); 18455 if (state.atlasTextureIcon) { 18456 context.activeTexture.set(gl.TEXTURE1); 18457 if (state.atlasTextureIcon) state.atlasTextureIcon.bind(state.atlasInterpolationIcon, gl.CLAMP_TO_EDGE); 18458 } 18459 const isHalo = state.isSDF && state.hasHalo; 18460 if (isHalo) { 18461 const uniformValues = state.uniformValues; 18462 uniformValues["u_is_halo"] = 1; 18463 if (isGlyphOverlap) { 18464 uniformValues["u_is_plain"] = 0; 18465 drawSymbolElements(state.buffers, segmentState.segments, layer, painter, state.program, depthMode, stencilMode, colorMode, uniformValues, state.projectionData, segmentState.terrainData); 18466 uniformValues["u_is_halo"] = 0; 18467 uniformValues["u_is_plain"] = 1; 18468 } 18469 } 18470 drawSymbolElements(state.buffers, segmentState.segments, layer, painter, state.program, depthMode, stencilMode, colorMode, state.uniformValues, state.projectionData, segmentState.terrainData); 18471 if (isHalo && !isGlyphOverlap) state.uniformValues["u_is_halo"] = 0; 18472 } 18473} 18474function drawSymbolElements(buffers, segments, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues, projectionData, terrainData) { 18475 const context = painter.context; 18476 const gl = context.gl; 18477 program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData, layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer, segments, layer.paint, painter.transform.zoom, buffers.programConfigurations.get(layer.id), buffers.dynamicLayoutVertexBuffer, buffers.opacityVertexBuffer); 18478} 18479//#endregion 18480//#region src/webgl/draw/draw_circle.ts 18481function drawCircles(painter, tileManager, layer, coords, renderContext) { 18482 if (renderContext.currentPass !== "translucent") return; 18483 const opacity = layer.paint.get("circle-opacity"); 18484 const strokeWidth = layer.paint.get("circle-stroke-width"); 18485 const strokeOpacity = layer.paint.get("circle-stroke-opacity"); 18486 const sortFeaturesByKey = !layer.layout.get("circle-sort-key").isConstant(); 18487 if (opacity.constantOr(1) === 0 && (strokeWidth.constantOr(1) === 0 || strokeOpacity.constantOr(1) === 0)) return; 18488 const context = painter.context; 18489 const gl = context.gl; 18490 const transform = painter.transform; 18491 const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); 18492 const stencilMode = StencilMode.disabled; 18493 const colorMode = painter.colorModeForRenderPass(); 18494 const segmentsRenderStates = []; 18495 const radiusCorrectionFactor = transform.getCircleRadiusCorrection(); 18496 for (const coord of coords) { 18497 const tile = tileManager.getTile(coord); 18498 const bucket = tile.getBucket(layer); 18499 if (!bucket) continue; 18500 const styleTranslate = layer.paint.get("circle-translate"); 18501 const styleTranslateAnchor = layer.paint.get("circle-translate-anchor"); 18502 const translateForUniforms = translatePosition(transform, tile, styleTranslate, styleTranslateAnchor); 18503 const programConfiguration = bucket.programConfigurations.get(layer.id); 18504 const program = painter.useProgram("circle", programConfiguration); 18505 const layoutVertexBuffer = bucket.layoutVertexBuffer; 18506 const indexBuffer = bucket.indexBuffer; 18507 const terrainData = getTerrainDataForTile(renderContext, coord); 18508 const state = { 18509 programConfiguration, 18510 program, 18511 layoutVertexBuffer, 18512 indexBuffer, 18513 uniformValues: circleUniformValues(painter, tile, layer, translateForUniforms, radiusCorrectionFactor), 18514 terrainData, 18515 projectionData: getProjectionDataForTile(renderContext, coord) 18516 }; 18517 if (sortFeaturesByKey) { 18518 const oldSegments = bucket.segments.get(); 18519 for (const segment of oldSegments) segmentsRenderStates.push({ 18520 segments: new SegmentVector([segment]), 18521 sortKey: segment.sortKey, 18522 state 18523 }); 18524 } else segmentsRenderStates.push({ 18525 segments: bucket.segments, 18526 sortKey: 0, 18527 state 18528 }); 18529 } 18530 if (sortFeaturesByKey) segmentsRenderStates.sort((a, b) => a.sortKey - b.sortKey); 18531 for (const segmentsState of segmentsRenderStates) { 18532 const { programConfiguration, program, layoutVertexBuffer, indexBuffer, uniformValues, terrainData, projectionData } = segmentsState.state; 18533 const segments = segmentsState.segments; 18534 program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData, layer.id, layoutVertexBuffer, indexBuffer, segments, layer.paint, painter.transform.zoom, programConfiguration); 18535 } 18536} 18537//#endregion 18538//#region src/webgl/draw/draw_heatmap.ts 18539function drawHeatmap(painter, tileManager, layer, tileIDs, renderContext) { 18540 if (layer.paint.get("heatmap-opacity") === 0) return; 18541 const context = painter.context; 18542 if (painter.style.map.terrain) { 18543 for (const coord of tileIDs) { 18544 const tile = tileManager.getTile(coord); 18545 if (tileManager.hasRenderableParent(coord)) continue; 18546 if (renderContext.currentPass === "offscreen") prepareHeatmapTerrain(painter, tile, layer, coord, renderContext); 18547 else if (renderContext.currentPass === "translucent") renderHeatmapTerrain(painter, layer, coord, renderContext); 18548 } 18549 context.viewport.set([ 18550 0, 18551 0, 18552 painter.width, 18553 painter.height 18554 ]); 18555 } else if (renderContext.currentPass === "offscreen") prepareHeatmapFlat(painter, tileManager, layer, tileIDs, renderContext); 18556 else if (renderContext.currentPass === "translucent") renderHeatmapFlat(painter, layer); 18557} 18558function prepareHeatmapFlat(painter, tileManager, layer, coords, renderContext) { 18559 const context = painter.context; 18560 const gl = context.gl; 18561 const transform = painter.transform; 18562 const stencilMode = StencilMode.disabled; 18563 const colorMode = new ColorMode([gl.ONE, gl.ONE], Color.transparent, [ 18564 true, 18565 true, 18566 true, 18567 true 18568 ]); 18569 bindFramebuffer(context, painter, layer); 18570 context.clear({ color: Color.transparent }); 18571 for (const coord of coords) { 18572 if (tileManager.hasRenderableParent(coord)) continue; 18573 const tile = tileManager.getTile(coord); 18574 const bucket = tile.getBucket(layer); 18575 if (!bucket) continue; 18576 const programConfiguration = bucket.programConfigurations.get(layer.id); 18577 const program = painter.useProgram("heatmap", programConfiguration); 18578 const projectionData = getProjectionDataForTile(renderContext, coord, { applyTerrainMatrix: false }); 18579 const radiusCorrectionFactor = transform.getCircleRadiusCorrection(); 18580 program.draw(context, gl.TRIANGLES, DepthMode.disabled, stencilMode, colorMode, CullFaceMode.backCCW, heatmapUniformValues(tile, transform.zoom, layer.paint.get("heatmap-intensity"), radiusCorrectionFactor), null, projectionData, layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, layer.paint, transform.zoom, programConfiguration); 18581 } 18582 context.viewport.set([ 18583 0, 18584 0, 18585 painter.width, 18586 painter.height 18587 ]); 18588} 18589function renderHeatmapFlat(painter, layer) { 18590 const context = painter.context; 18591 const gl = context.gl; 18592 context.setColorMode(painter.colorModeForRenderPass()); 18593 const fbo = layer.heatmapFbos.get(HEATMAP_FULL_RENDER_FBO_KEY); 18594 if (!fbo) return; 18595 context.activeTexture.set(gl.TEXTURE0); 18596 gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); 18597 context.activeTexture.set(gl.TEXTURE1); 18598 getColorRampTexture(context, layer).bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 18599 painter.useProgram("heatmapTexture").draw(context, gl.TRIANGLES, DepthMode.disabled, StencilMode.disabled, painter.colorModeForRenderPass(), CullFaceMode.disabled, heatmapTextureUniformValues(painter, layer, 0, 1), null, null, layer.id, painter.viewportBuffer, painter.quadTriangleIndexBuffer, painter.viewportSegments, layer.paint, painter.transform.zoom); 18600} 18601function prepareHeatmapTerrain(painter, tile, layer, coord, renderContext) { 18602 const context = painter.context; 18603 const gl = context.gl; 18604 const stencilMode = StencilMode.disabled; 18605 const colorMode = new ColorMode([gl.ONE, gl.ONE], Color.transparent, [ 18606 true, 18607 true, 18608 true, 18609 true 18610 ]); 18611 const bucket = tile.getBucket(layer); 18612 if (!bucket) return; 18613 const tileKey = coord.key; 18614 let fbo = layer.heatmapFbos.get(tileKey); 18615 if (!fbo) { 18616 fbo = createHeatmapFbo(context, tile.tileSize, tile.tileSize); 18617 layer.heatmapFbos.set(tileKey, fbo); 18618 } 18619 context.bindFramebuffer.set(fbo.framebuffer); 18620 context.viewport.set([ 18621 0, 18622 0, 18623 tile.tileSize, 18624 tile.tileSize 18625 ]); 18626 context.clear({ color: Color.transparent }); 18627 const programConfiguration = bucket.programConfigurations.get(layer.id); 18628 const program = painter.useProgram("heatmap", programConfiguration, !renderContext.isRenderingGlobe); 18629 const projectionData = getProjectionDataForTile(renderContext, tile.tileID); 18630 const terrainData = getTerrainDataForTile(renderContext, coord); 18631 program.draw(context, gl.TRIANGLES, DepthMode.disabled, stencilMode, colorMode, CullFaceMode.disabled, heatmapUniformValues(tile, painter.transform.zoom, layer.paint.get("heatmap-intensity"), 1), terrainData, projectionData, layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, layer.paint, painter.transform.zoom, programConfiguration); 18632} 18633function renderHeatmapTerrain(painter, layer, coord, renderContext) { 18634 const context = painter.context; 18635 const gl = context.gl; 18636 const transform = painter.transform; 18637 context.setColorMode(painter.colorModeForRenderPass()); 18638 const colorRampTexture = getColorRampTexture(context, layer); 18639 const tileKey = coord.key; 18640 const fbo = layer.heatmapFbos.get(tileKey); 18641 if (!fbo) return; 18642 context.activeTexture.set(gl.TEXTURE0); 18643 gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); 18644 context.activeTexture.set(gl.TEXTURE1); 18645 colorRampTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 18646 const projectionData = getProjectionDataForTile(renderContext, coord, { applyTerrainMatrix: renderContext.isRenderingGlobe }); 18647 painter.useProgram("heatmapTexture").draw(context, gl.TRIANGLES, DepthMode.disabled, StencilMode.disabled, painter.colorModeForRenderPass(), CullFaceMode.disabled, heatmapTextureUniformValues(painter, layer, 0, 1), null, projectionData, layer.id, painter.rasterBoundsBuffer, painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments, layer.paint, transform.zoom); 18648 fbo.destroy(); 18649 layer.heatmapFbos.delete(tileKey); 18650} 18651function bindFramebuffer(context, painter, layer) { 18652 const gl = context.gl; 18653 context.activeTexture.set(gl.TEXTURE1); 18654 context.viewport.set([ 18655 0, 18656 0, 18657 painter.width / 4, 18658 painter.height / 4 18659 ]); 18660 let fbo = layer.heatmapFbos.get(HEATMAP_FULL_RENDER_FBO_KEY); 18661 if (!fbo) { 18662 fbo = createHeatmapFbo(context, painter.width / 4, painter.height / 4); 18663 layer.heatmapFbos.set(HEATMAP_FULL_RENDER_FBO_KEY, fbo); 18664 } else { 18665 gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); 18666 context.bindFramebuffer.set(fbo.framebuffer); 18667 } 18668} 18669function createHeatmapFbo(context, width, height) { 18670 const gl = context.gl; 18671 const texture = gl.createTexture(); 18672 gl.bindTexture(gl.TEXTURE_2D, texture); 18673 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 18674 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 18675 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 18676 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 18677 gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA16F, width, height); 18678 const fbo = context.createFramebuffer(width, height, false, false); 18679 fbo.colorAttachment.set(texture); 18680 return fbo; 18681} 18682function getColorRampTexture(context, layer) { 18683 layer.colorRampTexture ||= new Texture(context, layer.colorRamp, context.gl.RGBA); 18684 return layer.colorRampTexture; 18685} 18686//#endregion 18687//#region src/webgl/draw/draw_layer_opacity.ts 18688/** 18689* Partial line-layer-opacity 18690* render the whole layer to a scratch FBO, then composite with `layerOpacity`. 18691* Applies opacity uniformly to the layer instead of accumulating alpha across overlapping segments. 18692*/ 18693function prepareDrawLayerOpacity(painter, layer, coords) { 18694 const context = painter.context; 18695 const compositeTarget = context.bindFramebuffer.get(); 18696 const compositeViewport = context.viewport.get(); 18697 const [, , width, height] = compositeViewport; 18698 bindLayerOpacity(painter, width, height); 18699 context.viewport.set([ 18700 0, 18701 0, 18702 width, 18703 height 18704 ]); 18705 context.clear({ 18706 color: Color.transparent, 18707 depth: 1, 18708 stencil: 0 18709 }); 18710 painter.currentStencilSource = void 0; 18711 painter.renderTileClippingMasks(layer, coords); 18712 return { 18713 compositeTarget, 18714 compositeViewport 18715 }; 18716} 18717function bindLayerOpacity(painter, width, height) { 18718 const gl = painter.context.gl; 18719 if (!painter.layerOpacityFbo) { 18720 const fbo = painter.context.createFramebuffer(width, height, true, true); 18721 const texture = gl.createTexture(); 18722 gl.bindTexture(gl.TEXTURE_2D, texture); 18723 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 18724 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 18725 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 18726 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 18727 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 18728 fbo.colorAttachment.set(texture); 18729 fbo.depthAttachment.set(painter.context.createRenderbuffer(gl.DEPTH_STENCIL, width, height)); 18730 painter.layerOpacityFbo = fbo; 18731 painter.context.bindFramebuffer.set(painter.layerOpacityFbo.framebuffer); 18732 return; 18733 } 18734 if (painter.layerOpacityFbo.width === width && painter.layerOpacityFbo.height === height) { 18735 painter.context.bindFramebuffer.set(painter.layerOpacityFbo.framebuffer); 18736 return; 18737 } 18738 const fbo = painter.layerOpacityFbo; 18739 gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); 18740 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 18741 painter.context.bindRenderbuffer.set(fbo.depthAttachment.get()); 18742 gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height); 18743 painter.context.bindRenderbuffer.set(null); 18744 fbo.width = width; 18745 fbo.height = height; 18746 painter.context.bindFramebuffer.set(fbo.framebuffer); 18747} 18748function drawLayerOpacity(painter, opacity, prepareDrawLayerOpacityResult, layer) { 18749 const context = painter.context; 18750 const gl = context.gl; 18751 context.bindFramebuffer.set(prepareDrawLayerOpacityResult.compositeTarget); 18752 context.viewport.set(prepareDrawLayerOpacityResult.compositeViewport); 18753 context.activeTexture.set(gl.TEXTURE0); 18754 gl.bindTexture(gl.TEXTURE_2D, painter.layerOpacityFbo.colorAttachment.get()); 18755 painter.useProgram("layerOpacity").draw(context, gl.TRIANGLES, DepthMode.disabled, StencilMode.disabled, painter.colorModeForRenderPass(), CullFaceMode.disabled, layerOpacityUniformValues(opacity, 0), null, null, layer.id, painter.viewportBuffer, painter.quadTriangleIndexBuffer, painter.viewportSegments, layer.paint, painter.transform.zoom); 18756 painter.currentStencilSource = void 0; 18757} 18758//#endregion 18759//#region src/webgl/draw/draw_line.ts 18760function updateGradientTexture(painter, tileManager, context, gl, layer, bucket, coord, layerGradient) { 18761 let textureResolution = 256; 18762 if (layer.stepInterpolant) { 18763 const sourceMaxZoom = tileManager.getSource().maxzoom; 18764 const potentialOverzoom = coord.canonical.z === sourceMaxZoom ? Math.ceil(1 << painter.transform.maxZoom - coord.canonical.z) : 1; 18765 const maxTextureCoverage = bucket.maxLineLength / EXTENT * 1024 * potentialOverzoom; 18766 textureResolution = clamp(nextPowerOfTwo(maxTextureCoverage), 256, context.maxTextureSize); 18767 } 18768 layerGradient.gradient = renderColorRamp({ 18769 expression: layer.gradientExpression(), 18770 evaluationKey: "lineProgress", 18771 resolution: textureResolution, 18772 image: layerGradient.gradient || void 0, 18773 clips: bucket.lineClipsArray 18774 }); 18775 if (layerGradient.texture) layerGradient.texture.update(layerGradient.gradient); 18776 else layerGradient.texture = new Texture(context, layerGradient.gradient, gl.RGBA); 18777 layerGradient.version = layer.gradientVersion; 18778 return layerGradient.texture; 18779} 18780function bindImagePatternTextures(context, gl, tile, programConfiguration, crossfade) { 18781 context.activeTexture.set(gl.TEXTURE0); 18782 tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 18783 programConfiguration.updatePaintBuffers(crossfade); 18784} 18785function bindDasharrayTextures(painter, context, gl, programConfiguration, programChanged, crossfade) { 18786 if (programChanged || painter.lineAtlas.dirty) { 18787 context.activeTexture.set(gl.TEXTURE0); 18788 painter.lineAtlas.bind(context); 18789 } 18790 programConfiguration.updatePaintBuffers(crossfade); 18791} 18792function bindGradientTextures(painter, tileManager, context, gl, layer, bucket, coord) { 18793 const layerGradient = bucket.gradients[layer.id]; 18794 let gradientTexture = layerGradient.texture; 18795 if (layer.gradientVersion !== layerGradient.version) gradientTexture = updateGradientTexture(painter, tileManager, context, gl, layer, bucket, coord, layerGradient); 18796 context.activeTexture.set(gl.TEXTURE0); 18797 gradientTexture.bind(layer.stepInterpolant ? gl.NEAREST : gl.LINEAR, gl.CLAMP_TO_EDGE); 18798} 18799function bindGradientAndDashTextures(painter, tileManager, context, gl, layer, bucket, coord, programConfiguration, crossfade) { 18800 const layerGradient = bucket.gradients[layer.id]; 18801 let gradientTexture = layerGradient.texture; 18802 if (layer.gradientVersion !== layerGradient.version) gradientTexture = updateGradientTexture(painter, tileManager, context, gl, layer, bucket, coord, layerGradient); 18803 context.activeTexture.set(gl.TEXTURE0); 18804 gradientTexture.bind(layer.stepInterpolant ? gl.NEAREST : gl.LINEAR, gl.CLAMP_TO_EDGE); 18805 context.activeTexture.set(gl.TEXTURE1); 18806 painter.lineAtlas.bind(context); 18807 programConfiguration.updatePaintBuffers(crossfade); 18808} 18809function drawLine(painter, tileManager, layer, coords, renderContext) { 18810 if (renderContext.currentPass !== "translucent") return; 18811 const opacity = layer.paint.get("line-opacity"); 18812 const width = layer.paint.get("line-width"); 18813 const layerOpacity = layer.paint.get("line-layer-opacity"); 18814 if (opacity.constantOr(1) === 0 || width.constantOr(1) === 0 || layerOpacity === 0) return; 18815 if (layerOpacity < 1) { 18816 const results = prepareDrawLayerOpacity(painter, layer, coords); 18817 drawLineTiles(painter, tileManager, layer, coords, renderContext); 18818 drawLayerOpacity(painter, layerOpacity, results, layer); 18819 return; 18820 } 18821 drawLineTiles(painter, tileManager, layer, coords, renderContext); 18822} 18823function drawLineTiles(painter, tileManager, layer, coords, renderContext) { 18824 const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); 18825 const colorMode = painter.colorModeForRenderPass(); 18826 const dasharrayProperty = layer.paint.get("line-dasharray"); 18827 const dasharray = dasharrayProperty.constantOr(1); 18828 const patternProperty = layer.paint.get("line-pattern"); 18829 const image = patternProperty.constantOr(1); 18830 const gradient = layer.paint.get("line-gradient"); 18831 const crossfade = layer.getCrossfadeParameters(); 18832 let programId; 18833 if (image) programId = "linePattern"; 18834 else if (dasharray && gradient) programId = "lineGradientSDF"; 18835 else if (dasharray) programId = "lineSDF"; 18836 else if (gradient) programId = "lineGradient"; 18837 else programId = "line"; 18838 const context = painter.context; 18839 const gl = context.gl; 18840 const transform = painter.transform; 18841 let firstTile = true; 18842 for (const coord of coords) { 18843 const tile = tileManager.getTile(coord); 18844 if (image && !tile.patternsLoaded()) continue; 18845 const bucket = tile.getBucket(layer); 18846 if (!bucket) continue; 18847 const programConfiguration = bucket.programConfigurations.get(layer.id); 18848 const prevProgram = painter.context.program.get(); 18849 const program = painter.useProgram(programId, programConfiguration); 18850 const programChanged = firstTile || program.program !== prevProgram; 18851 const terrainData = getTerrainDataForTile(renderContext, coord); 18852 const constantPattern = patternProperty.constantOr(null); 18853 const constantDasharray = dasharrayProperty?.constantOr(null); 18854 if (constantPattern && tile.imageAtlas) { 18855 const atlas = tile.imageAtlas; 18856 const posTo = atlas.patternPositions[constantPattern.to.toString()]; 18857 const posFrom = atlas.patternPositions[constantPattern.from.toString()]; 18858 if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); 18859 } else if (constantDasharray) { 18860 const round = layer.layout.get("line-cap").constantOr(null) === "round"; 18861 const dashTo = painter.lineAtlas.getDash(constantDasharray.to, round); 18862 const dashFrom = painter.lineAtlas.getDash(constantDasharray.from, round); 18863 programConfiguration.setConstantDashPositions(dashTo, dashFrom); 18864 } 18865 const projectionData = getProjectionDataForTile(renderContext, coord); 18866 const pixelRatio = transform.getPixelScale(); 18867 let uniformValues; 18868 if (image) { 18869 uniformValues = linePatternUniformValues(painter, tile, layer, pixelRatio, crossfade); 18870 bindImagePatternTextures(context, gl, tile, programConfiguration, crossfade); 18871 } else if (dasharray && gradient) { 18872 uniformValues = lineGradientSDFUniformValues(painter, tile, layer, pixelRatio, crossfade, bucket.lineClipsArray.length); 18873 bindGradientAndDashTextures(painter, tileManager, context, gl, layer, bucket, coord, programConfiguration, crossfade); 18874 } else if (dasharray) { 18875 uniformValues = lineSDFUniformValues(painter, tile, layer, pixelRatio, crossfade); 18876 bindDasharrayTextures(painter, context, gl, programConfiguration, programChanged, crossfade); 18877 } else if (gradient) { 18878 uniformValues = lineGradientUniformValues(painter, tile, layer, pixelRatio, bucket.lineClipsArray.length); 18879 bindGradientTextures(painter, tileManager, context, gl, layer, bucket, coord); 18880 } else uniformValues = lineUniformValues(painter, tile, layer, pixelRatio); 18881 const stencil = painter.stencilModeForClipping(coord); 18882 program.draw(context, gl.TRIANGLES, depthMode, stencil, colorMode, CullFaceMode.disabled, uniformValues, terrainData, projectionData, layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, layer.paint, painter.transform.zoom, programConfiguration, bucket.layoutVertexBuffer2); 18883 firstTile = false; 18884 } 18885} 18886//#endregion 18887//#region src/render/update_pattern_positions_in_program.ts 18888/** 18889* A simple helper shared by draw_fill and draw_fill_extrusions to find the correct pattern positions AND update program. 18890* For transitionable properties, especially 'fill-pattern' and 'fill-extrusion-pattern', while rendering certain frames 18891* tile.imageAtlas has been updated by worker to hold the new pattern only, but rendering code is still looking for the previous image. 18892* The mismatch was causing setConstantPatternPositions method not being called and pixelRatio was always the 18893* default of 1, instead of actual values set by original map.addImage. 18894* 18895* @param programConfiguration - to be used to set pattern position and device pixel ratio. 18896* @param propertyName - 'fill-pattern' or 'fill-extrusion-pattern' property key 18897* @param constantPattern - either 'fill-pattern' or 'fill-extrusion-pattern' property value 18898* @param tile - current tile being drawn 18899* @param layer - current layer being rendered 18900*/ 18901function updatePatternPositionsInProgram(programConfiguration, propertyName, constantPattern, tile, layer) { 18902 if (!constantPattern || !tile?.imageAtlas) return; 18903 const patternPositions = tile.imageAtlas.patternPositions; 18904 let posTo = patternPositions[constantPattern.to.toString()]; 18905 let posFrom = patternPositions[constantPattern.from.toString()]; 18906 if (!posTo && posFrom) posTo = posFrom; 18907 if (!posFrom && posTo) posFrom = posTo; 18908 if (!posTo || !posFrom) { 18909 const transitioned = layer.getPaintProperty(propertyName); 18910 posTo = patternPositions[transitioned]; 18911 posFrom = patternPositions[transitioned]; 18912 } 18913 if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom); 18914} 18915//#endregion 18916//#region src/webgl/draw/draw_fill.ts 18917function drawFill(painter, tileManager, layer, coords, renderContext) { 18918 const color = layer.paint.get("fill-color"); 18919 const opacity = layer.paint.get("fill-opacity"); 18920 const layerOpacity = layer.paint.get("fill-layer-opacity"); 18921 if (opacity.constantOr(1) === 0 || layerOpacity === 0) return; 18922 if (layerOpacity < 1) { 18923 if (renderContext.currentPass !== "translucent") return; 18924 const results = prepareDrawLayerOpacity(painter, layer, coords); 18925 drawFillAndOutline(painter, tileManager, layer, coords, renderContext); 18926 drawLayerOpacity(painter, layerOpacity, results, layer); 18927 return; 18928 } 18929 const pattern = layer.paint.get("fill-pattern"); 18930 const fillEligibleForOpaque = painter.opaquePassEnabledForLayer() && !pattern.constantOr(1) && color.constantOr(Color.transparent).a === 1 && opacity.constantOr(0) === 1; 18931 if (fillEligibleForOpaque && renderContext.currentPass === "opaque") { 18932 const colorMode = painter.colorModeForRenderPass(); 18933 drawFillTiles(painter, tileManager, layer, coords, painter.getDepthModeForSublayer(1, DepthMode.ReadWrite), colorMode, false, renderContext); 18934 return; 18935 } 18936 if (fillEligibleForOpaque && renderContext.currentPass === "translucent") { 18937 drawOutline(painter, tileManager, layer, coords, renderContext); 18938 return; 18939 } 18940 if (renderContext.currentPass === "translucent") drawFillAndOutline(painter, tileManager, layer, coords, renderContext); 18941} 18942/** 18943* Draw fill + outline in a single translucent pass with ReadOnly depth. 18944* Shared by the layer-opacity subpass (always) and the normal translucent path 18945* (when the fill is not opaque-pass-eligible). 18946*/ 18947function drawFillAndOutline(painter, tileManager, layer, coords, renderContext) { 18948 const colorMode = painter.colorModeForRenderPass(); 18949 drawFillTiles(painter, tileManager, layer, coords, painter.getDepthModeForSublayer(1, DepthMode.ReadOnly), colorMode, false, renderContext); 18950 drawOutline(painter, tileManager, layer, coords, renderContext); 18951} 18952function drawOutline(painter, tileManager, layer, coords, renderContext) { 18953 if (!layer.paint.get("fill-antialias")) return; 18954 const colorMode = painter.colorModeForRenderPass(); 18955 drawFillTiles(painter, tileManager, layer, coords, painter.getDepthModeForSublayer(layer.getPaintProperty("fill-outline-color") ? 2 : 0, DepthMode.ReadOnly), colorMode, true, renderContext); 18956} 18957function drawFillTiles(painter, tileManager, layer, coords, depthMode, colorMode, isOutline, renderContext) { 18958 const gl = painter.context.gl; 18959 const fillPropertyName = "fill-pattern"; 18960 const patternProperty = layer.paint.get(fillPropertyName); 18961 const image = patternProperty?.constantOr(1); 18962 const crossfade = layer.getCrossfadeParameters(); 18963 let drawMode, programName, uniformValues, indexBuffer, segments; 18964 const transform = painter.transform; 18965 const propertyFillTranslate = layer.paint.get("fill-translate"); 18966 const propertyFillTranslateAnchor = layer.paint.get("fill-translate-anchor"); 18967 if (!isOutline) { 18968 programName = image ? "fillPattern" : "fill"; 18969 drawMode = gl.TRIANGLES; 18970 } else { 18971 programName = image && !layer.getPaintProperty("fill-outline-color") ? "fillOutlinePattern" : "fillOutline"; 18972 drawMode = gl.LINES; 18973 } 18974 const constantPattern = patternProperty.constantOr(null); 18975 for (const coord of coords) { 18976 const tile = tileManager.getTile(coord); 18977 if (image && !tile.patternsLoaded()) continue; 18978 const bucket = tile.getBucket(layer); 18979 if (!bucket) continue; 18980 const isSdfPattern = bucket.sdfPatterns[layer.id] ?? false; 18981 const programConfiguration = bucket.programConfigurations.get(layer.id); 18982 const program = painter.useProgram(programName, programConfiguration); 18983 const terrainData = getTerrainDataForTile(renderContext, coord); 18984 if (image) { 18985 painter.context.activeTexture.set(gl.TEXTURE0); 18986 tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 18987 programConfiguration.updatePaintBuffers(crossfade); 18988 } 18989 updatePatternPositionsInProgram(programConfiguration, fillPropertyName, constantPattern, tile, layer); 18990 const projectionData = getProjectionDataForTile(renderContext, coord); 18991 const translateForUniforms = translatePosition(transform, tile, propertyFillTranslate, propertyFillTranslateAnchor); 18992 if (!isOutline) { 18993 indexBuffer = bucket.indexBuffer; 18994 segments = bucket.segments; 18995 uniformValues = image ? fillPatternUniformValues(painter, crossfade, tile, translateForUniforms, isSdfPattern) : fillUniformValues(translateForUniforms); 18996 } else { 18997 indexBuffer = bucket.indexBuffer2; 18998 segments = bucket.segments2; 18999 uniformValues = programName === "fillOutlinePattern" && image ? fillOutlinePatternUniformValues(painter, crossfade, tile, translateForUniforms, isSdfPattern) : fillOutlineUniformValues(translateForUniforms); 19000 } 19001 const stencil = painter.stencilModeForClipping(coord); 19002 program.draw(painter.context, drawMode, depthMode, stencil, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData, layer.id, bucket.layoutVertexBuffer, indexBuffer, segments, layer.paint, painter.transform.zoom, programConfiguration); 19003 } 19004} 19005//#endregion 19006//#region src/webgl/draw/draw_fill_extrusion.ts 19007function drawFillExtrusion(painter, tileManager, layer, coords, renderContext) { 19008 const opacity = layer.paint.get("fill-extrusion-opacity"); 19009 if (opacity === 0) return; 19010 if (renderContext.currentPass === "translucent") { 19011 const depthMode = new DepthMode(painter.context.gl.LEQUAL, DepthMode.ReadWrite, renderContext.depthRangeFor3D); 19012 if (opacity === 1 && !layer.paint.get("fill-extrusion-pattern").constantOr(1)) { 19013 const colorMode = painter.colorModeForRenderPass(); 19014 drawExtrusionTiles(painter, tileManager, layer, coords, depthMode, StencilMode.disabled, colorMode, renderContext); 19015 } else { 19016 drawExtrusionTiles(painter, tileManager, layer, coords, depthMode, StencilMode.disabled, ColorMode.disabled, renderContext); 19017 drawExtrusionTiles(painter, tileManager, layer, coords, depthMode, painter.stencilModeFor3D(), painter.colorModeForRenderPass(), renderContext); 19018 } 19019 } 19020} 19021function drawExtrusionTiles(painter, tileManager, layer, coords, depthMode, stencilMode, colorMode, renderContext) { 19022 const context = painter.context; 19023 const gl = context.gl; 19024 const fillPropertyName = "fill-extrusion-pattern"; 19025 const patternProperty = layer.paint.get(fillPropertyName); 19026 const image = patternProperty.constantOr(1); 19027 const crossfade = layer.getCrossfadeParameters(); 19028 const opacity = layer.paint.get("fill-extrusion-opacity"); 19029 const constantPattern = patternProperty.constantOr(null); 19030 const transform = painter.transform; 19031 for (const coord of coords) { 19032 const tile = tileManager.getTile(coord); 19033 const bucket = tile.getBucket(layer); 19034 if (!bucket) continue; 19035 const terrainData = getTerrainDataForTile(renderContext, coord); 19036 const programConfiguration = bucket.programConfigurations.get(layer.id); 19037 const program = painter.useProgram(image ? "fillExtrusionPattern" : "fillExtrusion", programConfiguration); 19038 if (image) { 19039 painter.context.activeTexture.set(gl.TEXTURE0); 19040 tile.imageAtlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 19041 programConfiguration.updatePaintBuffers(crossfade); 19042 } 19043 const projectionData = getProjectionDataForTile(renderContext, coord); 19044 updatePatternPositionsInProgram(programConfiguration, fillPropertyName, constantPattern, tile, layer); 19045 const translate = translatePosition(transform, tile, layer.paint.get("fill-extrusion-translate"), layer.paint.get("fill-extrusion-translate-anchor")); 19046 const shouldUseVerticalGradient = layer.paint.get("fill-extrusion-vertical-gradient"); 19047 const uniformValues = image ? fillExtrusionPatternUniformValues(painter, shouldUseVerticalGradient, opacity, translate, coord, crossfade, tile) : fillExtrusionUniformValues(painter, shouldUseVerticalGradient, opacity, translate); 19048 program.draw(context, context.gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData, layer.id, bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, layer.paint, painter.transform.zoom, programConfiguration, painter.style.map.terrain && bucket.centroidVertexBuffer); 19049 } 19050} 19051//#endregion 19052//#region src/webgl/draw/draw_hillshade.ts 19053function drawHillshade(painter, tileManager, layer, tileIDs, renderContext) { 19054 if (renderContext.currentPass !== "offscreen" && renderContext.currentPass !== "translucent") return; 19055 const context = painter.context; 19056 const useSubdivision = painter.style.projection.useSubdivision; 19057 const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); 19058 const colorMode = painter.colorModeForRenderPass(); 19059 if (renderContext.currentPass === "offscreen") { 19060 prepareHillshade(painter, tileManager, tileIDs, layer, depthMode, StencilMode.disabled, colorMode); 19061 context.viewport.set([ 19062 0, 19063 0, 19064 painter.width, 19065 painter.height 19066 ]); 19067 } else if (renderContext.currentPass === "translucent") { 19068 if (useSubdivision) { 19069 const [stencilBorderless, stencilBorders, coords] = painter.stencilConfigForOverlapTwoPass(tileIDs); 19070 renderHillshade(painter, tileManager, layer, coords, stencilBorderless, depthMode, colorMode, false, renderContext); 19071 renderHillshade(painter, tileManager, layer, coords, stencilBorders, depthMode, colorMode, true, renderContext); 19072 } else { 19073 const [stencil, coords] = painter.getStencilConfigForOverlapAndUpdateStencilID(tileIDs); 19074 renderHillshade(painter, tileManager, layer, coords, stencil, depthMode, colorMode, false, renderContext); 19075 } 19076 } 19077} 19078function renderHillshade(painter, tileManager, layer, coords, stencilModes, depthMode, colorMode, useBorder, renderContext) { 19079 const projection = painter.style.projection; 19080 const context = painter.context; 19081 const gl = context.gl; 19082 const defines = [`#define NUM_ILLUMINATION_SOURCES ${layer.paint.get("hillshade-highlight-color").values.length}`]; 19083 const program = painter.useProgram("hillshade", null, false, defines); 19084 const align = !painter.options.moving; 19085 for (const coord of coords) { 19086 const tile = tileManager.getTile(coord); 19087 const fbo = tile.fbo; 19088 if (!fbo) continue; 19089 const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, true, "raster"); 19090 const terrainData = getTerrainDataForTile(renderContext, coord); 19091 context.activeTexture.set(gl.TEXTURE0); 19092 gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); 19093 const projectionData = getProjectionDataForTile(renderContext, coord, { aligned: align }); 19094 program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.backCCW, hillshadeUniformValues(painter, tile, layer), terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19095 } 19096} 19097function prepareHillshade(painter, tileManager, tileIDs, layer, depthMode, stencilMode, colorMode) { 19098 const context = painter.context; 19099 const gl = context.gl; 19100 const textureFilter = layer.paint.get("resampling") === "nearest" ? gl.NEAREST : gl.LINEAR; 19101 for (const coord of tileIDs) { 19102 const tile = tileManager.getTile(coord); 19103 const dem = tile.dem; 19104 if (!dem?.data) continue; 19105 if (!tile.needsHillshadePrepare) continue; 19106 const hillshadeTextureSize = dem.dim + 2; 19107 const textureStride = dem.stride; 19108 const pixelData = dem.getPixels(); 19109 context.activeTexture.set(gl.TEXTURE1); 19110 context.pixelStoreUnpackPremultiplyAlpha.set(false); 19111 tile.demTexture ||= painter.getTileTexture(textureStride); 19112 if (tile.demTexture) { 19113 const demTexture = tile.demTexture; 19114 demTexture.update(pixelData, { premultiply: false }); 19115 demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); 19116 } else { 19117 tile.demTexture = new Texture(context, pixelData, gl.RGBA, { premultiply: false }); 19118 tile.demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); 19119 } 19120 context.activeTexture.set(gl.TEXTURE0); 19121 let fbo = tile.fbo; 19122 if (!fbo) { 19123 const renderTexture = new Texture(context, { 19124 width: hillshadeTextureSize, 19125 height: hillshadeTextureSize, 19126 data: null 19127 }, gl.RGBA); 19128 renderTexture.bind(textureFilter, gl.CLAMP_TO_EDGE); 19129 fbo = tile.fbo = context.createFramebuffer(hillshadeTextureSize, hillshadeTextureSize, true, false); 19130 fbo.colorAttachment.set(renderTexture.texture); 19131 } 19132 context.bindFramebuffer.set(fbo.framebuffer); 19133 context.viewport.set([ 19134 0, 19135 0, 19136 hillshadeTextureSize, 19137 hillshadeTextureSize 19138 ]); 19139 painter.useProgram("hillshadePrepare").draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, hillshadeUniformPrepareValues(tile.tileID, dem), null, null, layer.id, painter.rasterBoundsBuffer, painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); 19140 tile.needsHillshadePrepare = false; 19141 } 19142} 19143//#endregion 19144//#region src/webgl/draw/draw_color_relief.ts 19145function drawColorRelief(painter, tileManager, layer, tileIDs, renderContext) { 19146 if (renderContext.currentPass !== "translucent") return; 19147 if (!tileIDs.length) return; 19148 const useSubdivision = painter.style.projection.useSubdivision; 19149 const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); 19150 const colorMode = painter.colorModeForRenderPass(); 19151 if (useSubdivision) { 19152 const [stencilBorderless, stencilBorders, coords] = painter.stencilConfigForOverlapTwoPass(tileIDs); 19153 renderColorRelief(painter, tileManager, layer, coords, stencilBorderless, depthMode, colorMode, false, renderContext); 19154 renderColorRelief(painter, tileManager, layer, coords, stencilBorders, depthMode, colorMode, true, renderContext); 19155 } else { 19156 const [stencil, coords] = painter.getStencilConfigForOverlapAndUpdateStencilID(tileIDs); 19157 renderColorRelief(painter, tileManager, layer, coords, stencil, depthMode, colorMode, false, renderContext); 19158 } 19159} 19160let textureMaxSize = 0; 19161/** 19162* Draws the color-relief tiles of one pass. 19163* 19164* A loaded raster-DEM tile can carry no DEM at all (e.g. an empty 204 response); 19165* such tiles are skipped before the first-tile setup reads from them. 19166*/ 19167function renderColorRelief(painter, tileManager, layer, coords, stencilModes, depthMode, colorMode, useBorder, renderContext) { 19168 const projection = painter.style.projection; 19169 const context = painter.context; 19170 const gl = context.gl; 19171 const program = painter.useProgram("colorRelief"); 19172 const align = !painter.options.moving; 19173 const textureFilter = layer.paint.get("resampling") === "nearest" ? gl.NEAREST : gl.LINEAR; 19174 let firstTile = true; 19175 let colorRampSize = 0; 19176 for (const coord of coords) { 19177 const tile = tileManager.getTile(coord); 19178 const dem = tile.dem; 19179 if (!dem?.data) continue; 19180 if (firstTile) { 19181 textureMaxSize ||= gl.getParameter(gl.MAX_TEXTURE_SIZE); 19182 const maxLength = textureMaxSize; 19183 const { elevationTexture, colorTexture } = layer.getColorRampTextures(context, maxLength, dem.getUnpackVector()); 19184 context.activeTexture.set(gl.TEXTURE1); 19185 elevationTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); 19186 context.activeTexture.set(gl.TEXTURE4); 19187 colorTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 19188 firstTile = false; 19189 colorRampSize = elevationTexture.size[0]; 19190 } 19191 const textureStride = dem.stride; 19192 context.activeTexture.set(gl.TEXTURE0); 19193 if (!tile.demTexture || tile.needsColorReliefPrepare) { 19194 context.pixelStoreUnpackPremultiplyAlpha.set(false); 19195 tile.demTexture ||= painter.getTileTexture(textureStride) ?? new Texture(context, { 19196 width: textureStride, 19197 height: textureStride, 19198 data: null 19199 }, gl.RGBA); 19200 tile.demTexture.update(dem.getPixels(), { premultiply: false }); 19201 tile.needsColorReliefPrepare = false; 19202 } 19203 tile.demTexture.bind(textureFilter, gl.CLAMP_TO_EDGE); 19204 const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, true, "raster"); 19205 const terrainData = getTerrainDataForTile(renderContext, coord); 19206 const projectionData = getProjectionDataForTile(renderContext, coord, { aligned: align }); 19207 program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.backCCW, colorReliefUniformValues(layer, tile.dem, colorRampSize), terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19208 } 19209} 19210//#endregion 19211//#region src/webgl/draw/draw_raster.ts 19212const cornerCoords = [ 19213 new Point(0, 0), 19214 new Point(EXTENT, 0), 19215 new Point(EXTENT, EXTENT), 19216 new Point(0, EXTENT) 19217]; 19218function drawRaster(painter, tileManager, layer, tileIDs, renderContext) { 19219 if (renderContext.currentPass !== "translucent") return; 19220 if (layer.paint.get("raster-opacity") === 0) return; 19221 if (!tileIDs.length) return; 19222 const source = tileManager.getSource(); 19223 const useSubdivision = painter.style.projection.useSubdivision; 19224 if (source instanceof ImageSource) drawTiles(painter, tileManager, layer, tileIDs, null, false, false, source.tileCoords, source.imageWarp, source.flippedWindingOrder, renderContext, source.getMesh(painter.context, useSubdivision)); 19225 else if (useSubdivision) { 19226 const [stencilBorderless, stencilBorders, coords] = painter.stencilConfigForOverlapTwoPass(tileIDs); 19227 drawTiles(painter, tileManager, layer, coords, stencilBorderless, false, true, cornerCoords, bilinearImageWarp, false, renderContext); 19228 drawTiles(painter, tileManager, layer, coords, stencilBorders, true, true, cornerCoords, bilinearImageWarp, false, renderContext); 19229 } else { 19230 const [stencil, coords] = painter.getStencilConfigForOverlapAndUpdateStencilID(tileIDs); 19231 drawTiles(painter, tileManager, layer, coords, stencil, false, true, cornerCoords, bilinearImageWarp, false, renderContext); 19232 } 19233} 19234function drawTiles(painter, tileManager, layer, coords, stencilModes, useBorder, allowPoles, corners, imageWarp, flipCullfaceMode = false, renderContext, sourceMesh = null) { 19235 const minTileZ = coords[coords.length - 1].overscaledZ; 19236 const context = painter.context; 19237 const gl = context.gl; 19238 const program = painter.useProgram("raster"); 19239 const projection = painter.style.projection; 19240 const colorMode = painter.colorModeForRenderPass(); 19241 const align = !painter.options.moving; 19242 const rasterOpacity = layer.paint.get("raster-opacity"); 19243 const textureFilter = layer.paint.get("resampling") === "nearest" || layer.paint.get("raster-resampling") === "nearest" ? gl.NEAREST : gl.LINEAR; 19244 const fadeDuration = layer.paint.get("raster-fade-duration"); 19245 const isTerrain = !!painter.style.map.terrain; 19246 for (const coord of coords) { 19247 const depthMode = painter.getDepthModeForSublayer(coord.overscaledZ - minTileZ, rasterOpacity === 1 ? DepthMode.ReadWrite : DepthMode.ReadOnly, gl.LESS); 19248 const tile = tileManager.getTile(coord); 19249 context.activeTexture.set(gl.TEXTURE0); 19250 tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); 19251 context.activeTexture.set(gl.TEXTURE1); 19252 const { parentTile, parentScaleBy, parentTopLeft, fadeValues } = getFadeProperties(tile, tileManager, fadeDuration, isTerrain); 19253 tile.fadeOpacity = fadeValues.tileOpacity; 19254 if (parentTile) { 19255 parentTile.fadeOpacity = fadeValues.parentTileOpacity; 19256 parentTile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); 19257 } else tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); 19258 if (tile.texture.useMipmap && context.extTextureFilterAnisotropic && painter.transform.pitch > painter.options.anisotropicFilterPitch) gl.texParameterf(gl.TEXTURE_2D, context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, context.extTextureFilterAnisotropicMax); 19259 const terrainData = getTerrainDataForTile(renderContext, coord); 19260 const projectionData = getProjectionDataForTile(renderContext, coord, { aligned: align }); 19261 const uniformValues = rasterUniformValues(parentTopLeft, parentScaleBy, fadeValues.fadeMix, layer, corners, imageWarp); 19262 const mesh = sourceMesh ?? projection.getMeshFromTileID(context, coord.canonical, useBorder, allowPoles, "raster"); 19263 const stencilMode = stencilModes ? stencilModes[coord.overscaledZ] : StencilMode.disabled; 19264 program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, flipCullfaceMode ? CullFaceMode.frontCCW : CullFaceMode.backCCW, uniformValues, terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19265 } 19266} 19267/** 19268* Get fade properties for current tile - either cross-fading or self-fading properties. 19269*/ 19270function getFadeProperties(tile, tileManager, fadeDuration, isTerrain) { 19271 const defaults = { 19272 parentTile: null, 19273 parentScaleBy: 1, 19274 parentTopLeft: [0, 0], 19275 fadeValues: { 19276 tileOpacity: 1, 19277 parentTileOpacity: 1, 19278 fadeMix: { 19279 opacity: 1, 19280 mix: 0 19281 } 19282 } 19283 }; 19284 if (fadeDuration === 0 || isTerrain) return defaults; 19285 if (tile.fadingParentID) { 19286 const parentTile = tileManager.getLoadedTile(tile.fadingParentID); 19287 if (!parentTile) return defaults; 19288 const parentScaleBy = Math.pow(2, parentTile.tileID.overscaledZ - tile.tileID.overscaledZ); 19289 return { 19290 parentTile, 19291 parentScaleBy, 19292 parentTopLeft: [tile.tileID.canonical.x * parentScaleBy % 1, tile.tileID.canonical.y * parentScaleBy % 1], 19293 fadeValues: getCrossFadeValues(tile, parentTile, fadeDuration) 19294 }; 19295 } 19296 if (tile.selfFading) return { 19297 parentTile: null, 19298 parentScaleBy: 1, 19299 parentTopLeft: [0, 0], 19300 fadeValues: getSelfFadeValues(tile, fadeDuration) 19301 }; 19302 return defaults; 19303} 19304/** 19305* Cross-fade values for a base tile with a parent tile (for zooming in/out) 19306*/ 19307function getCrossFadeValues(tile, parentTile, fadeDuration) { 19308 const currentTime = now(); 19309 const timeSinceTile = (currentTime - tile.timeAdded) / fadeDuration; 19310 const timeSinceParent = (currentTime - parentTile.timeAdded) / fadeDuration; 19311 const doFadeIn = tile.fadingDirection === 1; 19312 const opacity1 = clamp(timeSinceTile, 0, 1); 19313 const opacity2 = clamp(1 - timeSinceParent, 0, 1); 19314 const tileOpacity = doFadeIn ? opacity1 : opacity2; 19315 return { 19316 tileOpacity, 19317 parentTileOpacity: doFadeIn ? opacity2 : opacity1, 19318 fadeMix: { 19319 opacity: 1, 19320 mix: 1 - tileOpacity 19321 } 19322 }; 19323} 19324/** 19325* Simple fade-in values for tile without a parent (i.e. edge tiles) 19326*/ 19327function getSelfFadeValues(tile, fadeDuration) { 19328 const timeSinceTile = (now() - tile.timeAdded) / fadeDuration; 19329 const tileOpacity = clamp(timeSinceTile, 0, 1); 19330 return { 19331 tileOpacity, 19332 fadeMix: { 19333 opacity: tileOpacity, 19334 mix: 0 19335 } 19336 }; 19337} 19338//#endregion 19339//#region src/webgl/draw/draw_background.ts 19340function drawBackground(painter, tileManager, layer, coords, renderContext) { 19341 const color = layer.paint.get("background-color"); 19342 const opacity = layer.paint.get("background-opacity"); 19343 if (opacity === 0) return; 19344 const context = painter.context; 19345 const gl = context.gl; 19346 const projection = painter.style.projection; 19347 const transform = painter.transform; 19348 const tileSize = transform.tileSize; 19349 const image = layer.paint.get("background-pattern"); 19350 if (painter.isPatternMissing(image)) return; 19351 const pass = !image && color.a === 1 && opacity === 1 && painter.opaquePassEnabledForLayer() ? "opaque" : "translucent"; 19352 if (renderContext.currentPass !== pass) return; 19353 const stencilMode = StencilMode.disabled; 19354 const depthMode = painter.getDepthModeForSublayer(0, pass === "opaque" ? DepthMode.ReadWrite : DepthMode.ReadOnly); 19355 const colorMode = painter.colorModeForRenderPass(); 19356 const program = painter.useProgram(image ? "backgroundPattern" : "background"); 19357 const tileIDs = coords ? coords : coveringTiles(transform, { 19358 tileSize, 19359 terrain: painter.style.map.terrain 19360 }); 19361 if (image) { 19362 context.activeTexture.set(gl.TEXTURE0); 19363 painter.patternAtlas.bind(painter.context); 19364 } 19365 const crossfade = layer.getCrossfadeParameters(); 19366 for (const tileID of tileIDs) { 19367 const projectionData = getProjectionDataForTile(renderContext, tileID); 19368 const uniformValues = image ? backgroundPatternUniformValues(opacity, painter, image, { 19369 tileID, 19370 tileSize 19371 }, crossfade) : backgroundUniformValues(opacity, color); 19372 const terrainData = getTerrainDataForTile(renderContext, tileID); 19373 const mesh = projection.getMeshFromTileID(context, tileID.canonical, false, true, "raster"); 19374 program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19375 } 19376} 19377//#endregion 19378//#region src/webgl/draw/draw_debug.ts 19379const topColor = new Color(1, 0, 0, 1); 19380const btmColor = new Color(0, 1, 0, 1); 19381const leftColor = new Color(0, 0, 1, 1); 19382const rightColor = new Color(1, 0, 1, 1); 19383const centerColor = new Color(0, 1, 1, 1); 19384function drawDebugPadding(painter) { 19385 const padding = painter.transform.padding; 19386 const lineWidth = 3; 19387 drawHorizontalLine(painter, painter.transform.height - (padding.top || 0), lineWidth, topColor); 19388 drawHorizontalLine(painter, padding.bottom || 0, lineWidth, btmColor); 19389 drawVerticalLine(painter, padding.left || 0, lineWidth, leftColor); 19390 drawVerticalLine(painter, painter.transform.width - (padding.right || 0), lineWidth, rightColor); 19391 const center = painter.transform.centerPoint; 19392 drawCrosshair(painter, center.x, painter.transform.height - center.y, centerColor); 19393} 19394function drawCrosshair(painter, x, y, color) { 19395 const size = 20; 19396 const lineWidth = 2; 19397 drawDebugSSRect(painter, x - lineWidth / 2, y - size / 2, lineWidth, size, color); 19398 drawDebugSSRect(painter, x - size / 2, y - lineWidth / 2, size, lineWidth, color); 19399} 19400function drawHorizontalLine(painter, y, lineWidth, color) { 19401 drawDebugSSRect(painter, 0, y + lineWidth / 2, painter.transform.width, lineWidth, color); 19402} 19403function drawVerticalLine(painter, x, lineWidth, color) { 19404 drawDebugSSRect(painter, x - lineWidth / 2, 0, lineWidth, painter.transform.height, color); 19405} 19406function drawDebugSSRect(painter, x, y, width, height, color) { 19407 const context = painter.context; 19408 const gl = context.gl; 19409 gl.enable(gl.SCISSOR_TEST); 19410 gl.scissor(x * painter.pixelRatio, y * painter.pixelRatio, width * painter.pixelRatio, height * painter.pixelRatio); 19411 context.clear({ color }); 19412 gl.disable(gl.SCISSOR_TEST); 19413} 19414function drawDebug(painter, tileManager, coords, renderContext) { 19415 for (const coord of coords) drawDebugTile(painter, tileManager, coord, renderContext); 19416} 19417function drawDebugTile(painter, tileManager, coord, renderContext) { 19418 const context = painter.context; 19419 const gl = context.gl; 19420 const program = painter.useProgram("debug"); 19421 const depthMode = DepthMode.disabled; 19422 const stencilMode = StencilMode.disabled; 19423 const colorMode = painter.colorModeForRenderPass(); 19424 const id = "$debug"; 19425 const terrainData = getTerrainDataForTile(renderContext, coord); 19426 context.activeTexture.set(gl.TEXTURE0); 19427 const tileByteLength = tileManager.getTileByID(coord.key).latestRawTileData?.byteLength || 0; 19428 const tileSizeKb = Math.floor(tileByteLength / 1024); 19429 const tileSize = tileManager.getTile(coord).tileSize; 19430 const scaleRatio = 512 / Math.min(tileSize, 512) * (coord.overscaledZ / painter.transform.zoom) * .5; 19431 let tileIdText = coord.canonical.toString(); 19432 if (coord.overscaledZ !== coord.canonical.z) tileIdText += ` => ${coord.overscaledZ}`; 19433 drawTextToOverlay(painter, `${tileIdText} ${tileSizeKb}kB`); 19434 const projectionData = getProjectionDataForTile(renderContext, coord); 19435 program.draw(context, gl.TRIANGLES, depthMode, stencilMode, ColorMode.alphaBlended, CullFaceMode.disabled, debugUniformValues(Color.transparent, scaleRatio), null, projectionData, id, painter.debugBuffer, painter.quadTriangleIndexBuffer, painter.debugSegments); 19436 program.draw(context, gl.LINE_STRIP, depthMode, stencilMode, colorMode, CullFaceMode.disabled, debugUniformValues(Color.red), terrainData, projectionData, id, painter.debugBuffer, painter.tileBorderIndexBuffer, painter.debugSegments); 19437} 19438function drawTextToOverlay(painter, text) { 19439 painter.initDebugOverlayCanvas(); 19440 const canvas = painter.debugOverlayCanvas; 19441 const gl = painter.context.gl; 19442 const ctx2d = painter.debugOverlayCanvas.getContext("2d"); 19443 ctx2d.clearRect(0, 0, canvas.width, canvas.height); 19444 ctx2d.shadowColor = "white"; 19445 ctx2d.shadowBlur = 2; 19446 ctx2d.lineWidth = 1.5; 19447 ctx2d.strokeStyle = "white"; 19448 ctx2d.textBaseline = "top"; 19449 ctx2d.font = `bold 36px Open Sans, sans-serif`; 19450 ctx2d.fillText(text, 5, 5); 19451 ctx2d.strokeText(text, 5, 5); 19452 painter.debugOverlayTexture.update(canvas); 19453 painter.debugOverlayTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); 19454} 19455function selectDebugSource(style, zoom) { 19456 let selectedSource = null; 19457 const sources = Object.values(style._layers).flatMap((layer) => { 19458 if (layer.source && !layer.isHidden(zoom)) return [style.tileManagers[layer.source]]; 19459 else return []; 19460 }); 19461 const vectorSources = sources.filter((source) => source.getSource().type === "vector"); 19462 const otherSources = sources.filter((source) => source.getSource().type !== "vector"); 19463 const considerSource = (source) => { 19464 if (!selectedSource || selectedSource.getSource().maxzoom < source.getSource().maxzoom) selectedSource = source; 19465 }; 19466 for (const source of vectorSources) considerSource(source); 19467 if (!selectedSource) for (const source of otherSources) considerSource(source); 19468 return selectedSource; 19469} 19470//#endregion 19471//#region src/webgl/draw/draw_custom.ts 19472function drawCustom(painter, tileManager, layer, renderContext) { 19473 const { isRenderingGlobe } = renderContext; 19474 const context = painter.context; 19475 const implementation = layer.implementation; 19476 const projection = painter.style.projection; 19477 const transform = painter.transform; 19478 const projectionData = transform.getProjectionDataForCustomLayer(isRenderingGlobe); 19479 const customLayerArgs = { 19480 farZ: transform.farZ, 19481 nearZ: transform.nearZ, 19482 fov: transform.fov * Math.PI / 180, 19483 modelViewProjectionMatrix: transform.modelViewProjectionMatrix, 19484 projectionMatrix: transform.projectionMatrix, 19485 shaderData: { 19486 variantName: projection.shaderVariantName, 19487 vertexShaderPrelude: `const float PI = 3.141592653589793;\nuniform mat4 u_projection_matrix;\n${projection.shaderPreludeCode.vertexSource}`, 19488 define: projection.shaderDefine 19489 }, 19490 defaultProjectionData: projectionData, 19491 getProjectionData: (params) => { 19492 return transform.getProjectionData({ 19493 overscaledTileID: new OverscaledTileID(params.tileID.canonical.z, params.tileID.wrap ?? 0, params.tileID.canonical.z, params.tileID.canonical.x, params.tileID.canonical.y), 19494 aligned: params.aligned, 19495 applyGlobeMatrix: params.applyGlobeMatrix, 19496 applyTerrainMatrix: params.applyTerrainMatrix 19497 }); 19498 } 19499 }; 19500 const renderingMode = implementation.renderingMode ? implementation.renderingMode : "2d"; 19501 if (renderContext.currentPass === "offscreen") { 19502 const prerender = implementation.prerender; 19503 if (prerender) { 19504 painter.setCustomLayerDefaults(); 19505 context.setColorMode(painter.colorModeForRenderPass()); 19506 prerender.call(implementation, context.gl, customLayerArgs); 19507 context.setDirty(); 19508 painter.setBaseState(); 19509 } 19510 } else if (renderContext.currentPass === "translucent") { 19511 painter.setCustomLayerDefaults(); 19512 context.setColorMode(painter.colorModeForRenderPass()); 19513 context.setStencilMode(StencilMode.disabled); 19514 const depthMode = renderingMode === "3d" ? painter.getDepthModeFor3D() : painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); 19515 context.setDepthMode(depthMode); 19516 implementation.render(context.gl, customLayerArgs); 19517 context.setDirty(); 19518 painter.setBaseState(); 19519 context.bindFramebuffer.set(null); 19520 } 19521} 19522//#endregion 19523//#region src/webgl/draw/draw_terrain.ts 19524/** 19525* Redraw the Depth Framebuffer 19526* @param painter - the painter 19527* @param terrain - the terrain 19528*/ 19529function drawDepth(painter, terrain) { 19530 const context = painter.context; 19531 const gl = context.gl; 19532 const tr = painter.transform; 19533 const colorMode = ColorMode.unblended; 19534 const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, [0, 1]); 19535 const tiles = terrain.tileManager.getRenderableTiles(); 19536 const program = painter.useProgram("terrainDepth"); 19537 context.bindFramebuffer.set(terrain.getFramebuffer().framebuffer); 19538 context.viewport.set([ 19539 0, 19540 0, 19541 painter.width / devicePixelRatio, 19542 painter.height / devicePixelRatio 19543 ]); 19544 context.clear({ 19545 color: Color.white, 19546 depth: 1 19547 }); 19548 for (const tile of tiles) { 19549 const mesh = terrain.getTerrainMesh(tile.tileID); 19550 const terrainData = terrain.getTerrainData(tile.tileID); 19551 const projectionData = tr.getProjectionData({ 19552 overscaledTileID: tile.tileID, 19553 applyTerrainMatrix: false, 19554 applyGlobeMatrix: true 19555 }); 19556 const uniformValues = terrainDepthUniformValues(terrain.getSkirtLength(tr.zoom)); 19557 program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData, "terrain", mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19558 } 19559 context.bindFramebuffer.set(null); 19560 context.viewport.set([ 19561 0, 19562 0, 19563 painter.width, 19564 painter.height 19565 ]); 19566} 19567function drawTerrain(painter, terrain, tiles, renderContext) { 19568 const { isRenderingGlobe } = renderContext; 19569 const context = painter.context; 19570 const gl = context.gl; 19571 const tr = painter.transform; 19572 const colorMode = painter.colorModeForRenderPass(); 19573 const depthMode = painter.getDepthModeFor3D(); 19574 const program = painter.useProgram("terrain"); 19575 context.bindFramebuffer.set(null); 19576 context.viewport.set([ 19577 0, 19578 0, 19579 painter.width, 19580 painter.height 19581 ]); 19582 for (const tile of tiles) { 19583 const mesh = terrain.getTerrainMesh(tile.tileID); 19584 const texture = painter.renderToTexture.getTexture(tile); 19585 const terrainData = terrain.getTerrainData(tile.tileID); 19586 context.activeTexture.set(gl.TEXTURE0); 19587 texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_LINEAR); 19588 const eleDelta = terrain.getSkirtLength(tr.zoom); 19589 const fogMatrix = tr.calculateFogMatrix(tile.tileID.toUnwrapped()); 19590 const uniformValues = terrainUniformValues(eleDelta, fogMatrix, painter.style.sky, tr.pitch, isRenderingGlobe); 19591 const projectionData = getProjectionDataForTile(renderContext, tile.tileID, { applyTerrainMatrix: false }); 19592 program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData, "terrain", mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19593 } 19594} 19595//#endregion 19596//#region src/webgl/draw/draw_sky.ts 19597function getMesh(context, sky) { 19598 if (!sky.mesh) { 19599 const vertexArray = new PosArray(); 19600 vertexArray.emplaceBack(-1, -1); 19601 vertexArray.emplaceBack(1, -1); 19602 vertexArray.emplaceBack(1, 1); 19603 vertexArray.emplaceBack(-1, 1); 19604 const indexArray = new TriangleIndexArray(); 19605 indexArray.emplaceBack(0, 1, 2); 19606 indexArray.emplaceBack(0, 2, 3); 19607 sky.mesh = new Mesh(context.createVertexBuffer(vertexArray, posAttributes.members), context.createIndexBuffer(indexArray), SegmentVector.simpleSegment(0, 0, vertexArray.length, indexArray.length)); 19608 } 19609 return sky.mesh; 19610} 19611function drawSky(painter, sky) { 19612 const context = painter.context; 19613 const gl = context.gl; 19614 const skyUniforms = skyUniformValues(sky, painter.transform, painter.pixelRatio); 19615 const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, [0, 1]); 19616 const stencilMode = StencilMode.disabled; 19617 const colorMode = painter.colorModeForRenderPass(); 19618 const program = painter.useProgram("sky"); 19619 const mesh = getMesh(context, sky); 19620 program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, skyUniforms, null, void 0, "sky", mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19621} 19622function getSunPos(light, transform) { 19623 const lightPos = light.getCartesianPosition(); 19624 negate(lightPos, lightPos); 19625 const lightMat = identity(/* @__PURE__ */ new Float64Array(16)); 19626 if (light.properties.get("anchor") === "map") { 19627 rotateZ(lightMat, lightMat, transform.rollInRadians); 19628 rotateX(lightMat, lightMat, -transform.pitchInRadians); 19629 rotateZ(lightMat, lightMat, transform.bearingInRadians); 19630 rotateX(lightMat, lightMat, transform.center.lat * Math.PI / 180); 19631 rotateY$1(lightMat, lightMat, -transform.center.lng * Math.PI / 180); 19632 } 19633 transformMat4$1(lightPos, lightPos, lightMat); 19634 return lightPos; 19635} 19636function drawAtmosphere(painter, sky, light) { 19637 const context = painter.context; 19638 const gl = context.gl; 19639 const program = painter.useProgram("atmosphere"); 19640 const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadOnly, [0, 1]); 19641 const transform = painter.transform; 19642 const sunPos = getSunPos(light, painter.transform); 19643 const projectionData = transform.getProjectionData({ 19644 overscaledTileID: null, 19645 applyGlobeMatrix: true, 19646 applyTerrainMatrix: true 19647 }); 19648 const globeRadius = getGlobeRadiusPixels(transform.worldSize, transform.center.lat); 19649 const globePosition = getGlobeCenterInViewSpace(transform); 19650 const altitudeBlend = getAtmosphereAltitudeBlend(length(globePosition) - globeRadius, globeRadius); 19651 const atmosphereBlend = sky.properties.get("atmosphere-blend") * projectionData.projectionTransition * altitudeBlend; 19652 if (atmosphereBlend === 0) return; 19653 const uniformValues = atmosphereUniformValues(sunPos, atmosphereBlend, globePosition, globeRadius, transform.inverseProjectionMatrix); 19654 const mesh = getMesh(context, sky); 19655 program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, ColorMode.alphaBlended, CullFaceMode.disabled, uniformValues, null, null, "atmosphere", mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19656} 19657//#endregion 19658//#region src/webgl/draw/index.ts 19659const webglDrawFunctions = {
vendor: 6,566 bytes, lines 19660-19828
19660 symbol: drawSymbols, 19661 circle: drawCircles, 19662 heatmap: drawHeatmap, 19663 line: drawLine, 19664 fill: drawFill, 19665 fillExtrusion: drawFillExtrusion, 19666 hillshade: drawHillshade, 19667 colorRelief: drawColorRelief, 19668 raster: drawRaster, 19669 background: drawBackground, 19670 sky: drawSky, 19671 atmosphere: drawAtmosphere, 19672 custom: drawCustom, 19673 debug: drawDebug, 19674 debugPadding: drawDebugPadding, 19675 terrainDepth: drawDepth 19676}; 19677//#endregion 19678//#region src/render/painter.ts 19679/** 19680* @internal 19681* Initialize a new painter object. 19682*/ 19683var Painter = class Painter { 19684 constructor(gl, transform) { 19685 this.drawFunctions = webglDrawFunctions; 19686 this.context = new Context(gl); 19687 this.transform = transform; 19688 this.layerOpacityFbo = null; 19689 this._tileTextures = {}; 19690 this._rttObjectRecyclePool = []; 19691 this._rttSharedFbo = null; 19692 this.terrainFacilitator = { 19693 depthDirty: true, 19694 matrix: identity(/* @__PURE__ */ new Float64Array(16)), 19695 renderTime: 0 19696 }; 19697 this.setup(); 19698 this.numSublayers = TileManager.maxOverzooming + TileManager.maxUnderzooming + 1; 19699 this.depthEpsilon = 1 / Math.pow(2, 16); 19700 this.crossTileSymbolIndex = new CrossTileSymbolIndex(); 19701 } 19702 resize(width, height, pixelRatio) { 19703 this.width = Math.floor(width * pixelRatio); 19704 this.height = Math.floor(height * pixelRatio); 19705 this.pixelRatio = pixelRatio; 19706 this.context.viewport.set([ 19707 0, 19708 0, 19709 this.width, 19710 this.height 19711 ]); 19712 if (this.style) for (const layerId of this.style._order) this.style._layers[layerId].resize(); 19713 } 19714 setup() { 19715 const context = this.context; 19716 const tileExtentArray = new PosArray(); 19717 tileExtentArray.emplaceBack(0, 0); 19718 tileExtentArray.emplaceBack(EXTENT, 0); 19719 tileExtentArray.emplaceBack(0, EXTENT); 19720 tileExtentArray.emplaceBack(EXTENT, EXTENT); 19721 this.tileExtentBuffer = context.createVertexBuffer(tileExtentArray, posAttributes.members); 19722 this.tileExtentSegments = SegmentVector.simpleSegment(0, 0, 4, 2); 19723 const debugArray = new PosArray(); 19724 debugArray.emplaceBack(0, 0); 19725 debugArray.emplaceBack(EXTENT, 0); 19726 debugArray.emplaceBack(0, EXTENT); 19727 debugArray.emplaceBack(EXTENT, EXTENT); 19728 this.debugBuffer = context.createVertexBuffer(debugArray, posAttributes.members); 19729 this.debugSegments = SegmentVector.simpleSegment(0, 0, 4, 5); 19730 const rasterBoundsArray = new RasterBoundsArray(); 19731 rasterBoundsArray.emplaceBack(0, 0, 0, 0); 19732 rasterBoundsArray.emplaceBack(EXTENT, 0, EXTENT, 0); 19733 rasterBoundsArray.emplaceBack(0, EXTENT, 0, EXTENT); 19734 rasterBoundsArray.emplaceBack(EXTENT, EXTENT, EXTENT, EXTENT); 19735 this.rasterBoundsBuffer = context.createVertexBuffer(rasterBoundsArray, rasterBoundsAttributes.members); 19736 this.rasterBoundsSegments = SegmentVector.simpleSegment(0, 0, 4, 2); 19737 const rasterBoundsArrayPosOnly = new PosArray(); 19738 rasterBoundsArrayPosOnly.emplaceBack(0, 0); 19739 rasterBoundsArrayPosOnly.emplaceBack(EXTENT, 0); 19740 rasterBoundsArrayPosOnly.emplaceBack(0, EXTENT); 19741 rasterBoundsArrayPosOnly.emplaceBack(EXTENT, EXTENT); 19742 this.rasterBoundsBufferPosOnly = context.createVertexBuffer(rasterBoundsArrayPosOnly, posAttributes.members); 19743 this.rasterBoundsSegmentsPosOnly = SegmentVector.simpleSegment(0, 0, 4, 5); 19744 const viewportArray = new PosArray(); 19745 viewportArray.emplaceBack(0, 0); 19746 viewportArray.emplaceBack(1, 0); 19747 viewportArray.emplaceBack(0, 1); 19748 viewportArray.emplaceBack(1, 1); 19749 this.viewportBuffer = context.createVertexBuffer(viewportArray, posAttributes.members); 19750 this.viewportSegments = SegmentVector.simpleSegment(0, 0, 4, 2); 19751 const tileLineStripIndices = new LineStripIndexArray(); 19752 tileLineStripIndices.emplaceBack(0); 19753 tileLineStripIndices.emplaceBack(1); 19754 tileLineStripIndices.emplaceBack(3); 19755 tileLineStripIndices.emplaceBack(2); 19756 tileLineStripIndices.emplaceBack(0); 19757 this.tileBorderIndexBuffer = context.createIndexBuffer(tileLineStripIndices); 19758 const quadTriangleIndices = new TriangleIndexArray(); 19759 quadTriangleIndices.emplaceBack(1, 0, 2); 19760 quadTriangleIndices.emplaceBack(1, 2, 3); 19761 this.quadTriangleIndexBuffer = context.createIndexBuffer(quadTriangleIndices); 19762 const gl = this.context.gl; 19763 this.stencilClearMode = new StencilMode({ 19764 func: gl.ALWAYS, 19765 mask: 0 19766 }, 0, 255, gl.ZERO, gl.ZERO, gl.ZERO); 19767 this.tileExtentMesh = new Mesh(this.tileExtentBuffer, this.quadTriangleIndexBuffer, this.tileExtentSegments); 19768 } 19769 clearStencil() { 19770 const context = this.context; 19771 const gl = context.gl; 19772 this.nextStencilID = 1; 19773 this.currentStencilSource = void 0; 19774 const matrix = create$1(); 19775 ortho(matrix, 0, this.width, this.height, 0, 0, 1); 19776 scale(matrix, matrix, [ 19777 gl.drawingBufferWidth, 19778 gl.drawingBufferHeight, 19779 0 19780 ]); 19781 const projectionData = { 19782 mainMatrix: matrix, 19783 tileMercatorCoords: [ 19784 0, 19785 0, 19786 1, 19787 1 19788 ], 19789 clippingPlane: [ 19790 0, 19791 0, 19792 0, 19793 0 19794 ], 19795 projectionTransition: 0, 19796 fallbackMatrix: matrix, 19797 clipAntimeridian: false 19798 }; 19799 this.useProgram("clippingMask", null, true).draw(context, gl.TRIANGLES, DepthMode.disabled, this.stencilClearMode, ColorMode.disabled, CullFaceMode.disabled, null, null, projectionData, "$clipping", this.viewportBuffer, this.quadTriangleIndexBuffer, this.viewportSegments); 19800 } 19801 renderTileClippingMasks(layer, tileIDs) { 19802 if (this.currentStencilSource === layer.source || !layer.isTileClipped() || !tileIDs?.length) return; 19803 this.currentStencilSource = layer.source; 19804 if (this.nextStencilID + tileIDs.length > 256) this.clearStencil(); 19805 const context = this.context; 19806 context.setColorMode(ColorMode.disabled); 19807 context.setDepthMode(DepthMode.disabled); 19808 const stencilRefs = {}; 19809 for (const tileID of tileIDs) stencilRefs[tileID.key] = this.nextStencilID++; 19810 if (this.style.projection.useSubdivision) this._renderTileMasks(stencilRefs, tileIDs, true); 19811 this._renderTileMasks(stencilRefs, tileIDs, false); 19812 this._tileClippingMaskIDs = stencilRefs; 19813 } 19814 _renderTileMasks(tileStencilRefs, tileIDs, useBorders) { 19815 const context = this.context; 19816 const gl = context.gl; 19817 const projection = this.style.projection; 19818 const renderContext = this.renderContext; 19819 const program = this.useProgram("clippingMask"); 19820 for (const tileID of tileIDs) { 19821 const stencilRef = tileStencilRefs[tileID.key]; 19822 const terrainData = getTerrainDataForTile(renderContext, tileID); 19823 const mesh = projection.getMeshFromTileID(this.context, tileID.canonical, useBorders, true, "stencil"); 19824 const projectionData = getProjectionDataForTile(renderContext, tileID); 19825 program.draw(context, gl.TRIANGLES, DepthMode.disabled, new StencilMode({ 19826 func: gl.ALWAYS, 19827 mask: 0 19828 }, stencilRef, 255, gl.KEEP, gl.KEEP, gl.REPLACE), ColorMode.disabled, renderContext.
vendor: 1,869 bytes, lines 19828-19870
19828isRenderingToTexture ? CullFaceMode.disabled : CullFaceMode.backCCW, null, terrainData, projectionData, "$clipping", mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19829 } 19830 } 19831 /** 19832 * Fills the depth buffer with the geometry of all supplied tiles. 19833 * Does not change the color buffer or the stencil buffer. 19834 */ 19835 _renderTilesDepthBuffer() { 19836 const context = this.context; 19837 const gl = context.gl; 19838 const projection = this.style.projection; 19839 const transform = this.transform; 19840 const program = this.useProgram("depth"); 19841 const depthMode = this.getDepthModeFor3D(); 19842 const tileIDs = coveringTiles(transform, { tileSize: transform.tileSize }); 19843 for (const tileID of tileIDs) { 19844 const terrainData = getTerrainDataForTile(this.renderContext, tileID); 19845 const mesh = projection.getMeshFromTileID(this.context, tileID.canonical, true, true, "raster"); 19846 const projectionData = getProjectionDataForTile(this.renderContext, tileID); 19847 program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, ColorMode.disabled, CullFaceMode.backCCW, null, terrainData, projectionData, "$clipping", mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); 19848 } 19849 } 19850 stencilModeFor3D() { 19851 this.currentStencilSource = void 0; 19852 if (this.nextStencilID + 1 > 256) this.clearStencil(); 19853 const id = this.nextStencilID++; 19854 const gl = this.context.gl; 19855 return new StencilMode({ 19856 func: gl.NOTEQUAL, 19857 mask: 255 19858 }, id, 255, gl.KEEP, gl.KEEP, gl.REPLACE); 19859 } 19860 stencilModeForClipping(tileID) { 19861 const gl = this.context.gl; 19862 return new StencilMode({ 19863 func: gl.EQUAL, 19864 mask: 255 19865 }, this._tileClippingMaskIDs[tileID.key], 0, gl.KEEP, gl.KEEP, gl.REPLACE); 19866 } 19867 getStencilConfigForOverlapAndUpdateStencilID(tileIDs) { 19868 const gl = this.context.gl; 19869 const coords = tileIDs.sort((a, b) => b.overscaledZ - a.overscaledZ); 19870 const minTileZ = coords[coords.length - 1].overscaledZ;
vendor: 5,660 bytes, lines 19871-20020
19871 const stencilValues = coords[0].overscaledZ - minTileZ + 1; 19872 if (stencilValues > 1) { 19873 this.currentStencilSource = void 0; 19874 if (this.nextStencilID + stencilValues > 256) this.clearStencil(); 19875 const zToStencilMode = {}; 19876 for (let i = 0; i < stencilValues; i++) zToStencilMode[i + minTileZ] = new StencilMode({ 19877 func: gl.GEQUAL, 19878 mask: 255 19879 }, i + this.nextStencilID, 255, gl.KEEP, gl.KEEP, gl.REPLACE); 19880 this.nextStencilID += stencilValues; 19881 return [zToStencilMode, coords]; 19882 } 19883 return [{ [minTileZ]: StencilMode.disabled }, coords]; 19884 } 19885 stencilConfigForOverlapTwoPass(tileIDs) { 19886 const gl = this.context.gl; 19887 const coords = tileIDs.sort((a, b) => b.overscaledZ - a.overscaledZ); 19888 const minTileZ = coords[coords.length - 1].overscaledZ; 19889 const stencilValues = coords[0].overscaledZ - minTileZ + 1; 19890 this.clearStencil(); 19891 if (stencilValues > 1) { 19892 const zToStencilModeHigh = {}; 19893 const zToStencilModeLow = {}; 19894 for (let i = 0; i < stencilValues; i++) { 19895 zToStencilModeHigh[i + minTileZ] = new StencilMode({ 19896 func: gl.GREATER, 19897 mask: 255 19898 }, stencilValues + 1 + i, 255, gl.KEEP, gl.KEEP, gl.REPLACE); 19899 zToStencilModeLow[i + minTileZ] = new StencilMode({ 19900 func: gl.GREATER, 19901 mask: 255 19902 }, 1 + i, 255, gl.KEEP, gl.KEEP, gl.REPLACE); 19903 } 19904 this.nextStencilID = stencilValues * 2 + 1; 19905 return [ 19906 zToStencilModeHigh, 19907 zToStencilModeLow, 19908 coords 19909 ]; 19910 } else { 19911 this.nextStencilID = 3; 19912 return [ 19913 { [minTileZ]: new StencilMode({ 19914 func: gl.GREATER, 19915 mask: 255 19916 }, 2, 255, gl.KEEP, gl.KEEP, gl.REPLACE) }, 19917 { [minTileZ]: new StencilMode({ 19918 func: gl.GREATER, 19919 mask: 255 19920 }, 1, 255, gl.KEEP, gl.KEEP, gl.REPLACE) }, 19921 coords 19922 ]; 19923 } 19924 } 19925 colorModeForRenderPass() { 19926 const gl = this.context.gl; 19927 if (this._showOverdrawInspector) { 19928 const a = 1 / 8; 19929 return new ColorMode([gl.CONSTANT_COLOR, gl.ONE], new Color(a, a, a, 0), [ 19930 true, 19931 true, 19932 true, 19933 true 19934 ]); 19935 } else if (this.renderContext.currentPass === "opaque") return ColorMode.unblended; 19936 else return ColorMode.alphaBlended; 19937 } 19938 getDepthModeForSublayer(n, mask, func) { 19939 if (!this.opaquePassEnabledForLayer()) return DepthMode.disabled; 19940 const depth = 1 - ((1 + this.renderContext.currentLayer) * this.numSublayers + n) * this.depthEpsilon; 19941 return new DepthMode(func || this.context.gl.LEQUAL, mask, [depth, depth]); 19942 } 19943 getDepthModeFor3D() { 19944 return new DepthMode(this.context.gl.LEQUAL, DepthMode.ReadWrite, this.renderContext.depthRangeFor3D); 19945 } 19946 opaquePassEnabledForLayer() { 19947 return this.renderContext.currentLayer < this.renderContext.opaquePassCutoff; 19948 } 19949 render(style, options) { 19950 this.style = style; 19951 this.options = options; 19952 const renderContext = this.renderContext = createRenderContext(this.transform, style.projection, style.map.terrain ?? null); 19953 this.lineAtlas = style.lineAtlas; 19954 this.imageManager = style.imageManager; 19955 this.patternAtlas = style.patternAtlas; 19956 this.glyphManager = style.glyphManager; 19957 this.symbolFadeChange = style.placement.symbolFadeChange(now()); 19958 updateFrameUniformBuffer(this.context.frameUniformBuffer, this); 19959 this.imageManager.beginFrame(); 19960 const layerIds = this.style._order; 19961 const tileManagers = this.style.tileManagers; 19962 const coordsAscending = {}; 19963 const coordsDescending = {}; 19964 const coordsDescendingSymbol = {}; 19965 for (const id in tileManagers) { 19966 const tileManager = tileManagers[id]; 19967 if (tileManager.used) tileManager.prepare(this.context); 19968 coordsAscending[id] = tileManager.getVisibleCoordinates(false); 19969 coordsDescending[id] = coordsAscending[id].slice().reverse(); 19970 coordsDescendingSymbol[id] = tileManager.getVisibleCoordinates(true).reverse(); 19971 } 19972 renderContext.opaquePassCutoff = Infinity; 19973 for (let i = 0; i < layerIds.length; i++) { 19974 const layerId = layerIds[i]; 19975 if (this.style._layers[layerId].is3D()) { 19976 renderContext.opaquePassCutoff = i; 19977 break; 19978 } 19979 } 19980 this.maybeDrawDepth(); 19981 if (this.renderToTexture) { 19982 this.renderToTexture.prepareForRender(this.style, this.transform.zoom); 19983 renderContext.opaquePassCutoff = 0; 19984 } 19985 renderContext.currentPass = "offscreen"; 19986 for (const layerId of layerIds) { 19987 const layer = this.style._layers[layerId]; 19988 if (!layer.hasOffscreenPass() || layer.isHidden(this.transform.zoom)) continue; 19989 const coords = coordsDescending[layer.source]; 19990 if (layer.type !== "custom" && !coords.length) continue; 19991 this.renderLayer(this, tileManagers[layer.source], layer, coords, renderContext); 19992 } 19993 this.context.viewport.set([ 19994 0, 19995 0, 19996 this.width, 19997 this.height 19998 ]); 19999 this.context.bindFramebuffer.set(null); 20000 this.context.clear({ 20001 color: options.showOverdrawInspector ? Color.black : Color.transparent, 20002 depth: 1 20003 }); 20004 this.clearStencil(); 20005 if (this.style.sky) this.drawFunctions.sky(this, this.style.sky); 20006 this._showOverdrawInspector = options.showOverdrawInspector; 20007 renderContext.depthRangeFor3D = [0, 1 - (style._order.length + 2) * this.numSublayers * this.depthEpsilon]; 20008 if (!this.renderToTexture) { 20009 renderContext.currentPass = "opaque"; 20010 for (renderContext.currentLayer = layerIds.length - 1; renderContext.currentLayer >= 0; renderContext.currentLayer--) { 20011 const layer = this.style._layers[layerIds[renderContext.currentLayer]]; 20012 if (layer.isHidden(this.transform.zoom)) continue; 20013 const tileManager = tileManagers[layer.source]; 20014 const coords = coordsAscending[layer.source]; 20015 this.renderTileClippingMasks(layer, coords); 20016 this.renderLayer(this, tileManager, layer, coords, renderContext); 20017 } 20018 } 20019 renderContext.currentPass = "translucent"; 20020 let globeDepthRendered = false;
vendor: 8,619 bytes, lines 20021-20207
20021 for (renderContext.currentLayer = 0; renderContext.currentLayer < layerIds.length; renderContext.currentLayer++) { 20022 const layer = this.style._layers[layerIds[renderContext.currentLayer]]; 20023 if (layer.isHidden(this.transform.zoom)) continue; 20024 const tileManager = tileManagers[layer.source]; 20025 if (this.renderToTexture?.renderLayer(layer, renderContext)) continue; 20026 if (!this.opaquePassEnabledForLayer() && !globeDepthRendered) { 20027 globeDepthRendered = true; 20028 if (renderContext.isRenderingGlobe && !this.style.map.terrain) this._renderTilesDepthBuffer(); 20029 } 20030 const coords = (layer.type === "symbol" ? coordsDescendingSymbol : coordsDescending)[layer.source]; 20031 this.renderTileClippingMasks(layer, coordsAscending[layer.source]); 20032 this.renderLayer(this, tileManager, layer, coords, renderContext); 20033 } 20034 if (renderContext.isRenderingGlobe) this.drawFunctions.atmosphere(this, this.style.sky, this.style.light); 20035 if (this.options.showTileBoundaries) { 20036 const selectedSource = selectDebugSource(this.style, this.transform.zoom); 20037 if (selectedSource) this.drawFunctions.debug(this, selectedSource, selectedSource.getVisibleCoordinates(), renderContext); 20038 } 20039 if (this.options.showPadding) this.drawFunctions.debugPadding(this); 20040 if (this.renderToTexture && !this.options.moving) this.clearRTTPool(); 20041 this.context.setDefault(); 20042 } 20043 /** 20044 * Invalidates cached terrain depth so the next eligible depth pass redraws it. 20045 * DEM data can change the rendered surface while the camera and terrain tile set stay unchanged. 20046 * Repeated calls coalesce without rendering or scheduling a frame, even if terrain is not yet present. 20047 */ 20048 markTerrainDepthDirty() { 20049 this.terrainFacilitator.depthDirty = true; 20050 } 20051 /** 20052 * Updates the depth framebuffer after explicit invalidation, camera movement, or tile reloading. 20053 */ 20054 maybeDrawDepth() { 20055 if (!this.style?.projection || !this.style.map?.terrain) return; 20056 const prevMatrix = this.terrainFacilitator.matrix; 20057 const currMatrix = this.transform.modelViewProjectionMatrix; 20058 let doUpdate = this.terrainFacilitator.depthDirty; 20059 doUpdate ||= !equals(prevMatrix, currMatrix); 20060 doUpdate ||= this.style.map.terrain.tileManager.anyTilesAfterTime(this.terrainFacilitator.renderTime); 20061 if (!doUpdate) return; 20062 copy(prevMatrix, currMatrix); 20063 this.terrainFacilitator.renderTime = now(); 20064 this.terrainFacilitator.depthDirty = false; 20065 this.drawFunctions.terrainDepth(this, this.style.map.terrain); 20066 } 20067 renderLayer(painter, tileManager, layer, coords, renderContext) { 20068 if (layer.isHidden(this.transform.zoom)) return; 20069 if (layer.type !== "background" && layer.type !== "custom" && !(coords || []).length) return; 20070 this.id = layer.id; 20071 const draw = this.drawFunctions; 20072 if (isSymbolStyleLayer(layer)) draw.symbol(painter, tileManager, layer, coords, this.style.placement.variableOffsets, renderContext); 20073 else if (isCircleStyleLayer(layer)) draw.circle(painter, tileManager, layer, coords, renderContext); 20074 else if (isHeatmapStyleLayer(layer)) draw.heatmap(painter, tileManager, layer, coords, renderContext); 20075 else if (isLineStyleLayer(layer)) draw.line(painter, tileManager, layer, coords, renderContext); 20076 else if (isFillStyleLayer(layer)) draw.fill(painter, tileManager, layer, coords, renderContext); 20077 else if (isFillExtrusionStyleLayer(layer)) draw.fillExtrusion(painter, tileManager, layer, coords, renderContext); 20078 else if (isHillshadeStyleLayer(layer)) draw.hillshade(painter, tileManager, layer, coords, renderContext); 20079 else if (isColorReliefStyleLayer(layer)) draw.colorRelief(painter, tileManager, layer, coords, renderContext); 20080 else if (isRasterStyleLayer(layer)) draw.raster(painter, tileManager, layer, coords, renderContext); 20081 else if (isBackgroundStyleLayer(layer)) draw.background(painter, tileManager, layer, coords, renderContext); 20082 else if (isCustomStyleLayer(layer)) draw.custom(painter, tileManager, layer, renderContext); 20083 } 20084 static { 20085 this.MAX_TEXTURE_POOL_SIZE_PER_BUCKET = 50; 20086 } 20087 saveTileTexture(texture) { 20088 const textures = this._tileTextures[texture.size[0]]; 20089 if (!textures) this._tileTextures[texture.size[0]] = [texture]; 20090 else if (textures.length < Painter.MAX_TEXTURE_POOL_SIZE_PER_BUCKET) textures.push(texture); 20091 else texture.destroy(); 20092 } 20093 getTileTexture(size) { 20094 const textures = this._tileTextures[size]; 20095 return textures && textures.length > 0 ? textures.pop() : null; 20096 } 20097 acquireRTT(size) { 20098 const gl = this.context.gl; 20099 const obj = this._rttObjectRecyclePool.pop(); 20100 if (obj) { 20101 if (obj.size !== size) { 20102 obj.texture.update({ 20103 width: size, 20104 height: size, 20105 data: null 20106 }, { 20107 premultiply: false, 20108 useMipmap: true 20109 }); 20110 obj.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_LINEAR); 20111 if (this.context.extTextureFilterAnisotropic) gl.texParameterf(gl.TEXTURE_2D, this.context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, this.context.extTextureFilterAnisotropicMax); 20112 obj.size = size; 20113 } 20114 return obj; 20115 } 20116 const texture = new Texture(this.context, { 20117 width: size, 20118 height: size, 20119 data: null 20120 }, gl.RGBA, { 20121 premultiply: false, 20122 useMipmap: true 20123 }); 20124 texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_LINEAR); 20125 if (this.context.extTextureFilterAnisotropic) gl.texParameterf(gl.TEXTURE_2D, this.context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, this.context.extTextureFilterAnisotropicMax); 20126 return { 20127 texture, 20128 size 20129 }; 20130 } 20131 /** 20132 * Binds the shared RTT FBO with the given RTTObject's texture attached 20133 * as color target. Creates / resizes the shared FBO and depth-stencil 20134 * renderbuffer lazily. 20135 */ 20136 bindRTT(obj) { 20137 const gl = this.context.gl; 20138 const size = obj.size; 20139 if (!this._rttSharedFbo) { 20140 const fbo = this.context.createFramebuffer(size, size, true, true); 20141 const depthRenderbuffer = this.context.createRenderbuffer(gl.DEPTH_STENCIL, size, size); 20142 fbo.depthAttachment.set(depthRenderbuffer); 20143 this._rttSharedFbo = { 20144 fbo, 20145 depthRenderbuffer, 20146 size 20147 }; 20148 } 20149 if (this._rttSharedFbo.size !== size) { 20150 this.context.bindRenderbuffer.set(this._rttSharedFbo.depthRenderbuffer); 20151 gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, size, size); 20152 this.context.bindRenderbuffer.set(null); 20153 this._rttSharedFbo.fbo.width = size; 20154 this._rttSharedFbo.fbo.height = size; 20155 this._rttSharedFbo.size = size; 20156 } 20157 this._rttSharedFbo.fbo.colorAttachment.set(obj.texture.texture); 20158 this.context.bindFramebuffer.set(this._rttSharedFbo.fbo.framebuffer); 20159 } 20160 releaseRTT(obj) { 20161 this._rttObjectRecyclePool.push(obj); 20162 } 20163 /** 20164 * Destroys the pooled {@link RTTObject}s, the drapes no tile holds. Called at the end of a frame at rest, 20165 * when they would otherwise stay resident until the next gesture reuses them. 20166 */ 20167 clearRTTPool() { 20168 for (const obj of this._rttObjectRecyclePool) obj.texture.destroy(); 20169 this._rttObjectRecyclePool.length = 0; 20170 } 20171 /** 20172 * Frees the pooled drapes and the shared render-to-texture FBO. Called when terrain is removed, after its tiles 20173 * released their drapes. 20174 */ 20175 destroyRTTResources() { 20176 this.clearRTTPool(); 20177 if (!this._rttSharedFbo) return; 20178 this._rttSharedFbo.fbo.colorAttachment.set(null); 20179 this._rttSharedFbo.fbo.depthAttachment.set(null); 20180 const gl = this.context.gl; 20181 gl.deleteRenderbuffer(this._rttSharedFbo.depthRenderbuffer); 20182 gl.deleteFramebuffer(this._rttSharedFbo.fbo.framebuffer); 20183 this._rttSharedFbo = null; 20184 } 20185 /** 20186 * Checks whether a pattern image is needed, and if it is, whether it is not loaded. 20187 * 20188 * @returns true if a needed image is missing and rendering needs to be skipped. 20189 */ 20190 isPatternMissing(image) { 20191 if (!image) return false; 20192 if (!image.from || !image.to) return true; 20193 const imagePosA = this.patternAtlas.getPattern(image.from.toString()); 20194 const imagePosB = this.patternAtlas.getPattern(image.to.toString()); 20195 return !imagePosA || !imagePosB; 20196 } 20197 /** 20198 * Finds the required shader and its variant (base/terrain/globe, etc.) and binds it, compiling a new shader if required. 20199 * @param name - Name of the desired shader. 20200 * @param programConfiguration - Configuration of shader's inputs. 20201 * @param forceSimpleProjection - Whether to force the use of a shader variant with simple mercator projection vertex shader. 20202 * @param defines - Additional macros to be injected at the beginning of the shader. Expected format is `['#define XYZ']`, etc. 20203 * False by default. Use true when drawing with a simple projection matrix is desired, eg. when drawing a fullscreen quad. 20204 * @returns 20205 */ 20206 useProgram(name, programConfiguration, forceSimpleProjection = false, defines = []) { 20207 this.cache ||= {};
vendor: 4,457 bytes, lines 20208-20307
20208 const useTerrain = !!this.style.map.terrain; 20209 const projection = this.style.projection; 20210 const projectionPrelude = forceSimpleProjection ? shaders.projectionMercator : projection.shaderPreludeCode; 20211 const projectionDefine = forceSimpleProjection ? MercatorShaderDefine : projection.shaderDefine; 20212 const projectionKey = `/${forceSimpleProjection ? MercatorShaderVariantKey : projection.shaderVariantName}`; 20213 const configurationKey = programConfiguration ? programConfiguration.cacheKey : ""; 20214 const overdrawKey = this._showOverdrawInspector ? "/overdraw" : ""; 20215 const terrainKey = useTerrain ? "/terrain" : ""; 20216 const definesKey = defines ? `/${defines.join("/")}` : ""; 20217 const key = name + configurationKey + projectionKey + overdrawKey + terrainKey + definesKey; 20218 this.cache[key] ||= new Program(this.context, shaders[name], programConfiguration, programUniforms[name], this._showOverdrawInspector, useTerrain, projectionPrelude, projectionDefine, defines); 20219 return this.cache[key]; 20220 } 20221 setCustomLayerDefaults() { 20222 this.context.setCustomLayerDefaults(); 20223 } 20224 setBaseState() { 20225 const gl = this.context.gl; 20226 this.context.cullFace.set(false); 20227 this.context.viewport.set([ 20228 0, 20229 0, 20230 this.width, 20231 this.height 20232 ]); 20233 this.context.blendEquation.set(gl.FUNC_ADD); 20234 } 20235 initDebugOverlayCanvas() { 20236 if (this.debugOverlayCanvas == null) { 20237 this.debugOverlayCanvas = document.createElement("canvas"); 20238 this.debugOverlayCanvas.width = 512; 20239 this.debugOverlayCanvas.height = 512; 20240 const gl = this.context.gl; 20241 this.debugOverlayTexture = new Texture(this.context, this.debugOverlayCanvas, gl.RGBA); 20242 } 20243 } 20244 destroy() { 20245 if (this._tileTextures) { 20246 for (const size in this._tileTextures) { 20247 const textures = this._tileTextures[size]; 20248 if (textures) for (const texture of textures) texture.destroy(); 20249 } 20250 this._tileTextures = {}; 20251 } 20252 this.destroyRTTResources(); 20253 this.layerOpacityFbo?.destroy(); 20254 this.layerOpacityFbo = null; 20255 if (this.tileExtentBuffer) this.tileExtentBuffer.destroy(); 20256 if (this.debugBuffer) this.debugBuffer.destroy(); 20257 if (this.rasterBoundsBuffer) this.rasterBoundsBuffer.destroy(); 20258 if (this.rasterBoundsBufferPosOnly) this.rasterBoundsBufferPosOnly.destroy(); 20259 if (this.viewportBuffer) this.viewportBuffer.destroy(); 20260 if (this.tileBorderIndexBuffer) this.tileBorderIndexBuffer.destroy(); 20261 if (this.quadTriangleIndexBuffer) this.quadTriangleIndexBuffer.destroy(); 20262 if (this.tileExtentMesh) this.tileExtentMesh.vertexBuffer?.destroy(); 20263 if (this.tileExtentMesh) this.tileExtentMesh.indexBuffer?.destroy(); 20264 if (this.debugOverlayTexture) this.debugOverlayTexture.destroy(); 20265 this.context.projectionUniformBuffer.destroy(); 20266 this.context.terrainUniformBuffer.destroy(); 20267 this.context.frameUniformBuffer.destroy(); 20268 if (this.cache) { 20269 for (const key in this.cache) { 20270 const program = this.cache[key]; 20271 if (program?.program) this.context.gl.deleteProgram(program.program); 20272 } 20273 this.cache = {}; 20274 } 20275 if (this.context) this.context.setDefault(); 20276 } 20277 overLimit() { 20278 const { drawingBufferWidth, drawingBufferHeight } = this.context.gl; 20279 return this.width !== drawingBufferWidth || this.height !== drawingBufferHeight; 20280 } 20281}; 20282//#endregion 20283//#region src/util/gpu_initialization_error.ts 20284/** 20285* Thrown by the `Map` constructor when a GPU rendering context cannot be created, 20286* or fired via the map's `error` event when recreating the context after a `webglcontextrestored` fails. 20287* 20288* Carries the canvas attributes that were requested and, when the browser provided one, 20289* the originating `webglcontextcreationerror` `statusMessage`. 20290* Consumers can branch on `instanceof GPUInitializationError` and inspect the cause programmatically. 20291* @see https://wiki.openstreetmap.org/wiki/This_map_requires_WebGL 20292*/ 20293var GPUInitializationError = class extends Error { 20294 constructor(requestedAttributes, creationEvent) { 20295 super("WebGL2 is required to display this map. We are sorry, but it seems that your browser does not support WebGL2, a technology for rendering 3D graphics on the web. Read more on https://wiki.openstreetmap.org/wiki/This_map_requires_WebGL"); 20296 this.name = "GPUInitializationError"; 20297 this.requestedAttributes = requestedAttributes; 20298 this.statusMessage = creationEvent?.statusMessage ?? null; 20299 } 20300}; 20301//#endregion 20302//#region src/util/throttle.ts 20303/** 20304* Throttle the given function to run at most every `period` milliseconds. 20305*/ 20306function throttle(fn, time) { 20307 let pending = false;
vendor: 20,635 bytes, lines 20308-20962
20308 let timerId = null; 20309 let lastCallArgs; 20310 const later = () => { 20311 timerId = null; 20312 if (pending) { 20313 fn(...lastCallArgs); 20314 timerId = setTimeout(later, time); 20315 pending = false; 20316 } 20317 }; 20318 return (...args) => { 20319 pending = true; 20320 lastCallArgs = args; 20321 if (!timerId) later(); 20322 return timerId; 20323 }; 20324} 20325//#endregion 20326//#region src/ui/hash.ts 20327/** 20328* Adds the map's position to its page's location hash. 20329* Passed as an option to the map object. 20330* 20331* @group Markers and Controls 20332*/ 20333var Hash = class { 20334 constructor(hashName) { 20335 this._getHashParams = () => { 20336 return new URLSearchParams(window.location.hash.replace("#", "")); 20337 }; 20338 this._getCurrentHash = () => { 20339 const params = this._getHashParams(); 20340 if (this._hashName) return (params.get(this._hashName) || "").split("/"); 20341 return ([...params.keys()][0] ?? "").split("/"); 20342 }; 20343 this._onHashChange = () => { 20344 const hash = this._getCurrentHash(); 20345 if (!this._isValidHash(hash)) return false; 20346 const bearing = this._map.dragRotate.isEnabled() && this._map.touchZoomRotate.isEnabled() ? +(hash[3] || 0) : this._map.getBearing(); 20347 this._map.jumpTo({ 20348 center: [+hash[2], +hash[1]], 20349 zoom: +hash[0], 20350 bearing, 20351 pitch: +(hash[4] || 0) 20352 }); 20353 return true; 20354 }; 20355 this._updateHashUnthrottled = () => { 20356 const location = window.location.href.replace(/(#.*)?$/, this.getHashString()); 20357 window.history.replaceState(window.history.state, null, location); 20358 }; 20359 this._removeHash = () => { 20360 const params = this._getHashParams(); 20361 if (this._hashName) params.delete(this._hashName); 20362 else { 20363 const keys = Array.from(params.keys()); 20364 if (keys.length > 0) params.delete(keys[0]); 20365 } 20366 const newHash = decodeURIComponent(params.toString()).replace(/=&/g, "&").replace(/=$/g, ""); 20367 const replaceString = newHash ? `#${newHash}` : ""; 20368 let location = window.location.href.replace(/(#.+)?$/, replaceString); 20369 location = location.replace("&&", "&"); 20370 window.history.replaceState(window.history.state, null, location); 20371 }; 20372 this._updateHash = throttle(this._updateHashUnthrottled, 300); 20373 this._hashName = hashName && encodeURIComponent(hashName); 20374 } 20375 /** 20376 * Map element to listen for coordinate changes 20377 * 20378 * @param map - The map object 20379 */ 20380 addTo(map) { 20381 this._map = map; 20382 addEventListener("hashchange", this._onHashChange, false); 20383 this._map.on("moveend", this._updateHash); 20384 return this; 20385 } 20386 /** 20387 * Removes hash 20388 */ 20389 remove() { 20390 removeEventListener("hashchange", this._onHashChange, false); 20391 this._map.off("moveend", this._updateHash); 20392 clearTimeout(this._updateHash()); 20393 this._removeHash(); 20394 delete this._map; 20395 return this; 20396 } 20397 getHashString(mapFeedback) { 20398 const center = this._map.getCenter(), zoom = Math.round(this._map.getZoom() * 100) / 100, precision = Math.ceil((zoom * Math.LN2 + Math.log(512 / 360 / .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(); 20399 let hash = ""; 20400 if (mapFeedback) hash += `/${lng}/${lat}/${zoom}`; 20401 else hash += `${zoom}/${lat}/${lng}`; 20402 if (bearing || pitch) hash += `/${Math.round(bearing * 10) / 10}`; 20403 if (pitch) hash += `/${Math.round(pitch)}`; 20404 if (this._hashName) { 20405 const params = this._getHashParams(); 20406 params.set(this._hashName, hash); 20407 return `#${decodeURIComponent(params.toString()).replace(/=&/g, "&").replace(/=$/g, "")}`; 20408 } 20409 return `#${hash}`; 20410 } 20411 _isValidHash(hash) { 20412 if (hash.length < 3 || hash.some((h) => isNaN(+h))) return false; 20413 try { 20414 new LngLat(+hash[2], +hash[1]); 20415 } catch { 20416 return false; 20417 } 20418 const zoom = +hash[0]; 20419 const bearing = +(hash[3] || 0); 20420 const pitch = +(hash[4] || 0); 20421 return zoom >= this._map.getMinZoom() && zoom <= this._map.getMaxZoom() && bearing >= -180 && bearing <= 180 && pitch >= this._map.getMinPitch() && pitch <= this._map.getMaxPitch(); 20422 } 20423}; 20424//#endregion 20425//#region src/ui/handler_inertia.ts 20426const defaultInertiaOptions = { 20427 linearity: .3, 20428 easing: bezier(0, 0, .3, 1) 20429}; 20430const defaultPanInertiaOptions = extend({ 20431 deceleration: 2500, 20432 maxSpeed: 1400 20433}, defaultInertiaOptions); 20434const defaultZoomInertiaOptions = extend({ 20435 deceleration: 20, 20436 maxSpeed: 1400 20437}, defaultInertiaOptions); 20438const defaultBearingInertiaOptions = extend({ 20439 deceleration: 1e3, 20440 maxSpeed: 360 20441}, defaultInertiaOptions); 20442const defaultPitchInertiaOptions = extend({ 20443 deceleration: 1e3, 20444 maxSpeed: 90 20445}, defaultInertiaOptions); 20446const defaultRollInertiaOptions = extend({ 20447 deceleration: 1e3, 20448 maxSpeed: 360 20449}, defaultInertiaOptions); 20450/** 20451* The maximum age of a recorded gesture update, in milliseconds. 20452*/ 20453const BUFFER_CUTOFF = 160; 20454/** 20455* The time window the gesture velocity is measured over, in milliseconds. 20456*/ 20457const VELOCITY_WINDOW = 60; 20458var HandlerInertia = class { 20459 constructor(map) { 20460 this._map = map; 20461 this.clear(); 20462 } 20463 clear() { 20464 this._inertiaBuffer = []; 20465 } 20466 record(settings) { 20467 this._drainInertiaBuffer(); 20468 this._inertiaBuffer.push({ 20469 time: now(), 20470 settings 20471 }); 20472 } 20473 _drainInertiaBuffer() { 20474 const inertia = this._inertiaBuffer, currentTime = now(); 20475 while (inertia.length > 0 && currentTime - inertia[0].time > BUFFER_CUTOFF) inertia.shift(); 20476 } 20477 /** 20478 * Returns the entries the velocity is measured from: everything recorded within 20479 * {@link VELOCITY_WINDOW} before now, but never fewer than the last two, so that 20480 * inertia also works below two recorded updates per window. 20481 */ 20482 _getVelocityEntries() { 20483 const inertia = this._inertiaBuffer; 20484 const windowStart = now() - VELOCITY_WINDOW; 20485 let firstIndex = Math.max(0, inertia.length - 2); 20486 while (firstIndex > 0 && inertia[firstIndex - 1].time >= windowStart) firstIndex--; 20487 return inertia.slice(firstIndex); 20488 } 20489 /** 20490 * Returns the ease which continues the gesture that has just ended. 20491 * 20492 * The velocity is measured over the time up to the release and not up to the last 20493 * recorded movement, so that a gesture held still before being released has a lower 20494 * velocity, down to no inertia at all. The delta of an entry happened before it was 20495 * recorded, so the first entry only marks the start of the measured interval; anchors 20496 * are the exception, as they describe the state at the moment of their entry. 20497 * 20498 * @param panInertiaOptions - overrides for the pan inertia defaults 20499 * @returns the ease options, or `undefined` when the measured interval holds no 20500 * movement, which terrain gestures produce by recording updates without any delta 20501 */ 20502 _onMoveEnd(panInertiaOptions) { 20503 this._drainInertiaBuffer(); 20504 const entries = this._getVelocityEntries(); 20505 if (entries.length < 2) { 20506 this.clear(); 20507 return; 20508 } 20509 const deltas = { 20510 zoom: 0, 20511 bearing: 0, 20512 pitch: 0, 20513 roll: 0, 20514 pan: new Point(0, 0), 20515 pinchAround: void 0, 20516 around: void 0 20517 }; 20518 for (const { settings } of entries) { 20519 if (settings.around) deltas.around = settings.around; 20520 if (settings.pinchAround) deltas.pinchAround = settings.pinchAround; 20521 } 20522 for (const { settings } of entries.slice(1)) { 20523 deltas.zoom += settings.zoomDelta || 0; 20524 deltas.bearing += settings.bearingDelta || 0; 20525 deltas.pitch += settings.pitchDelta || 0; 20526 deltas.roll += settings.rollDelta || 0; 20527 if (settings.panDelta) deltas.pan._add(settings.panDelta); 20528 } 20529 if (!deltas.pan.mag() && !deltas.zoom && !deltas.bearing && !deltas.pitch && !deltas.roll) { 20530 this.clear(); 20531 return; 20532 } 20533 const duration = now() - entries[0].time; 20534 const easeOptions = {}; 20535 if (deltas.pan.mag()) { 20536 const result = calculateEasing(deltas.pan.mag(), duration, extend({}, defaultPanInertiaOptions, panInertiaOptions || {})); 20537 const finalPan = deltas.pan.mult(result.amount / deltas.pan.mag()); 20538 const computedEaseOptions = this._map._camera.cameraHelper.handlePanInertia(finalPan, this._map._camera.transform); 20539 easeOptions.center = computedEaseOptions.easingCenter; 20540 easeOptions.offset = computedEaseOptions.easingOffset; 20541 extendDuration(easeOptions, result); 20542 } 20543 if (deltas.zoom) { 20544 const result = calculateEasing(deltas.zoom, duration, defaultZoomInertiaOptions); 20545 easeOptions.zoom = evaluateZoomSnap(this._map.getZoom() + result.amount, this._map.getZoomSnap(), result.amount); 20546 extendDuration(easeOptions, result); 20547 } 20548 if (deltas.bearing) { 20549 const result = calculateEasing(deltas.bearing, duration, defaultBearingInertiaOptions); 20550 easeOptions.bearing = this._map.getBearing() + clamp(result.amount, -179, 179); 20551 extendDuration(easeOptions, result); 20552 } 20553 if (deltas.pitch) { 20554 const result = calculateEasing(deltas.pitch, duration, defaultPitchInertiaOptions); 20555 easeOptions.pitch = this._map.getPitch() + result.amount; 20556 extendDuration(easeOptions, result); 20557 } 20558 if (deltas.roll) { 20559 const result = calculateEasing(deltas.roll, duration, defaultRollInertiaOptions); 20560 easeOptions.roll = this._map.getRoll() + clamp(result.amount, -179, 179); 20561 extendDuration(easeOptions, result); 20562 } 20563 if (easeOptions.zoom || easeOptions.bearing) { 20564 const last = deltas.pinchAround === void 0 ? deltas.around : deltas.pinchAround; 20565 easeOptions.around = last ? this._map.unproject(last) : this._map.getCenter(); 20566 } 20567 this.clear(); 20568 return extend(easeOptions, { noMoveStart: true }); 20569 } 20570}; 20571function extendDuration(easeOptions, result) { 20572 if (!easeOptions.duration || easeOptions.duration < result.duration) { 20573 easeOptions.duration = result.duration; 20574 easeOptions.easing = result.easing; 20575 } 20576} 20577function calculateEasing(amount, inertiaDuration, inertiaOptions) { 20578 const { maxSpeed, linearity, deceleration } = inertiaOptions; 20579 const speed = clamp(amount * linearity / (inertiaDuration / 1e3), -maxSpeed, maxSpeed); 20580 const duration = Math.abs(speed) / (deceleration * linearity); 20581 return { 20582 easing: inertiaOptions.easing, 20583 duration: duration * 1e3, 20584 amount: speed * (duration / 2) 20585 }; 20586} 20587//#endregion 20588//#region src/ui/handler/map_event.ts 20589var MapEventHandler = class { 20590 constructor(map, options) { 20591 this._map = map; 20592 this._clickTolerance = options.clickTolerance; 20593 } 20594 reset() { 20595 delete this._mousedownPos; 20596 } 20597 wheel(e) { 20598 return this._firePreventable(new MapWheelEvent(this._map, e)); 20599 } 20600 mousedown(e, point) { 20601 this._mousedownPos = point; 20602 return this._firePreventable(new MapMouseEvent(e.type, this._map, e)); 20603 } 20604 mouseup(e) { 20605 this._map.fire(new MapMouseEvent(e.type, this._map, e)); 20606 } 20607 click(e, point) { 20608 if (this._mousedownPos && this._mousedownPos.dist(point) >= this._clickTolerance) return; 20609 this._map.fire(new MapMouseEvent(e.type, this._map, e)); 20610 } 20611 dblclick(e) { 20612 return this._firePreventable(new MapMouseEvent(e.type, this._map, e)); 20613 } 20614 mouseover(e) { 20615 this._map.fire(new MapMouseEvent(e.type, this._map, e)); 20616 } 20617 mouseout(e) { 20618 this._map.fire(new MapMouseEvent(e.type, this._map, e)); 20619 } 20620 touchstart(e) { 20621 return this._firePreventable(new MapTouchEvent(e.type, this._map, e)); 20622 } 20623 touchmove(e) { 20624 this._map.fire(new MapTouchEvent(e.type, this._map, e)); 20625 } 20626 touchend(e) { 20627 this._map.fire(new MapTouchEvent(e.type, this._map, e)); 20628 } 20629 touchcancel(e) { 20630 this._map.fire(new MapTouchEvent(e.type, this._map, e)); 20631 } 20632 _firePreventable(mapEvent) { 20633 this._map.fire(mapEvent); 20634 if (mapEvent.defaultPrevented) return {}; 20635 } 20636 isEnabled() { 20637 return true; 20638 } 20639 isActive() { 20640 return false; 20641 } 20642 enable() {} 20643 disable() {} 20644}; 20645/** 20646* A single finger held in place this long fires a long-press contextmenu. 20647*/ 20648const LONG_PRESS_DELAY = 500; 20649var BlockableMapEventHandler = class { 20650 constructor(map, options) { 20651 this._map = map; 20652 this._clickTolerance = options.clickTolerance; 20653 } 20654 reset() { 20655 this._delayContextMenu = false; 20656 this._ignoreContextMenu = true; 20657 delete this._contextMenuEvent; 20658 this._clearLongPress(); 20659 this._touchActive = false; 20660 } 20661 mousemove(e) { 20662 this._map.fire(new MapMouseEvent(e.type, this._map, e)); 20663 } 20664 mousedown() { 20665 this._delayContextMenu = true; 20666 this._ignoreContextMenu = false; 20667 } 20668 mouseup() { 20669 this._delayContextMenu = false; 20670 if (this._contextMenuEvent) { 20671 this._map.fire(new MapMouseEvent("contextmenu", this._map, this._contextMenuEvent)); 20672 delete this._contextMenuEvent; 20673 } 20674 } 20675 /** 20676 * Starts the touch long press. Touch devices have no right click, so a long press opens 20677 * the context menu. It is detected with a timer rather than by relying on a native 20678 * contextmenu event, because Android Chrome fires one on a long press but iOS 20679 * Safari does not. A single finger held past the delay without moving fires a 20680 * contextmenu at that point; a pan or a lift cancels it. 20681 * @param e - the touch event 20682 * @param points - the touch points, in map coordinates 20683 * @param mapTouches - the touches that are on the map 20684 */ 20685 touchstart(e, points, mapTouches) { 20686 this._clearLongPress(); 20687 this._touchActive = mapTouches.length > 0; 20688 if (mapTouches.length !== 1) return; 20689 this._longPressStart = points[0]; 20690 const touch = mapTouches[0]; 20691 this._longPressTimer = setTimeout(() => { 20692 this._longPressTimer = void 0; 20693 const originalEvent = new MouseEvent("contextmenu", { 20694 clientX: touch.clientX, 20695 clientY: touch.clientY, 20696 button: 2, 20697 bubbles: true, 20698 cancelable: true 20699 }); 20700 this._map.fire(new MapMouseEvent("contextmenu", this._map, originalEvent)); 20701 }, LONG_PRESS_DELAY); 20702 } 20703 /** 20704 * Cancels a pending long press once the finger has moved further than the map's 20705 * click tolerance, or once a second finger lands. 20706 * @param e - the touch event 20707 * @param points - the touch points, in map coordinates 20708 * @param mapTouches - the touches that are on the map 20709 */ 20710 touchmove(e, points, mapTouches) { 20711 if (!this._longPressTimer) return; 20712 if (mapTouches.length !== 1 || !this._longPressStart || points[0].dist(this._longPressStart) > this._clickTolerance) this._clearLongPress(); 20713 } 20714 touchend() { 20715 this._touchActive = false; 20716 this._clearLongPress(); 20717 } 20718 touchcancel() { 20719 this._touchActive = false; 20720 this._clearLongPress(); 20721 } 20722 _clearLongPress() { 20723 if (this._longPressTimer) { 20724 clearTimeout(this._longPressTimer); 20725 this._longPressTimer = void 0; 20726 } 20727 this._longPressStart = void 0; 20728 } 20729 /** 20730 * Handles a contextmenu event. A native one during a touch is suppressed, because the 20731 * long press timer already fires the event and the browser menu would double up. 20732 * @param e - the mouse event 20733 */ 20734 contextmenu(e) { 20735 if (this._touchActive) { 20736 e.preventDefault(); 20737 return; 20738 } 20739 if (this._delayContextMenu) this._contextMenuEvent = e; 20740 else if (!this._ignoreContextMenu) this._map.fire(new MapMouseEvent(e.type, this._map, e)); 20741 if (this._map.listens("contextmenu")) e.preventDefault(); 20742 } 20743 isEnabled() { 20744 return true; 20745 } 20746 isActive() { 20747 return false; 20748 } 20749 enable() {} 20750 disable() {} 20751}; 20752//#endregion 20753//#region src/ui/handler/box_zoom.ts 20754/** 20755* The `BoxZoomHandler` allows the user to zoom the map to fit within a bounding box. 20756* The bounding box is defined by clicking and holding `shift` while dragging the cursor. 20757* 20758* @group Handlers 20759*/ 20760var BoxZoomHandler = class { 20761 /** @internal */ 20762 constructor(map, options, transformProvider) { 20763 this._map = map; 20764 this._tr = transformProvider; 20765 this._el = map.getCanvasContainer(); 20766 this._container = map.getContainer(); 20767 this._clickTolerance = options.clickTolerance || 1; 20768 if (options.boxZoom && typeof options.boxZoom === "object") this._boxZoomEnd = options.boxZoom.boxZoomEnd; 20769 } 20770 /** 20771 * Returns a Boolean indicating whether the "box zoom" interaction is enabled. 20772 * 20773 * @returns `true` if the "box zoom" interaction is enabled. 20774 */ 20775 isEnabled() { 20776 return !!this._enabled; 20777 } 20778 /** 20779 * Returns a Boolean indicating whether the "box zoom" interaction is active, i.e. currently being used. 20780 * 20781 * @returns `true` if the "box zoom" interaction is active. 20782 */ 20783 isActive() { 20784 return !!this._active; 20785 } 20786 /** 20787 * Enables the "box zoom" interaction. 20788 * 20789 * @example 20790 * ```ts 20791 * map.boxZoom.enable(); 20792 * ``` 20793 */ 20794 enable() { 20795 if (this.isEnabled()) return; 20796 this._enabled = true; 20797 } 20798 /** 20799 * Disables the "box zoom" interaction. 20800 * 20801 * @example 20802 * ```ts 20803 * map.boxZoom.disable(); 20804 * ``` 20805 */ 20806 disable() { 20807 if (!this.isEnabled()) return; 20808 this._enabled = false; 20809 } 20810 mousedown(e, point) { 20811 if (!this.isEnabled()) return; 20812 if (!(e.shiftKey && e.button === 0)) return; 20813 DOM.disableDrag(); 20814 this._startPos = this._lastPos = point; 20815 this._active = true; 20816 } 20817 mousemoveWindow(e, point) { 20818 if (!this._active) return; 20819 const pos = point; 20820 if (this._lastPos.equals(pos) || !this._box && pos.dist(this._startPos) < this._clickTolerance) return; 20821 const p0 = this._startPos; 20822 this._lastPos = pos; 20823 if (!this._box) { 20824 this._box = DOM.create("div", "maplibregl-boxzoom", this._container); 20825 this._container.classList.add("maplibregl-crosshair"); 20826 this._fireEvent("boxzoomstart", e); 20827 } 20828 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); 20829 this._box.style.transform = `translate(${minX}px,${minY}px)`; 20830 this._box.style.width = `${maxX - minX}px`; 20831 this._box.style.height = `${maxY - minY}px`; 20832 } 20833 mouseupWindow(e, point) { 20834 if (!this._active) return; 20835 if (e.button !== 0) return; 20836 const p0 = this._startPos, p1 = point; 20837 this.reset(); 20838 DOM.suppressClick(); 20839 if (p0.x === p1.x && p0.y === p1.y) this._fireEvent("boxzoomcancel", e); 20840 else { 20841 this._map.fire(new MapBoxZoomEvent("boxzoomend", { originalEvent: e })); 20842 if (this._boxZoomEnd) { 20843 this._boxZoomEnd(this._map, p0, p1, e); 20844 return; 20845 } 20846 return { cameraAnimation: (map) => map.fitScreenCoordinates(p0, p1, this._tr.bearing, { linear: true }) }; 20847 } 20848 } 20849 keydown(e) { 20850 if (!this._active) return; 20851 if (e.keyCode === 27) { 20852 this.reset(); 20853 this._fireEvent("boxzoomcancel", e); 20854 } 20855 } 20856 reset() { 20857 this._active = false; 20858 this._container.classList.remove("maplibregl-crosshair"); 20859 if (this._box) { 20860 this._box.remove(); 20861 this._box = null; 20862 } 20863 DOM.enableDrag(); 20864 delete this._startPos; 20865 delete this._lastPos; 20866 } 20867 _fireEvent(type, e) { 20868 return this._map.fire(new MapBoxZoomEvent(type, { originalEvent: e })); 20869 } 20870}; 20871//#endregion 20872//#region src/ui/handler/handler_util.ts 20873function indexTouches(touches, points) { 20874 if (touches.length !== points.length) throw new Error(`The number of touches and points are not equal - touches ${touches.length}, points ${points.length}`); 20875 const obj = {}; 20876 for (let i = 0; i < touches.length; i++) obj[touches[i].identifier] = points[i]; 20877 return obj; 20878} 20879//#endregion 20880//#region src/ui/handler/tap_recognizer.ts 20881function getCentroid(points) { 20882 const sum = new Point(0, 0); 20883 for (const point of points) sum._add(point); 20884 return sum.div(points.length); 20885} 20886const MAX_TOUCH_TIME = 500; 20887var SingleTapRecognizer = class { 20888 constructor(options) { 20889 this.reset(); 20890 this.numTouches = options.numTouches; 20891 } 20892 reset() { 20893 delete this.centroid; 20894 delete this.startTime; 20895 delete this.touches; 20896 this.aborted = false; 20897 } 20898 touchstart(e, points, mapTouches) { 20899 if (this.centroid || mapTouches.length > this.numTouches) this.aborted = true; 20900 if (this.aborted) return; 20901 if (this.startTime === void 0) this.startTime = e.timeStamp; 20902 if (mapTouches.length === this.numTouches) { 20903 this.centroid = getCentroid(points); 20904 this.touches = indexTouches(mapTouches, points); 20905 } 20906 } 20907 touchmove(e, points, mapTouches) { 20908 if (this.aborted || !this.centroid) return; 20909 const newTouches = indexTouches(mapTouches, points); 20910 for (const id in this.touches) { 20911 const prevPos = this.touches[id]; 20912 const pos = newTouches[id]; 20913 if (!pos || pos.dist(prevPos) > 30) this.aborted = true; 20914 } 20915 } 20916 touchend(e, points, mapTouches) { 20917 if (!this.centroid || e.timeStamp - this.startTime > MAX_TOUCH_TIME) this.aborted = true; 20918 if (mapTouches.length === 0) { 20919 const centroid = !this.aborted && this.centroid; 20920 this.reset(); 20921 if (centroid) return centroid; 20922 } 20923 } 20924}; 20925var TapRecognizer = class { 20926 constructor(options) { 20927 this.singleTap = new SingleTapRecognizer(options); 20928 this.numTaps = options.numTaps; 20929 this.reset(); 20930 } 20931 reset() { 20932 this.lastTime = Infinity; 20933 delete this.lastTap; 20934 this.count = 0; 20935 this.singleTap.reset(); 20936 } 20937 touchstart(e, points, mapTouches) { 20938 this.singleTap.touchstart(e, points, mapTouches); 20939 } 20940 touchmove(e, points, mapTouches) { 20941 this.singleTap.touchmove(e, points, mapTouches); 20942 } 20943 touchend(e, points, mapTouches) { 20944 const tap = this.singleTap.touchend(e, points, mapTouches); 20945 if (tap) { 20946 const soonEnough = e.timeStamp - this.lastTime < 500; 20947 const closeEnough = !this.lastTap || this.lastTap.dist(tap) < 30; 20948 if (!soonEnough || !closeEnough) this.reset(); 20949 this.count++; 20950 this.lastTime = e.timeStamp; 20951 this.lastTap = tap; 20952 if (this.count === this.numTaps) { 20953 this.reset(); 20954 return tap; 20955 } 20956 } 20957 } 20958}; 20959//#endregion 20960//#region src/ui/handler/tap_zoom.ts 20961/** 20962* A `TapZoomHandler` allows the user to zoom the map at a point b
vendor: 9,287 bytes, lines 20962-21270
20962y double tapping 20963*/ 20964var TapZoomHandler = class { 20965 constructor(map, transformProvider) { 20966 this._tr = transformProvider; 20967 this._zoomIn = new TapRecognizer({ 20968 numTouches: 1, 20969 numTaps: 2 20970 }); 20971 this._zoomOut = new TapRecognizer({ 20972 numTouches: 2, 20973 numTaps: 1 20974 }); 20975 this.reset(); 20976 } 20977 reset() { 20978 this._active = false; 20979 this._zoomIn.reset(); 20980 this._zoomOut.reset(); 20981 } 20982 touchstart(e, points, mapTouches) { 20983 this._zoomIn.touchstart(e, points, mapTouches); 20984 this._zoomOut.touchstart(e, points, mapTouches); 20985 } 20986 touchmove(e, points, mapTouches) { 20987 this._zoomIn.touchmove(e, points, mapTouches); 20988 this._zoomOut.touchmove(e, points, mapTouches); 20989 } 20990 touchend(e, points, mapTouches) { 20991 const zoomInPoint = this._zoomIn.touchend(e, points, mapTouches); 20992 const zoomOutPoint = this._zoomOut.touchend(e, points, mapTouches); 20993 const tr = this._tr; 20994 if (zoomInPoint) { 20995 this._active = true; 20996 e.preventDefault(); 20997 setTimeout(() => this.reset(), 0); 20998 return { cameraAnimation: (map) => map.easeTo({ 20999 duration: 300, 21000 zoom: evaluateZoomSnap(tr.zoom + 1, map.getZoomSnap()), 21001 around: tr.unproject(zoomInPoint) 21002 }, { originalEvent: e }) }; 21003 } else if (zoomOutPoint) { 21004 this._active = true; 21005 e.preventDefault(); 21006 setTimeout(() => this.reset(), 0); 21007 return { cameraAnimation: (map) => map.easeTo({ 21008 duration: 300, 21009 zoom: evaluateZoomSnap(tr.zoom - 1, map.getZoomSnap()), 21010 around: tr.unproject(zoomOutPoint) 21011 }, { originalEvent: e }) }; 21012 } 21013 } 21014 touchcancel() { 21015 this.reset(); 21016 } 21017 enable() { 21018 this._enabled = true; 21019 } 21020 disable() { 21021 this._enabled = false; 21022 this.reset(); 21023 } 21024 isEnabled() { 21025 return this._enabled; 21026 } 21027 isActive() { 21028 return this._active; 21029 } 21030}; 21031//#endregion 21032//#region src/ui/handler/drag_handler.ts 21033/** 21034* A generic class to create handlers for drag events, from both mouse and touch events. 21035*/ 21036var DragHandler = class { 21037 constructor(options) { 21038 this._enabled = !!options.enable; 21039 this._moveStateManager = options.moveStateManager; 21040 this._clickTolerance = options.clickTolerance || 1; 21041 this._moveFunction = options.move; 21042 this._activateOnStart = !!options.activateOnStart; 21043 options.assignEvents(this); 21044 this.reset(); 21045 } 21046 reset(e) { 21047 this._active = false; 21048 this._moved = false; 21049 delete this._lastPoint; 21050 this._moveStateManager.endMove(e); 21051 } 21052 _move(...params) { 21053 const move = this._moveFunction(...params); 21054 if (move.bearingDelta || move.pitchDelta || move.rollDelta || move.around || move.panDelta) { 21055 this._active = true; 21056 return move; 21057 } 21058 } 21059 dragStart(e, point) { 21060 if (!this.isEnabled() || this._lastPoint) return; 21061 if (!this._moveStateManager.isValidStartEvent(e)) return; 21062 this._moveStateManager.startMove(e); 21063 this._lastPoint = Array.isArray(point) ? point[0] : point; 21064 if (this._activateOnStart && this._lastPoint) this._active = true; 21065 } 21066 dragMove(e, point) { 21067 if (!this.isEnabled()) return; 21068 const lastPoint = this._lastPoint; 21069 if (!lastPoint) return; 21070 e.preventDefault(); 21071 if (!this._moveStateManager.isValidMoveEvent(e)) { 21072 this.reset(e); 21073 return; 21074 } 21075 const movePoint = Array.isArray(point) ? point[0] : point; 21076 if (!this._moved && movePoint.dist(lastPoint) < this._clickTolerance) return; 21077 this._moved = true; 21078 this._lastPoint = movePoint; 21079 return this._move(lastPoint, movePoint); 21080 } 21081 dragEnd(e) { 21082 if (!this.isEnabled() || !this._lastPoint) return; 21083 if (!this._moveStateManager.isValidEndEvent(e)) return; 21084 if (this._moved) DOM.suppressClick(); 21085 this.reset(e); 21086 } 21087 enable() { 21088 this._enabled = true; 21089 } 21090 disable() { 21091 this._enabled = false; 21092 this.reset(); 21093 } 21094 isEnabled() { 21095 return this._enabled; 21096 } 21097 isActive() { 21098 return this._active; 21099 } 21100 getClickTolerance() { 21101 return this._clickTolerance; 21102 } 21103}; 21104//#endregion 21105//#region src/ui/handler/drag_move_state_manager.ts 21106const LEFT_BUTTON$1 = 0; 21107const RIGHT_BUTTON$1 = 2; 21108const BUTTONS_FLAGS = { 21109 [LEFT_BUTTON$1]: 1, 21110 [RIGHT_BUTTON$1]: 2 21111}; 21112function buttonNoLongerPressed(e, button) { 21113 const flag = BUTTONS_FLAGS[button]; 21114 return e.buttons === void 0 || (e.buttons & flag) !== flag; 21115} 21116var MouseMoveStateManager = class { 21117 constructor(options) { 21118 this._correctEvent = options.checkCorrectEvent; 21119 } 21120 startMove(e) { 21121 this._eventButton = e.button; 21122 } 21123 endMove(_e) { 21124 delete this._eventButton; 21125 } 21126 isValidStartEvent(e) { 21127 return this._correctEvent(e); 21128 } 21129 isValidMoveEvent(e) { 21130 return !buttonNoLongerPressed(e, this._eventButton); 21131 } 21132 isValidEndEvent(e) { 21133 return e.button === this._eventButton; 21134 } 21135}; 21136var OneFingerTouchMoveStateManager = class { 21137 constructor() { 21138 this._firstTouch = void 0; 21139 } 21140 _isOneFingerTouch(e) { 21141 return e.targetTouches.length === 1; 21142 } 21143 _isSameTouchEvent(e) { 21144 return e.targetTouches[0].identifier === this._firstTouch; 21145 } 21146 startMove(e) { 21147 this._firstTouch = e.targetTouches[0].identifier; 21148 } 21149 endMove(_e) { 21150 delete this._firstTouch; 21151 } 21152 isValidStartEvent(e) { 21153 return this._isOneFingerTouch(e); 21154 } 21155 isValidMoveEvent(e) { 21156 return this._isOneFingerTouch(e) && this._isSameTouchEvent(e); 21157 } 21158 isValidEndEvent(e) { 21159 return this._isOneFingerTouch(e) && this._isSameTouchEvent(e); 21160 } 21161}; 21162var MouseOrTouchMoveStateManager = class { 21163 constructor(mouseMoveStateManager = new MouseMoveStateManager({ checkCorrectEvent: () => true }), oneFingerTouchMoveStateManager = new OneFingerTouchMoveStateManager()) { 21164 this.mouseMoveStateManager = mouseMoveStateManager; 21165 this.oneFingerTouchMoveStateManager = oneFingerTouchMoveStateManager; 21166 } 21167 _executeRelevantHandler(e, onMouseEvent, onTouchEvent) { 21168 if (e instanceof MouseEvent) return onMouseEvent(e); 21169 if (typeof TouchEvent !== "undefined" && e instanceof TouchEvent) return onTouchEvent(e); 21170 } 21171 startMove(e) { 21172 this._executeRelevantHandler(e, (e) => { 21173 this.mouseMoveStateManager.startMove(e); 21174 }, (e) => { 21175 this.oneFingerTouchMoveStateManager.startMove(e); 21176 }); 21177 } 21178 endMove(e) { 21179 this._executeRelevantHandler(e, (e) => { 21180 this.mouseMoveStateManager.endMove(e); 21181 }, (e) => { 21182 this.oneFingerTouchMoveStateManager.endMove(e); 21183 }); 21184 } 21185 isValidStartEvent(e) { 21186 return !!this._executeRelevantHandler(e, (e) => this.mouseMoveStateManager.isValidStartEvent(e), (e) => this.oneFingerTouchMoveStateManager.isValidStartEvent(e)); 21187 } 21188 isValidMoveEvent(e) { 21189 return !!this._executeRelevantHandler(e, (e) => this.mouseMoveStateManager.isValidMoveEvent(e), (e) => this.oneFingerTouchMoveStateManager.isValidMoveEvent(e)); 21190 } 21191 isValidEndEvent(e) { 21192 return !!this._executeRelevantHandler(e, (e) => this.mouseMoveStateManager.isValidEndEvent(e), (e) => this.oneFingerTouchMoveStateManager.isValidEndEvent(e)); 21193 } 21194}; 21195//#endregion 21196//#region src/ui/handler/mouse.ts 21197const LEFT_BUTTON = 0; 21198const RIGHT_BUTTON = 2; 21199const assignEvents = (handler) => { 21200 handler.mousedown = handler.dragStart; 21201 handler.mousemoveWindow = handler.dragMove; 21202 handler.mouseup = handler.dragEnd; 21203 handler.contextmenu = (e) => { 21204 e.preventDefault(); 21205 }; 21206}; 21207function generateMousePanHandler({ enable, clickTolerance }) { 21208 return new DragHandler({ 21209 clickTolerance, 21210 move: (lastPoint, point) => ({ 21211 around: point, 21212 panDelta: point.sub(lastPoint) 21213 }), 21214 activateOnStart: true, 21215 moveStateManager: new MouseMoveStateManager({ checkCorrectEvent: (e) => e.button === LEFT_BUTTON && !e.ctrlKey }), 21216 enable, 21217 assignEvents 21218 }); 21219} 21220function generateMouseRotationHandler({ enable, clickTolerance, aroundCenter = true, minPixelCenterThreshold = 100, rotateSpeed = .8 }, getCenter) { 21221 return new DragHandler({ 21222 clickTolerance, 21223 move: (lastPoint, currentPoint) => { 21224 const center = getCenter(); 21225 if (aroundCenter && Math.abs(center.y - lastPoint.y) > minPixelCenterThreshold) return { bearingDelta: getAngleDelta(new Point(lastPoint.x, currentPoint.y), currentPoint, center) }; 21226 let bearingDelta = (currentPoint.x - lastPoint.x) * rotateSpeed; 21227 if (aroundCenter && currentPoint.y < center.y) bearingDelta = -bearingDelta; 21228 return { bearingDelta }; 21229 }, 21230 moveStateManager: new MouseMoveStateManager({ checkCorrectEvent: (e) => e.button === LEFT_BUTTON && e.ctrlKey || e.button === RIGHT_BUTTON && !e.ctrlKey }), 21231 enable, 21232 assignEvents 21233 }); 21234} 21235function generateMousePitchHandler({ enable, clickTolerance, pitchSpeed = -.5 }) { 21236 return new DragHandler({ 21237 clickTolerance, 21238 move: (lastPoint, point) => ({ pitchDelta: (point.y - lastPoint.y) * pitchSpeed }), 21239 moveStateManager: new MouseMoveStateManager({ checkCorrectEvent: (e) => e.button === LEFT_BUTTON && e.ctrlKey || e.button === RIGHT_BUTTON }), 21240 enable, 21241 assignEvents 21242 }); 21243} 21244function generateMouseRollHandler({ enable, clickTolerance, rollDegreesPerPixelMoved = .3 }, getCenter) { 21245 return new DragHandler({ 21246 clickTolerance, 21247 move: (lastPoint, currentPoint) => { 21248 const center = getCenter(); 21249 let rollDelta = (currentPoint.x - lastPoint.x) * rollDegreesPerPixelMoved; 21250 if (currentPoint.y < center.y) rollDelta = -rollDelta; 21251 return { rollDelta }; 21252 }, 21253 moveStateManager: new MouseMoveStateManager({ checkCorrectEvent: (e) => e.button === RIGHT_BUTTON && e.ctrlKey }), 21254 enable, 21255 assignEvents 21256 }); 21257} 21258//#endregion 21259//#region src/ui/handler/touch_pan.ts 21260/** 21261* A `TouchPanHandler` allows the user to pan the map using touch gestures. 21262*/ 21263var TouchPanHandler = class { 21264 constructor(options, map) { 21265 this._clickTolerance = options.clickTolerance || 1; 21266 this._map = map; 21267 this.reset(); 21268 } 21269 reset() { 21270 this._active = false;
vendor: 21,256 bytes, lines 21271-22043
21271 this._touches = {}; 21272 this._sum = new Point(0, 0); 21273 } 21274 _shouldBePrevented(touchesCount) { 21275 return touchesCount < (this._map.cooperativeGestures.isEnabled() ? 2 : 1); 21276 } 21277 touchstart(e, points, mapTouches) { 21278 return this._calculateTransform(e, points, mapTouches); 21279 } 21280 touchmove(e, points, mapTouches) { 21281 if (!this._active) return; 21282 if (this._shouldBePrevented(mapTouches.length)) { 21283 this._map.cooperativeGestures.notifyGestureBlocked("touch_pan", e); 21284 return; 21285 } 21286 e.preventDefault(); 21287 return this._calculateTransform(e, points, mapTouches); 21288 } 21289 touchend(e, points, mapTouches) { 21290 this._calculateTransform(e, points, mapTouches); 21291 if (this._active && this._shouldBePrevented(mapTouches.length)) this.reset(); 21292 } 21293 touchcancel() { 21294 this.reset(); 21295 } 21296 _calculateTransform(e, points, mapTouches) { 21297 if (mapTouches.length > 0) this._active = true; 21298 const touches = indexTouches(mapTouches, points); 21299 const touchPointSum = new Point(0, 0); 21300 const touchDeltaSum = new Point(0, 0); 21301 let touchDeltaCount = 0; 21302 for (const identifier in touches) { 21303 const point = touches[identifier]; 21304 const prevPoint = this._touches[identifier]; 21305 if (prevPoint) { 21306 touchPointSum._add(point); 21307 touchDeltaSum._add(point.sub(prevPoint)); 21308 touchDeltaCount++; 21309 touches[identifier] = point; 21310 } 21311 } 21312 this._touches = touches; 21313 if (this._shouldBePrevented(touchDeltaCount) || !touchDeltaSum.mag()) return; 21314 const panDelta = touchDeltaSum.div(touchDeltaCount); 21315 this._sum._add(panDelta); 21316 if (this._sum.mag() < this._clickTolerance) return; 21317 return { 21318 around: touchPointSum.div(touchDeltaCount), 21319 panDelta 21320 }; 21321 } 21322 enable() { 21323 this._enabled = true; 21324 } 21325 disable() { 21326 this._enabled = false; 21327 this.reset(); 21328 } 21329 isEnabled() { 21330 return this._enabled; 21331 } 21332 isActive() { 21333 return this._active; 21334 } 21335}; 21336//#endregion 21337//#region src/ui/handler/two_fingers_touch.ts 21338/** 21339* The `TwoFingersTouchHandler`s allows the user to zoom, pitch and rotate the map using two fingers 21340* 21341*/ 21342var TwoFingersTouchHandler = class { 21343 /** @internal */ 21344 constructor() { 21345 this.reset(); 21346 } 21347 reset() { 21348 this._active = false; 21349 delete this._firstTwoTouches; 21350 } 21351 touchstart(e, points, mapTouches) { 21352 if (this._firstTwoTouches || mapTouches.length < 2) return; 21353 this._firstTwoTouches = [mapTouches[0].identifier, mapTouches[1].identifier]; 21354 this._start([points[0], points[1]]); 21355 } 21356 touchmove(e, points, mapTouches) { 21357 if (!this._firstTwoTouches) return; 21358 e.preventDefault(); 21359 const [idA, idB] = this._firstTwoTouches; 21360 const a = getTouchById(mapTouches, points, idA); 21361 const b = getTouchById(mapTouches, points, idB); 21362 if (!a || !b) return; 21363 const pinchAround = this._aroundCenter ? null : a.add(b).div(2); 21364 return this._move([a, b], pinchAround, e); 21365 } 21366 touchend(e, points, mapTouches) { 21367 if (!this._firstTwoTouches) return; 21368 const [idA, idB] = this._firstTwoTouches; 21369 const a = getTouchById(mapTouches, points, idA); 21370 const b = getTouchById(mapTouches, points, idB); 21371 if (a && b) return; 21372 if (this._active) DOM.suppressClick(); 21373 this.reset(); 21374 } 21375 touchcancel() { 21376 this.reset(); 21377 } 21378 /** 21379 * Enables the "drag to pitch" interaction. 21380 * 21381 * @example 21382 * ```ts 21383 * map.touchPitch.enable(); 21384 * ``` 21385 */ 21386 enable(options) { 21387 this._enabled = true; 21388 this._aroundCenter = !!options && options.around === "center"; 21389 } 21390 /** 21391 * Disables the "drag to pitch" interaction. 21392 * 21393 * @example 21394 * ```ts 21395 * map.touchPitch.disable(); 21396 * ``` 21397 */ 21398 disable() { 21399 this._enabled = false; 21400 this.reset(); 21401 } 21402 /** 21403 * Returns a Boolean indicating whether the "drag to pitch" interaction is enabled. 21404 * 21405 * @returns `true` if the "drag to pitch" interaction is enabled. 21406 */ 21407 isEnabled() { 21408 return !!this._enabled; 21409 } 21410 /** 21411 * Returns a Boolean indicating whether the "drag to pitch" interaction is active, i.e. currently being used. 21412 * 21413 * @returns `true` if the "drag to pitch" interaction is active. 21414 */ 21415 isActive() { 21416 return !!this._active; 21417 } 21418}; 21419function getTouchById(mapTouches, points, identifier) { 21420 for (let i = 0; i < mapTouches.length; i++) if (mapTouches[i].identifier === identifier) return points[i]; 21421} 21422const defaultZoomRate$2 = 1; 21423const defaultZoomThreshold = .1; 21424function getZoomDelta(distance, lastDistance) { 21425 return Math.log(distance / lastDistance) / Math.LN2; 21426} 21427/** 21428* The `TwoFingersTouchHandler`s allows the user to zoom the map two fingers 21429* 21430* @group Handlers 21431*/ 21432var TwoFingersTouchZoomHandler = class extends TwoFingersTouchHandler { 21433 constructor() { 21434 super(); 21435 this._zoomRate = defaultZoomRate$2; 21436 this._zoomThreshold = defaultZoomThreshold; 21437 } 21438 /** 21439 * Sets the zoom rate of touch gestures. 21440 * @param zoomRate - 1 The rate used to scale touch movement to a zoom value. Set to `undefined` to restore the default. 21441 * @example 21442 * Slow down touch zoom 21443 * ```ts 21444 * map.touchZoomRotate.setZoomRate(0.5); 21445 * ``` 21446 */ 21447 setZoomRate(zoomRate) { 21448 this._zoomRate = zoomRate ?? defaultZoomRate$2; 21449 } 21450 /** 21451 * Sets the threshold before a pinch gesture starts zooming. 21452 * @param zoomThreshold - 0.1 The minimum zoom delta before the pinch gesture becomes active. Set to `undefined` to restore the default. 21453 * @example 21454 * Make pinch zoom less sensitive 21455 * ```ts 21456 * map.touchZoomRotate.setZoomThreshold(0.3); 21457 * ``` 21458 */ 21459 setZoomThreshold(zoomThreshold) { 21460 this._zoomThreshold = zoomThreshold ?? defaultZoomThreshold; 21461 } 21462 reset() { 21463 super.reset(); 21464 delete this._distance; 21465 delete this._startDistance; 21466 } 21467 _start(points) { 21468 this._startDistance = this._distance = points[0].dist(points[1]); 21469 } 21470 _move(points, pinchAround) { 21471 const lastDistance = this._distance; 21472 this._distance = points[0].dist(points[1]); 21473 if (!this._active && Math.abs(getZoomDelta(this._distance, this._startDistance)) < this._zoomThreshold) return; 21474 this._active = true; 21475 return { 21476 zoomDelta: getZoomDelta(this._distance, lastDistance) * this._zoomRate, 21477 pinchAround 21478 }; 21479 } 21480}; 21481const ROTATION_THRESHOLD = 25; 21482function getBearingDelta(a, b) { 21483 return a.angleWith(b) * 180 / Math.PI; 21484} 21485/** 21486* The `TwoFingersTouchHandler`s allows the user to rotate the map two fingers 21487* 21488* @group Handlers 21489*/ 21490var TwoFingersTouchRotateHandler = class extends TwoFingersTouchHandler { 21491 reset() { 21492 super.reset(); 21493 delete this._minDiameter; 21494 delete this._startVector; 21495 delete this._vector; 21496 } 21497 _start(points) { 21498 this._startVector = this._vector = points[0].sub(points[1]); 21499 this._minDiameter = points[0].dist(points[1]); 21500 } 21501 _move(points, pinchAround, _e) { 21502 const lastVector = this._vector; 21503 this._vector = points[0].sub(points[1]); 21504 if (!this._active && this._isBelowThreshold(this._vector)) return; 21505 this._active = true; 21506 return { 21507 bearingDelta: getBearingDelta(this._vector, lastVector), 21508 pinchAround 21509 }; 21510 } 21511 _isBelowThreshold(vector) { 21512 this._minDiameter = Math.min(this._minDiameter, vector.mag()); 21513 const circumference = Math.PI * this._minDiameter; 21514 const threshold = ROTATION_THRESHOLD / circumference * 360; 21515 const bearingDeltaSinceStart = getBearingDelta(vector, this._startVector); 21516 return Math.abs(bearingDeltaSinceStart) < threshold; 21517 } 21518}; 21519function isVertical(vector) { 21520 return Math.abs(vector.y) > Math.abs(vector.x); 21521} 21522const ALLOWED_SINGLE_TOUCH_TIME = 100; 21523/** 21524* The `TwoFingersTouchPitchHandler` allows the user to pitch the map by dragging up and down with two fingers. 21525* 21526* @group Handlers 21527*/ 21528var TwoFingersTouchPitchHandler = class extends TwoFingersTouchHandler { 21529 constructor(map) { 21530 super(); 21531 this._currentTouchCount = 0; 21532 this._map = map; 21533 } 21534 reset() { 21535 super.reset(); 21536 this._valid = void 0; 21537 delete this._firstMove; 21538 delete this._lastPoints; 21539 } 21540 touchstart(e, points, mapTouches) { 21541 super.touchstart(e, points, mapTouches); 21542 this._currentTouchCount = mapTouches.length; 21543 } 21544 _start(points) { 21545 this._lastPoints = points; 21546 if (isVertical(points[0].sub(points[1]))) this._valid = false; 21547 } 21548 _move(points, center, e) { 21549 if (this._map.cooperativeGestures.isEnabled() && this._currentTouchCount < 3) return; 21550 const vectorA = points[0].sub(this._lastPoints[0]); 21551 const vectorB = points[1].sub(this._lastPoints[1]); 21552 this._valid = this.gestureBeginsVertically(vectorA, vectorB, e.timeStamp); 21553 if (!this._valid) return; 21554 this._lastPoints = points; 21555 this._active = true; 21556 return { pitchDelta: (vectorA.y + vectorB.y) / 2 * -.5 }; 21557 } 21558 gestureBeginsVertically(vectorA, vectorB, timeStamp) { 21559 if (this._valid !== void 0) return this._valid; 21560 const threshold = 2; 21561 const movedA = vectorA.mag() >= threshold; 21562 const movedB = vectorB.mag() >= threshold; 21563 if (!movedA && !movedB) return; 21564 if (!movedA || !movedB) { 21565 if (this._firstMove === void 0) this._firstMove = timeStamp; 21566 if (timeStamp - this._firstMove < ALLOWED_SINGLE_TOUCH_TIME) return; 21567 else return false; 21568 } 21569 const isSameDirection = vectorA.y > 0 === vectorB.y > 0; 21570 return isVertical(vectorA) && isVertical(vectorB) && isSameDirection; 21571 } 21572}; 21573//#endregion 21574//#region src/ui/handler/keyboard.ts 21575const defaultOptions$5 = { 21576 panStep: 100, 21577 bearingStep: 15, 21578 pitchStep: 10 21579}; 21580/** 21581* The `KeyboardHandler` allows the user to zoom, rotate, and pan the map using 21582* the following keyboard shortcuts: 21583* 21584* - `=` / `+`: Increase the zoom level by 1. 21585* - `Shift-=` / `Shift-+`: Increase the zoom level by 2. 21586* - `-`: Decrease the zoom level by 1. 21587* - `Shift--`: Decrease the zoom level by 2. 21588* - Arrow keys: Pan by 100 pixels. 21589* - `Shift+â¢`: Increase the rotation by 15 degrees. 21590* - `Shift+â `: Decrease the rotation by 15 degrees. 21591* - `Shift+â¡`: Increase the pitch by 10 degrees. 21592* - `Shift+â£`: Decrease the pitch by 10 degrees. 21593* 21594* @group Handlers 21595*/ 21596var KeyboardHandler = class { 21597 /** @internal */ 21598 constructor(map, transformProvider) { 21599 this._tr = transformProvider; 21600 const stepOptions = defaultOptions$5; 21601 this._panStep = stepOptions.panStep; 21602 this._bearingStep = stepOptions.bearingStep; 21603 this._pitchStep = stepOptions.pitchStep; 21604 this._rotationDisabled = false; 21605 } 21606 reset() { 21607 this._active = false; 21608 } 21609 keydown(e) { 21610 if (e.altKey || e.ctrlKey || e.metaKey) return; 21611 let zoomDir = 0; 21612 let bearingDir = 0; 21613 let pitchDir = 0; 21614 let xDir = 0; 21615 let yDir = 0; 21616 switch (e.keyCode) { 21617 case 61: 21618 case 107: 21619 case 171: 21620 case 187: 21621 zoomDir = 1; 21622 break; 21623 case 189: 21624 case 109: 21625 case 173: 21626 zoomDir = -1; 21627 break; 21628 case 37: 21629 if (e.shiftKey) bearingDir = -1; 21630 else { 21631 e.preventDefault(); 21632 xDir = -1; 21633 } 21634 break; 21635 case 39: 21636 if (e.shiftKey) bearingDir = 1; 21637 else { 21638 e.preventDefault(); 21639 xDir = 1; 21640 } 21641 break; 21642 case 38: 21643 if (e.shiftKey) pitchDir = 1; 21644 else { 21645 e.preventDefault(); 21646 yDir = -1; 21647 } 21648 break; 21649 case 40: 21650 if (e.shiftKey) pitchDir = -1; 21651 else { 21652 e.preventDefault(); 21653 yDir = 1; 21654 } 21655 break; 21656 default: return; 21657 } 21658 if (this._rotationDisabled) { 21659 bearingDir = 0; 21660 pitchDir = 0; 21661 } 21662 return { cameraAnimation: (map) => { 21663 const tr = this._tr; 21664 map.easeTo({ 21665 duration: 300, 21666 easeId: "keyboardHandler", 21667 easing: easeOut, 21668 zoom: zoomDir ? evaluateZoomSnap(tr.zoom + zoomDir * (e.shiftKey ? 2 : 1), map.getZoomSnap()) : tr.zoom, 21669 bearing: tr.bearing + bearingDir * this._bearingStep, 21670 pitch: tr.pitch + pitchDir * this._pitchStep, 21671 offset: [-xDir * this._panStep, -yDir * this._panStep], 21672 center: tr.center 21673 }, { originalEvent: e }); 21674 } }; 21675 } 21676 /** 21677 * Enables the "keyboard rotate and zoom" interaction. 21678 * 21679 * @example 21680 * ```ts 21681 * map.keyboard.enable(); 21682 * ``` 21683 */ 21684 enable() { 21685 this._enabled = true; 21686 } 21687 /** 21688 * Disables the "keyboard rotate and zoom" interaction. 21689 * 21690 * @example 21691 * ```ts 21692 * map.keyboard.disable(); 21693 * ``` 21694 */ 21695 disable() { 21696 this._enabled = false; 21697 this.reset(); 21698 } 21699 /** 21700 * Returns a Boolean indicating whether the "keyboard rotate and zoom" 21701 * interaction is enabled. 21702 * 21703 * @returns `true` if the "keyboard rotate and zoom" 21704 * interaction is enabled. 21705 */ 21706 isEnabled() { 21707 return this._enabled; 21708 } 21709 /** 21710 * Returns true if the handler is enabled and has detected the start of a 21711 * zoom/rotate gesture. 21712 * 21713 * @returns `true` if the handler is enabled and has detected the 21714 * start of a zoom/rotate gesture. 21715 */ 21716 isActive() { 21717 return this._active; 21718 } 21719 /** 21720 * Disables the "keyboard pan/rotate" interaction, leaving the 21721 * "keyboard zoom" interaction enabled. 21722 * 21723 * @example 21724 * ```ts 21725 * map.keyboard.disableRotation(); 21726 * ``` 21727 */ 21728 disableRotation() { 21729 this._rotationDisabled = true; 21730 } 21731 /** 21732 * Enables the "keyboard pan/rotate" interaction. 21733 * 21734 * @example 21735 * ```ts 21736 * map.keyboard.enable(); 21737 * map.keyboard.enableRotation(); 21738 * ``` 21739 */ 21740 enableRotation() { 21741 this._rotationDisabled = false; 21742 } 21743}; 21744function easeOut(t) { 21745 return t * (2 - t); 21746} 21747//#endregion 21748//#region src/ui/handler/scroll_zoom.ts 21749const wheelZoomDelta = 4.000244140625; 21750const defaultZoomRate$1 = 1 / 100; 21751const wheelZoomRate = 1 / 450; 21752const maxScalePerFrame = 2; 21753const wheelEventTimeDiffAdjustment = 5; 21754/** 21755* The `ScrollZoomHandler` allows the user to zoom the map by scrolling. 21756* 21757* @group Handlers 21758*/ 21759var ScrollZoomHandler = class { 21760 /** @internal */ 21761 constructor(map, triggerRenderFrame, transformProvider) { 21762 this._onTimeout = (initialEvent) => { 21763 this._type = "wheel"; 21764 this._delta -= this._lastValue; 21765 if (!this._active) this._start(initialEvent); 21766 }; 21767 this._map = map; 21768 this._tr = transformProvider; 21769 this._triggerRenderFrame = triggerRenderFrame; 21770 this._delta = 0; 21771 this._defaultZoomRate = defaultZoomRate$1; 21772 this._wheelZoomRate = wheelZoomRate; 21773 } 21774 /** 21775 * Set the zoom rate of a trackpad 21776 * @param zoomRate - 1/100 The rate used to scale trackpad movement to a zoom value. 21777 * @example 21778 * Speed up trackpad zoom 21779 * ```ts 21780 * map.scrollZoom.setZoomRate(1/25); 21781 * ``` 21782 */ 21783 setZoomRate(zoomRate) { 21784 this._defaultZoomRate = zoomRate; 21785 } 21786 /** 21787 * Set the zoom rate of a mouse wheel 21788 * @param wheelZoomRate - 1/450 The rate used to scale mouse wheel movement to a zoom value. 21789 * @example 21790 * Slow down zoom of mouse wheel 21791 * ```ts 21792 * map.scrollZoom.setWheelZoomRate(1/600); 21793 * ``` 21794 */ 21795 setWheelZoomRate(wheelZoomRate) { 21796 this._wheelZoomRate = wheelZoomRate; 21797 } 21798 /** 21799 * Returns a Boolean indicating whether the "scroll to zoom" interaction is enabled. 21800 * @returns `true` if the "scroll to zoom" interaction is enabled. 21801 */ 21802 isEnabled() { 21803 return !!this._enabled; 21804 } 21805 isActive() { 21806 return !!this._active || this._finishTimeout !== void 0; 21807 } 21808 isZooming() { 21809 return !!this._zooming; 21810 } 21811 /** 21812 * Enables the "scroll to zoom" interaction. 21813 * 21814 * @param options - Options object. 21815 * @example 21816 * ```ts 21817 * map.scrollZoom.enable(); 21818 * map.scrollZoom.enable({ around: 'center' }) 21819 * ``` 21820 */ 21821 enable(options) { 21822 if (this.isEnabled()) return; 21823 this._enabled = true; 21824 this._aroundCenter = !!options && options.around === "center"; 21825 } 21826 /** 21827 * Disables the "scroll to zoom" interaction. 21828 * 21829 * @example 21830 * ```ts 21831 * map.scrollZoom.disable(); 21832 * ``` 21833 */ 21834 disable() { 21835 if (!this.isEnabled()) return; 21836 this._enabled = false; 21837 } 21838 /** 21839 * Determines whether or not the gesture is blocked due to cooperativeGestures. 21840 */ 21841 _shouldBePrevented(e) { 21842 if (!this._map.cooperativeGestures.isEnabled()) return false; 21843 return !(e.ctrlKey || this._map.cooperativeGestures.isBypassed(e)); 21844 } 21845 wheel(e) { 21846 if (!this.isEnabled()) return; 21847 if (this._shouldBePrevented(e)) { 21848 this._map.cooperativeGestures.notifyGestureBlocked("wheel_zoom", e); 21849 return; 21850 } 21851 let value = e.deltaMode === WheelEvent.DOM_DELTA_LINE ? e.deltaY * 40 : e.deltaY; 21852 const currentTime = now(), timeDelta = currentTime - (this._lastWheelEventTime || 0); 21853 this._lastWheelEventTime = currentTime; 21854 if (value !== 0 && value % wheelZoomDelta === 0) this._type = "wheel"; 21855 else if (value !== 0 && Math.abs(value) < 4) this._type = "trackpad"; 21856 else if (timeDelta > 400) { 21857 this._type = null; 21858 this._lastValue = value; 21859 this._timeout = setTimeout(this._onTimeout, 40, e); 21860 } else if (!this._type) { 21861 this._type = Math.abs(timeDelta * value) < 200 ? "trackpad" : "wheel"; 21862 if (this._timeout) { 21863 clearTimeout(this._timeout); 21864 this._timeout = null; 21865 value += this._lastValue; 21866 } 21867 } 21868 if (e.shiftKey && value) value = value / 4; 21869 if (this._type) { 21870 this._lastWheelEvent = e; 21871 this._delta -= value; 21872 if (!this._active) this._start(e); 21873 } 21874 e.preventDefault(); 21875 } 21876 _start(e) { 21877 if (!this._delta) return; 21878 this._needsRerender = false; 21879 this._active = true; 21880 if (!this.isZooming()) this._zooming = true; 21881 if (this._finishTimeout) { 21882 clearTimeout(this._finishTimeout); 21883 delete this._finishTimeout; 21884 } 21885 const pos = DOM.mousePos(this._map.getCanvas(), e); 21886 const tr = this._tr; 21887 if (this._aroundCenter) this._aroundPoint = tr.transform.locationToScreenPoint(LngLat.convert(tr.center)); 21888 else this._aroundPoint = pos; 21889 if (!this._needsRerender) { 21890 this._needsRerender = true; 21891 this._triggerRenderFrame(); 21892 } 21893 } 21894 renderFrame() { 21895 if (!this._needsRerender) return; 21896 this._needsRerender = false; 21897 if (!this.isActive()) return; 21898 const tr = this._tr.transform; 21899 if (typeof this._lastExpectedZoom === "number") { 21900 const externalZoomChange = tr.zoom - this._lastExpectedZoom; 21901 if (typeof this._startZoom === "number") this._startZoom += externalZoomChange; 21902 if (typeof this._targetZoom === "number") this._targetZoom += externalZoomChange; 21903 } 21904 if (this._delta !== 0) { 21905 const zoomRate = this._type === "wheel" && Math.abs(this._delta) > wheelZoomDelta ? this._wheelZoomRate : this._defaultZoomRate; 21906 let scale = maxScalePerFrame / (1 + Math.exp(-Math.abs(this._delta * zoomRate))); 21907 if (this._delta < 0 && scale !== 0) scale = 1 / scale; 21908 const fromScale = typeof this._targetZoom !== "number" ? tr.scale : zoomScale(this._targetZoom); 21909 const target = tr.applyConstrain(tr.getCameraLngLat(), scaleZoom(fromScale * scale)).zoom; 21910 const zoomSnap = this._map.getZoomSnap(); 21911 if (this._type === "wheel" && zoomSnap > 0) { 21912 const currentSnapped = evaluateZoomSnap(tr.zoom, zoomSnap); 21913 this._targetZoom = evaluateZoomSnap(target, zoomSnap, target - currentSnapped); 21914 } else this._targetZoom = target; 21915 if (this._type === "wheel") { 21916 this._startZoom = tr.zoom; 21917 this._easing = this._smoothOutEasing(200); 21918 } 21919 this._delta = 0; 21920 } 21921 const targetZoom = typeof this._targetZoom !== "number" ? tr.zoom : this._targetZoom; 21922 const startZoom = this._startZoom; 21923 const easing = this._easing; 21924 let finished = false; 21925 let zoom; 21926 if (this._type === "wheel" && startZoom && easing) { 21927 const lastWheelEventTimeDiff = now() - this._lastWheelEventTime; 21928 const t = Math.min((lastWheelEventTimeDiff + wheelEventTimeDiffAdjustment) / 200, 1); 21929 const k = easing(t); 21930 zoom = interpolateFactory.number(startZoom, targetZoom, k); 21931 if (t < 1) this._needsRerender = true; 21932 else finished = true; 21933 } else { 21934 zoom = targetZoom; 21935 finished = true; 21936 } 21937 this._active = true; 21938 if (finished) { 21939 this._active = false; 21940 this._finishTimeout = setTimeout(() => { 21941 this._zooming = false; 21942 this._triggerRenderFrame(); 21943 delete this._targetZoom; 21944 delete this._lastExpectedZoom; 21945 delete this._finishTimeout; 21946 }, 200); 21947 } 21948 this._lastExpectedZoom = zoom; 21949 return { 21950 noInertia: true, 21951 needsRenderFrame: !finished, 21952 zoomDelta: zoom - tr.zoom, 21953 around: this._aroundPoint, 21954 originalEvent: this._lastWheelEvent 21955 }; 21956 } 21957 _smoothOutEasing(duration) { 21958 let easing = defaultEasing; 21959 if (this._prevEase) { 21960 const currentEase = this._prevEase; 21961 const t = (now() - currentEase.start) / currentEase.duration; 21962 const speed = currentEase.easing(t + .01) - currentEase.easing(t); 21963 const x = .27 / Math.sqrt(speed * speed + 1e-4) * .01; 21964 const y = Math.sqrt(.0729 - x * x); 21965 easing = bezier(x, y, .25, 1); 21966 } 21967 this._prevEase = { 21968 start: now(), 21969 duration, 21970 easing 21971 }; 21972 return easing; 21973 } 21974 reset() { 21975 this._active = false; 21976 this._zooming = false; 21977 delete this._targetZoom; 21978 delete this._lastExpectedZoom; 21979 if (this._finishTimeout) { 21980 clearTimeout(this._finishTimeout); 21981 delete this._finishTimeout; 21982 } 21983 } 21984}; 21985//#endregion 21986//#region src/ui/handler/shim/dblclick_zoom.ts 21987/** 21988* The `DoubleClickZoomHandler` allows the user to zoom the map at a point by 21989* double clicking or double tapping. 21990* 21991* @group Handlers 21992*/ 21993var DoubleClickZoomHandler = class { 21994 /** @internal */ 21995 constructor(clickZoom, TapZoom) { 21996 this._clickZoom = clickZoom; 21997 this._tapZoom = TapZoom; 21998 } 21999 /** 22000 * Enables the "double click to zoom" interaction. 22001 * 22002 * @example 22003 * ```ts 22004 * map.doubleClickZoom.enable(); 22005 * ``` 22006 */ 22007 enable() { 22008 this._clickZoom.enable(); 22009 this._tapZoom.enable(); 22010 } 22011 /** 22012 * Disables the "double click to zoom" interaction. 22013 * 22014 * @example 22015 * ```ts 22016 * map.doubleClickZoom.disable(); 22017 * ``` 22018 */ 22019 disable() { 22020 this._clickZoom.disable(); 22021 this._tapZoom.disable(); 22022 } 22023 /** 22024 * Returns a Boolean indicating whether the "double click to zoom" interaction is enabled. 22025 * 22026 * @returns `true` if the "double click to zoom" interaction is enabled. 22027 */ 22028 isEnabled() { 22029 return this._clickZoom.isEnabled() && this._tapZoom.isEnabled(); 22030 } 22031 /** 22032 * Returns a Boolean indicating whether the "double click to zoom" interaction is active, i.e. currently being used. 22033 * 22034 * @returns `true` if the "double click to zoom" interaction is active. 22035 */ 22036 isActive() { 22037 return this._clickZoom.isActive() || this._tapZoom.isActive(); 22038 } 22039}; 22040//#endregion 22041//#region src/ui/handler/click_zoom.ts 22042/** 22043* The `ClickZoomHandler` allows the user to zoom the map at a point b
vendor: 4,838 bytes, lines 22043-22241
22043y double clicking 22044* It is used by other handlers 22045*/ 22046var ClickZoomHandler = class { 22047 /** @internal */ 22048 constructor(map, transformProvider) { 22049 this._tr = transformProvider; 22050 this.reset(); 22051 } 22052 reset() { 22053 this._active = false; 22054 } 22055 dblclick(e, point) { 22056 e.preventDefault(); 22057 return { cameraAnimation: (map) => { 22058 map.easeTo({ 22059 duration: 300, 22060 zoom: evaluateZoomSnap(this._tr.zoom + (e.shiftKey ? -1 : 1), map.getZoomSnap()), 22061 around: this._tr.unproject(point) 22062 }, { originalEvent: e }); 22063 } }; 22064 } 22065 enable() { 22066 this._enabled = true; 22067 } 22068 disable() { 22069 this._enabled = false; 22070 this.reset(); 22071 } 22072 isEnabled() { 22073 return this._enabled; 22074 } 22075 isActive() { 22076 return this._active; 22077 } 22078}; 22079//#endregion 22080//#region src/ui/handler/tap_drag_zoom.ts 22081const defaultZoomRate = 1; 22082/** 22083* A `TapDragZoomHandler` allows the user to zoom the map at a point by double tapping. It also allows the user pan the map by dragging. 22084*/ 22085var TapDragZoomHandler = class { 22086 constructor() { 22087 this._tap = new TapRecognizer({ 22088 numTouches: 1, 22089 numTaps: 1 22090 }); 22091 this._zoomRate = defaultZoomRate; 22092 this.reset(); 22093 } 22094 setZoomRate(zoomRate) { 22095 this._zoomRate = zoomRate ?? defaultZoomRate; 22096 } 22097 reset() { 22098 this._active = false; 22099 delete this._swipePoint; 22100 delete this._swipeTouch; 22101 delete this._tapTime; 22102 delete this._tapPoint; 22103 this._tap.reset(); 22104 } 22105 touchstart(e, points, mapTouches) { 22106 if (this._swipePoint) return; 22107 if (!this._tapTime) this._tap.touchstart(e, points, mapTouches); 22108 else { 22109 const swipePoint = points[0]; 22110 const soonEnough = e.timeStamp - this._tapTime < 500; 22111 const closeEnough = this._tapPoint.dist(swipePoint) < 30; 22112 if (!soonEnough || !closeEnough) this.reset(); 22113 else if (mapTouches.length > 0) { 22114 this._swipePoint = swipePoint; 22115 this._swipeTouch = mapTouches[0].identifier; 22116 } 22117 } 22118 } 22119 touchmove(e, points, mapTouches) { 22120 if (!this._tapTime) this._tap.touchmove(e, points, mapTouches); 22121 else if (this._swipePoint) { 22122 if (mapTouches[0].identifier !== this._swipeTouch) return; 22123 const newSwipePoint = points[0]; 22124 const dist = newSwipePoint.y - this._swipePoint.y; 22125 this._swipePoint = newSwipePoint; 22126 e.preventDefault(); 22127 this._active = true; 22128 return { zoomDelta: dist / 128 * this._zoomRate }; 22129 } 22130 } 22131 touchend(e, points, mapTouches) { 22132 if (!this._tapTime) { 22133 const point = this._tap.touchend(e, points, mapTouches); 22134 if (point) { 22135 this._tapTime = e.timeStamp; 22136 this._tapPoint = point; 22137 } 22138 } else if (this._swipePoint) { 22139 if (mapTouches.length === 0) this.reset(); 22140 } 22141 } 22142 touchcancel() { 22143 this.reset(); 22144 } 22145 enable() { 22146 this._enabled = true; 22147 } 22148 disable() { 22149 this._enabled = false; 22150 this.reset(); 22151 } 22152 isEnabled() { 22153 return this._enabled; 22154 } 22155 isActive() { 22156 return this._active; 22157 } 22158}; 22159//#endregion 22160//#region src/ui/handler/shim/drag_pan.ts 22161/** 22162* The `DragPanHandler` allows the user to pan the map by clicking and dragging 22163* the cursor. 22164* 22165* @group Handlers 22166*/ 22167var DragPanHandler = class { 22168 /** @internal */ 22169 constructor(el, mousePan, touchPan) { 22170 this._el = el; 22171 this._mousePan = mousePan; 22172 this._touchPan = touchPan; 22173 } 22174 /** 22175 * Enables the "drag to pan" interaction. 22176 * 22177 * @param options - Options object 22178 * @example 22179 * ```ts 22180 * map.dragPan.enable(); 22181 * map.dragPan.enable({ 22182 * linearity: 0.3, 22183 * easing: bezier(0, 0, 0.3, 1), 22184 * maxSpeed: 1400, 22185 * deceleration: 2500, 22186 * }); 22187 * ``` 22188 */ 22189 enable(options) { 22190 this._inertiaOptions = options || {}; 22191 this._mousePan.enable(); 22192 this._touchPan.enable(); 22193 this._el.classList.add("maplibregl-touch-drag-pan"); 22194 } 22195 /** 22196 * Disables the "drag to pan" interaction. 22197 * 22198 * @example 22199 * ```ts 22200 * map.dragPan.disable(); 22201 * ``` 22202 */ 22203 disable() { 22204 this._mousePan.disable(); 22205 this._touchPan.disable(); 22206 this._el.classList.remove("maplibregl-touch-drag-pan"); 22207 } 22208 /** 22209 * Returns a Boolean indicating whether the "drag to pan" interaction is enabled. 22210 * 22211 * @returns `true` if the "drag to pan" interaction is enabled. 22212 */ 22213 isEnabled() { 22214 return this._mousePan.isEnabled() && this._touchPan.isEnabled(); 22215 } 22216 /** 22217 * Returns a Boolean indicating whether the "drag to pan" interaction is active, i.e. currently being used. 22218 * 22219 * @returns `true` if the "drag to pan" interaction is active. 22220 */ 22221 isActive() { 22222 return this._mousePan.isActive() || this._touchPan.isActive(); 22223 } 22224}; 22225//#endregion 22226//#region src/ui/handler/shim/drag_rotate.ts 22227/** 22228* The `DragRotateHandler` allows the user to rotate the map by clicking and 22229* dragging the cursor while holding the right mouse button or `ctrl` key. 22230* 22231* @group Handlers 22232*/ 22233var DragRotateHandler = class { 22234 /** @internal */ 22235 constructor(options, mouseRotate, mousePitch, mouseRoll) { 22236 this._pitchWithRotate = options.pitchWithRotate; 22237 this._rollEnabled = options.rollEnabled; 22238 this._mouseRotate = mouseRotate; 22239 this._mousePitch = mousePitch; 22240 this._mouseRoll = mouseRoll; 22241 }
vendor: 8,930 bytes, lines 22242-22547
22242 /** 22243 * Enables the "drag to rotate" interaction. 22244 * 22245 * @example 22246 * ```ts 22247 * map.dragRotate.enable(); 22248 * ``` 22249 */ 22250 enable() { 22251 this._mouseRotate.enable(); 22252 if (this._pitchWithRotate) this._mousePitch.enable(); 22253 if (this._rollEnabled) this._mouseRoll.enable(); 22254 } 22255 /** 22256 * Disables the "drag to rotate" interaction. 22257 * 22258 * @example 22259 * ```ts 22260 * map.dragRotate.disable(); 22261 * ``` 22262 */ 22263 disable() { 22264 this._mouseRotate.disable(); 22265 this._mousePitch.disable(); 22266 this._mouseRoll.disable(); 22267 } 22268 /** 22269 * Returns a Boolean indicating whether the "drag to rotate" interaction is enabled. 22270 * 22271 * @returns `true` if the "drag to rotate" interaction is enabled. 22272 */ 22273 isEnabled() { 22274 return this._mouseRotate.isEnabled() && (!this._pitchWithRotate || this._mousePitch.isEnabled()) && (!this._rollEnabled || this._mouseRoll.isEnabled()); 22275 } 22276 /** 22277 * Returns a Boolean indicating whether the "drag to rotate" interaction is active, i.e. currently being used. 22278 * 22279 * @returns `true` if the "drag to rotate" interaction is active. 22280 */ 22281 isActive() { 22282 return this._mouseRotate.isActive() || this._mousePitch.isActive() || this._mouseRoll.isActive(); 22283 } 22284}; 22285//#endregion 22286//#region src/ui/handler/shim/two_fingers_touch.ts 22287/** 22288* The `TwoFingersTouchZoomRotateHandler` allows the user to zoom and rotate the map by 22289* pinching on a touchscreen. 22290* 22291* They can zoom with one finger by double tapping and dragging. On the second tap, 22292* hold the finger down and drag up or down to zoom in or out. 22293* 22294* @group Handlers 22295*/ 22296var TwoFingersTouchZoomRotateHandler = class { 22297 /** @internal */ 22298 constructor(el, touchZoom, touchRotate, tapDragZoom) { 22299 this._el = el; 22300 this._touchZoom = touchZoom; 22301 this._touchRotate = touchRotate; 22302 this._tapDragZoom = tapDragZoom; 22303 this._rotationDisabled = false; 22304 this._enabled = true; 22305 } 22306 /** 22307 * Enables the "pinch to rotate and zoom" interaction. 22308 * 22309 * @param options - Options object. 22310 * 22311 * @example 22312 * ```ts 22313 * map.touchZoomRotate.enable(); 22314 * map.touchZoomRotate.enable({ around: 'center' }); 22315 * ``` 22316 */ 22317 enable(options) { 22318 this._touchZoom.enable(options); 22319 if (!this._rotationDisabled) this._touchRotate.enable(options); 22320 this._tapDragZoom.enable(); 22321 this._el.classList.add("maplibregl-touch-zoom-rotate"); 22322 } 22323 /** 22324 * Disables the "pinch to rotate and zoom" interaction. 22325 * 22326 * @example 22327 * ```ts 22328 * map.touchZoomRotate.disable(); 22329 * ``` 22330 */ 22331 disable() { 22332 this._touchZoom.disable(); 22333 this._touchRotate.disable(); 22334 this._tapDragZoom.disable(); 22335 this._el.classList.remove("maplibregl-touch-zoom-rotate"); 22336 } 22337 /** 22338 * Returns a Boolean indicating whether the "pinch to rotate and zoom" interaction is enabled. 22339 * 22340 * @returns `true` if the "pinch to rotate and zoom" interaction is enabled. 22341 */ 22342 isEnabled() { 22343 return this._touchZoom.isEnabled() && (this._rotationDisabled || this._touchRotate.isEnabled()) && this._tapDragZoom.isEnabled(); 22344 } 22345 /** 22346 * Returns true if the handler is enabled and has detected the start of a zoom/rotate gesture. 22347 * 22348 * @returns `true` if the handler is active, `false` otherwise 22349 */ 22350 isActive() { 22351 return this._touchZoom.isActive() || this._touchRotate.isActive() || this._tapDragZoom.isActive(); 22352 } 22353 /** 22354 * Sets the zoom rate of touch gestures. 22355 * @param zoomRate - 1 The rate used to scale touch movement to a zoom value. Set to `undefined` to restore the default. 22356 * @example 22357 * Slow down touch zoom 22358 * ```ts 22359 * map.touchZoomRotate.setZoomRate(0.5); 22360 * ``` 22361 */ 22362 setZoomRate(zoomRate) { 22363 this._touchZoom.setZoomRate(zoomRate); 22364 this._tapDragZoom.setZoomRate(zoomRate); 22365 } 22366 /** 22367 * Sets the threshold before a pinch gesture starts zooming. 22368 * @param zoomThreshold - 0.1 The minimum zoom delta before the pinch gesture becomes active. Set to `undefined` to restore the default. 22369 * @example 22370 * Make pinch zoom less sensitive 22371 * ```ts 22372 * map.touchZoomRotate.setZoomThreshold(0.3); 22373 * ``` 22374 */ 22375 setZoomThreshold(zoomThreshold) { 22376 this._touchZoom.setZoomThreshold(zoomThreshold); 22377 } 22378 /** 22379 * Disables the "pinch to rotate" interaction, leaving the "pinch to zoom" 22380 * interaction enabled. 22381 * 22382 * @example 22383 * ```ts 22384 * map.touchZoomRotate.disableRotation(); 22385 * ``` 22386 */ 22387 disableRotation() { 22388 this._rotationDisabled = true; 22389 this._touchRotate.disable(); 22390 } 22391 /** 22392 * Enables the "pinch to rotate" interaction. 22393 * 22394 * @example 22395 * ```ts 22396 * map.touchZoomRotate.enable(); 22397 * map.touchZoomRotate.enableRotation(); 22398 * ``` 22399 */ 22400 enableRotation() { 22401 this._rotationDisabled = false; 22402 if (this._touchZoom.isEnabled()) this._touchRotate.enable(); 22403 } 22404}; 22405//#endregion 22406//#region src/ui/handler/cooperative_gestures.ts 22407/** 22408* A `CooperativeGestureHandler` is a control that adds cooperative gesture info when user tries to zoom in/out. 22409* 22410* When the CooperativeGestureHandler blocks a gesture, it will emit a `cooperativegestureprevented` event. 22411* 22412* @group Handlers 22413* 22414* @example 22415* ```ts 22416* const map = new Map({ 22417* cooperativeGestures: true 22418* }); 22419* ``` 22420* @see [Example: cooperative gestures](https://maplibre.org/maplibre-gl-js/docs/examples/cooperative-gestures/) 22421**/ 22422var CooperativeGesturesHandler = class { 22423 constructor(map, options) { 22424 this._bypassKey = navigator.userAgent.includes("Mac") ? "metaKey" : "ctrlKey"; 22425 this._map = map; 22426 this._options = options; 22427 this._enabled = false; 22428 } 22429 isActive() { 22430 return false; 22431 } 22432 reset() {} 22433 _setupUI() { 22434 if (this._container) return; 22435 const mapCanvasContainer = this._map.getCanvasContainer(); 22436 mapCanvasContainer.classList.add("maplibregl-cooperative-gestures"); 22437 this._container = DOM.create("div", "maplibregl-cooperative-gesture-screen", mapCanvasContainer); 22438 let desktopMessage = this._map._getUIString("CooperativeGesturesHandler.WindowsHelpText"); 22439 if (this._bypassKey === "metaKey") desktopMessage = this._map._getUIString("CooperativeGesturesHandler.MacHelpText"); 22440 const mobileMessage = this._map._getUIString("CooperativeGesturesHandler.MobileHelpText"); 22441 const desktopDiv = document.createElement("div"); 22442 desktopDiv.className = "maplibregl-desktop-message"; 22443 desktopDiv.textContent = desktopMessage; 22444 this._container.appendChild(desktopDiv); 22445 const mobileDiv = document.createElement("div"); 22446 mobileDiv.className = "maplibregl-mobile-message"; 22447 mobileDiv.textContent = mobileMessage; 22448 this._container.appendChild(mobileDiv); 22449 this._container.setAttribute("aria-hidden", "true"); 22450 } 22451 _destroyUI() { 22452 if (this._container) { 22453 this._container.remove(); 22454 this._map.getCanvasContainer().classList.remove("maplibregl-cooperative-gestures"); 22455 } 22456 delete this._container; 22457 } 22458 enable() { 22459 this._setupUI(); 22460 this._enabled = true; 22461 } 22462 disable() { 22463 this._enabled = false; 22464 this._destroyUI(); 22465 } 22466 isEnabled() { 22467 return this._enabled; 22468 } 22469 isBypassed(event) { 22470 return event[this._bypassKey]; 22471 } 22472 notifyGestureBlocked(gestureType, originalEvent) { 22473 if (!this._enabled) return; 22474 this._map.fire(new MapLibreEvent("cooperativegestureprevented", { 22475 gestureType, 22476 originalEvent 22477 })); 22478 this._container.classList.add("maplibregl-show"); 22479 setTimeout(() => { 22480 this._container.classList.remove("maplibregl-show"); 22481 }, 100); 22482 } 22483}; 22484//#endregion 22485//#region src/ui/handler/transform-provider.ts 22486/** 22487* @internal 22488* Shared utilities for the Handler classes to access the correct camera state. 22489* If Camera.transformCameraUpdate is specified or terrain is enabled, the 22490* "desired state" of camera may differ from the state used for rendering. The 22491* handlers need the "desired state" to track accumulated changes. 22492*/ 22493var TransformProvider = class { 22494 constructor(camera) { 22495 this._camera = camera; 22496 } 22497 get transform() { 22498 return this._camera._requestedCameraState || this._camera.transform; 22499 } 22500 get center() { 22501 return { 22502 lng: this.transform.center.lng, 22503 lat: this.transform.center.lat 22504 }; 22505 } 22506 get zoom() { 22507 return this.transform.zoom; 22508 } 22509 get pitch() { 22510 return this.transform.pitch; 22511 } 22512 get bearing() { 22513 return this.transform.bearing; 22514 } 22515 unproject(point) { 22516 return this.transform.screenPointToLocation(Point.convert(point), this._camera.terrain); 22517 } 22518}; 22519//#endregion 22520//#region src/ui/handler_manager.ts 22521const isMoving = (p) => p.zoom || p.drag || p.roll || p.pitch || p.rotate; 22522/** 22523* A terrain gesture's anchor plane must stay below this fraction of the camera's 22524* altitude over the center-elevation plane, or the ray-plane solve degenerates. 22525*/ 22526const TERRAIN_ANCHOR_MAX_CAMERA_ALTITUDE_FRACTION = .9; 22527var RenderFrameEvent = class extends Event {}; 22528function hasChange(result) { 22529 return result.panDelta?.mag() || result.zoomDelta || result.bearingDelta || result.pitchDelta || result.rollDelta; 22530} 22531var HandlerManager = class { 22532 /** 22533 * @internal 22534 * The document that contains the map container element, for cross-window support. 22535 */ 22536 get _ownerDocument() { 22537 return this._el?.ownerDocument || document; 22538 } 22539 /** 22540 * @internal 22541 * The window that contains the map container element, for cross-window support. 22542 */ 22543 get _ownerWindow() { 22544 return this._el?.ownerDocument?.defaultView || window; 22545 } 22546 constructor(map, camera, options) { 22547 this._terrainGesture
vendor: 9,089 bytes, lines 22547-22818
22547AnchorElevation = null; 22548 this.handleWindowEvent = (e) => { 22549 this.handleEvent(e, `${e.type}Window`); 22550 }; 22551 this.handleEvent = (e, eventName) => { 22552 if (e.type === "blur") { 22553 this.stop(true); 22554 return; 22555 } 22556 this._updatingCamera = true; 22557 const inputEvent = e.type === "renderFrame" ? void 0 : e; 22558 const mergedHandlerResult = { needsRenderFrame: false }; 22559 const eventsInProgress = {}; 22560 const activeHandlers = {}; 22561 for (const { handlerName, handler, allowed } of this._handlers) { 22562 if (!handler.isEnabled()) continue; 22563 let data; 22564 if (this._blockedByActive(activeHandlers, allowed, handlerName)) handler.reset(); 22565 else if (handler[eventName || e.type]) { 22566 if (isPointableEvent(e, eventName || e.type)) { 22567 const point = DOM.mousePos(this._map.getCanvas(), e); 22568 data = handler[eventName || e.type](e, point); 22569 } else if (isTouchableEvent(e, eventName || e.type)) { 22570 const eventTouches = e.touches; 22571 const mapTouches = this._getMapTouches(eventTouches); 22572 const points = DOM.touchPos(this._map.getCanvas(), mapTouches); 22573 data = handler[eventName || e.type](e, points, mapTouches); 22574 } else if (!isTouchableOrPointableType(eventName || e.type)) data = handler[eventName || e.type](e); 22575 this.mergeHandlerResult(mergedHandlerResult, eventsInProgress, data, handlerName, inputEvent); 22576 if (data?.needsRenderFrame) this._triggerRenderFrame(); 22577 } 22578 if (data || handler.isActive()) activeHandlers[handlerName] = handler; 22579 } 22580 const deactivatedHandlers = {}; 22581 for (const name in this._previousActiveHandlers) if (!activeHandlers[name]) deactivatedHandlers[name] = inputEvent; 22582 this._previousActiveHandlers = activeHandlers; 22583 if (Object.keys(deactivatedHandlers).length || hasChange(mergedHandlerResult)) { 22584 this._changes.push([ 22585 mergedHandlerResult, 22586 eventsInProgress, 22587 deactivatedHandlers 22588 ]); 22589 this._triggerRenderFrame(); 22590 } 22591 if (Object.keys(activeHandlers).length || hasChange(mergedHandlerResult)) this._camera.stop(true); 22592 this._updatingCamera = false; 22593 const { cameraAnimation } = mergedHandlerResult; 22594 if (cameraAnimation) { 22595 this._inertia.clear(); 22596 this._fireEvents({}, {}, true); 22597 this._changes = []; 22598 cameraAnimation(this._map); 22599 } 22600 }; 22601 this._map = map; 22602 this._camera = camera; 22603 this._transformProvider = new TransformProvider(this._camera); 22604 this._el = this._map.getCanvasContainer(); 22605 this._handlers = []; 22606 this._handlersById = {}; 22607 this._changes = []; 22608 this._inertia = new HandlerInertia(map); 22609 this._bearingSnap = options.bearingSnap; 22610 this._previousActiveHandlers = {}; 22611 this._eventsInProgress = {}; 22612 this._addDefaultHandlers(options); 22613 const el = this._el; 22614 this._listeners = [ 22615 [ 22616 el, 22617 "touchstart", 22618 { passive: true } 22619 ], 22620 [ 22621 el, 22622 "touchmove", 22623 { passive: false } 22624 ], 22625 [ 22626 el, 22627 "touchend", 22628 void 0 22629 ], 22630 [ 22631 el, 22632 "touchcancel", 22633 void 0 22634 ], 22635 [ 22636 el, 22637 "mousedown", 22638 void 0 22639 ], 22640 [ 22641 el, 22642 "mousemove", 22643 void 0 22644 ], 22645 [ 22646 el, 22647 "mouseup", 22648 void 0 22649 ], 22650 [ 22651 this._ownerDocument, 22652 "mousemove", 22653 { capture: true } 22654 ], 22655 [ 22656 this._ownerDocument, 22657 "mouseup", 22658 void 0 22659 ], 22660 [ 22661 el, 22662 "mouseover", 22663 void 0 22664 ], 22665 [ 22666 el, 22667 "mouseout", 22668 void 0 22669 ], 22670 [ 22671 el, 22672 "dblclick", 22673 void 0 22674 ], 22675 [ 22676 el, 22677 "click", 22678 void 0 22679 ], 22680 [ 22681 el, 22682 "keydown", 22683 { capture: false } 22684 ], 22685 [ 22686 el, 22687 "keyup", 22688 void 0 22689 ], 22690 [ 22691 el, 22692 "wheel", 22693 { passive: false } 22694 ], 22695 [ 22696 el, 22697 "contextmenu", 22698 void 0 22699 ], 22700 [ 22701 this._ownerWindow, 22702 "blur", 22703 void 0 22704 ] 22705 ]; 22706 for (const [target, type, listenerOptions] of this._listeners) target.addEventListener(type, target === this._ownerDocument ? this.handleWindowEvent : this.handleEvent, listenerOptions); 22707 } 22708 destroy() { 22709 for (const [target, type, listenerOptions] of this._listeners) target.removeEventListener(type, target === this._ownerDocument ? this.handleWindowEvent : this.handleEvent, listenerOptions); 22710 } 22711 _addDefaultHandlers(options) { 22712 const map = this._map; 22713 const el = map.getCanvasContainer(); 22714 this._add("mapEvent", new MapEventHandler(map, options)); 22715 const boxZoom = map.boxZoom = new BoxZoomHandler(map, options, this._transformProvider); 22716 this._add("boxZoom", boxZoom); 22717 if (options.interactive && options.boxZoom) boxZoom.enable(); 22718 const cooperativeGestures = map.cooperativeGestures = new CooperativeGesturesHandler(map, options.cooperativeGestures); 22719 this._add("cooperativeGestures", cooperativeGestures); 22720 if (options.cooperativeGestures) cooperativeGestures.enable(); 22721 const tapZoom = new TapZoomHandler(map, this._transformProvider); 22722 const clickZoom = new ClickZoomHandler(map, this._transformProvider); 22723 map.doubleClickZoom = new DoubleClickZoomHandler(clickZoom, tapZoom); 22724 this._add("tapZoom", tapZoom); 22725 this._add("clickZoom", clickZoom); 22726 if (options.interactive && options.doubleClickZoom) map.doubleClickZoom.enable(); 22727 const tapDragZoom = new TapDragZoomHandler(); 22728 this._add("tapDragZoom", tapDragZoom); 22729 const touchPitch = map.touchPitch = new TwoFingersTouchPitchHandler(map); 22730 this._add("touchPitch", touchPitch); 22731 if (options.interactive && options.touchPitch) map.touchPitch.enable(options.touchPitch); 22732 const getCenter = () => map.project(map.getCenter()); 22733 const mouseRotate = generateMouseRotationHandler(options, getCenter); 22734 const mousePitch = generateMousePitchHandler(options); 22735 const mouseRoll = generateMouseRollHandler(options, getCenter); 22736 map.dragRotate = new DragRotateHandler(options, mouseRotate, mousePitch, mouseRoll); 22737 this._add("mouseRotate", mouseRotate, ["mousePitch"]); 22738 this._add("mousePitch", mousePitch, ["mouseRotate", "mouseRoll"]); 22739 this._add("mouseRoll", mouseRoll, ["mousePitch"]); 22740 if (options.interactive && options.dragRotate) map.dragRotate.enable(); 22741 const mousePan = generateMousePanHandler(options); 22742 const touchPan = new TouchPanHandler(options, map); 22743 map.dragPan = new DragPanHandler(el, mousePan, touchPan); 22744 this._add("mousePan", mousePan); 22745 this._add("touchPan", touchPan, ["touchZoom", "touchRotate"]); 22746 if (options.interactive && options.dragPan) map.dragPan.enable(options.dragPan); 22747 const touchRotate = new TwoFingersTouchRotateHandler(); 22748 const touchZoom = new TwoFingersTouchZoomHandler(); 22749 map.touchZoomRotate = new TwoFingersTouchZoomRotateHandler(el, touchZoom, touchRotate, tapDragZoom); 22750 this._add("touchRotate", touchRotate, ["touchPan", "touchZoom"]); 22751 this._add("touchZoom", touchZoom, ["touchPan", "touchRotate"]); 22752 if (options.interactive && options.touchZoomRotate) map.touchZoomRotate.enable(options.touchZoomRotate); 22753 this._add("blockableMapEvent", new BlockableMapEventHandler(map, options), ["touchPan"]); 22754 const scrollZoom = map.scrollZoom = new ScrollZoomHandler(map, () => this._triggerRenderFrame(), this._transformProvider); 22755 this._add("scrollZoom", scrollZoom, ["mousePan"]); 22756 if (options.interactive && options.scrollZoom) map.scrollZoom.enable(options.scrollZoom); 22757 const keyboard = map.keyboard = new KeyboardHandler(map, this._transformProvider); 22758 this._add("keyboard", keyboard); 22759 if (options.interactive && options.keyboard) map.keyboard.enable(); 22760 } 22761 _add(handlerName, handler, allowed) { 22762 this._handlers.push({ 22763 handlerName, 22764 handler, 22765 allowed 22766 }); 22767 this._handlersById[handlerName] = handler; 22768 } 22769 stop(allowEndAnimation) { 22770 if (this._updatingCamera) return; 22771 for (const { handler } of this._handlers) handler.reset(); 22772 this._inertia.clear(); 22773 this._fireEvents({}, {}, allowEndAnimation); 22774 this._changes = []; 22775 } 22776 isActive() { 22777 for (const { handler } of this._handlers) if (handler.isActive()) return true; 22778 return false; 22779 } 22780 isZooming() { 22781 return !!this._eventsInProgress.zoom || this._map.scrollZoom.isZooming(); 22782 } 22783 isRotating() { 22784 return !!this._eventsInProgress.rotate; 22785 } 22786 isMoving() { 22787 return Boolean(isMoving(this._eventsInProgress)) || this.isZooming(); 22788 } 22789 _blockedByActive(activeHandlers, allowed, myName) { 22790 for (const name in activeHandlers) { 22791 if (name === myName) continue; 22792 if (!allowed?.includes(name)) return true; 22793 } 22794 return false; 22795 } 22796 _getMapTouches(touches) { 22797 const mapTouches = []; 22798 for (const t of touches) { 22799 const target = t.target; 22800 if (this._el.contains(target)) mapTouches.push(t); 22801 } 22802 return mapTouches; 22803 } 22804 mergeHandlerResult(mergedHandlerResult, eventsInProgress, handlerResult, name, e) { 22805 if (!handlerResult) return; 22806 extend(mergedHandlerResult, handlerResult); 22807 const eventData = { 22808 handlerName: name, 22809 originalEvent: handlerResult.originalEvent || e 22810 }; 22811 if (handlerResult.zoomDelta !== void 0) eventsInProgress.zoom = eventData; 22812 if (handlerResult.panDelta !== void 0) eventsInProgress.drag = eventData; 22813 if (handlerResult.rollDelta !== void 0) eventsInProgress.roll = eventData; 22814 if (handlerResult.pitchDelta !== void 0) eventsInProgress.pitch = eventData; 22815 if (handlerResult.bearingDelta !== void 0) eventsInProgress.rotate = eventData; 22816 } 22817 _applyChanges() { 22818 const combined = {};
22819 const combinedEventsInProgress = {}; 22820 const combinedDeactivatedHandlers = {}; 22821 for (const [change, eventsInProgress, deactivatedHandlers] of this._changes) { 22822 if (change.panDelta) combined.panDelta = (combined.panDelta || new Point(0, 0))._add(change.panDelta); 22823 if (change.zoomDelta) combined.zoomDelta = (combined.zoomDelta || 0) + change.zoomDelta; 22824 if (change.bearingDelta) combined.bearingDelta = (combined.bearingDelta || 0) + change.bearingDelta; 22825 if (change.pitchDelta) combined.pitchDelta = (combined.pitchDelta || 0) + change.pitchDelta; 22826 if (change.rollDelta) combined.rollDelta = (combined.rollDelta || 0) + change.rollDelta; 22827 if (change.around !== void 0) combined.around = change.around; 22828 if (change.pinchAround !== void 0) combined.pinchAround = change.pinchAround; 22829 if (change.noInertia) combined.noInertia = change.noInertia; 22830 extend(combinedEventsInProgress, eventsInProgress); 22831 extend(combinedDeactivatedHandlers, deactivatedHandlers); 22832 } 22833 this._updateMapTransform(combined, combinedEventsInProgress, combinedDeactivatedHandlers); 22834 this._changes = []; 22835 } 22836 _updateMapTransform(combinedResult, combinedEventsInProgress, deactivatedHandlers) { 22837 const map = this._map; 22838 const tr = this._camera.getTransformForUpdate(); 22839 const terrain = map.terrain; 22840 if (!hasChange(combinedResult) && !(terrain && this._terrainMovement)) { 22841 this._fireEvents(combinedEventsInProgress, deactivatedHandlers, true); 22842 return; 22843 } 22844 this._camera.stop(true); 22845 const { panDelta, zoomDelta, bearingDelta, pitchDelta, rollDelta } = combinedResult; 22846 let { around, aroundOnSurface } = this._resolveAround(combinedResult, terrain, tr); 22847 const aroundElevation = terrain ? this._terrainGestureElevation(terrain, around, aroundOnSurface, tr, combinedEventsInProgress) : void 0; 22848 const deltasForHelper = { 22849 panDelta, 22850 zoomDelta, 22851 rollDelta, 22852 pitchDelta, 22853 bearingDelta, 22854 around, 22855 aroundElevation 22856 }; 22857 if (this._camera.cameraHelper.useGlobeControls && !tr.isPointOnMapSurface(around)) around = tr.centerPoint; 22858 const preZoomAroundLoc = this._computePreZoomAroundLoc(tr, around, panDelta, aroundElevation); 22859 this._handleMapControls({ 22860 terrain, 22861 tr, 22862 deltasForHelper, 22863 preZoomAroundLoc,
22864 combinedEventsInProgress, 22865 panDelta 22866 }); 22867 this._camera.applyUpdatedTransform(tr); 22868 this._map._update(); 22869 if (!combinedResult.noInertia) this._inertia.record(combinedResult); 22870 this._fireEvents(combinedEventsInProgress, deactivatedHandlers, true); 22871 } 22872 /** 22873 * The gesture's anchor point: the pinch midpoint when pinching, otherwise the 22874 * pan's anchor, clamped to the center point when it does not lie on the map. 22875 */ 22876 _resolveAround(combinedResult, terrain, tr) { 22877 let around = combinedResult.pinchAround !== void 0 ? combinedResult.pinchAround : combinedResult.around; 22878 around ||= this._camera.transform.centerPoint; 22879 if (terrain && !tr.isPointOnMapSurface(around)) return { 22880 around: tr.centerPoint, 22881 aroundOnSurface: false 22882 }; 22883 return { 22884 around, 22885 aroundOnSurface: true 22886 }; 22887 } 22888 /** 22889 * The elevation of the plane a terrain gesture is solved on: the elevation of the 22890 * terrain point grabbed at gesture start, captured here on the gesture's first 22891 * frame. Undefined means the gesture is solved at the center's elevation instead â 22892 * when the grabbed terrain is not loaded, the anchor is the center point (a 22893 * center-point anchor is skipped by the camera helper and would lose the pan), or 22894 * the anchor plane is too close to the camera for a stable ray-plane solve. 22895 */ 22896 _terrainGestureElevation(terrain, around, aroundOnSurface, tr, combinedEventsInProgress) { 22897 if (!aroundOnSurface) return; 22898 if (!this._terrainMovement && (combinedEventsInProgress.drag || combinedEventsInProgress.zoom)) { 22899 const anchor = tr.screenTerrainPointToMercatorCoordinate(around, terrain); 22900 this._terrainGestureAnchorElevation = anchor ? anchor.z : null; 22901 } 22902 if (this._terrainGestureAnchorElevation === null) return; 22903 const elevation = this._terrainGestureAnchorElevation; 22904 if (around.distSqr(tr.centerPoint) < .01) return; 22905 if (elevation - tr.elevation >= TERRAIN_ANCHOR_MAX_CAMERA_ALTITUDE_FRACTION * (tr.getCameraAltitude() - tr.elevation)) return; 22906 return elevation; 22907 } 22908 /** 22909 * The location that was under `around` before this frame's deltas â the point the 22910 * camera helper re-anchors after zooming â solved at `aroundElevation` when a 22911 * terrain gesture provides one. When rotating about the center point, the 22912 * transform's center is used directly to avoid numerical issues near the horizon. 22913 */ 22914 _computePreZoomAroundLoc(tr, around, panDelta, aroundElevation) { 22915 if (around.distSqr(tr.centerPoint) < .01) return tr.center; 22916 const aroundPreviousPoint = panDelta ? around.sub(panDelta) : around; 22917 if (aroundElevation !== void 0) return tr.screenPointToLocationAtElevation(aroundPreviousPoint, aroundElevation); 22918 return tr.screenPointToLocation(aroundPreviousPoint); 22919 } 22920 _handleMapControls({ terrain, tr, deltasForHelper, preZoomAroundLoc, combinedEventsInProgress, panDelta }) { 22921 const cameraHelper = this._camera.cameraHelper; 22922 cameraHelper.handleMapControlsRollPitchBearingZoom(deltasForHelper, tr); 22923 if (!terrain) { 22924 cameraHelper.handleMapControlsPan(deltasForHelper, tr, preZoomAroundLoc); 22925 return; 22926 } 22927 if (cameraHelper.useGlobeControls) { 22928 if (!this._terrainMovement && (combinedEventsInProgress.drag || combinedEventsInProgress.zoom)) { 22929 this._terrainMovement = true; 22930 this._camera.elevationFreeze = true; 22931 } 22932 cameraHelper.handleMapControlsPan(deltasForHelper, tr, preZoomAroundLoc); 22933 return; 22934 } 22935 if (!this._terrainMovement && (combinedEventsInProgress.drag || combinedEventsInProgress.zoom)) { 22936 this._terrainMovement = true; 22937 this._camera.elevationFreeze = true; 22938 cameraHelper.handleMapControlsPan(deltasForHelper, tr, preZoomAroundLoc); 22939 return; 22940 } 22941 if (deltasForHelper.aroundElevation === void 0 && combinedEventsInProgress.drag && this._terrainMovement && panDelta) { 22942 tr.setCenter(tr.screenPointToLocation(tr.centerPoint.sub(panDelta))); 22943 return; 22944 } 22945 cameraHelper.handleMapControlsPan(deltasForHelper, tr, preZoomAroundLoc); 22946 } 22947 _fireEvents(newEventsInProgress, deactivatedHandlers, allowEndAnimation) { 22948 const wasMoving = isMoving(this._eventsInProgress); 22949 const nowMoving = isMoving(newEventsInProgress); 22950 const startEvents = {}; 22951 for (const eventName in newEventsInProgress) { 22952 const { originalEvent } = newEventsInProgress[eventName]; 22953 if (!this._eventsInProgress[eventName]) startEvents[`${eventName}start`] = originalEvent; 22954 this._eventsInProgress[eventName] = newEventsInProgress[eventName]; 22955 } 22956 if (!wasMoving && nowMoving) this._fireEvent("movestart", nowMoving.originalEvent); 22957 for (const name in startEvents) this._fireEvent(name, startEvents[name]); 22958 if (nowMoving) this._fireEvent("move", nowMoving.originalEvent); 22959 for (const eventName in newEventsInProgress) { 22960 const { originalEvent } = newEventsInProgress[eventName]; 22961 this._fireEvent(eventName, originalEvent); 22962 } 22963 const endEvents = {}; 22964 let originalEndEvent; 22965 for (const eventName in this._eventsInProgress) { 22966 const { handlerName, originalEvent } = this._eventsInProgress[eventName]; 22967 if (!this._handlersById[handlerName].isActive()) { 22968 delete this._eventsInProgress[eventName]; 22969 originalEndEvent = deactivatedHandlers[handlerName] || originalEvent; 22970 endEvents[`${eventName}
22970end`] = originalEndEvent; 22971 } 22972 } 22973 for (const name in endEvents) this._fireEvent(name, endEvents[name]); 22974 const stillMoving = isMoving(this._eventsInProgress); 22975 const finishedMoving = (wasMoving || nowMoving) && !stillMoving; 22976 if (finishedMoving && this._terrainMovement) { 22977 this._camera.elevationFreeze = false; 22978 this._terrainMovement = false; 22979 this._terrainGestureAnchorElevation = null; 22980 const tr = this._camera.getTransformForUpdate(); 22981 if (this._map.getCenterClampedToGround()) tr.recalculateZoomAndCenter(this._map.terrain); 22982 this._camera.applyUpdatedTransform(tr); 22983 } 22984 if (allowEndAnimation && finishedMoving) { 22985 this._updatingCamera = true; 22986 const inertialEase = this._inertia._onMoveEnd(this._map.dragPan._inertiaOptions); 22987 const shouldSnapToNorth = (bearing) => bearing !== 0 && -this._bearingSnap < bearing && bearing < this._bearingSnap; 22988 if (inertialEase && (inertialEase.essential || !browser.prefersReducedMotion)) { 22989 if (shouldSnapToNorth(inertialEase.bearing || this._map.getBearing())) inertialEase.bearing = 0; 22990 inertialEase.freezeElevation = true; 22991 this._map.easeTo(inertialEase, { originalEvent: originalEndEvent }); 22992 } else { 22993 this._map.fire(new MapMovementEvent("moveend", { originalEvent: originalEndEvent })); 22994 if (shouldSnapToNorth(this._map.getBearing())) this._map.resetNorth(); 22995 } 22996 this._updatingCamera = false; 22997 } 22998 } 22999 _fireEvent(type, e) { 23000 this._map.fire(new MapMovementEvent(type, e ? { originalEvent: e } : {})); 23001 } 23002 _requestFrame() { 23003 this._map.triggerRepaint(); 23004 return this._map._renderTaskQueue.add((timeStamp) => { 23005 delete this._frameId; 23006 this.handleEvent(new RenderFrameEvent("renderFrame", { timeStamp })); 23007 this._applyChanges(); 23008 }); 23009 } 23010 _triggerRenderFrame() { 23011 if (this._frameId === void 0) this._frameId = this._requestFrame(); 23012 } 23013}; 23014//#endregion 23015//#region src/ui/camera.ts 23016var Camera = class extends Evented { 23017 constructor(options) { 23018 super(); 23019 this._renderFrameCallback = () => { 23020 const t = Math.min((now() - this._easeStart) / this._easeOptions.duration, 1); 23021 this._onEaseFrame(this._easeOptions.easing(t)); 23022 if (t < 1 && this._easeFrameId) this._easeFrameId = this._requestRenderFrame(this._renderFrameCallback); 23023 else this.stop(); 23024 }; 23025 this.transform = new MercatorTransform(); 23026 this.cameraHelper = new MercatorCameraHelper(); 23027 if (options.minZoom !== void 0) this.transform.setMinZoom(options.minZoom); 23028 if (options.maxZoom !== void 0) this.transform.setMaxZoom(options.maxZoom); 23029 if (options.minPitch !== void 0) this.transform.setMinPitch(options.minPitch); 23030 if (options.maxPitch !== void 0) this.transform.setMaxPitch(options.maxPitch); 23031 if (options.renderWorldCopies !== void 0) this.transform.setRenderWorldCopies(options.renderWorldCopies); 23032 if (options.transformConstrain !== null) this.transform.setConstrainOverride(options.transformConstrain); 23033 this._moving = false; 23034 this._zooming = false; 23035 this._bearingSnap = options.bearingSnap; 23036 this._zoomSnap = options.zoomSnap; 23037 this._requestRenderFrame = options.requestRenderFrame; 23038 this._cancelRenderFrame = options.cancelRenderFrame; 23039 this.terrain = options.terrain; 23040 this._centerClampedToGround = options.centerClampedToGround ?? true; 23041 this.transformCameraUpdate = options.transformCameraUpdate ?? null; 23042 this._stopHandlers = options.stopHandlers ?? (() => {}); 23043 this.on("moveend", () => { 23044 delete this._requestedCameraState; 23045 }
vendor: 11,108 bytes, lines 23045-23351
23045); 23046 } 23047 migrateProjection(newTransform, newCameraHelper) { 23048 newTransform.apply(this.transform, true); 23049 this.transform = newTransform; 23050 this.cameraHelper = newCameraHelper; 23051 if (this._requestedCameraState) { 23052 const requestedCameraState = newTransform.clone(); 23053 requestedCameraState.apply(this._requestedCameraState, true); 23054 this._requestedCameraState = requestedCameraState; 23055 } 23056 } 23057 getCenter() { 23058 return new LngLat(this.transform.center.lng, this.transform.center.lat); 23059 } 23060 setCenter(center, eventData) { 23061 return this.jumpTo({ center }, eventData); 23062 } 23063 getCenterElevation() { 23064 return this.transform.elevation; 23065 } 23066 setCenterElevation(elevation, eventData) { 23067 this.jumpTo({ elevation }, eventData); 23068 return this; 23069 } 23070 getCenterClampedToGround() { 23071 return this._centerClampedToGround; 23072 } 23073 setCenterClampedToGround(centerClampedToGround) { 23074 this._centerClampedToGround = centerClampedToGround; 23075 } 23076 panBy(offset, options, eventData) { 23077 offset = Point.convert(offset).mult(-1); 23078 return this.panTo(this.transform.center, extend({ offset }, options), eventData); 23079 } 23080 panTo(lnglat, options, eventData) { 23081 return this.easeTo(extend({ center: lnglat }, options), eventData); 23082 } 23083 getZoom() { 23084 return this.transform.zoom; 23085 } 23086 setZoom(zoom, eventData) { 23087 this.jumpTo({ zoom }, eventData); 23088 return this; 23089 } 23090 zoomTo(zoom, options, eventData) { 23091 return this.easeTo(extend({ zoom }, options), eventData); 23092 } 23093 zoomIn(options, eventData) { 23094 this.zoomTo(evaluateZoomSnap(this.getZoom() + 1, this._zoomSnap), options, eventData); 23095 return this; 23096 } 23097 zoomOut(options, eventData) { 23098 this.zoomTo(evaluateZoomSnap(this.getZoom() - 1, this._zoomSnap), options, eventData); 23099 return this; 23100 } 23101 getVerticalFieldOfView() { 23102 return this.transform.fov; 23103 } 23104 setVerticalFieldOfView(fov, eventData) { 23105 if (fov != this.transform.fov) { 23106 this.transform.setFov(fov); 23107 this.fire(new MapMovementEvent("movestart", eventData)).fire(new MapMovementEvent("move", eventData)).fire(new MapMovementEvent("moveend", eventData)); 23108 } 23109 return this; 23110 } 23111 getBearing() { 23112 return this.transform.bearing; 23113 } 23114 setZoomSnap(snap) { 23115 this._zoomSnap = snap; 23116 return this; 23117 } 23118 getZoomSnap() { 23119 return this._zoomSnap; 23120 } 23121 setBearing(bearing, eventData) { 23122 this.jumpTo({ bearing }, eventData); 23123 return this; 23124 } 23125 getPadding() { 23126 return this.transform.padding; 23127 } 23128 setPadding(padding, eventData) { 23129 this.jumpTo({ padding }, eventData); 23130 return this; 23131 } 23132 rotateTo(bearing, options, eventData) { 23133 return this.easeTo(extend({ bearing }, options), eventData); 23134 } 23135 resetNorth(options, eventData) { 23136 this.rotateTo(0, extend({ duration: 1e3 }, options), eventData); 23137 return this; 23138 } 23139 resetNorthPitch(options, eventData) { 23140 this.easeTo(extend({ 23141 bearing: 0, 23142 pitch: 0, 23143 roll: 0, 23144 duration: 1e3 23145 }, options), eventData); 23146 return this; 23147 } 23148 snapToNorth(options, eventData) { 23149 if (Math.abs(this.getBearing()) < this._bearingSnap) return this.resetNorth(options, eventData); 23150 return this; 23151 } 23152 getPitch() { 23153 return this.transform.pitch; 23154 } 23155 setPitch(pitch, eventData) { 23156 this.jumpTo({ pitch }, eventData); 23157 return this; 23158 } 23159 getRoll() { 23160 return this.transform.roll; 23161 } 23162 setRoll(roll, eventData) { 23163 this.jumpTo({ roll }, eventData); 23164 return this; 23165 } 23166 cameraForBounds(bounds, options) { 23167 bounds = LngLatBounds.convert(bounds).adjustAntiMeridian(); 23168 const bearing = options?.bearing || 0; 23169 return this._cameraForBoxAndBearing(bounds.getNorthWest(), bounds.getSouthEast(), bearing, options); 23170 } 23171 /** 23172 * @internal 23173 * Calculate the center of these two points in the viewport and use 23174 * the highest zoom level up to and including {@link Map.getMaxZoom} that fits 23175 * the AABB defined by these points in the viewport at the specified bearing. 23176 * @param p0 - First point 23177 * @param p1 - Second point 23178 * @param bearing - Desired map bearing at end of animation, in degrees 23179 * @param options - the camera options 23180 * @returns If map is able to fit to provided bounds, returns `center`, `zoom`, and `bearing`. 23181 * If map is unable to fit, method will warn and return undefined. 23182 * @example 23183 * ```ts 23184 * let p0 = [-79, 43]; 23185 * let p1 = [-73, 45]; 23186 * let bearing = 90; 23187 * let newCameraTransform = map._cameraForBoxAndBearing(p0, p1, bearing, { 23188 * padding: {top: 10, bottom:25, left: 15, right: 5} 23189 * }); 23190 * ``` 23191 */ 23192 _cameraForBoxAndBearing(p0, p1, bearing, options) { 23193 const defaultPadding = { 23194 top: 0, 23195 bottom: 0, 23196 right: 0, 23197 left: 0 23198 }; 23199 options = extend({ 23200 padding: defaultPadding, 23201 offset: [0, 0], 23202 maxZoom: this.transform.maxZoom 23203 }, options); 23204 if (typeof options.padding === "number") { 23205 const p = options.padding; 23206 options.padding = { 23207 top: p, 23208 bottom: p, 23209 right: p, 23210 left: p 23211 }; 23212 } 23213 const padding = extend(defaultPadding, options.padding); 23214 options.padding = padding; 23215 const tr = this.transform; 23216 const bounds = new LngLatBounds(p0, p1); 23217 const result = this.cameraHelper.cameraForBoxAndBearing(options, padding, bounds, bearing, tr); 23218 if (result && this._zoomSnap) result.zoom = evaluateZoomSnap(result.zoom, this._zoomSnap, -1); 23219 return result; 23220 } 23221 fitBounds(bounds, options, eventData) { 23222 return this._fitInternal(this.cameraForBounds(bounds, options), options, eventData); 23223 } 23224 fitScreenCoordinates(p0, p1, bearing, options, eventData) { 23225 return this._fitInternal(this._cameraForBoxAndBearing(this.transform.screenPointToLocation(Point.convert(p0)), this.transform.screenPointToLocation(Point.convert(p1)), bearing, options), options, eventData); 23226 } 23227 _fitInternal(calculatedOptions, options, eventData) { 23228 if (!calculatedOptions) return this; 23229 options = extend(calculatedOptions, options); 23230 delete options.padding; 23231 return options.linear ? this.easeTo(options, eventData) : this.flyTo(options, eventData); 23232 } 23233 jumpTo(options, eventData) { 23234 this.stop(); 23235 if (options.zoom !== void 0 && this._zoomSnap) options.zoom = evaluateZoomSnap(options.zoom, this._zoomSnap); 23236 const tr = this.getTransformForUpdate(); 23237 let bearingChanged = false, pitchChanged = false; 23238 let rollChanged = false; 23239 const oldZoom = tr.zoom; 23240 if (this.terrain) tr.setElevation(this.terrain.getElevationForLngLat(options.center ? LngLat.convert(options.center) : tr.center, tr)); 23241 this.cameraHelper.handleJumpToCenterZoom(tr, options); 23242 const zoomChanged = tr.zoom !== oldZoom; 23243 if (options.elevation !== void 0 && tr.elevation !== +options.elevation) tr.setElevation(+options.elevation); 23244 if (options.bearing !== void 0 && tr.bearing !== +options.bearing) { 23245 bearingChanged = true; 23246 tr.setBearing(+options.bearing); 23247 } 23248 if (options.pitch !== void 0 && tr.pitch !== +options.pitch) { 23249 pitchChanged = true; 23250 tr.setPitch(+options.pitch); 23251 } 23252 if (options.roll !== void 0 && tr.roll !== +options.roll) { 23253 rollChanged = true; 23254 tr.setRoll(+options.roll); 23255 } 23256 if (options.padding != null && !tr.isPaddingEqual(options.padding)) tr.setPadding(options.padding); 23257 this.applyUpdatedTransform(tr); 23258 this.fire(new MapMovementEvent("movestart", eventData)).fire(new MapMovementEvent("move", eventData)); 23259 if (zoomChanged) this.fire(new MapMovementEvent("zoomstart", eventData)).fire(new MapMovementEvent("zoom", eventData)).fire(new MapMovementEvent("zoomend", eventData)); 23260 if (bearingChanged) this.fire(new MapMovementEvent("rotatestart", eventData)).fire(new MapMovementEvent("rotate", eventData)).fire(new MapMovementEvent("rotateend", eventData)); 23261 if (pitchChanged) this.fire(new MapMovementEvent("pitchstart", eventData)).fire(new MapMovementEvent("pitch", eventData)).fire(new MapMovementEvent("pitchend", eventData)); 23262 if (rollChanged) this.fire(new MapMovementEvent("rollstart", eventData)).fire(new MapMovementEvent("roll", eventData)).fire(new MapMovementEvent("rollend", eventData)); 23263 return this.fire(new MapMovementEvent("moveend", eventData)); 23264 } 23265 /** 23266 * Calculates camera options for moving a geographic anchor to a screen point without 23267 * changing this camera. 23268 */ 23269 calculateAnchoredCameraOptions(options) { 23270 const tr = this.transform.clone(); 23271 const anchor = LngLat.convert(options.anchorLocation); 23272 const target = Point.convert(options.anchorScreenPoint); 23273 const deltas = { 23274 panDelta: target.sub(this.transform.locationToScreenPoint(anchor, this.terrain)), 23275 zoomDelta: options.zoom === void 0 ? 0 : options.zoom - tr.zoom, 23276 bearingDelta: 0, 23277 pitchDelta: 0, 23278 rollDelta: 0, 23279 around: target, 23280 aroundElevation: this.terrain?.getElevationForLngLat(anchor, tr) 23281 }; 23282 this.cameraHelper.handleMapControlsRollPitchBearingZoom(deltas, tr); 23283 this.cameraHelper.handleMapControlsPan(deltas, tr, anchor); 23284 return { 23285 center: tr.center, 23286 zoom: tr.zoom 23287 }; 23288 } 23289 calculateCameraOptionsFromCameraLngLatAltRotation(cameraLngLat, cameraAlt, bearing, pitch, roll) { 23290 const centerInfo = this.transform.calculateCenterFromCameraLngLatAlt(cameraLngLat, cameraAlt, bearing, pitch); 23291 return { 23292 center: centerInfo.center, 23293 elevation: centerInfo.elevation, 23294 zoom: centerInfo.zoom, 23295 bearing, 23296 pitch, 23297 roll 23298 }; 23299 } 23300 easeTo(options, eventData) { 23301 this._stop(false, options.easeId); 23302 options = extend({ 23303 offset: [0, 0], 23304 duration: 500, 23305 easing: defaultEasing 23306 }, options); 23307 if (options.zoom !== void 0 && this._zoomSnap) options.zoom = evaluateZoomSnap(options.zoom, this._zoomSnap); 23308 if (options.animate === false || !options.essential && browser.prefersReducedMotion) options.duration = 0; 23309 const tr = this.getTransformForUpdate(); 23310 const startBearing = this.getBearing(), startPitch = tr.pitch, startRoll = tr.roll, bearing = options.bearing !== void 0 ? this._normalizeBearing(options.bearing, startBearing) : startBearing, pitch = options.pitch !== void 0 ? +options.pitch : startPitch, roll = options.roll !== void 0 ? this._normalizeBearing(options.roll, startRoll) : startRoll, padding = options.padding !== void 0 ? options.padding : tr.padding; 23311 const offsetAsPoint = Point.convert(options.offset); 23312 let around, aroundPoint; 23313 if (options.around) { 23314 around = LngLat.convert(options.around); 23315 aroundPoint = tr.locationToScreenPoint(around); 23316 } 23317 const currently = { 23318 moving: this._moving, 23319 zooming: this._zooming, 23320 rotating: this._rotating, 23321 pitching: this._pitching, 23322 rolling: this._rolling 23323 }; 23324 const easeHandler = this.cameraHelper.handleEaseTo(tr, { 23325 bearing, 23326 pitch, 23327 roll, 23328 padding, 23329 around, 23330 aroundPoint, 23331 offsetAsPoint, 23332 offset: options.offset, 23333 zoom: options.zoom, 23334 center: options.center 23335 }); 23336 this._rotating ||= startBearing !== bearing; 23337 this._pitching ||= pitch !== startPitch; 23338 this._rolling ||= roll !== startRoll; 23339 this._padding = !tr.isPaddingEqual(padding); 23340 this._zooming ||= easeHandler.isZooming; 23341 this._easeId = options.easeId; 23342 this._prepareEase(eventData, options.noMoveStart, currently); 23343 if (this.terrain) this._prepareElevation(easeHandler.elevationCenter); 23344 this._ease((k) => { 23345 easeHandler.easeFunc(k); 23346 if (this.terrain && !options.freezeElevation) this._updateElevation(k); 23347 this.applyUpdatedTransform(tr); 23348 this._fireMoveEvents(eventData); 23349 }, (interruptingEaseId) => { 23350 if (this.terrain && options.freezeElevation) this._finalizeElevation(); 23351
vendor: 9,851 bytes, lines 23351-23585
23351this._afterEase(eventData, interruptingEaseId); 23352 }, options); 23353 return this; 23354 } 23355 _prepareEase(eventData, noMoveStart, currently = {}) { 23356 this._moving = true; 23357 if (!noMoveStart && !currently.moving) this.fire(new MapMovementEvent("movestart", eventData)); 23358 if (this._zooming && !currently.zooming) this.fire(new MapMovementEvent("zoomstart", eventData)); 23359 if (this._rotating && !currently.rotating) this.fire(new MapMovementEvent("rotatestart", eventData)); 23360 if (this._pitching && !currently.pitching) this.fire(new MapMovementEvent("pitchstart", eventData)); 23361 if (this._rolling && !currently.rolling) this.fire(new MapMovementEvent("rollstart", eventData)); 23362 } 23363 _prepareElevation(center) { 23364 this._elevationCenter = center; 23365 this._elevationStart = this.transform.elevation; 23366 this._elevationTarget = this.terrain.getElevationForLngLat(center, this.transform); 23367 this.elevationFreeze = true; 23368 } 23369 _updateElevation(k) { 23370 if (this._elevationStart === void 0 || this._elevationCenter === void 0) this._prepareElevation(this.transform.center); 23371 this.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._elevationCenter, this.transform.tileZoom)); 23372 const elevation = this.terrain.getElevationForLngLat(this._elevationCenter, this.transform); 23373 if (k < 1 && elevation !== this._elevationTarget) { 23374 const pitch1 = this._elevationTarget - this._elevationStart; 23375 const pitch2 = (elevation - (pitch1 * k + this._elevationStart)) / (1 - k); 23376 this._elevationStart += k * (pitch1 - pitch2); 23377 this._elevationTarget = elevation; 23378 } 23379 this.transform.setElevation(interpolateFactory.number(this._elevationStart, this._elevationTarget, k)); 23380 } 23381 _finalizeElevation() { 23382 this.elevationFreeze = false; 23383 if (this.getCenterClampedToGround()) this.transform.recalculateZoomAndCenter(this.terrain); 23384 } 23385 /** 23386 * @internal 23387 * Called when the camera is about to be manipulated. 23388 * If `transformCameraUpdate` is specified or terrain is enabled, a copy of 23389 * the current transform is created to track the accumulated changes. 23390 * This underlying transform represents the "desired state" proposed by input handlers / animations / UI controls. 23391 * It may differ from the state used for rendering (`this.transform`). 23392 * @returns Transform to apply changes to 23393 */ 23394 getTransformForUpdate() { 23395 if (!this.transformCameraUpdate && !this.terrain) return this.transform; 23396 this._requestedCameraState ||= this.transform.clone(); 23397 return this._requestedCameraState; 23398 } 23399 /** 23400 * @internal 23401 * Checks the given transform for the camera being below terrain surface and 23402 * returns new pitch and zoom to fix that. 23403 * 23404 * With the new pitch and zoom, the camera will be at the same ground 23405 * position but at higher altitude. It will still point to the same spot on 23406 * the map. 23407 * 23408 * @param tr - The transform to check. 23409 */ 23410 _elevateCameraIfInsideTerrain(tr) { 23411 if (!this.terrain && tr.elevation >= 0 && tr.pitch <= 90) return {}; 23412 const cameraLngLat = tr.getCameraLngLat(); 23413 const cameraAltitude = tr.getCameraAltitude(); 23414 const minAltitude = this.terrain ? this.terrain.getElevationForLngLatZoom(cameraLngLat, tr.zoom) : 0; 23415 if (cameraAltitude < minAltitude) { 23416 const newCamera = tr.calculateCameraOptionsFromTo(cameraLngLat, minAltitude, tr.center, tr.elevation); 23417 return { 23418 pitch: newCamera.pitch, 23419 zoom: newCamera.zoom 23420 }; 23421 } 23422 return {}; 23423 } 23424 /** 23425 * @internal 23426 * Called after the camera is done being manipulated. 23427 * @param tr - the requested camera end state 23428 * If the camera is inside terrain, it gets elevated. 23429 * Call `transformCameraUpdate` if present, and then apply the "approved" changes. 23430 */ 23431 applyUpdatedTransform(tr) { 23432 const modifiers = []; 23433 modifiers.push((tr) => this._elevateCameraIfInsideTerrain(tr)); 23434 if (this.transformCameraUpdate) modifiers.push((tr) => this.transformCameraUpdate(tr)); 23435 if (!modifiers.length) return; 23436 const finalTransform = tr.clone(); 23437 for (const modifier of modifiers) { 23438 const nextTransform = finalTransform.clone(); 23439 const { center, zoom, roll, pitch, bearing, elevation } = modifier(nextTransform); 23440 if (center) nextTransform.setCenter(center); 23441 if (elevation !== void 0) nextTransform.setElevation(elevation); 23442 if (zoom !== void 0) nextTransform.setZoom(zoom); 23443 if (roll !== void 0) nextTransform.setRoll(roll); 23444 if (pitch !== void 0) nextTransform.setPitch(pitch); 23445 if (bearing !== void 0) nextTransform.setBearing(bearing); 23446 finalTransform.apply(nextTransform, false); 23447 } 23448 this.transform.apply(finalTransform, false); 23449 } 23450 _fireMoveEvents(eventData) { 23451 this.fire(new MapMovementEvent("move", eventData)); 23452 if (this._zooming) this.fire(new MapMovementEvent("zoom", eventData)); 23453 if (this._rotating) this.fire(new MapMovementEvent("rotate", eventData)); 23454 if (this._pitching) this.fire(new MapMovementEvent("pitch", eventData)); 23455 if (this._rolling) this.fire(new MapMovementEvent("roll", eventData)); 23456 } 23457 _afterEase(eventData, easeId) { 23458 if (this._easeId && easeId && this._easeId === easeId) return; 23459 delete this._easeId; 23460 const wasZooming = this._zooming; 23461 const wasRotating = this._rotating; 23462 const wasPitching = this._pitching; 23463 const wasRolling = this._rolling; 23464 this._moving = false; 23465 this._zooming = false; 23466 this._rotating = false; 23467 this._pitching = false; 23468 this._rolling = false; 23469 this._padding = false; 23470 if (wasZooming) this.fire(new MapMovementEvent("zoomend", eventData)); 23471 if (wasRotating) this.fire(new MapMovementEvent("rotateend", eventData)); 23472 if (wasPitching) this.fire(new MapMovementEvent("pitchend", eventData)); 23473 if (wasRolling) this.fire(new MapMovementEvent("rollend", eventData)); 23474 this.fire(new MapMovementEvent("moveend", eventData)); 23475 } 23476 flyTo(options, eventData) { 23477 if (!options.essential && browser.prefersReducedMotion) { 23478 const coercedOptions = pick(options, [ 23479 "center", 23480 "zoom", 23481 "bearing", 23482 "pitch", 23483 "roll", 23484 "elevation", 23485 "padding" 23486 ]); 23487 return this.jumpTo(coercedOptions, eventData); 23488 } 23489 this.stop(); 23490 options = extend({ 23491 offset: [0, 0], 23492 speed: 1.2, 23493 curve: 1.42, 23494 easing: defaultEasing 23495 }, options); 23496 if (options.zoom !== void 0 && this._zoomSnap) options.zoom = evaluateZoomSnap(options.zoom, this._zoomSnap); 23497 const tr = this.getTransformForUpdate(), startBearing = tr.bearing, startPitch = tr.pitch, startRoll = tr.roll, startPadding = tr.padding; 23498 const bearing = options.bearing !== void 0 ? this._normalizeBearing(options.bearing, startBearing) : startBearing; 23499 const pitch = options.pitch !== void 0 ? +options.pitch : startPitch; 23500 const roll = options.roll !== void 0 ? this._normalizeBearing(options.roll, startRoll) : startRoll; 23501 const padding = options.padding !== void 0 ? options.padding : tr.padding; 23502 const offsetAsPoint = Point.convert(options.offset); 23503 let pointAtOffset = tr.centerPoint.add(offsetAsPoint); 23504 const locationAtOffset = tr.screenPointToLocation(pointAtOffset); 23505 const flyToHandler = this.cameraHelper.handleFlyTo(tr, { 23506 bearing, 23507 pitch, 23508 roll, 23509 padding, 23510 locationAtOffset, 23511 offsetAsPoint, 23512 center: options.center, 23513 minZoom: options.minZoom, 23514 zoom: options.zoom 23515 }); 23516 let rho = options.curve; 23517 const w0 = Math.max(tr.width, tr.height); 23518 const w1 = w0 / flyToHandler.scaleOfZoom; 23519 const u1 = flyToHandler.pixelPathLength; 23520 const wMax = w0 / flyToHandler.scaleOfMinZoom; 23521 rho = Math.min(rho, Math.sqrt(wMax / u1 * 2)); 23522 const rho2 = rho * rho; 23523 /** 23524 * ráµ¢: Returns the zoom-out factor at one end of the animation. 23525 * 23526 * @param descent - `true` for the descent, `false` for the ascent 23527 */ 23528 function zoomOutFactor(descent) { 23529 const b = (w1 * w1 - w0 * w0 + (descent ? -1 : 1) * rho2 * rho2 * u1 * u1) / (2 * (descent ? w1 : w0) * rho2 * u1); 23530 return Math.log(Math.sqrt(b * b + 1) - b); 23531 } 23532 function sinh(n) { 23533 return (Math.exp(n) - Math.exp(-n)) / 2; 23534 } 23535 function cosh(n) { 23536 return (Math.exp(n) + Math.exp(-n)) / 2; 23537 } 23538 function tanh(n) { 23539 return sinh(n) / cosh(n); 23540 } 23541 const r0 = zoomOutFactor(false); 23542 let w = function(s) { 23543 return cosh(r0) / cosh(r0 + rho * s); 23544 }; 23545 let u = function(s) { 23546 return w0 * ((cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2) / u1; 23547 }; 23548 let S = (zoomOutFactor(true) - r0) / rho; 23549 if (Math.abs(u1) < 2e-6 || !isFinite(S)) { 23550 if (Math.abs(w0 - w1) < 1e-6) return this.easeTo(options, eventData); 23551 const k = w1 < w0 ? -1 : 1; 23552 S = Math.abs(Math.log(w1 / w0)) / rho; 23553 u = () => 0; 23554 w = (s) => Math.exp(k * rho * s); 23555 } 23556 if (options.duration !== void 0) options.duration = +options.duration; 23557 else { 23558 const V = options.screenSpeed !== void 0 ? +options.screenSpeed / rho : +options.speed; 23559 options.duration = 1e3 * S / V; 23560 } 23561 if (options.maxDuration && options.duration > options.maxDuration) options.duration = 0; 23562 this._zooming = true; 23563 this._rotating = startBearing !== bearing; 23564 this._pitching = pitch !== startPitch; 23565 this._rolling = roll !== startRoll; 23566 this._padding = !tr.isPaddingEqual(padding); 23567 this._prepareEase(eventData, false); 23568 if (this.terrain) this._prepareElevation(flyToHandler.targetCenter); 23569 this._ease((k) => { 23570 const s = k * S; 23571 const scale = 1 / w(s); 23572 const centerFactor = u(s); 23573 if (this._rotating) tr.setBearing(interpolateFactory.number(startBearing, bearing, k)); 23574 if (this._pitching) tr.setPitch(interpolateFactory.number(startPitch, pitch, k)); 23575 if (this._rolling) tr.setRoll(interpolateFactory.number(startRoll, roll, k)); 23576 if (this._padding) { 23577 tr.interpolatePadding(startPadding, padding, k); 23578 pointAtOffset = tr.centerPoint.add(offsetAsPoint); 23579 } 23580 flyToHandler.easeFunc(k, scale, centerFactor, pointAtOffset); 23581 if (this.terrain && !options.freezeElevation) this._updateElevation(k); 23582 this.applyUpdatedTransform(tr); 23583 this._fireMoveEvents(eventData); 23584 }, () => { 23585 if (this.terrain && options.freezeElevation) this._finalizeElevation()
vendor: 65,568 bytes, lines 23585-25308
23585; 23586 this._afterEase(eventData); 23587 }, options); 23588 return this; 23589 } 23590 isEasing() { 23591 return !!this._easeFrameId; 23592 } 23593 stop(allowGestures) { 23594 return this._stop(allowGestures); 23595 } 23596 _stop(allowGestures, easeId) { 23597 if (this._easeFrameId) { 23598 this._cancelRenderFrame(this._easeFrameId); 23599 delete this._easeFrameId; 23600 delete this._onEaseFrame; 23601 } 23602 if (this._onEaseEnd) { 23603 const onEaseEnd = this._onEaseEnd; 23604 delete this._onEaseEnd; 23605 onEaseEnd.call(this, easeId); 23606 } 23607 if (!allowGestures) this._stopHandlers(); 23608 return this; 23609 } 23610 _ease(frame, finish, options) { 23611 if (options.animate === false || options.duration === 0) { 23612 frame(1); 23613 finish(); 23614 } else { 23615 this._easeStart = now(); 23616 this._easeOptions = options; 23617 this._onEaseFrame = frame; 23618 this._onEaseEnd = finish; 23619 this._easeFrameId = this._requestRenderFrame(this._renderFrameCallback); 23620 } 23621 } 23622 _normalizeBearing(bearing, currentBearing) { 23623 bearing = wrap(bearing, -180, 180); 23624 const diff = Math.abs(bearing - currentBearing); 23625 if (Math.abs(bearing - 360 - currentBearing) < diff) bearing -= 360; 23626 if (Math.abs(bearing + 360 - currentBearing) < diff) bearing += 360; 23627 return bearing; 23628 } 23629 isMoving() { 23630 return this._moving; 23631 } 23632 isZooming() { 23633 return this._zooming; 23634 } 23635 isRotating() { 23636 return this._rotating; 23637 } 23638}; 23639//#endregion 23640//#region src/ui/control/attribution_control.ts 23641const defaultAttributionControlOptions = { 23642 compact: true, 23643 customAttribution: "<a href=\"https://maplibre.org/\" target=\"_blank\">MapLibre</a>" 23644}; 23645/** 23646* An `AttributionControl` control presents the map's attribution information. By default, the attribution control is expanded (regardless of map width). 23647* @group Markers and Controls 23648* @example 23649* ```ts 23650* let map = new Map({attributionControl: false}) 23651* .addControl(new AttributionControl({ 23652* compact: true 23653* })); 23654* ``` 23655* @see [Change the default position for attribution](https://maplibre.org/maplibre-gl-js/docs/examples/change-the-default-position-for-attribution/) 23656*/ 23657var AttributionControl = class { 23658 /** 23659 * @param options - the attribution options 23660 */ 23661 constructor(options = defaultAttributionControlOptions) { 23662 this._toggleAttribution = () => { 23663 if (this._container.classList.contains("maplibregl-compact")) { 23664 if (this._container.classList.contains("maplibregl-compact-show")) { 23665 this._container.setAttribute("open", ""); 23666 this._container.classList.remove("maplibregl-compact-show"); 23667 } else { 23668 this._container.classList.add("maplibregl-compact-show"); 23669 this._container.removeAttribute("open"); 23670 } 23671 } 23672 }; 23673 this._updateData = (e) => { 23674 if (e && (e.type === "terrain" || e.dataType === "style" || e.dataType === "source" && (e.sourceDataType === "metadata" || e.sourceDataType === "visibility"))) this._updateAttributions(); 23675 }; 23676 this._updateCompact = () => { 23677 if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) { 23678 if (this._compact === false) this._container.setAttribute("open", ""); 23679 else if (!this._container.classList.contains("maplibregl-compact") && !this._container.classList.contains("maplibregl-attrib-empty")) { 23680 this._container.setAttribute("open", ""); 23681 this._container.classList.add("maplibregl-compact", "maplibregl-compact-show"); 23682 } 23683 } else { 23684 this._container.setAttribute("open", ""); 23685 if (this._container.classList.contains("maplibregl-compact")) this._container.classList.remove("maplibregl-compact", "maplibregl-compact-show"); 23686 } 23687 }; 23688 this._updateCompactMinimize = () => { 23689 if (this._container.classList.contains("maplibregl-compact")) { 23690 if (this._container.classList.contains("maplibregl-compact-show")) this._container.classList.remove("maplibregl-compact-show"); 23691 } 23692 }; 23693 this.options = options; 23694 } 23695 getDefaultPosition() { 23696 return "bottom-right"; 23697 } 23698 /** {@inheritDoc IControl.onAdd} */ 23699 onAdd(map) { 23700 this._map = map; 23701 this._compact = this.options.compact; 23702 this._container = DOM.create("details", "maplibregl-ctrl maplibregl-ctrl-attrib"); 23703 this._compactButton = DOM.create("summary", "maplibregl-ctrl-attrib-button", this._container); 23704 this._compactButton.addEventListener("click", this._toggleAttribution); 23705 this._setElementTitle(this._compactButton, "ToggleAttribution"); 23706 this._innerContainer = DOM.create("div", "maplibregl-ctrl-attrib-inner", this._container); 23707 this._updateAttributions(); 23708 this._updateCompact(); 23709 this._map.on("styledata", this._updateData); 23710 this._map.on("sourcedata", this._updateData); 23711 this._map.on("terrain", this._updateData); 23712 this._map.on("resize", this._updateCompact); 23713 this._map.on("drag", this._updateCompactMinimize); 23714 return this._container; 23715 } 23716 /** {@inheritDoc IControl.onRemove} */ 23717 onRemove() { 23718 this._container.remove(); 23719 this._map.off("styledata", this._updateData); 23720 this._map.off("sourcedata", this._updateData); 23721 this._map.off("terrain", this._updateData); 23722 this._map.off("resize", this._updateCompact); 23723 this._map.off("drag", this._updateCompactMinimize); 23724 this._map = void 0; 23725 this._compact = void 0; 23726 this._attribHTML = void 0; 23727 } 23728 _setElementTitle(element, title) { 23729 const str = this._map._getUIString(`AttributionControl.${title}`); 23730 element.title = str; 23731 element.setAttribute("aria-label", str); 23732 } 23733 _updateAttributions() { 23734 if (!this._map.style) return; 23735 let attributions = []; 23736 if (this.options.customAttribution) { 23737 if (Array.isArray(this.options.customAttribution)) attributions = attributions.concat(this.options.customAttribution.map((attribution) => { 23738 if (typeof attribution !== "string") return ""; 23739 return attribution; 23740 })); 23741 else if (typeof this.options.customAttribution === "string") attributions.push(this.options.customAttribution); 23742 } 23743 if (this._map.style.stylesheet) { 23744 const stylesheet = this._map.style.stylesheet; 23745 this.styleOwner = stylesheet.owner; 23746 this.styleId = stylesheet.id; 23747 } 23748 const tileManagers = this._map.style.tileManagers; 23749 for (const id in tileManagers) { 23750 const tileManager = tileManagers[id]; 23751 if (tileManager.used || tileManager.usedForTerrain) { 23752 const source = tileManager.getSource(); 23753 if (source.attribution && !attributions.includes(source.attribution)) attributions.push(source.attribution); 23754 } 23755 } 23756 attributions = attributions.filter((e) => String(e).trim()); 23757 attributions.sort((a, b) => a.length - b.length); 23758 attributions = attributions.filter((attrib, i) => { 23759 for (let j = i + 1; j < attributions.length; j++) if (attributions[j].includes(attrib)) return false; 23760 return true; 23761 }); 23762 const attribHTML = attributions.join(" | "); 23763 if (attribHTML === this._attribHTML) return; 23764 this._attribHTML = attribHTML; 23765 if (attributions.length) { 23766 this._innerContainer.replaceChildren(DOM.sanitize(attribHTML)); 23767 this._container.classList.remove("maplibregl-attrib-empty"); 23768 } else this._container.classList.add("maplibregl-attrib-empty"); 23769 this._updateCompact(); 23770 this._editLink = null; 23771 } 23772}; 23773//#endregion 23774//#region src/ui/control/logo_control.ts 23775/** 23776* A `LogoControl` is a control that adds the watermark. 23777* 23778* @group Markers and Controls 23779* 23780* @example 23781* ```ts 23782* map.addControl(new LogoControl({compact: false})); 23783* ``` 23784**/ 23785var LogoControl = class { 23786 /** 23787 * @param options - the control's options 23788 */ 23789 constructor(options = {}) { 23790 this._updateCompact = () => { 23791 const containerChildren = this._container.children; 23792 if (containerChildren.length) { 23793 const anchor = containerChildren[0]; 23794 if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) { 23795 if (this._compact !== false) anchor.classList.add("maplibregl-compact"); 23796 } else anchor.classList.remove("maplibregl-compact"); 23797 } 23798 }; 23799 this.options = options; 23800 } 23801 getDefaultPosition() { 23802 return "bottom-left"; 23803 } 23804 /** {@inheritDoc IControl.onAdd} */ 23805 onAdd(map) { 23806 this._map = map; 23807 this._compact = this.options?.compact; 23808 this._container = DOM.create("div", "maplibregl-ctrl"); 23809 const anchor = DOM.create("a", "maplibregl-ctrl-logo"); 23810 anchor.target = "_blank"; 23811 anchor.rel = "noopener nofollow"; 23812 anchor.href = "https://maplibre.org/"; 23813 anchor.setAttribute("aria-label", this._map._getUIString("LogoControl.Title")); 23814 anchor.setAttribute("rel", "noopener nofollow"); 23815 this._container.appendChild(anchor); 23816 this._container.style.display = "block"; 23817 this._map.on("resize", this._updateCompact); 23818 this._updateCompact(); 23819 return this._container; 23820 } 23821 /** {@inheritDoc IControl.onRemove} */ 23822 onRemove() { 23823 this._container.remove(); 23824 this._map.off("resize", this._updateCompact); 23825 this._map = void 0; 23826 this._compact = void 0; 23827 } 23828}; 23829//#endregion 23830//#region src/util/task_queue.ts 23831var TaskQueue = class { 23832 constructor() { 23833 this._queue = []; 23834 this._id = 0; 23835 this._cleared = false; 23836 this._currentlyRunning = false; 23837 } 23838 add(callback) { 23839 const id = ++this._id; 23840 this._queue.push({ 23841 callback, 23842 id, 23843 cancelled: false 23844 }); 23845 return id; 23846 } 23847 remove(id) { 23848 const running = this._currentlyRunning; 23849 const queue = running ? this._queue.concat(running) : this._queue; 23850 for (const task of queue) if (task.id === id) { 23851 task.cancelled = true; 23852 return; 23853 } 23854 } 23855 run(timeStamp = 0) { 23856 if (this._currentlyRunning) throw new Error("Attempting to run(), but is already running."); 23857 const queue = this._currentlyRunning = this._queue; 23858 this._queue = []; 23859 let failed = false; 23860 let firstError; 23861 for (const task of queue) { 23862 if (task.cancelled) continue; 23863 try { 23864 task.callback(timeStamp); 23865 } catch (error) { 23866 if (!failed) { 23867 failed = true; 23868 firstError = error; 23869 } 23870 } 23871 if (this._cleared) break; 23872 } 23873 this._cleared = false; 23874 this._currentlyRunning = false; 23875 if (failed) throw firstError; 23876 } 23877 clear() { 23878 if (this._currentlyRunning) this._cleared = true; 23879 this._queue = []; 23880 } 23881}; 23882//#endregion 23883//#region src/webgl/rtt_fingerprint.ts 23884/** 23885* What a render-to-texture tile's textures differ in from the state this frame would render them from, 23886* least to most severe: `zoom` (the same layers and source tiles, evaluated at another zoom), `sourceTiles` 23887* (other source tiles under the terrain tile), `visibleLayers` (a layer entered or left its zoom range) and 23888* `revision` (the source data changed, or the textures were rendered without this source). 23889* 23890* @internal 23891*/ 23892const RTT_DIFFERENCES = [ 23893 "none", 23894 "zoom", 23895 "sourceTiles", 23896 "visibleLayers", 23897 "revision" 23898]; 23899/** 23900* Immutable value describing the state a render-to-texture tile's textures were rendered from: the source 23901* tiles drawn into them, the source data revision, the map zoom (zoom-dependent style properties are 23902* evaluated then) and the ids of the layers visible at that zoom. 23903* 23904* @internal 23905*/ 23906var RTTFingerprint = class { 23907 constructor(coords, revision, zoom, visibleLayerIds) { 23908 this._tileKeys = coords.map((c) => c.key).sort().join(); 23909 this._revision = revision; 23910 this._zoom = zoom; 23911 this._visibleLayerIds = visibleLayerIds; 23912 } 23913 /** 23914 * The most severe difference between this fingerprint and the one the textures were rendered from, 23915 * `none` when they match. A missing fingerprint counts as a revision difference. 23916 */ 23917 difference(other) { 23918 if (this._revision !== other?._revision) return "revision"; 23919 if (this._visibleLayerIds !== other._visibleLayerIds) return "visibleLayers"; 23920 if (this._tileKeys !== other._tileKeys) return "sourceTiles"; 23921 return this._zoom === other._zoom ? "none" : "zoom"; 23922 } 23923}; 23924//#endregion 23925//#region src/webgl/render_to_texture.ts 23926/** 23927* lookup table which layers should rendered to texture 23928*/ 23929const LAYERS_TO_TEXTURES = { 23930 background: true, 23931 fill: true, 23932 line: true, 23933 raster: true, 23934 hillshade: true, 23935 "color-relief": true 23936}; 23937/** 23938* @internal 23939* Renders RTT-eligible layers into per-tile cached textures, then drapes 23940* them onto the terrain mesh. Slots live on each Tile so their lifetime 23941* tracks the tile itself; the underlying FBO+texture handles are recycled 23942* via the painter's RTT pool. 23943*/ 23944var RenderToTexture = class { 23945 constructor(painter, terrain) { 23946 this.needsFollowUpFrame = false; 23947 this.painter = painter; 23948 this.terrain = terrain; 23949 this.rttSize = terrain.tileManager.tileSize * terrain.qualityFactor; 23950 } 23951 getTexture(tile) { 23952 return tile.getRTT(this._stacks.length - 1).texture; 23953 } 23954 /** 23955 * Collects the frame's tiles, layers and source fingerprints and releases the textures that need 23956 * re-rendering. Textures that differ only by zoom are kept while the zoom is changing or the map is moving 23957 * and re-rendered one tile per frame once it has stopped, nearest to the camera first; textures with other 23958 * source tiles under them are re-rendered one per frame too, `needsFollowUpFrame` bringing in the rest. 23959 * Textures whose visible layer set changed (a layer entered or left its zoom range) are kept while the zoom 23960 * is changing and then all re-rendered in the same frame, since a tile-by-tile change would show; a source 23961 * data change re-renders immediately. 23962 */ 23963 prepareForRender(style, zoom) { 23964 const zoomChanged = zoom !== this._lastPrepareZoom; 23965 this._lastPrepareZoom = zoom; 23966 this._stacks = []; 23967 this._prevType = null; 23968 this._rttTiles = []; 23969 this._renderableTiles = this.terrain.tileManager.getRenderableTiles(); 23970 this._renderableLayerIds = style._order.filter((id) => !style._layers[id].isHidden(zoom)); 23971 const visibleLayerIds = this._renderableLayerIds.join(); 23972 const rttSourceIds = /* @__PURE__ */ new Set(); 23973 for (const layerId of this._renderableLayerIds) { 23974 const layer = style._layers[layerId]; 23975 const source = layer.source; 23976 if (source && LAYERS_TO_TEXTURES[layer.type]) rttSourceIds.add(source); 23977 } 23978 this._coordsAscending = {}; 23979 this._rttFingerprints = {}; 23980 for (const sourceId of rttSourceIds) { 23981 const tileManager = style.tileManagers[sourceId]; 23982 if (!tileManager) continue; 23983 this._coordsAscending[sourceId] = {}; 23984 const coordsAscending = this._coordsAscending[sourceId]; 23985 const source = tileManager.getSource(); 23986 const terrainTileRanges = source instanceof ImageSource ? source.terrainTileRanges : null; 23987 for (const tileID of tileManager.getVisibleCoordinates()) { 23988 const keys = this.terrain.tileManager.getTerrainCoords(tileID, terrainTileRanges); 23989 for (const key in keys) { 23990 coordsAscending[key] ||= []; 23991 coordsAscending[key].push(keys[key]); 23992 } 23993 } 23994 this._rttFingerprints[sourceId] = {}; 23995 const fingerprints = this._rttFingerprints[sourceId]; 23996 const revision = tileManager.getState().revision; 23997 for (const key in coordsAscending) fingerprints[key] = new RTTFingerprint(coordsAscending[key], revision, zoom, visibleLayerIds); 23998 } 23999 this.needsFollowUpFrame = false; 24000 const moving = zoomChanged || this.painter.options.moving; 24001 let staleTileReleased = false; 24002 for (const tile of this._renderableTiles) { 24003 const difference = this._textureDifference(tile); 24004 if (difference === "none") continue; 24005 if (difference === "zoom" && moving || difference === "visibleLayers" && zoomChanged) this.needsFollowUpFrame = true; 24006 else if (difference === "zoom" || difference === "sourceTiles") { 24007 if (staleTileReleased) this.needsFollowUpFrame = true; 24008 else tile.releaseRTT(this.painter); 24009 staleTileReleased = true; 24010 } else tile.releaseRTT(this.painter); 24011 } 24012 } 24013 /** 24014 * The most severe difference, over the sources rendered to texture, between the tile's cached textures 24015 * and what this frame would render into them. 24016 */ 24017 _textureDifference(tile) { 24018 let worst = "none"; 24019 for (const source in this._rttFingerprints) { 24020 const frameFingerprint = this._rttFingerprints[source][tile.tileID.key]; 24021 if (!frameFingerprint) continue; 24022 const difference = frameFingerprint.difference(tile.rttFingerprint[source]); 24023 if (RTT_DIFFERENCES.indexOf(difference) > RTT_DIFFERENCES.indexOf(worst)) worst = difference; 24024 } 24025 return worst; 24026 } 24027 /** 24028 * due that switching textures is relatively slow, the render 24029 * layer-by-layer context is not practicable. To bypass this problem 24030 * this lines of code stack all layers and later render all at once. 24031 * Because of the stylesheet possibility to mixing render-to-texture layers 24032 * and 'live'-layers (f.e. symbols) it is necessary to create more stacks. For example 24033 * a symbol-layer is in between of fill-layers. 24034 * @param layer - the layer to render 24035 * @param renderContext - shared state for the current render 24036 * @returns if true layer is rendered to texture, otherwise false 24037 */ 24038 renderLayer(layer, renderContext) { 24039 if (layer.isHidden(this.painter.transform.zoom)) return false; 24040 const type = layer.type; 24041 const painter = this.painter; 24042 const isLastLayer = this._renderableLayerIds[this._renderableLayerIds.length - 1] === layer.id; 24043 if (LAYERS_TO_TEXTURES[type]) { 24044 if (!this._prevType || !LAYERS_TO_TEXTURES[this._prevType]) this._stacks.push([]); 24045 this._prevType = type; 24046 this._stacks[this._stacks.length - 1].push(layer.id); 24047 if (!isLastLayer) return true; 24048 } 24049 if (LAYERS_TO_TEXTURES[this._prevType] || LAYERS_TO_TEXTURES[type] && isLastLayer) { 24050 this._prevType = type; 24051 const stack = this._stacks.length - 1, layers = this._stacks[stack] || []; 24052 renderContext.isRenderingToTexture = true; 24053 for (const tile of this._renderableTiles) { 24054 this._rttTiles.push(tile); 24055 if (tile.getRTT(stack)) continue; 24056 const obj = tile.acquireRTT(painter, stack, this.rttSize); 24057 painter.bindRTT(obj); 24058 painter.context.clear({ 24059 color: Color.transparent, 24060 stencil: 0 24061 }); 24062 painter.currentStencilSource = void 0; 24063 for (const layerId of layers) { 24064 const layer = painter.style._layers[layerId]; 24065 const coords = layer.source ? this._coordsAscending[layer.source][tile.tileID.key] : [tile.tileID]; 24066 painter.context.viewport.set([ 24067 0, 24068 0, 24069 this.rttSize, 24070 this.rttSize 24071 ]); 24072 painter.renderTileClippingMasks(layer, coords); 24073 painter.renderLayer(painter, painter.style.tileManagers[layer.source], layer, coords, renderContext); 24074 if (layer.source) tile.rttFingerprint[layer.source] = this._rttFingerprints[layer.source][tile.tileID.key]; 24075 } 24076 obj.texture.generateMipmap(); 24077 } 24078 renderContext.isRenderingToTexture = false; 24079 drawTerrain(this.painter, this.terrain, this._rttTiles, renderContext); 24080 this._rttTiles = []; 24081 return LAYERS_TO_TEXTURES[type]; 24082 } 24083 return false; 24084 } 24085}; 24086//#endregion 24087//#region src/ui/default_locale.ts 24088const defaultLocale = { 24089 "AttributionControl.ToggleAttribution": "Toggle attribution", 24090 "AttributionControl.MapFeedback": "Map feedback", 24091 "FullscreenControl.Enter": "Enter fullscreen", 24092 "FullscreenControl.Exit": "Exit fullscreen", 24093 "GeolocateControl.FindMyLocation": "Find my location", 24094 "GeolocateControl.LocationNotAvailable": "Location not available", 24095 "LogoControl.Title": "MapLibre logo", 24096 "Map.Title": "Map", 24097 "Marker.Title": "Map marker", 24098 "NavigationControl.ResetBearing": "Drag to rotate map, click to reset north", 24099 "NavigationControl.ZoomIn": "Zoom in", 24100 "NavigationControl.ZoomOut": "Zoom out", 24101 "Popup.Close": "Close popup", 24102 "ScaleControl.Feet": "ft", 24103 "ScaleControl.Meters": "m", 24104 "ScaleControl.Kilometers": "km", 24105 "ScaleControl.Miles": "mi", 24106 "ScaleControl.NauticalMiles": "nm", 24107 "GlobeControl.Enable": "Enable globe", 24108 "GlobeControl.Disable": "Disable globe", 24109 "TerrainControl.Enable": "Enable terrain", 24110 "TerrainControl.Disable": "Disable terrain", 24111 "CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map", 24112 "CooperativeGesturesHandler.MacHelpText": "Use â + scroll to zoom the map", 24113 "CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map" 24114}; 24115//#endregion 24116//#region src/ui/map.ts 24117const version$1 = version$2; 24118const defaultMinZoom = -2; 24119const defaultMaxZoom = 22; 24120const defaultMinPitch = 0; 24121const defaultMaxPitch = 60; 24122const defaultAnisotropicFilterPitch = 20; 24123const maxPitchThreshold = 180; 24124const defaultOptions$4 = { 24125 hash: false, 24126 interactive: true, 24127 bearingSnap: 7, 24128 zoomSnap: 0, 24129 attributionControl: defaultAttributionControlOptions, 24130 maplibreLogo: false, 24131 refreshExpiredTiles: true, 24132 canvasContextAttributes: { 24133 antialias: false, 24134 preserveDrawingBuffer: false, 24135 powerPreference: "high-performance", 24136 failIfMajorPerformanceCaveat: false, 24137 desynchronized: false, 24138 contextType: void 0 24139 }, 24140 scrollZoom: true, 24141 minZoom: defaultMinZoom, 24142 maxZoom: defaultMaxZoom, 24143 minPitch: defaultMinPitch, 24144 maxPitch: defaultMaxPitch, 24145 boxZoom: true, 24146 dragRotate: true, 24147 dragPan: true, 24148 keyboard: true, 24149 doubleClickZoom: true, 24150 touchZoomRotate: true, 24151 touchPitch: true, 24152 cooperativeGestures: false, 24153 trackResize: true, 24154 center: [0, 0], 24155 elevation: 0, 24156 zoom: 0, 24157 bearing: 0, 24158 pitch: 0, 24159 roll: 0, 24160 renderWorldCopies: true, 24161 maxTileCacheSize: null, 24162 maxTileCacheZoomLevels: config.MAX_TILE_CACHE_ZOOM_LEVELS, 24163 transformRequest: null, 24164 transformCameraUpdate: null, 24165 transformConstrain: null, 24166 fadeDuration: 300, 24167 crossSourceCollisions: true, 24168 clickTolerance: 3, 24169 localIdeographFontFamily: "sans-serif", 24170 pitchWithRotate: true, 24171 rollEnabled: false, 24172 rotateSpeed: .8, 24173 pitchSpeed: -.5, 24174 reduceMotion: void 0, 24175 validateStyle: true, 24176 /**Because GL MAX_TEXTURE_SIZE is usually at least 4096px. */ 24177 maxCanvasSize: [4096, 4096], 24178 cancelPendingTileRequestsWhileZooming: true, 24179 centerClampedToGround: true, 24180 terrainSkirtLength: "auto", 24181 zoomLevelsToOverscale: 4, 24182 anisotropicFilterPitch: defaultAnisotropicFilterPitch 24183}; 24184/** 24185* The `Map` object represents the map on your page. It exposes methods 24186* and properties that enable you to programmatically change the map, 24187* and fires events as users interact with it. 24188* 24189* You create a `Map` by specifying a `container` and other options, see {@link MapOptions} for the full list. 24190* Then MapLibre GL JS initializes the map on the page and returns your `Map` object. 24191* 24192* @group Main 24193* 24194* @example 24195* ```ts 24196* let map = new Map({ 24197* container: 'map', 24198* center: [-122.420679, 37.772537], 24199* zoom: 13, 24200* style: style_object, 24201* hash: true, 24202* transformRequest: (url, resourceType)=> { 24203* if(resourceType === 'Source' && url.startsWith('http://myHost')) { 24204* return { 24205* url: url.replace('http', 'https'), 24206* headers: { 'my-custom-header': true}, 24207* credentials: 'include' // Include cookies for cross-origin requests 24208* } 24209* } 24210* } 24211* }); 24212* ``` 24213* @see [Display a map](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-map/) 24214*/ 24215var Map$1 = class extends Evented { 24216 /** 24217 * @internal 24218 * The window that owns the map container element, for cross-window support. 24219 * Returns `typeof window` to include global constructors like ResizeObserver. 24220 */ 24221 get _ownerWindow() { 24222 return this._container?.ownerDocument?.defaultView || window; 24223 } 24224 constructor(options) { 24225 super(); 24226 this._idleTriggered = false; 24227 this._crossFadingFactor = 1; 24228 this._renderTaskQueue = new TaskQueue(); 24229 this._controls = []; 24230 this._mapId = uniqueId(); 24231 this._styleUrl = null; 24232 this._missingStyleImageResolver = null; 24233 this._lostContextStyle = { 24234 style: null, 24235 images: null 24236 }; 24237 this._contextLost = (event) => { 24238 event.preventDefault(); 24239 if (this._frameRequest) { 24240 this._frameRequest.abort(); 24241 this._frameRequest = null; 24242 } 24243 this.painter.destroy(); 24244 this._lostContextStyle = this._getStyleAndImages(); 24245 if (!this.style) { 24246 this.fire(new MapContextEvent("webglcontextlost", { originalEvent: event })); 24247 return; 24248 } 24249 for (const layer of Object.values(this.style._layers)) { 24250 if (layer.type === "custom") console.warn(`Custom layer with id '${layer.id}' cannot be restored after WebGL context loss. You will need to re-add it manually after context restoration.`); 24251 if (layer._listeners) for (const [event] of Object.entries(layer._listeners)) console.warn(`Custom layer with id '${layer.id}' had event listeners for event '${event}' which cannot be restored after WebGL context loss. You will need to re-add them manually after context restoration.`); 24252 } 24253 this.style.destroy(); 24254 this.style = null; 24255 this.fire(new MapContextEvent("webglcontextlost", { originalEvent: event })); 24256 }; 24257 this._contextRestored = (event) => { 24258 if (this._lostContextStyle.style) this.setStyle(this._lostContextStyle.style, { diff: false }); 24259 if (this._lostContextStyle.images && this.style) { 24260 this.style.imageManager.images = this._lostContextStyle.images; 24261 for (const id in this._lostContextStyle.images) { 24262 const image = this._lostContextStyle.images[id]; 24263 if (image.isWebGLImage) this.style.imageManager.updateImage(id, image, false); 24264 } 24265 } 24266 this._lostContextStyle = { 24267 style: null, 24268 images: null 24269 }; 24270 try { 24271 this._setupPainter(); 24272 } catch (error) { 24273 this.fire(new ErrorEvent(error)); 24274 return; 24275 } 24276 this.resize(); 24277 this._update(); 24278 this._resizeInternal(); 24279 this.fire(new MapContextEvent("webglcontextrestored", { originalEvent: event })); 24280 }; 24281 this._onMapScroll = (event) => { 24282 if (event.target !== this._container) return; 24283 this._container.scrollTop = 0; 24284 this._container.scrollLeft = 0; 24285 return false; 24286 }; 24287 this._onWindowOnline = () => { 24288 this._update(); 24289 }; 24290 const resolvedOptions = { 24291 ...defaultOptions$4, 24292 ...options, 24293 canvasContextAttributes: { 24294 ...defaultOptions$4.canvasContextAttributes, 24295 ...options.canvasContextAttributes 24296 } 24297 }; 24298 if (resolvedOptions.minZoom != null && resolvedOptions.maxZoom != null && resolvedOptions.minZoom > resolvedOptions.maxZoom) throw new Error("maxZoom must be greater than or equal to minZoom"); 24299 if (resolvedOptions.minPitch != null && resolvedOptions.maxPitch != null && resolvedOptions.minPitch > resolvedOptions.maxPitch) throw new Error("maxPitch must be greater than or equal to minPitch"); 24300 if (resolvedOptions.minPitch != null && resolvedOptions.minPitch < defaultMinPitch) throw new Error(`minPitch must be greater than or equal to ${defaultMinPitch}`); 24301 if (resolvedOptions.maxPitch != null && resolvedOptions.maxPitch > maxPitchThreshold) throw new Error(`maxPitch must be less than or equal to ${maxPitchThreshold}`); 24302 this._camera = new Camera({ 24303 minZoom: resolvedOptions.minZoom, 24304 maxZoom: resolvedOptions.maxZoom, 24305 minPitch: resolvedOptions.minPitch, 24306 maxPitch: resolvedOptions.maxPitch, 24307 bearingSnap: resolvedOptions.bearingSnap, 24308 zoomSnap: resolvedOptions.zoomSnap, 24309 renderWorldCopies: resolvedOptions.renderWorldCopies, 24310 centerClampedToGround: resolvedOptions.centerClampedToGround, 24311 terrain: this.terrain, 24312 transformConstrain: resolvedOptions.transformConstrain, 24313 requestRenderFrame: (callback) => this._requestRenderFrame(callback), 24314 cancelRenderFrame: (id) => this._cancelRenderFrame(id), 24315 transformCameraUpdate: resolvedOptions.transformCameraUpdate, 24316 stopHandlers: () => this._handlers?.stop(false) 24317 }); 24318 this._camera.setEventedParent(this); 24319 this._interactive = resolvedOptions.interactive; 24320 this._maxTileCacheSize = resolvedOptions.maxTileCacheSize; 24321 this._maxTileCacheZoomLevels = resolvedOptions.maxTileCacheZoomLevels; 24322 this._canvasContextAttributes = { ...resolvedOptions.canvasContextAttributes }; 24323 this._trackResize = resolvedOptions.trackResize === true; 24324 this._terrainSkirtLength = resolvedOptions.terrainSkirtLength; 24325 this._refreshExpiredTiles = resolvedOptions.refreshExpiredTiles === true; 24326 this._fadeDuration = resolvedOptions.fadeDuration; 24327 this._crossSourceCollisions = resolvedOptions.crossSourceCollisions === true; 24328 this._collectResourceTiming = resolvedOptions.collectResourceTiming === true; 24329 this._locale = { 24330 ...defaultLocale, 24331 ...resolvedOptions.locale 24332 }; 24333 this._clickTolerance = resolvedOptions.clickTolerance; 24334 this._overridePixelRatio = resolvedOptions.pixelRatio; 24335 this._maxCanvasSize = resolvedOptions.maxCanvasSize; 24336 this._zoomLevelsToOverscale = resolvedOptions.zoomLevelsToOverscale; 24337 this.cancelPendingTileRequestsWhileZooming = resolvedOptions.cancelPendingTileRequestsWhileZooming === true; 24338 this.setAnisotropicFilterPitch(resolvedOptions.anisotropicFilterPitch); 24339 if (resolvedOptions.reduceMotion !== void 0) browser.prefersReducedMotion = resolvedOptions.reduceMotion; 24340 this._requestManager = new RequestManager(resolvedOptions.transformRequest); 24341 this._container = this._resolveContainer(resolvedOptions.container); 24342 if (resolvedOptions.maxBounds) this.setMaxBounds(resolvedOptions.maxBounds); 24343 this._setupContainer(); 24344 try { 24345 this._setupPainter(); 24346 } catch (error) { 24347 this._cleanupContainer(); 24348 throw error; 24349 } 24350 this._imageQueueHandle = ImageRequest.addThrottleControl(() => this.isMoving()); 24351 this.on("move", () => this._update(false)); 24352 this.on("moveend", () => this._update(false)); 24353 this.on("zoom", () => this._update(true)); 24354 this.on("terrain", () => { 24355 this.painter.markTerrainDepthDirty(); 24356 this._update(true); 24357 }); 24358 this.once("idle", () => this._idleTriggered = true); 24359 this._handlers = new HandlerManager(this, this._camera, resolvedOptions); 24360 if (typeof window !== "undefined") { 24361 this._ownerWindow.addEventListener("online", this._onWindowOnline, false); 24362 this._setupResizeObserver(); 24363 } 24364 const hashName = typeof resolvedOptions.hash === "string" && resolvedOptions.hash || void 0; 24365 this._hash = resolvedOptions.hash ? new Hash(hashName).addTo(this) : void 0; 24366 if (!this._hash?._onHashChange()) { 24367 this.jumpTo({ 24368 center: resolvedOptions.center, 24369 elevation: resolvedOptions.elevation, 24370 zoom: resolvedOptions.zoom, 24371 bearing: resolvedOptions.bearing, 24372 pitch: resolvedOptions.pitch, 24373 roll: resolvedOptions.roll 24374 }); 24375 if (resolvedOptions.bounds) { 24376 this.resize(); 24377 this.fitBounds(resolvedOptions.bounds, extend({}, resolvedOptions.fitBoundsOptions, { duration: 0 })); 24378 } 24379 } 24380 const shouldConstrainUsingMercatorTransform = typeof resolvedOptions.style === "string" || !(resolvedOptions.style?.projection?.type === "globe"); 24381 this.resize(null, shouldConstrainUsingMercatorTransform); 24382 this._localIdeographFontFamily = resolvedOptions.localIdeographFontFamily; 24383 this._validateStyle = resolvedOptions.validateStyle; 24384 if (resolvedOptions.style) this.setStyle(resolvedOptions.style, { localIdeographFontFamily: resolvedOptions.localIdeographFontFamily }); 24385 if (resolvedOptions.attributionControl) this.addControl(new AttributionControl(typeof resolvedOptions.attributionControl === "boolean" ? void 0 : resolvedOptions.attributionControl)); 24386 if (resolvedOptions.maplibreLogo) this.addControl(new LogoControl(), resolvedOptions.logoPosition); 24387 this.on("style.load", () => { 24388 if (!shouldConstrainUsingMercatorTransform) this._resizeTransform(); 24389 if (this._camera.transform.unmodified) { 24390 const coercedOptions = pick(this.style.stylesheet, [ 24391 "center", 24392 "zoom", 24393 "bearing", 24394 "pitch", 24395 "roll" 24396 ]); 24397 this.jumpTo(coercedOptions); 24398 } 24399 }); 24400 this.on("data", (event) => { 24401 this._update(event.dataType === "style"); 24402 this.fire(event.dataType === "style" ? new MapStyleDataEvent("styledata", event) : new MapSourceDataEvent("sourcedata", event)); 24403 }); 24404 this.on("dataloading", (event) => { 24405 this.fire(event.dataType === "style" ? new MapStyleDataEvent("styledataloading", event) : new MapSourceDataEvent("sourcedataloading", event)); 24406 }); 24407 this.on("dataabort", (event) => { 24408 this.fire(new MapSourceDataEvent("sourcedataabort", event)); 24409 }); 24410 } 24411 /** 24412 * @internal 24413 * Returns a unique number for this map instance which is used for the MapLoadEvent 24414 * to make sure we only fire one event per instantiated map object. 24415 * @returns the uniq map ID 24416 */ 24417 _getMapId() { 24418 return this._mapId; 24419 } 24420 /** 24421 * Sets a global state property that can be retrieved with the [`global-state` expression](https://maplibre.org/maplibre-style-spec/expressions/#global-state). 24422 * If the value is null, it resets the property to its default value defined in the [`state` style property](https://maplibre.org/maplibre-style-spec/root/#state). 24423 * 24424 * @param propertyName - The name of the state property to set. 24425 * @param value - The value of the state property to set. 24426 */ 24427 setGlobalStateProperty(propertyName, value) { 24428 this.style.setGlobalStateProperty(propertyName, value); 24429 return this._update(true); 24430 } 24431 /** 24432 * Returns the global map state 24433 * 24434 * @returns The map state object. 24435 */ 24436 getGlobalState() { 24437 return this.style.getGlobalState(); 24438 } 24439 /** 24440 * Adds an {@link IControl} to the map, calling `control.onAdd(this)`. 24441 * 24442 * An {@link ErrorEvent} will be fired if the control is invalid. 24443 * 24444 * @param control - The {@link IControl} to add. 24445 * @param position - position on the map to which the control will be added. 24446 * Valid values are `'top-left'`, `'top-right'`, `'bottom-left'`, and `'bottom-right'`. Defaults to `'top-right'`. 24447 * @example 24448 * Add zoom and rotation controls to the map. 24449 * ```ts 24450 * map.addControl(new NavigationControl()); 24451 * ``` 24452 * @see [Display map navigation controls](https://maplibre.org/maplibre-gl-js/docs/examples/display-map-navigation-controls/) 24453 */ 24454 addControl(control, position) { 24455 if (position === void 0) { 24456 if (control.getDefaultPosition) position = control.getDefaultPosition(); 24457 else position = "top-right"; 24458 } 24459 if (!control?.onAdd) return this.fire(new ErrorEvent(/* @__PURE__ */ new Error("Invalid argument to map.addControl(). Argument must be a control with onAdd and onRemove methods."))); 24460 const controlElement = control.onAdd(this); 24461 this._controls.push(control); 24462 const positionContainer = this._controlPositions[position]; 24463 if (position.includes("bottom")) positionContainer.insertBefore(controlElement, positionContainer.firstChild); 24464 else positionContainer.appendChild(controlElement); 24465 return this; 24466 } 24467 /** 24468 * Removes the control from the map. 24469 * 24470 * An {@link ErrorEvent} will be fired if the control is invalid. 24471 * 24472 * @param control - The {@link IControl} to remove. 24473 * @example 24474 * ```ts 24475 * // Define a new navigation control. 24476 * let navigation = new NavigationControl(); 24477 * // Add zoom and rotation controls to the map. 24478 * map.addControl(navigation); 24479 * // Remove zoom and rotation controls from the map. 24480 * map.removeControl(navigation); 24481 * ``` 24482 */ 24483 removeControl(control) { 24484 if (!control?.onRemove) return this.fire(new ErrorEvent(/* @__PURE__ */ new Error("Invalid argument to map.removeControl(). Argument must be a control with onAdd and onRemove methods."))); 24485 const ci = this._controls.indexOf(control); 24486 if (ci > -1) this._controls.splice(ci, 1); 24487 control.onRemove(this); 24488 return this; 24489 } 24490 /** 24491 * Checks if a control exists on the map. 24492 * 24493 * @param control - The {@link IControl} to check. 24494 * @returns true if map contains control. 24495 * @example 24496 * ```ts 24497 * // Define a new navigation control. 24498 * let navigation = new NavigationControl(); 24499 * // Add zoom and rotation controls to the map. 24500 * map.addControl(navigation); 24501 * // Check that the navigation control exists on the map. 24502 * map.hasControl(navigation); 24503 * ``` 24504 */ 24505 hasControl(control) { 24506 return this._controls.includes(control); 24507 } 24508 /** 24509 * Returns an array of `OverscaledTileID` objects that cover the current viewport for a given tile size. 24510 * This method is useful for determining which tiles are visible in the current viewport. 24511 * 24512 * @param options - Options for calculating the covering tiles. 24513 * @returns An array of `OverscaledTileID` objects. 24514 * @example 24515 * ```ts 24516 * // Get the tiles to cover the view for a 512x512px tile source 24517 * const tiles = map.coveringTiles({tileSize: 512}); 24518 * ``` 24519 */ 24520 coveringTiles(options) { 24521 return coveringTiles(this._camera.transform, options); 24522 } 24523 /** 24524 * Sets the callback used to defer camera updates or apply arbitrary constraints. 24525 * If specified, this Camera instance can be used as a stateless component in React etc. 24526 */ 24527 setTransformCameraUpdate(value) { 24528 this._camera.transformCameraUpdate = value; 24529 } 24530 /** 24531 * Returns the map's geographical centerpoint. 24532 * 24533 * @returns The map's geographical centerpoint. 24534 * @example 24535 * Return a LngLat object such as `{lng: 0, lat: 0}` 24536 * ```ts 24537 * let center = map.getCenter(); 24538 * // access longitude and latitude values directly 24539 * let {lng, lat} = map.getCenter(); 24540 * ``` 24541 */ 24542 getCenter() { 24543 return new LngLat(this._camera.transform.center.lng, this._camera.transform.center.lat); 24544 } 24545 /** 24546 * Sets the map's geographical centerpoint. Equivalent to `jumpTo({center: center})`. 24547 * 24548 * Triggers the following events: `movestart` and `moveend`. 24549 * 24550 * @param center - The centerpoint to set. 24551 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24552 * @example 24553 * ```ts 24554 * map.setCenter([-74, 38]); 24555 * ``` 24556 */ 24557 setCenter(center, eventData) { 24558 this._camera.setCenter(center, eventData); 24559 return this; 24560 } 24561 /** 24562 * Returns the elevation of the map's center point. 24563 * 24564 * @returns The elevation of the map's center point, in meters above sea level. 24565 */ 24566 getCenterElevation() { 24567 return this._camera.transform.elevation; 24568 } 24569 /** 24570 * Sets the elevation of the map's center point, in meters above sea level. Equivalent to `jumpTo({elevation: elevation})`. 24571 * 24572 * Triggers the following events: `movestart` and `moveend`. 24573 * 24574 * @param elevation - The elevation to set, in meters above sea level. 24575 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24576 */ 24577 setCenterElevation(elevation, eventData) { 24578 this._camera.setCenterElevation(elevation, eventData); 24579 return this; 24580 } 24581 /** 24582 * Sets the value of `centerClampedToGround`. 24583 * 24584 * If true, the elevation of the center point will automatically be set to the terrain elevation 24585 * (or zero if terrain is not enabled). If false, the elevation of the center point will default 24586 * to sea level and will not automatically update. Defaults to true. Needs to be set to false to 24587 * keep the camera above ground when pitch \> 90 degrees. 24588 */ 24589 setCenterClampedToGround(centerClampedToGround) { 24590 this._camera.setCenterClampedToGround(centerClampedToGround); 24591 } 24592 /** 24593 * Pans the map by the specified offset. 24594 * 24595 * Triggers the following events: `movestart` and `moveend`. 24596 * 24597 * @param offset - `x` and `y` coordinates by which to pan the map. 24598 * @param options - Options object 24599 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24600 * @see [Navigate the map with game-like controls](https://maplibre.org/maplibre-gl-js/docs/examples/navigate-the-map-with-game-like-controls/) 24601 */ 24602 panBy(offset, options, eventData) { 24603 this._camera.panBy(offset, options, eventData); 24604 return this; 24605 } 24606 /** 24607 * Pans the map to the specified location with an animated transition. 24608 * 24609 * Triggers the following events: `movestart` and `moveend`. 24610 * 24611 * @param lnglat - The location to pan the map to. 24612 * @param options - Options describing the destination and animation of the transition. 24613 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24614 * @example 24615 * ```ts 24616 * map.panTo([-74, 38]); 24617 * // Specify that the panTo animation should last 5000 milliseconds. 24618 * map.panTo([-74, 38], {duration: 5000}); 24619 * ``` 24620 * @see [Update a feature in realtime](https://maplibre.org/maplibre-gl-js/docs/examples/update-a-feature-in-realtime/) 24621 */ 24622 panTo(lnglat, options, eventData) { 24623 this._camera.panTo(lnglat, options, eventData); 24624 return this; 24625 } 24626 /** 24627 * Returns the map's current zoom level. 24628 * 24629 * @returns The map's current zoom level. 24630 * @example 24631 * ```ts 24632 * map.getZoom(); 24633 * ``` 24634 */ 24635 getZoom() { 24636 return this._camera.transform.zoom; 24637 } 24638 /** 24639 * Sets the map's zoom level. Equivalent to `jumpTo({zoom: zoom})`. 24640 * 24641 * Triggers the following events: `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, and `zoomend`. 24642 * 24643 * @param zoom - The zoom level to set (0-20). 24644 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24645 * @example 24646 * Zoom to the zoom level 5 without an animated transition 24647 * ```ts 24648 * map.setZoom(5); 24649 * ``` 24650 */ 24651 setZoom(zoom, eventData) { 24652 this._camera.setZoom(zoom, eventData); 24653 return this; 24654 } 24655 /** 24656 * Zooms the map to the specified zoom level, with an animated transition. 24657 * 24658 * Triggers the following events: `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, and `zoomend`. 24659 * 24660 * @param zoom - The zoom level to transition to. 24661 * @param options - Options object 24662 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24663 * @example 24664 * ```ts 24665 * // Zoom to the zoom level 5 without an animated transition 24666 * map.zoomTo(5); 24667 * // Zoom to the zoom level 8 with an animated transition 24668 * map.zoomTo(8, { 24669 * duration: 2000, 24670 * offset: [100, 50] 24671 * }); 24672 * ``` 24673 */ 24674 zoomTo(zoom, options, eventData) { 24675 this._camera.zoomTo(zoom, options, eventData); 24676 return this; 24677 } 24678 /** 24679 * Incrementally increases the map's zoom level by 1, first snapping to the nearest `zoomSnap` increment. 24680 * 24681 * Triggers the following events: `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, and `zoomend`. 24682 * 24683 * @param options - Options object 24684 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24685 * @example 24686 * Zoom the map in one level with a custom animation duration 24687 * ```ts 24688 * map.zoomIn({duration: 1000}); 24689 * ``` 24690 */ 24691 zoomIn(options, eventData) { 24692 this._camera.zoomIn(options, eventData); 24693 return this; 24694 } 24695 /** 24696 * Decreases the map's zoom level by 1, first snapping to the nearest `zoomSnap` increment. 24697 * 24698 * Triggers the following events: `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, and `zoomend`. 24699 * 24700 * @param options - Options object 24701 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24702 * @example 24703 * Zoom the map out one level with a custom animation offset 24704 * ```ts 24705 * map.zoomOut({offset: [80, 60]}); 24706 * ``` 24707 */ 24708 zoomOut(options, eventData) { 24709 this._camera.zoomOut(options, eventData); 24710 return this; 24711 } 24712 /** 24713 * Returns the map's current vertical field of view, in degrees. 24714 * 24715 * @returns The map's current vertical field of view. 24716 * @defaultValue 36.87 24717 * @example 24718 * ```ts 24719 * const verticalFieldOfView = map.getVerticalFieldOfView(); 24720 * ``` 24721 */ 24722 getVerticalFieldOfView() { 24723 return this._camera.transform.fov; 24724 } 24725 /** 24726 * Sets the map's vertical field of view, in degrees. 24727 * 24728 * Triggers the following events: `movestart`, `move`, and `moveend`. 24729 * 24730 * @param fov - The vertical field of view to set, in degrees (0-180). 24731 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24732 * @defaultValue 36.87 24733 * @example 24734 * Change vertical field of view to 30 degrees 24735 * ```ts 24736 * map.setVerticalFieldOfView(30); 24737 * ``` 24738 */ 24739 setVerticalFieldOfView(fov, eventData) { 24740 this._camera.setVerticalFieldOfView(fov, eventData); 24741 return this; 24742 } 24743 /** 24744 * Returns the map's current bearing. The bearing is the compass direction that is "up"; for example, a bearing 24745 * of 90° orients the map so that east is up. 24746 * 24747 * @returns The map's current bearing. 24748 * @see [Navigate the map with game-like controls](https://maplibre.org/maplibre-gl-js/docs/examples/navigate-the-map-with-game-like-controls/) 24749 */ 24750 getBearing() { 24751 return this._camera.transform.bearing; 24752 } 24753 /** 24754 * Sets the map's bearing (rotation). The bearing is the compass direction that is "up"; for example, a bearing 24755 * of 90° orients the map so that east is up. 24756 * 24757 * Equivalent to `jumpTo({bearing: bearing})`. 24758 * 24759 * Triggers the following events: `movestart`, `moveend`, and `rotate`. 24760 * 24761 * @param bearing - The desired bearing. 24762 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24763 * @example 24764 * Rotate the map to 90 degrees 24765 * ```ts 24766 * map.setBearing(90); 24767 * ``` 24768 */ 24769 setBearing(bearing, eventData) { 24770 this._camera.setBearing(bearing, eventData); 24771 return this; 24772 } 24773 /** 24774 * Returns the map's current zoom snap level. 24775 * 24776 * @returns The map's current zoom snap level. 24777 */ 24778 getZoomSnap() { 24779 return this._camera.getZoomSnap(); 24780 } 24781 /** 24782 * Sets the map's zoom snap level. 24783 * 24784 * @param snap - The zoom snap level to set. 24785 */ 24786 setZoomSnap(snap) { 24787 this._camera.setZoomSnap(snap); 24788 return this; 24789 } 24790 /** 24791 * Returns the current padding applied around the map viewport. 24792 * 24793 * @returns The current padding around the map viewport. 24794 */ 24795 getPadding() { 24796 return this._camera.transform.padding; 24797 } 24798 /** 24799 * Sets the padding in pixels around the viewport. 24800 * 24801 * Equivalent to `jumpTo({padding: padding})`. 24802 * 24803 * Triggers the following events: `movestart` and `moveend`. 24804 * 24805 * @param padding - The desired padding. 24806 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24807 * @example 24808 * Sets a left padding of 300px, and a top padding of 50px 24809 * ```ts 24810 * map.setPadding({ left: 300, top: 50 }); 24811 * ``` 24812 */ 24813 setPadding(padding, eventData) { 24814 this._camera.setPadding(padding, eventData); 24815 return this; 24816 } 24817 /** 24818 * Rotates the map to the specified bearing, with an animated transition. The bearing is the compass direction 24819 * that is "up"; for example, a bearing of 90° orients the map so that east is up. 24820 * 24821 * Triggers the following events: `movestart`, `moveend`, and `rotate`. 24822 * 24823 * @param bearing - The desired bearing. 24824 * @param options - Options object 24825 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24826 */ 24827 rotateTo(bearing, options, eventData) { 24828 this._camera.rotateTo(bearing, options, eventData); 24829 return this; 24830 } 24831 /** 24832 * Rotates the map so that north is up (0° bearing), with an animated transition. 24833 * 24834 * Triggers the following events: `movestart`, `moveend`, and `rotate`. 24835 * 24836 * @param options - Options object 24837 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24838 */ 24839 resetNorth(options, eventData) { 24840 this._camera.resetNorth(options, eventData); 24841 return this; 24842 } 24843 /** 24844 * Rotates and pitches the map so that north is up (0° bearing) and pitch and roll are 0°, with an animated transition. 24845 * 24846 * Triggers the following events: `movestart`, `move`, `moveend`, `pitchstart`, `pitch`, `pitchend`, `rollstart`, `roll`, `rollend`, and `rotate`. 24847 * 24848 * @param options - Options object 24849 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24850 */ 24851 resetNorthPitch(options, eventData) { 24852 this._camera.resetNorthPitch(options, eventData); 24853 return this; 24854 } 24855 /** 24856 * Snaps the map so that north is up (0° bearing), if the current bearing is close enough to it (i.e. within the 24857 * `bearingSnap` threshold). 24858 * 24859 * Triggers the following events: `movestart`, `moveend`, and `rotate`. 24860 * 24861 * @param options - Options object 24862 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24863 */ 24864 snapToNorth(options, eventData) { 24865 this._camera.snapToNorth(options, eventData); 24866 return this; 24867 } 24868 /** 24869 * Returns the map's current pitch (tilt). 24870 * 24871 * @returns The map's current pitch, measured in degrees away from the plane of the screen. 24872 */ 24873 getPitch() { 24874 return this._camera.transform.pitch; 24875 } 24876 /** 24877 * Sets the map's pitch (tilt). Equivalent to `jumpTo({pitch: pitch})`. 24878 * 24879 * Triggers the following events: `movestart`, `moveend`, `pitchstart`, and `pitchend`. 24880 * 24881 * @param pitch - The pitch to set, measured in degrees away from the plane of the screen (0-60). 24882 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24883 */ 24884 setPitch(pitch, eventData) { 24885 this._camera.setPitch(pitch, eventData); 24886 return this; 24887 } 24888 /** 24889 * Returns the map's current roll angle. 24890 * 24891 * @returns The map's current roll, measured in degrees about the camera boresight. 24892 */ 24893 getRoll() { 24894 return this._camera.transform.roll; 24895 } 24896 /** 24897 * Sets the map's roll angle. Equivalent to `jumpTo({roll: roll})`. 24898 * 24899 * Triggers the following events: `movestart`, `moveend`, `rollstart`, and `rollend`. 24900 * 24901 * @param roll - The roll to set, measured in degrees about the camera boresight 24902 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24903 */ 24904 setRoll(roll, eventData) { 24905 this._camera.setRoll(roll, eventData); 24906 return this; 24907 } 24908 /** 24909 * @param bounds - Calculate the center for these bounds in the viewport and use 24910 * the highest zoom level up to and including {@link Map.getMaxZoom} that fits 24911 * in the viewport. LngLatBounds represent a box that is always axis-aligned with bearing 0. 24912 * Bounds will be taken in `[sw, ne]` order. Southwest point will always be to the left of the northeast point. 24913 * @param options - Options object 24914 * @returns If map is able to fit to provided bounds, returns `center`, `zoom`, and `bearing`. 24915 * If map is unable to fit, method will warn and return undefined. 24916 * @example 24917 * ```ts 24918 * let bbox = [[-79, 43], [-73, 45]]; 24919 * let newCameraTransform = map.cameraForBounds(bbox, { 24920 * padding: {top: 10, bottom:25, left: 15, right: 5} 24921 * }); 24922 * ``` 24923 */ 24924 cameraForBounds(bounds, options) { 24925 return this._camera.cameraForBounds(bounds, options); 24926 } 24927 /** 24928 * Pans and zooms the map to contain its visible area within the specified geographical bounds. 24929 * This function will also reset the map's bearing to 0 if bearing is nonzero. 24930 * 24931 * Triggers the following events: `movestart` and `moveend`. 24932 * 24933 * @param bounds - Center these bounds in the viewport and use the highest 24934 * zoom level up to and including {@link Map.getMaxZoom} that fits them in the viewport. 24935 * Bounds will be taken in `[sw, ne]` order. Southwest point will always be to the left of the northeast point. 24936 * @param options - Options supports all properties from {@link AnimationOptions} and {@link CameraOptions} in addition to the fields below. 24937 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24938 * @example 24939 * ```ts 24940 * let bbox = [[-79, 43], [-73, 45]]; 24941 * map.fitBounds(bbox, { 24942 * padding: {top: 10, bottom:25, left: 15, right: 5} 24943 * }); 24944 * ``` 24945 * @see [Fit a map to a bounding box](https://maplibre.org/maplibre-gl-js/docs/examples/fit-a-map-to-a-bounding-box/) 24946 */ 24947 fitBounds(bounds, options, eventData) { 24948 this._camera.fitBounds(bounds, options, eventData); 24949 return this; 24950 } 24951 /** 24952 * Pans, rotates and zooms the map to to fit the box made by points p0 and p1 24953 * once the map is rotated to the specified bearing. To zoom without rotating, 24954 * pass in the current map bearing. 24955 * 24956 * Triggers the following events: `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, `zoomend` and `rotate`. 24957 * 24958 * @param p0 - First point on screen, in pixel coordinates 24959 * @param p1 - Second point on screen, in pixel coordinates 24960 * @param bearing - Desired map bearing at end of animation, in degrees 24961 * @param options - Options object 24962 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24963 * @example 24964 * ```ts 24965 * let p0 = [220, 400]; 24966 * let p1 = [500, 900]; 24967 * map.fitScreenCoordinates(p0, p1, map.getBearing(), { 24968 * padding: {top: 10, bottom:25, left: 15, right: 5} 24969 * }); 24970 * ``` 24971 * @see Used by {@link BoxZoomHandler} 24972 */ 24973 fitScreenCoordinates(p0, p1, bearing, options, eventData) { 24974 this._camera.fitScreenCoordinates(p0, p1, bearing, options, eventData); 24975 return this; 24976 } 24977 /** 24978 * Changes any combination of center, zoom, bearing, pitch, and roll, without 24979 * an animated transition. The map will retain its current values for any 24980 * details not specified in `options`. 24981 * 24982 * Triggers the following events: `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, `zoomend`, `pitchstart`, 24983 * `pitch`, `pitchend`, `rollstart`, `roll`, `rollend` and `rotate`. 24984 * 24985 * @param options - Options object 24986 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 24987 * @example 24988 * ```ts 24989 * // jump to coordinates at current zoom 24990 * map.jumpTo({center: [0, 0]}); 24991 * // jump with zoom, pitch, and bearing options 24992 * map.jumpTo({ 24993 * center: [0, 0], 24994 * zoom: 8, 24995 * pitch: 45, 24996 * bearing: 90 24997 * }); 24998 * ``` 24999 * @see [Jump to a series of locations](https://maplibre.org/maplibre-gl-js/docs/examples/jump-to-a-series-of-locations/) 25000 * @see [Update a feature in realtime](https://maplibre.org/maplibre-gl-js/docs/examples/update-a-feature-in-realtime/) 25001 */ 25002 jumpTo(options, eventData) { 25003 this._camera.jumpTo(options, eventData); 25004 return this; 25005 } 25006 /** 25007 * Calculates constrained camera options that place a geographic anchor at a screen point 25008 * without changing the map, using the same logic as MapLibre's interaction handlers. 25009 * 25010 * @param options - Anchor and optional zoom. 25011 * @returns Camera options that can be passed to {@link Map.jumpTo}. 25012 * @example 25013 * ```ts 25014 * const cameraOptions = map.calculateAnchoredCameraOptions({ 25015 * anchorLocation: map.unproject(pointerDownPosition), 25016 * anchorScreenPoint: currentPointerPosition, 25017 * zoom: map.getZoom() + 1, 25018 * }); 25019 * map.jumpTo(cameraOptions); 25020 * ``` 25021 */ 25022 calculateAnchoredCameraOptions(options) { 25023 return this._camera.calculateAnchoredCameraOptions(options); 25024 } 25025 /** 25026 * Given a camera position and rotation, calculates zoom and center point and returns them as {@link CameraOptions}. 25027 * @param cameraLngLat - The lng, lat of the camera to look from 25028 * @param cameraAlt - The altitude of the camera to look from, in meters above sea level 25029 * @param bearing - Bearing of the camera, in degrees 25030 * @param pitch - Pitch of the camera, in degrees 25031 * @param roll - Roll of the camera, in degrees 25032 * @returns the calculated camera options 25033 * @example 25034 * ```ts 25035 * // Calculate options to look from camera position(1°, 0°, 1000m) with bearing = 90°, pitch = 30°, and roll = 45° 25036 * const cameraLngLat = new LngLat(1, 0); 25037 * const cameraAltitude = 1000; 25038 * const bearing = 90; 25039 * const pitch = 30; 25040 * const roll = 45; 25041 * const cameraOptions = map.calculateCameraOptionsFromCameraLngLatAltRotation(cameraLngLat, cameraAltitude, bearing, pitch, roll); 25042 * // Apply calculated options 25043 * map.jumpTo(cameraOptions); 25044 * ``` 25045 */ 25046 calculateCameraOptionsFromCameraLngLatAltRotation(cameraLngLat, cameraAlt, bearing, pitch, roll) { 25047 return this._camera.calculateCameraOptionsFromCameraLngLatAltRotation(cameraLngLat, cameraAlt, bearing, pitch, roll); 25048 } 25049 /** 25050 * Changes any combination of `center`, `zoom`, `bearing`, `pitch`, `roll`, and `padding` with an animated transition 25051 * between old and new values. The map will retain its current values for any 25052 * details not specified in `options`. 25053 * 25054 * !!! note "Reduced Motion" 25055 * The transition will happen instantly if the user has enabled 25056 * the `reduced motion` accessibility feature enabled in their operating system, 25057 * unless `options` includes `essential: true`. 25058 * 25059 * Triggers the following events: `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, `zoomend`, `pitchstart`, 25060 * `pitch`, `pitchend`, `rollstart`, `roll`, `rollend`, and `rotate`. 25061 * 25062 * @param options - Options describing the destination and animation of the transition. 25063 * Accepts {@link CameraOptions} and {@link AnimationOptions}. 25064 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 25065 * @see [Navigate the map with game-like controls](https://maplibre.org/maplibre-gl-js/docs/examples/navigate-the-map-with-game-like-controls/) 25066 */ 25067 easeTo(options, eventData) { 25068 this._camera.easeTo(options, eventData); 25069 return this; 25070 } 25071 /** 25072 * Changes any combination of center, zoom, bearing, pitch, and roll, animating the transition along a curve that 25073 * evokes flight. The animation seamlessly incorporates zooming and panning to help 25074 * the user maintain her bearings even after traversing a great distance. 25075 * 25076 * !!! note "Reduced Motion" 25077 * The animation will be skipped, and this will behave equivalently to `jumpTo` 25078 * if the user has the `reduced motion` accessibility feature enabled in their operating system, 25079 * unless 'options' includes `essential: true`. 25080 * 25081 * Triggers the following events: `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, `zoomend`, `pitchstart`, 25082 * `pitch`, `pitchend`, `rollstart`, `roll`, `rollend`, and `rotate`. 25083 * 25084 * @param options - Options describing the destination and animation of the transition. 25085 * Accepts {@link CameraOptions}, {@link AnimationOptions}, 25086 * and the following additional options. 25087 * @param eventData - Additional properties to be added to event objects of events triggered by this method. 25088 * @example 25089 * ```ts 25090 * // fly with default options to null island 25091 * map.flyTo({center: [0, 0], zoom: 9}); 25092 * // using flyTo options 25093 * map.flyTo({ 25094 * center: [0, 0], 25095 * zoom: 9, 25096 * speed: 0.2, 25097 * curve: 1, 25098 * easing(t) { 25099 * return t; 25100 * } 25101 * }); 25102 * ``` 25103 * @see [Fly to a location](https://maplibre.org/maplibre-gl-js/docs/examples/fly-to-a-location/) 25104 * @see [Slowly fly to a location](https://maplibre.org/maplibre-gl-js/docs/examples/slowly-fly-to-a-location/) 25105 * @see [Fly to a location based on scroll position](https://maplibre.org/maplibre-gl-js/docs/examples/fly-to-a-location-based-on-scroll-position/) 25106 */ 25107 flyTo(options, eventData) { 25108 this._camera.flyTo(options, eventData); 25109 return this; 25110 } 25111 /** 25112 * Stops any animated transition underway. 25113 */ 25114 stop() { 25115 this._camera.stop(); 25116 return this; 25117 } 25118 /** 25119 * Gets the elevation at a given location, in meters above sea level. 25120 * Returns null if terrain is not enabled. 25121 * If terrain is enabled with some exaggeration value, the value returned here will be reflective of (multiplied by) that exaggeration value. 25122 * This method should be used for proper positioning of custom 3d objects, as explained [here](https://maplibre.org/maplibre-gl-js/docs/examples/adding-3d-models-using-threejs-on-terrain/) 25123 * @param lngLatLike - `[x, y]` or LngLat coordinates of the location 25124 * @returns elevation in meters 25125 */ 25126 queryTerrainElevation(lngLatLike) { 25127 if (!this.terrain) return null; 25128 return this.terrain.getElevationForLngLat(LngLat.convert(lngLatLike), this._camera.transform); 25129 } 25130 /** 25131 * Returns the value of `centerClampedToGround`. 25132 * 25133 * If true, the elevation of the center point will automatically be set to the terrain elevation 25134 * (or zero if terrain is not enabled). If false, the elevation of the center point will default 25135 * to sea level and will not automatically update. Defaults to true. Needs to be set to false to 25136 * keep the camera above ground when pitch \> 90 degrees. 25137 */ 25138 getCenterClampedToGround() { 25139 return this._camera.getCenterClampedToGround(); 25140 } 25141 /** 25142 * Given a camera 'from' position and a position to look at (`to`), calculates zoom and camera rotation and returns them as {@link CameraOptions}. 25143 * Under `globe` and `vertical-perspective` the calculation follows the sphere while the map renders as a globe, keeping the point looked at on the sea-level sphere; `altitudeTo` only becomes the center elevation. 25144 * @param from - The camera to look from 25145 * @param altitudeFrom - The altitude of the camera to look from 25146 * @param to - The center to look at 25147 * @param altitudeTo - Optional altitude of the center to look at. If none given the ground height will be used. 25148 * @returns the calculated camera options 25149 * @example 25150 * ```ts 25151 * // Calculate options to look from (1°, 0°, 1000m) to (1°, 1°, 0m) 25152 * const cameraLngLat = new LngLat(1, 0); 25153 * const cameraAltitude = 1000; 25154 * const targetLngLat = new LngLat(1, 1); 25155 * const targetAltitude = 0; 25156 * const cameraOptions = map.calculateCameraOptionsFromTo(cameraLngLat, cameraAltitude, targetLngLat, targetAltitude); 25157 * // Apply calculated options 25158 * map.jumpTo(cameraOptions); 25159 * ``` 25160 */ 25161 calculateCameraOptionsFromTo(from, altitudeFrom, to, altitudeTo) { 25162 if (altitudeTo == null && this.terrain) altitudeTo = this.terrain.getElevationForLngLat(to, this._camera.transform); 25163 return this._camera.transform.calculateCameraOptionsFromTo(from, altitudeFrom, to, altitudeTo ?? 0); 25164 } 25165 /** 25166 * Resizes the map according to the dimensions of its 25167 * `container` element. 25168 * 25169 * Checks if the map container size changed and updates the map if it has changed. 25170 * With the default `trackResize: true`, container size changes are picked up automatically, 25171 * including a container that becomes visible after being hidden with CSS. Call this method 25172 * explicitly when `trackResize` is `false`, or when the map's size changes in a way the 25173 * container's `ResizeObserver` cannot observe. 25174 * 25175 * Triggers the following events: `movestart`, `move`, `moveend`, and `resize`. 25176 * 25177 * @param eventData - Additional properties to be passed to `movestart`, `move`, `resize`, and `moveend` 25178 * events that get triggered as a result of resize. This can be useful for differentiating the 25179 * source of an event (for example, user-initiated or programmatically-triggered events). 25180 * @example 25181 * Resize a map with `trackResize` disabled when its container is shown after being hidden with CSS. 25182 * ```ts 25183 * let mapDiv = document.getElementById('map'); 25184 * if (mapDiv.style.visibility === 'visible') map.resize(); 25185 * ``` 25186 */ 25187 resize(eventData, constrainTransform = true) { 25188 if (this._lostContextStyle.style !== null) return this; 25189 this._resizeInternal(constrainTransform); 25190 const fireMoving = !this._camera._moving; 25191 if (fireMoving) { 25192 this.stop(); 25193 this.fire(new MapMovementEvent("movestart", eventData)).fire(new MapMovementEvent("move", eventData)); 25194 } 25195 this.fire(new MapLibreEvent("resize", eventData)); 25196 if (fireMoving) this.fire(new MapMovementEvent("moveend", eventData)); 25197 return this; 25198 } 25199 /** 25200 * Resizes the map according to the dimensions of its 25201 * `container` element. 25202 * 25203 * It does not trigger any events, and does not check for context loss. 25204 * 25205 * It is used internally and by {@link Map.resize}. 25206 * 25207 * @internal 25208 * 25209 * @param constrainTransform - whether to constrain the transform after resizing. 25210 */ 25211 _resizeInternal(constrainTransform = true) { 25212 const [width, height] = this._containerDimensions(); 25213 const clampedPixelRatio = this._getClampedPixelRatio(width, height); 25214 this._resizeCanvas(width, height, clampedPixelRatio); 25215 this.painter.resize(width, height, clampedPixelRatio); 25216 if (this.painter.overLimit()) { 25217 const gl = this.painter.context.gl; 25218 this._maxCanvasSize = [gl.drawingBufferWidth, gl.drawingBufferHeight]; 25219 const clampedPixelRatio = this._getClampedPixelRatio(width, height); 25220 this._resizeCanvas(width, height, clampedPixelRatio); 25221 this.painter.resize(width, height, clampedPixelRatio); 25222 } 25223 this._resizeTransform(constrainTransform); 25224 } 25225 _resizeTransform(constrainTransform = true) { 25226 const [width, height] = this._containerDimensions(); 25227 this._camera.transform.resize(width, height, constrainTransform); 25228 this._camera._requestedCameraState?.resize(width, height, constrainTransform); 25229 } 25230 /** 25231 * @internal 25232 * Return the map's pixel ratio eventually scaled down to respect maxCanvasSize. 25233 * Internally you should use this and not getPixelRatio(). 25234 * Warns once when the ratio is scaled down. The message carries no sizes: this runs on every 25235 * resize and `warnOnce` de-duplicates by message, so sizes would warn on every drag frame. 25236 */ 25237 _getClampedPixelRatio(width, height) { 25238 const { 0: maxCanvasWidth, 1: maxCanvasHeight } = this._maxCanvasSize; 25239 const pixelRatio = this.getPixelRatio(); 25240 const canvasWidth = width * pixelRatio; 25241 const canvasHeight = height * pixelRatio; 25242 const widthScaleFactor = canvasWidth > maxCanvasWidth ? maxCanvasWidth / canvasWidth : 1; 25243 const heightScaleFactor = canvasHeight > maxCanvasHeight ? maxCanvasHeight / canvasHeight : 1; 25244 const scaleFactor = Math.min(widthScaleFactor, heightScaleFactor); 25245 if (scaleFactor < 1) warnOnce("The canvas is larger than maxCanvasSize and is rendered at a lower pixel ratio to fit. Increase maxCanvasSize, within MAX_TEXTURE_SIZE, to render at full resolution."); 25246 return scaleFactor * pixelRatio; 25247 } 25248 /** 25249 * Returns the map's pixel ratio. 25250 * Note that the pixel ratio actually applied may be lower to respect maxCanvasSize. 25251 * @returns The pixel ratio. 25252 */ 25253 getPixelRatio() { 25254 return this._overridePixelRatio ?? devicePixelRatio; 25255 } 25256 /** 25257 * Sets the map's pixel ratio. This allows to override `devicePixelRatio`. 25258 * After this call, the canvas' `width` attribute will be `container.clientWidth * pixelRatio` 25259 * and its height attribute will be `container.clientHeight * pixelRatio`. 25260 * Set this to null to disable `devicePixelRatio` override. 25261 * Note that the pixel ratio actually applied may be lower to respect maxCanvasSize. 25262 * @param pixelRatio - The pixel ratio. 25263 */ 25264 setPixelRatio(pixelRatio) { 25265 this._overridePixelRatio = pixelRatio; 25266 this.resize(); 25267 } 25268 /** 25269 * Returns the map's geographical bounds. When the bearing or pitch is non-zero, the visible region is not 25270 * an axis-aligned rectangle, and the result is the smallest bounds that encompasses the visible region. 25271 * @returns The geographical bounds of the map as {@link LngLatBounds}. 25272 * @example 25273 * ```ts 25274 * let bounds = map.getBounds(); 25275 * ``` 25276 */ 25277 getBounds() { 25278 return this._camera.transform.getBounds(); 25279 } 25280 /** 25281 * Returns the maximum geographical bounds the map is constrained to, or `null` if none set. 25282 * @returns The map object. 25283 * @example 25284 * ```ts 25285 * let maxBounds = map.getMaxBounds(); 25286 * ``` 25287 */ 25288 getMaxBounds() { 25289 return this._camera.transform.getMaxBounds(); 25290 } 25291 /** 25292 * Sets or clears the map's geographical bounds. 25293 * 25294 * Pan and zoom operations are constrained within these bounds. 25295 * If a pan or zoom is performed that would 25296 * display regions outside these bounds, the map will 25297 * instead display a position and zoom level 25298 * as close as possible to the operation's request while still 25299 * remaining within the bounds. 25300 * 25301 * @param bounds - The maximum bounds to set. If `null` or `undefined` is provided, the function removes the map's maximum bounds. 25302 * @example 25303 * Define bounds that conform to the `LngLatBoundsLike` object as set the max bounds. 25304 * ```ts 25305 * let bounds = [ 25306 * [-74.04728, 40.68392], // [west, south] 25307 * [-73.91058, 40.87764] // [east, north] 25308 * ];
vendor: 24,954 bytes, lines 25309-25876
25309 * map.setMaxBounds(bounds); 25310 * ``` 25311 */ 25312 setMaxBounds(bounds) { 25313 this._camera.transform.setMaxBounds(LngLatBounds.convert(bounds)); 25314 return this._update(); 25315 } 25316 /** 25317 * Sets or clears the map's minimum zoom level. 25318 * If the map's current zoom level is lower than the new minimum, 25319 * the map will zoom to the new minimum and trigger the following events: 25320 * `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, and `zoomend`. 25321 * 25322 * It is not always possible to zoom out and reach the set `minZoom`. 25323 * Other factors such as map height may restrict zooming. For example, 25324 * if the map is 512px tall it will not be possible to zoom below zoom 0 25325 * no matter what the `minZoom` is set to. 25326 * 25327 * A {@link ErrorEvent} event will be fired if minZoom is out of bounds. 25328 * 25329 * @param minZoom - The minimum zoom level to set (-2 - 24). 25330 * If `null` or `undefined` is provided, the function removes the current minimum zoom (i.e. sets it to -2). 25331 * @example 25332 * ```ts 25333 * map.setMinZoom(12.25); 25334 * ``` 25335 */ 25336 setMinZoom(minZoom) { 25337 minZoom = minZoom === null || minZoom === void 0 ? defaultMinZoom : minZoom; 25338 if (minZoom >= defaultMinZoom && minZoom <= this._camera.transform.maxZoom) { 25339 const zoomBefore = this._camera.transform.zoom; 25340 const tr = this._camera.getTransformForUpdate(); 25341 tr.setMinZoom(minZoom); 25342 this._camera.applyUpdatedTransform(tr); 25343 this._update(); 25344 if (zoomBefore !== this._camera.transform.zoom) this.fire(new MapMovementEvent("zoomstart")).fire(new MapMovementEvent("zoom")).fire(new MapMovementEvent("zoomend")).fire(new MapMovementEvent("movestart")).fire(new MapMovementEvent("move")).fire(new MapMovementEvent("moveend")); 25345 return this; 25346 } else throw new Error(`minZoom must be between ${defaultMinZoom} and the current maxZoom, inclusive`); 25347 } 25348 /** 25349 * Returns the map's minimum allowable zoom level. 25350 * 25351 * @param constrained - If `true`, returns the effective minimum zoom after applying the map's viewport constraints. 25352 * If `false` or omitted, returns the configured minimum zoom. 25353 * @returns minZoom 25354 * @example 25355 * ```ts 25356 * let minZoom = map.getMinZoom(); 25357 * ``` 25358 */ 25359 getMinZoom(constrained = false) { 25360 const transform = this._camera.transform; 25361 return constrained ? transform.applyConstrain(transform.center, transform.minZoom).zoom : transform.minZoom; 25362 } 25363 /** 25364 * Sets or clears the map's maximum zoom level. 25365 * If the map's current zoom level is higher than the new maximum, 25366 * the map will zoom to the new maximum and trigger the following events: 25367 * `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, and `zoomend`. 25368 * 25369 * A {@link ErrorEvent} event will be fired if minZoom is out of bounds. 25370 * 25371 * @param maxZoom - The maximum zoom level to set. 25372 * If `null` or `undefined` is provided, the function removes the current maximum zoom (sets it to 22). 25373 * @example 25374 * ```ts 25375 * map.setMaxZoom(18.75); 25376 * ``` 25377 */ 25378 setMaxZoom(maxZoom) { 25379 maxZoom = maxZoom === null || maxZoom === void 0 ? defaultMaxZoom : maxZoom; 25380 if (maxZoom >= this._camera.transform.minZoom) { 25381 const zoomBefore = this._camera.transform.zoom; 25382 const tr = this._camera.getTransformForUpdate(); 25383 tr.setMaxZoom(maxZoom); 25384 this._camera.applyUpdatedTransform(tr); 25385 this._update(); 25386 if (zoomBefore !== this._camera.transform.zoom) this.fire(new MapMovementEvent("zoomstart")).fire(new MapMovementEvent("zoom")).fire(new MapMovementEvent("zoomend")).fire(new MapMovementEvent("movestart")).fire(new MapMovementEvent("move")).fire(new MapMovementEvent("moveend")); 25387 return this; 25388 } else throw new Error("maxZoom must be greater than the current minZoom"); 25389 } 25390 /** 25391 * Returns the map's maximum allowable zoom level. 25392 * 25393 * @returns The maxZoom 25394 * @example 25395 * ```ts 25396 * let maxZoom = map.getMaxZoom(); 25397 * ``` 25398 */ 25399 getMaxZoom() { 25400 return this._camera.transform.maxZoom; 25401 } 25402 /** 25403 * Sets or clears the map's minimum pitch. 25404 * If the map's current pitch is lower than the new minimum, 25405 * the map will pitch to the new minimum and trigger the following events: 25406 * `movestart`, `move`, `moveend`, `pitchstart`, `pitch`, and `pitchend`. 25407 * 25408 * A {@link ErrorEvent} event will be fired if minPitch is out of bounds. 25409 * 25410 * @param minPitch - The minimum pitch to set (0-180). Values greater than 60 degrees are experimental and may result in rendering issues. If you encounter any, please raise an issue with details in the MapLibre project. 25411 * If `null` or `undefined` is provided, the function removes the current minimum pitch (i.e. sets it to 0). 25412 */ 25413 setMinPitch(minPitch) { 25414 minPitch = minPitch === null || minPitch === void 0 ? defaultMinPitch : minPitch; 25415 if (minPitch < defaultMinPitch) throw new Error(`minPitch must be greater than or equal to ${defaultMinPitch}`); 25416 if (minPitch >= defaultMinPitch && minPitch <= this._camera.transform.maxPitch) { 25417 const pitchBefore = this._camera.transform.pitch; 25418 const tr = this._camera.getTransformForUpdate(); 25419 tr.setMinPitch(minPitch); 25420 this._camera.applyUpdatedTransform(tr); 25421 this._update(); 25422 if (pitchBefore !== this._camera.transform.pitch) this.fire(new MapMovementEvent("pitchstart")).fire(new MapMovementEvent("pitch")).fire(new MapMovementEvent("pitchend")).fire(new MapMovementEvent("movestart")).fire(new MapMovementEvent("move")).fire(new MapMovementEvent("moveend")); 25423 return this; 25424 } else throw new Error(`minPitch must be between ${defaultMinPitch} and the current maxPitch, inclusive`); 25425 } 25426 /** 25427 * Returns the map's minimum allowable pitch. 25428 * 25429 * @returns The minPitch 25430 */ 25431 getMinPitch() { 25432 return this._camera.transform.minPitch; 25433 } 25434 /** 25435 * Sets or clears the map's maximum pitch. 25436 * If the map's current pitch is higher than the new maximum, 25437 * the map will pitch to the new maximum and trigger the following events: 25438 * `movestart`, `move`, `moveend`, `pitchstart`, `pitch`, and `pitchend`. 25439 * 25440 * A {@link ErrorEvent} event will be fired if maxPitch is out of bounds. 25441 * 25442 * @param maxPitch - The maximum pitch to set (0-180). Values greater than 60 degrees are experimental and may result in rendering issues. If you encounter any, please raise an issue with details in the MapLibre project. 25443 * If `null` or `undefined` is provided, the function removes the current maximum pitch (sets it to 60). 25444 */ 25445 setMaxPitch(maxPitch) { 25446 maxPitch = maxPitch === null || maxPitch === void 0 ? defaultMaxPitch : maxPitch; 25447 if (maxPitch > maxPitchThreshold) throw new Error(`maxPitch must be less than or equal to ${maxPitchThreshold}`); 25448 if (maxPitch >= this._camera.transform.minPitch) { 25449 const pitchBefore = this._camera.transform.pitch; 25450 const tr = this._camera.getTransformForUpdate(); 25451 tr.setMaxPitch(maxPitch); 25452 this._camera.applyUpdatedTransform(tr); 25453 this._update(); 25454 if (pitchBefore !== this._camera.transform.pitch) this.fire(new MapMovementEvent("pitchstart")).fire(new MapMovementEvent("pitch")).fire(new MapMovementEvent("pitchend")).fire(new MapMovementEvent("movestart")).fire(new MapMovementEvent("move")).fire(new MapMovementEvent("moveend")); 25455 return this; 25456 } else throw new Error("maxPitch must be greater than the current minPitch"); 25457 } 25458 /** 25459 * Returns the map's maximum allowable pitch. 25460 * 25461 * @returns The maxPitch 25462 */ 25463 getMaxPitch() { 25464 return this._camera.transform.maxPitch; 25465 } 25466 /** 25467 * Returns the map's anisotropic filter pitch. 25468 * If the map is pitched beyond this threshold, anisotropic filtering will be applied to all raster layers. 25469 * 25470 * @returns The anisotropicFilterPitch 25471 * @example 25472 * ```ts 25473 * let anisotropicFilterPitch = map.getAnisotropicFilterPitch(); 25474 * ``` 25475 */ 25476 getAnisotropicFilterPitch() { 25477 return this._anisotropicFilterPitch; 25478 } 25479 /** 25480 * Sets the map's anisotropic filter pitch or reverts it to its default. 25481 * 25482 * A {@link ErrorEvent} event will be fired if anisotropicFilterPitch is out of bounds. 25483 * 25484 * @param anisotropicFilterPitch - The pitch above which to apply anisotropic filtering to the map's raster layers (0-180). 25485 * If `null` or `undefined` is provided, the function reverts to the default pitch threshold (20). 25486 * 25487 * 25488 * @example 25489 * ```ts 25490 * map.setAnisotropicFilterPitch(85); 25491 * ``` 25492 */ 25493 setAnisotropicFilterPitch(anisotropicFilterPitch) { 25494 anisotropicFilterPitch = anisotropicFilterPitch === null || anisotropicFilterPitch === void 0 ? defaultAnisotropicFilterPitch : anisotropicFilterPitch; 25495 if (anisotropicFilterPitch > maxPitchThreshold) throw new Error(`anisotropicFilterPitch must be less than or equal to ${maxPitchThreshold}`); 25496 if (anisotropicFilterPitch < defaultMinPitch) throw new Error(`anisotropicFilterPitch must be greater than or equal to ${defaultMinPitch}`); 25497 this._anisotropicFilterPitch = anisotropicFilterPitch; 25498 return this._update(); 25499 } 25500 /** 25501 * Returns the state of `renderWorldCopies`. If `true`, multiple copies of the world will be rendered side by side beyond -180 and 180 degrees longitude. If set to `false`: 25502 * 25503 * - When the map is zoomed out far enough that a single representation of the world does not fill the map's entire 25504 * container, there will be blank space beyond 180 and -180 degrees longitude. 25505 * - Features that cross 180 and -180 degrees longitude will be cut in two (with one portion on the right edge of the 25506 * map and the other on the left edge of the map) at every zoom level. 25507 * @returns The renderWorldCopies 25508 * @example 25509 * ```ts 25510 * let worldCopiesRendered = map.getRenderWorldCopies(); 25511 * ``` 25512 * @see [Render world copies](https://maplibre.org/maplibre-gl-js/docs/examples/render-world-copies/) 25513 */ 25514 getRenderWorldCopies() { 25515 return this._camera.transform.renderWorldCopies; 25516 } 25517 /** 25518 * Sets the state of `renderWorldCopies`. 25519 * 25520 * @param renderWorldCopies - If `true`, multiple copies of the world will be rendered side by side beyond -180 and 180 degrees longitude. If set to `false`: 25521 * 25522 * - When the map is zoomed out far enough that a single representation of the world does not fill the map's entire 25523 * container, there will be blank space beyond 180 and -180 degrees longitude. 25524 * - Features that cross 180 and -180 degrees longitude will be cut in two (with one portion on the right edge of the 25525 * map and the other on the left edge of the map) at every zoom level. 25526 * 25527 * `undefined` is treated as `true`, `null` is treated as `false`. 25528 * @example 25529 * ```ts 25530 * map.setRenderWorldCopies(true); 25531 * ``` 25532 * @see [Render world copies](https://maplibre.org/maplibre-gl-js/docs/examples/render-world-copies/) 25533 */ 25534 setRenderWorldCopies(renderWorldCopies) { 25535 this._camera.transform.setRenderWorldCopies(renderWorldCopies); 25536 return this._update(); 25537 } 25538 /** Sets or clears the callback overriding how the map constrains the viewport's lnglat and zoom to respect the longitude and latitude bounds. 25539 * 25540 * @param constrain - A {@link TransformConstrainFunction} callback defining how the viewport should respect the bounds. 25541 * 25542 * `null` clears the callback and reverts the constrain to the map transform's default constrain function. 25543 * @example 25544 * ```ts 25545 * function customTransformConstrain(lngLat, zoom) { 25546 * return {center: lngLat, zoom: zoom ?? 0}; 25547 * }; 25548 * map.setTransformConstrain(customTransformConstrain); 25549 * ``` 25550 * @see [Customize the map transform constrain](https://maplibre.org/maplibre-gl-js/docs/examples/customize-the-map-transform-constrain/) 25551 */ 25552 setTransformConstrain(constrain) { 25553 this._camera.transform.setConstrainOverride(constrain); 25554 return this._update(); 25555 } 25556 /** 25557 * Returns a [Point](https://github.com/mapbox/point-geometry) representing pixel coordinates, relative to the map's `container`, 25558 * that correspond to the specified geographical location. 25559 * 25560 * A location behind the camera has no corresponding pixel. For such a location the 25561 * returned point is outside the viewport, on the side through which the location left 25562 * the screen, one viewport width or height away from the edge. 25563 * 25564 * @param lnglat - The geographical location to project. 25565 * @returns The [Point](https://github.com/mapbox/point-geometry) corresponding to `lnglat`, relative to the map's `container`. 25566 * @example 25567 * ```ts 25568 * let coordinate = [-122.420679, 37.772537]; 25569 * let point = map.project(coordinate); 25570 * ``` 25571 */ 25572 project(lnglat) { 25573 return this._camera.transform.locationToScreenPoint(LngLat.convert(lnglat), this.style && this.terrain); 25574 } 25575 /** 25576 * Returns a {@link LngLat} representing geographical coordinates that correspond 25577 * to the specified pixel coordinates. 25578 * 25579 * @param point - The pixel coordinates to unproject. 25580 * @returns The {@link LngLat} corresponding to `point`. 25581 * @example 25582 * ```ts 25583 * map.on('click', (e) => { 25584 * // When the map is clicked, get the geographic coordinate. 25585 * let coordinate = map.unproject(e.point); 25586 * }); 25587 * ``` 25588 */ 25589 unproject(point) { 25590 return this._camera.transform.screenPointToLocation(Point.convert(point), this.terrain); 25591 } 25592 /** 25593 * Returns true if the map is panning, zooming, rotating, or pitching due to a camera animation or user gesture. 25594 * @returns true if the map is moving. 25595 * @example 25596 * ```ts 25597 * let isMoving = map.isMoving(); 25598 * ``` 25599 */ 25600 isMoving() { 25601 return this._camera.isMoving() || this._handlers?.isMoving() || false; 25602 } 25603 /** 25604 * Returns true if the map is zooming due to a camera animation or user gesture. 25605 * @returns true if the map is zooming. 25606 * @example 25607 * ```ts 25608 * let isZooming = map.isZooming(); 25609 * ``` 25610 */ 25611 isZooming() { 25612 return this._camera.isZooming() || this._handlers?.isZooming() || false; 25613 } 25614 /** 25615 * Returns true if the map is rotating due to a camera animation or user gesture. 25616 * @returns true if the map is rotating. 25617 * @example 25618 * ```ts 25619 * map.isRotating(); 25620 * ``` 25621 */ 25622 isRotating() { 25623 return this._camera.isRotating() || this._handlers?.isRotating() || false; 25624 } 25625 _createDelegatedListener(type, layerIds, listener) { 25626 if (type === "mouseenter" || type === "mouseover") { 25627 let mousein = false; 25628 const mousemove = (e) => { 25629 const existingLayers = layerIds.filter((layerId) => this.getLayer(layerId)); 25630 const features = existingLayers.length !== 0 ? this.queryRenderedFeatures(e.point, { layers: existingLayers }) : []; 25631 if (!features.length) mousein = false; 25632 else if (!mousein) { 25633 mousein = true; 25634 listener.call(this, new MapMouseEvent(type, this, e.originalEvent, { features })); 25635 } 25636 }; 25637 const mouseout = () => { 25638 mousein = false; 25639 }; 25640 return { 25641 layers: layerIds, 25642 listener, 25643 delegates: { 25644 mousemove, 25645 mouseout 25646 } 25647 }; 25648 } else if (type === "mouseleave" || type === "mouseout") { 25649 let mousein = false; 25650 const mousemove = (e) => { 25651 const existingLayers = layerIds.filter((layerId) => this.getLayer(layerId)); 25652 if ((existingLayers.length !== 0 ? this.queryRenderedFeatures(e.point, { layers: existingLayers }) : []).length) mousein = true; 25653 else if (mousein) { 25654 mousein = false; 25655 listener.call(this, new MapMouseEvent(type, this, e.originalEvent)); 25656 } 25657 }; 25658 const mouseout = (e) => { 25659 if (mousein) { 25660 mousein = false; 25661 listener.call(this, new MapMouseEvent(type, this, e.originalEvent)); 25662 } 25663 }; 25664 return { 25665 layers: layerIds, 25666 listener, 25667 delegates: { 25668 mousemove, 25669 mouseout 25670 } 25671 }; 25672 } else { 25673 const delegate = (e) => { 25674 const existingLayers = layerIds.filter((layerId) => this.getLayer(layerId)); 25675 const features = existingLayers.length !== 0 ? this.queryRenderedFeatures(e.point, { layers: existingLayers }) : []; 25676 if (features.length) { 25677 e.features = features; 25678 listener.call(this, e); 25679 delete e.features; 25680 } 25681 }; 25682 return { 25683 layers: layerIds, 25684 listener, 25685 delegates: { [type]: delegate } 25686 }; 25687 } 25688 } 25689 _saveDelegatedListener(type, delegatedListener) { 25690 this._delegatedListeners ||= {}; 25691 this._delegatedListeners[type] ||= []; 25692 this._delegatedListeners[type].push(delegatedListener); 25693 } 25694 _removeDelegatedListener(type, layerIds, listener) { 25695 if (!this._delegatedListeners?.[type]) return; 25696 const listeners = this._delegatedListeners[type]; 25697 for (let i = 0; i < listeners.length; i++) { 25698 const delegatedListener = listeners[i]; 25699 if (delegatedListener.listener === listener && delegatedListener.layers.length === layerIds.length && delegatedListener.layers.every((layerId) => layerIds.includes(layerId))) { 25700 for (const event in delegatedListener.delegates) this.off(event, delegatedListener.delegates[event]); 25701 listeners.splice(i, 1); 25702 return; 25703 } 25704 } 25705 } 25706 on(type, layerIdsOrListener, listener) { 25707 if (listener === void 0) return super.on(type, layerIdsOrListener); 25708 const layerIds = typeof layerIdsOrListener === "string" ? [layerIdsOrListener] : layerIdsOrListener; 25709 const delegatedListener = this._createDelegatedListener(type, layerIds, listener); 25710 this._saveDelegatedListener(type, delegatedListener); 25711 for (const event in delegatedListener.delegates) this.on(event, delegatedListener.delegates[event]); 25712 return { unsubscribe: () => { 25713 this._removeDelegatedListener(type, layerIds, listener); 25714 } }; 25715 } 25716 once(type, layerIdsOrListener, listener) { 25717 if (listener === void 0) return super.once(type, layerIdsOrListener); 25718 const layerIds = typeof layerIdsOrListener === "string" ? [layerIdsOrListener] : layerIdsOrListener; 25719 const delegatedListener = this._createDelegatedListener(type, layerIds, listener); 25720 for (const key in delegatedListener.delegates) { 25721 const delegate = delegatedListener.delegates[key]; 25722 delegatedListener.delegates[key] = (...args) => { 25723 this._removeDelegatedListener(type, layerIds, listener); 25724 delegate(...args); 25725 }; 25726 } 25727 this._saveDelegatedListener(type, delegatedListener); 25728 for (const event in delegatedListener.delegates) this.once(event, delegatedListener.delegates[event]); 25729 return this; 25730 } 25731 off(type, layerIdsOrListener, listener) { 25732 if (listener === void 0) return super.off(type, layerIdsOrListener); 25733 const layerIds = typeof layerIdsOrListener === "string" ? [layerIdsOrListener] : layerIdsOrListener; 25734 this._removeDelegatedListener(type, layerIds, listener); 25735 return this; 25736 } 25737 /** 25738 * Returns an array of MapGeoJSONFeature objects 25739 * representing visible features that satisfy the query parameters. 25740 * 25741 * @param geometryOrOptions - (optional) The geometry of the query region in pixel points within the map viewport: 25742 * either a single pixel point or a pair of top-left and bottom-right pixel points describing a bounding box. 25743 * The origin of the pixel points is at the top-left of the map viewport. 25744 * Omitting this parameter (i.e. calling {@link Map.queryRenderedFeatures} with zero arguments, 25745 * or with only a `options` argument) is equivalent to passing a bounding box encompassing the entire 25746 * map viewport. 25747 * The geometryOrOptions can receive a {@link QueryRenderedFeaturesOptions} only to support a situation where the function receives only one parameter which is the options parameter. 25748 * @param options - (optional) Options object. 25749 * 25750 * @returns An array of MapGeoJSONFeature objects. 25751 * 25752 * The `properties` value of each returned feature object contains the properties of its source feature. For GeoJSON sources, only 25753 * string and numeric property values are supported (i.e. `null`, `Array`, and `Object` values are not supported). 25754 * 25755 * Each feature includes top-level `layer`, `source`, and `sourceLayer` properties. The `layer` property is an object 25756 * representing the style layer to which the feature belongs. Layout and paint properties in this object contain values 25757 * which are fully evaluated for the given zoom level and feature. 25758 * 25759 * Only features that are currently rendered are included. Some features will **not** be included, like: 25760 * 25761 * - Features from layers whose `visibility` property is `"none"`. 25762 * - Features from layers whose zoom range excludes the current zoom level. 25763 * - Symbol features that have been hidden due to text or icon collision. 25764 * 25765 * Features from all other layers are included, including features that may have no visible 25766 * contribution to the rendered result; for example, because the layer's opacity or color alpha component is set to 25767 * 0. 25768 * 25769 * The topmost rendered feature appears first in the returned array, and subsequent features are sorted by 25770 * descending z-order. Features that are rendered multiple times (due to wrapping across the antemeridian at low 25771 * zoom levels) are returned only once (though subject to the following caveat). 25772 * 25773 * Because features come from tiled vector data or GeoJSON data that is converted to tiles internally, feature 25774 * geometries may be split or duplicated across tile boundaries and, as a result, features may appear multiple 25775 * times in query results. For example, suppose there is a highway running through the bounding rectangle of a query. 25776 * The results of the query will be those parts of the highway that lie within the map tiles covering the bounding 25777 * rectangle, even if the highway extends into other tiles, and the portion of the highway within each map tile 25778 * will be returned as a separate feature. Similarly, a point feature near a tile boundary may appear in multiple 25779 * tiles due to tile buffering. 25780 * 25781 * @example 25782 * Find all features at a point 25783 * ```ts 25784 * let features = map.queryRenderedFeatures( 25785 * [20, 35], 25786 * { layers: ['my-layer-name'] } 25787 * ); 25788 * ``` 25789 * 25790 * @example 25791 * Find all features within a static bounding box 25792 * ```ts 25793 * let features = map.queryRenderedFeatures( 25794 * [[10, 20], [30, 50]], 25795 * { layers: ['my-layer-name'] } 25796 * ); 25797 * ``` 25798 * 25799 * @example 25800 * Find all features within a bounding box around a point 25801 * ```ts 25802 * let width = 10; 25803 * let height = 20; 25804 * let features = map.queryRenderedFeatures([ 25805 * [point.x - width / 2, point.y - height / 2], 25806 * [point.x + width / 2, point.y + height / 2] 25807 * ], { layers: ['my-layer-name'] }); 25808 * ``` 25809 * 25810 * @example 25811 * Query all rendered features from a single layer 25812 * ```ts 25813 * let features = map.queryRenderedFeatures({ layers: ['my-layer-name'] }); 25814 * ``` 25815 * @see [Get features under the mouse pointer](https://maplibre.org/maplibre-gl-js/docs/examples/get-features-under-the-mouse-pointer/) 25816 */ 25817 queryRenderedFeatures(geometryOrOptions, options) { 25818 if (!this.style) return []; 25819 let queryGeometry; 25820 const isGeometry = geometryOrOptions instanceof Point || Array.isArray(geometryOrOptions); 25821 const geometry = isGeometry ? geometryOrOptions : [[0, 0], [this._camera.transform.width, this._camera.transform.height]]; 25822 options ||= (isGeometry ? {} : geometryOrOptions) || {}; 25823 if (geometry instanceof Point || typeof geometry[0] === "number") queryGeometry = [Point.convert(geometry)]; 25824 else { 25825 const tl = Point.convert(geometry[0]); 25826 const br = Point.convert(geometry[1]); 25827 queryGeometry = [ 25828 tl, 25829 new Point(br.x, tl.y), 25830 br, 25831 new Point(tl.x, br.y), 25832 tl 25833 ]; 25834 } 25835 return this.style.queryRenderedFeatures(queryGeometry, options, this._camera.transform); 25836 } 25837 /** 25838 * Returns an array of MapGeoJSONFeature objects 25839 * representing features within the specified vector tile or GeoJSON source that satisfy the query parameters. 25840 * 25841 * @param sourceId - The ID of the vector tile or GeoJSON source to query. 25842 * @param parameters - The options object. 25843 * @returns An array of MapGeoJSONFeature objects. 25844 * 25845 * In contrast to {@link Map.queryRenderedFeatures}, this function returns all features matching the query parameters, 25846 * whether or not they are rendered by the current style (i.e. visible). The domain of the query includes all currently-loaded 25847 * vector tiles and GeoJSON source tiles: this function does not check tiles outside the currently 25848 * visible viewport. 25849 * 25850 * Because features come from tiled vector data or GeoJSON data that is converted to tiles internally, feature 25851 * geometries may be split or duplicated across tile boundaries and, as a result, features may appear multiple 25852 * times in query results. For example, suppose there is a highway running through the bounding rectangle of a query. 25853 * The results of the query will be those parts of the highway that lie within the map tiles covering the bounding 25854 * rectangle, even if the highway extends into other tiles, and the portion of the highway within each map tile 25855 * will be returned as a separate feature. Similarly, a point feature near a tile boundary may appear in multiple 25856 * tiles due to tile buffering. 25857 * 25858 * @example 25859 * Find all features in one source layer in a vector source 25860 * ```ts 25861 * let features = map.querySourceFeatures('your-source-id', { 25862 * sourceLayer: 'your-source-layer' 25863 * }); 25864 * ``` 25865 * 25866 */ 25867 querySourceFeatures(sourceId, parameters) { 25868 return this.style.querySourceFeatures(sourceId, parameters); 25869 } 25870 /** 25871 * Updates the map's MapLibre style object with a new value. 25872 * 25873 * If a style is already set when this is used and options.diff is set to true, the map renderer will attempt to compare the given style 25874 * against the map's current state and perform only the changes necessary to make the map style match the desired state. Changes in sprites 25875 * (images used for icons and patterns) and glyphs (fonts for label text) **cannot** be diffed. If the sprites or fonts used in the current 25876 * style and the given style are different in any way, the map renderer will force a full update, remov
vendor: 8,825 bytes, lines 25876-26158
25876ing the current style and building 25877 * the given one from scratch. 25878 * 25879 * 25880 * @param style - A JSON object conforming to the schema described in the 25881 * [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/), or a URL to such JSON. 25882 * @param options - The options object. 25883 * 25884 * @example 25885 * ```ts 25886 * map.setStyle("https://demotiles.maplibre.org/style.json"); 25887 * 25888 * map.setStyle('https://demotiles.maplibre.org/style.json', { 25889 * transformStyle: (previousStyle, nextStyle) => ({ 25890 * ...nextStyle, 25891 * sources: { 25892 * ...nextStyle.sources, 25893 * // copy a source from previous style 25894 * 'osm': previousStyle.sources.osm 25895 * }, 25896 * layers: [ 25897 * // background layer 25898 * nextStyle.layers[0], 25899 * // copy a layer from previous style 25900 * previousStyle.layers[0], 25901 * // other layers from the next style 25902 * ...nextStyle.layers.slice(1).map(layer => { 25903 * // hide the layers we don't need from demotiles style 25904 * if (layer.id.startsWith('geolines')) { 25905 * layer.layout = {...layer.layout || {}, visibility: 'none'}; 25906 * // filter out US polygons 25907 * } else if (layer.id.startsWith('coastline') || layer.id.startsWith('countries')) { 25908 * layer.filter = ['!=', ['get', 'ADM0_A3'], 'USA']; 25909 * } 25910 * return layer; 25911 * }) 25912 * ] 25913 * }) 25914 * }); 25915 * ``` 25916 */ 25917 setStyle(style, options) { 25918 options = extend({}, { 25919 localIdeographFontFamily: this._localIdeographFontFamily, 25920 validate: this._validateStyle 25921 }, options); 25922 this._styleUrl = typeof style === "string" ? style : null; 25923 if (options.diff !== false && options.localIdeographFontFamily === this._localIdeographFontFamily && this.style && style) { 25924 this._diffStyle(style, options); 25925 return this; 25926 } else { 25927 this._localIdeographFontFamily = options.localIdeographFontFamily; 25928 return this._updateStyle(style, options); 25929 } 25930 } 25931 /** 25932 * Updates the requestManager's transform request with a new function 25933 * 25934 * @param transformRequest - A callback run before the Map makes a request for an external URL. The callback can be used to modify the url, set headers, or set the credentials property for cross-origin requests. 25935 * Expected to return an object with a `url` property and optionally `headers` and `credentials` properties 25936 * 25937 * @example 25938 * ```ts 25939 * map.setTransformRequest((url: string, resourceType: string) => {}); 25940 * ``` 25941 */ 25942 setTransformRequest(transformRequest) { 25943 this._requestManager.setTransformRequest(transformRequest); 25944 return this; 25945 } 25946 _getUIString(key) { 25947 const str = this._locale[key]; 25948 if (str == null) throw new Error(`Missing UI string '${key}'`); 25949 return str; 25950 } 25951 _updateStyle(style, options) { 25952 this._diffStyleRequest?.abort(); 25953 this._diffStyleRequest = null; 25954 if (options.transformStyle && this.style && !this.style._loaded) { 25955 this.style.once("style.load", () => this._updateStyle(style, options)); 25956 return; 25957 } 25958 const previousStyle = this.style && options.transformStyle ? this.style.serialize() : void 0; 25959 if (this.style) { 25960 this.style.setEventedParent(null); 25961 this.style._remove(!style); 25962 } 25963 if (!style) { 25964 if (this._frameRequest) { 25965 this._frameRequest.abort(); 25966 this._frameRequest = null; 25967 } 25968 this.style?.projection?.destroy(); 25969 delete this.style; 25970 return this; 25971 } else this.style = new Style(this, options || {}); 25972 this.style.setEventedParent(this, { style: this.style }); 25973 if (typeof style === "string") this.style.loadURL(style, options, previousStyle); 25974 else this.style.loadJSON(style, options, previousStyle); 25975 return this; 25976 } 25977 _lazyInitEmptyStyle() { 25978 if (!this.style) { 25979 this.style = new Style(this, {}); 25980 this.style.setEventedParent(this, { style: this.style }); 25981 this.style.loadEmpty(); 25982 } 25983 } 25984 async _diffStyle(style, options) { 25985 this._diffStyleRequest?.abort(); 25986 if (typeof style === "string") { 25987 const url = style; 25988 this._diffStyleRequest = new AbortController(); 25989 const abortController = this._diffStyleRequest; 25990 try { 25991 const request = await this._requestManager.transformRequest(url, "Style"); 25992 if (abortController.signal.aborted) { 25993 this._diffStyleRequest = null; 25994 return; 25995 } 25996 const response = await getJSON(request, abortController); 25997 this._diffStyleRequest = null; 25998 this._updateDiff(response.data, options); 25999 } catch (error) { 26000 this._diffStyleRequest = null; 26001 if (!isAbortError(error)) this.fire(new ErrorEvent(ensureError(error))); 26002 } 26003 } else if (typeof style === "object") { 26004 this._diffStyleRequest = null; 26005 this._updateDiff(style, options); 26006 } 26007 } 26008 _updateDiff(style, options) { 26009 try { 26010 if (this.style.setState(style, options)) this._update(true); 26011 } catch (e) { 26012 warnOnce(`Unable to perform style diff: ${ensureError(e).message}. Rebuilding the style from scratch.`); 26013 this._updateStyle(style, options); 26014 } 26015 } 26016 /** 26017 * Returns the map's MapLibre style object, a JSON object which can be used to recreate the map's style. 26018 * 26019 * @returns The map's style JSON object. 26020 * 26021 * @example 26022 * ```ts 26023 * let styleJson = map.getStyle(); 26024 * ``` 26025 * 26026 */ 26027 getStyle() { 26028 if (this.style) return this.style.serialize(); 26029 } 26030 /** 26031 * Returns the URL the map's style was loaded from. 26032 * 26033 * @returns The URL given to {@link Map.setStyle} or the `style` map option, or `null` when the style was given as an object or the map has no style. 26034 * 26035 * @example 26036 * ```ts 26037 * const styleUrl = map.getStyleUrl(); 26038 * ``` 26039 */ 26040 getStyleUrl() { 26041 return this._styleUrl; 26042 } 26043 /** 26044 * @internal 26045 * Returns the map's style and cloned images to restore context. 26046 * @returns An object containing the style and images. 26047 */ 26048 _getStyleAndImages() { 26049 if (this.style) return { 26050 style: this.style.serialize(), 26051 images: this.style.imageManager.cloneImages() 26052 }; 26053 return { 26054 style: null, 26055 images: {} 26056 }; 26057 } 26058 /** 26059 * Returns a Boolean indicating whether the map's style is fully loaded. 26060 * 26061 * @returns A Boolean indicating whether the style is fully loaded. 26062 * 26063 * @example 26064 * ```ts 26065 * let styleLoadStatus = map.isStyleLoaded(); 26066 * ``` 26067 */ 26068 isStyleLoaded() { 26069 if (!this.style) { 26070 warnOnce("There is no style added to the map."); 26071 return; 26072 } 26073 return this.style.loaded(); 26074 } 26075 /** 26076 * Adds a source to the map's style. 26077 * 26078 * Events triggered: 26079 * 26080 * Triggers the `source.add` event. 26081 * 26082 * @param id - The ID of the source to add. Must not conflict with existing sources. 26083 * @param source - The source object, conforming to the 26084 * MapLibre Style Specification's [source definition](https://maplibre.org/maplibre-style-spec/sources) or 26085 * {@link CanvasSourceSpecification}. 26086 * @example 26087 * ```ts 26088 * map.addSource('my-data', { 26089 * type: 'vector', 26090 * url: 'https://demotiles.maplibre.org/tiles/tiles.json' 26091 * }); 26092 * ``` 26093 * @example 26094 * ```ts 26095 * map.addSource('my-data', { 26096 * "type": "geojson", 26097 * "data": { 26098 * "type": "Feature", 26099 * "geometry": { 26100 * "type": "Point", 26101 * "coordinates": [-77.0396, 38.8891] 26102 * }, 26103 * "properties": { 26104 * "title": "Washington DC", 26105 * "marker-symbol": "monument" 26106 * } 26107 * } 26108 * }); 26109 * ``` 26110 * @see GeoJSON source: [Add live realtime data](https://maplibre.org/maplibre-gl-js/docs/examples/add-live-realtime-data/) 26111 */ 26112 addSource(id, source) { 26113 this._lazyInitEmptyStyle(); 26114 this.style.addSource(id, source); 26115 return this._update(true); 26116 } 26117 /** 26118 * Returns a Boolean indicating whether the source is loaded. Returns `true` if the source with 26119 * the given ID in the map's style has no outstanding network requests, otherwise `false`. 26120 * 26121 * A {@link ErrorEvent} event will be fired if there is no source with the specified ID. 26122 * 26123 * @param id - The ID of the source to be checked. 26124 * @returns A Boolean indicating whether the source is loaded. 26125 * @example 26126 * ```ts 26127 * let sourceLoaded = map.isSourceLoaded('bathymetry-data'); 26128 * ``` 26129 */ 26130 isSourceLoaded(id) { 26131 const tileManager = this.style?.tileManagers[id]; 26132 if (tileManager === void 0) { 26133 this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`There is no tile manager with ID '${id}'`))); 26134 return; 26135 } 26136 return tileManager.loaded(); 26137 } 26138 /** 26139 * Loads a 3D terrain mesh, based on a "raster-dem" source. 26140 * 26141 * Triggers the `terrain` event. 26142 * 26143 * @param options - Options object. 26144 * @example 26145 * ```ts 26146 * map.setTerrain({ source: 'terrain' }); 26147 * ``` 26148 */ 26149 setTerrain(options, styleOptions = {}) { 26150 this.style._checkLoaded(); 26151 if (options && validateAndEmit(this, validateStyle.terrain, { value: options }, styleOptions)) return this; 26152 if (this._terrainDataCallback) this.style.off("data", this._terrainDataCallback); 26153 if (!options) { 26154 if (this.terrain) this.terrain.destroy(); 26155 this.terrain = null; 26156 this.painter.renderToTexture = null; 26157 this.painter.destroyRTTResources(); 26158 this._camera.
26158terrain = null; 26159 this._camera.transform.setMinElevationForCurrentTile(0); 26160 if (this.getCenterClampedToGround()) this._camera.transform.setElevation(0); 26161 } else { 26162 const tileManager = this.style.tileManagers[options.source]; 26163 if (!tileManager) throw new Error(`cannot load terrain, because there exists no source with ID: ${options.source}`); 26164 if (this.terrain === null) tileManager.reload(); 26165 for (const index in this.style._layers) { 26166 const thisLayer = this.style._layers[index]; 26167 if (thisLayer.type === "hillshade" && thisLayer.source === options.source) warnOnce("You are using the same source for a hillshade layer and for 3D terrain. Please consider using two separate sources to improve rendering quality."); 26168 if (thisLayer.type === "color-relief" && thisLayer.source === options.source) warnOnce("You are using the same source for a color-relief layer and for 3D terrain. Please consider using two separate sources to improve rendering quality."); 26169 } 26170 if (this.terrain) this.terrain.destroy(); 26171 this.terrain = new Terrain(this.painter, tileManager, options, this._terrainSkirtLength); 26172 this.painter.renderToTexture = new RenderToTexture(this.painter, this.terrain); 26173 this._camera.terrain = this.terrain; 26174 this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom)); 26175 this._camera.transform.setElevation(this.terrain.getElevationForLngLat(this._camera.transform.center, this._camera.transform)); 26176 this._terrainDataCallback = (e) => this._handleTerrainDataEvent(e, options.source); 26177 this.style.on("data", this._terrainDataCallback); 26178 } 26179 this.style.triggerSymbolPlacement(); 26180 this.fire(new MapTerrainEvent({ terrain: options })); 26181 return this; 26182 } 26183 _handleTerrainDataEvent(event, terrainSourceId) { 26184 if (event.dataType === "style") { 26185 this.terrain.tileManager.releaseAllRTT(); 26186 return; 26187 } 26188 const isTerrainSourceEvent = event.sourceId === terrainSourceId; 26189 if (isTerrainSourceEvent) { 26190 this.terrain.resetElevationCache(); 26191 this.style.triggerSymbolPlacement(); 26192 } 26193 if (isTerrainSourceEvent && event.tile) this.painter.markTerrainDepthDirty(); 26194 if (isTerrainSourceEvent && event.tile && !this._camera.elevationFreeze) { 26195 this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
26196 if (this.getCenterClampedToGround()) this._camera.transform.setElevation(this.terrain.getElevationForLngLat(this._camera.transform.center, this._camera.transform)
vendor: 35,539 bytes, lines 26196-27126
26196); 26197 } 26198 if (!event.tile) return; 26199 if (event.source?.type === "image") { 26200 this.terrain.tileManager.releaseAllRTT(); 26201 return; 26202 } 26203 this.terrain.tileManager.releaseRTT(event.tile.tileID); 26204 } 26205 /** 26206 * Get the terrain-options if terrain is loaded 26207 * @returns the TerrainSpecification passed to setTerrain 26208 * @example 26209 * ```ts 26210 * map.getTerrain(); // { source: 'terrain' }; 26211 * ``` 26212 */ 26213 getTerrain() { 26214 return this.terrain?.options ?? null; 26215 } 26216 /** 26217 * Returns a Boolean indicating whether all tiles in the viewport from all sources on 26218 * the style are loaded. 26219 * 26220 * @returns A Boolean indicating whether all tiles are loaded. 26221 * @example 26222 * ```ts 26223 * let tilesLoaded = map.areTilesLoaded(); 26224 * ``` 26225 */ 26226 areTilesLoaded() { 26227 const tileManagers = this.style?.tileManagers; 26228 for (const tileManager of Object.values(tileManagers)) if (!tileManager.areTilesLoaded()) return false; 26229 return true; 26230 } 26231 /** 26232 * Removes a source from the map's style. 26233 * 26234 * @param id - The ID of the source to remove. 26235 * @example 26236 * ```ts 26237 * map.removeSource('bathymetry-data'); 26238 * ``` 26239 */ 26240 removeSource(id) { 26241 this.style.removeSource(id); 26242 return this._update(true); 26243 } 26244 /** 26245 * Returns the source with the specified ID in the map's style. 26246 * 26247 * This method is often used to update a source using the instance members for the relevant 26248 * source type as defined in classes that derive from {@link Source}. 26249 * For example, setting the `data` for a GeoJSON source or updating the `url` and `coordinates` 26250 * of an image source. 26251 * 26252 * @param id - The ID of the source to get. 26253 * @returns The style source with the specified ID or `undefined` if the ID 26254 * corresponds to no existing sources. 26255 * The shape of the object varies by source type. 26256 * A list of options for each source type is available on the MapLibre Style Specification's 26257 * [Sources](https://maplibre.org/maplibre-style-spec/sources/) page. 26258 * @example 26259 * ```ts 26260 * let sourceObject = map.getSource('points'); 26261 * ``` 26262 * @see [Create a draggable point](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-draggable-point/) 26263 * @see [Animate a point](https://maplibre.org/maplibre-gl-js/docs/examples/animate-a-point/) 26264 * @see [Add live realtime data](https://maplibre.org/maplibre-gl-js/docs/examples/add-live-realtime-data/) 26265 */ 26266 getSource(id) { 26267 return this.style?.getSource(id); 26268 } 26269 /** 26270 * Change the tile Level of Detail behavior of the specified source. These parameters have no effect when 26271 * pitch == 0, and the largest effect when the horizon is visible on screen. 26272 * 26273 * @param maxZoomLevelsOnScreen - The maximum number of distinct zoom levels allowed on screen at a time. 26274 * There will generally be fewer zoom levels on the screen, the maximum can only be reached when the horizon 26275 * is at the top of the screen. Increasing the maximum number of zoom levels causes the zoom level to decay 26276 * faster toward the horizon. 26277 * @param tileCountMaxMinRatio - The ratio of the maximum number of tiles loaded (at high pitch) to the minimum 26278 * number of tiles loaded. Increasing this ratio allows more tiles to be loaded at high pitch angles. If the ratio 26279 * would otherwise be exceeded, the zoom level is reduced uniformly to keep the number of tiles within the limit. 26280 * @param sourceId - The ID of the source to set tile LOD parameters for. All sources will be updated if unspecified. 26281 * If `sourceId` is specified but a corresponding source does not exist, an error is thrown. 26282 * @example 26283 * ```ts 26284 * map.setSourceTileLodParams(4.0, 3.0, 'terrain'); 26285 * ``` 26286 * @see [Modify Level of Detail behavior](https://maplibre.org/maplibre-gl-js/docs/examples/level-of-detail-control/) 26287 26288 */ 26289 setSourceTileLodParams(maxZoomLevelsOnScreen, tileCountMaxMinRatio, sourceId) { 26290 if (sourceId) { 26291 const source = this.getSource(sourceId); 26292 if (!source) throw new Error(`There is no source with ID "${sourceId}", cannot set LOD parameters`); 26293 source.calculateTileZoom = createCalculateTileZoomFunction(Math.max(1, maxZoomLevelsOnScreen), Math.max(1, tileCountMaxMinRatio)); 26294 } else for (const id in this.style.tileManagers) this.style.tileManagers[id].getSource().calculateTileZoom = createCalculateTileZoomFunction(Math.max(1, maxZoomLevelsOnScreen), Math.max(1, tileCountMaxMinRatio)); 26295 this._update(true); 26296 return this; 26297 } 26298 /** 26299 * Triggers a reload of the selected tiles 26300 * 26301 * @param sourceId - The ID of the source 26302 * @param tileIds - An array of tile IDs to be reloaded. If not defined, all tiles will be reloaded. 26303 * @example 26304 * ```ts 26305 * map.refreshTiles('satellite', [{x:1024, y: 1023, z: 11}, {x:1023, y: 1023, z: 11}]); 26306 * ``` 26307 */ 26308 refreshTiles(sourceId, tileIds) { 26309 const tileManager = this.style.tileManagers[sourceId]; 26310 if (!tileManager) throw new Error(`There is no tile manager with ID "${sourceId}", cannot refresh tile`); 26311 if (tileIds === void 0) tileManager.reload(true); 26312 else tileManager.refreshTiles(tileIds.map((tileId) => { 26313 return new CanonicalTileID(tileId.z, tileId.x, tileId.y); 26314 })); 26315 } 26316 /** 26317 * Add an image to the style. This image can be displayed on the map like any other icon in the style's 26318 * sprite using the image's ID with 26319 * [`icon-image`](https://maplibre.org/maplibre-style-spec/layers/#layout-symbol-icon-image), 26320 * [`background-pattern`](https://maplibre.org/maplibre-style-spec/layers/#paint-background-background-pattern), 26321 * [`fill-pattern`](https://maplibre.org/maplibre-style-spec/layers/#paint-fill-fill-pattern), 26322 * or [`line-pattern`](https://maplibre.org/maplibre-style-spec/layers/#paint-line-line-pattern). 26323 * 26324 * A {@link ErrorEvent} event will be fired if the image parameter is invalid or there is not enough space in the sprite to add this image. 26325 * 26326 * @param id - The ID of the image. 26327 * @param image - The image as an `HTMLImageElement`, `ImageData`, `ImageBitmap` or object with `width`, `height`, and `data` 26328 * properties with the same format as `ImageData`. 26329 * @param options - Options object. 26330 * @example 26331 * ```ts 26332 * // If the style's sprite does not already contain an image with ID 'cat', 26333 * // add the image 'cat-icon.png' to the style's sprite with the ID 'cat'. 26334 * const image = await map.loadImage('https://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Cat_silhouette.svg/400px-Cat_silhouette.svg.png'); 26335 * if (!map.hasImage('cat')) map.addImage('cat', image.data); 26336 * 26337 * // Add a stretchable image that can be used with `icon-text-fit` 26338 * // In this example, the image is 600px wide by 400px high. 26339 * const image = await map.loadImage('https://upload.wikimedia.org/wikipedia/commons/8/89/Black_and_White_Boxed_%28bordered%29.png'); 26340 * if (map.hasImage('border-image')) return; 26341 * map.addImage('border-image', image.data, { 26342 * content: [16, 16, 300, 384], // place text over left half of image, avoiding the 16px border 26343 * stretchX: [[16, 584]], // stretch everything horizontally except the 16px border 26344 * stretchY: [[16, 384]], // stretch everything vertically except the 16px border 26345 * }); 26346 * ``` 26347 * @see Use `HTMLImageElement`: [Add an icon to the map](https://maplibre.org/maplibre-gl-js/docs/examples/add-an-icon-to-the-map/) 26348 * @see Use `ImageData`: [Add a generated icon to the map](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-generated-icon-to-the-map/) 26349 */ 26350 addImage(id, image, options = {}) { 26351 this._lazyInitEmptyStyle(); 26352 const styleImage = this._createStyleImage(image, options); 26353 if (!styleImage) return this; 26354 this.style.addImage(id, styleImage); 26355 if (styleImage.userImage?.onAdd) styleImage.userImage.onAdd(this, id); 26356 return this; 26357 } 26358 /** 26359 * Sets a callback that is invoked when an icon or pattern needed by the style is missing. 26360 * 26361 * The resolver typically loads or generates the image and registers it with {@link Map.addImage}. 26362 * MapLibre awaits the returned promise before treating the image as missing, so async work is 26363 * supported. If the image is still missing afterwards, the `styleimagemissing` event is fired. 26364 * 26365 * @param resolver - Callback used to resolve missing images, or `null` to remove the resolver. 26366 * @example 26367 * ```ts 26368 * map.setMissingStyleImageResolver(async (id) => { 26369 * const response = await fetch(`/icons/${id}.png`); 26370 * const image = await createImageBitmap(await response.blob()); 26371 * map.addImage(id, image, {pixelRatio: 2}); 26372 * }); 26373 * ``` 26374 */ 26375 setMissingStyleImageResolver(resolver) { 26376 this._missingStyleImageResolver = resolver; 26377 this.style?.setMissingImageResolver(resolver); 26378 return this; 26379 } 26380 _createStyleImage(image, options = {}) { 26381 const { pixelRatio = 1, sdf = false, stretchX, stretchY, content, textFitWidth, textFitHeight } = options; 26382 const version = 0; 26383 if (image instanceof HTMLImageElement || isImageBitmap(image)) { 26384 const { width, height, data } = browser.getImageData(image); 26385 return { 26386 data: new RGBAImage({ 26387 width, 26388 height 26389 }, data), 26390 pixelRatio, 26391 stretchX, 26392 stretchY, 26393 content, 26394 textFitWidth, 26395 textFitHeight, 26396 sdf, 26397 version 26398 }; 26399 } else if (image.width === void 0 || image.height === void 0) { 26400 this.fire(new ErrorEvent(/* @__PURE__ */ 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`"))); 26401 return null; 26402 } else { 26403 const { width, height, data } = image; 26404 const userImage = image; 26405 const isWebGLImage = isStyleImageWebGLData(userImage.data); 26406 return { 26407 data: isWebGLImage ? new RGBAImage({ 26408 width, 26409 height 26410 }) : new RGBAImage({ 26411 width, 26412 height 26413 }, new Uint8Array(data)), 26414 pixelRatio, 26415 stretchX, 26416 stretchY, 26417 content, 26418 textFitWidth, 26419 textFitHeight, 26420 sdf, 26421 version, 26422 isWebGLImage, 26423 userImage 26424 }; 26425 } 26426 } 26427 /** 26428 * Update an existing image in a style. This image can be displayed on the map like any other icon in the style's 26429 * sprite using the image's ID with 26430 * [`icon-image`](https://maplibre.org/maplibre-style-spec/layers/#layout-symbol-icon-image), 26431 * [`background-pattern`](https://maplibre.org/maplibre-style-spec/layers/#paint-background-background-pattern), 26432 * [`fill-pattern`](https://maplibre.org/maplibre-style-spec/layers/#paint-fill-fill-pattern), 26433 * or [`line-pattern`](https://maplibre.org/maplibre-style-spec/layers/#paint-line-line-pattern). 26434 * 26435 * An {@link ErrorEvent} will be fired if the image parameter is invalid. 26436 * 26437 * @param id - The ID of the image. 26438 * @param image - The image as an `HTMLImageElement`, `ImageData`, `ImageBitmap` or object with `width`, `height`, and `data` 26439 * properties with the same format as `ImageData`. 26440 * @example 26441 * ```ts 26442 * // If an image with the ID 'cat' already exists in the style's sprite, 26443 * // replace that image with a new image, 'other-cat-icon.png'. 26444 * if (map.hasImage('cat')) map.updateImage('cat', './other-cat-icon.png'); 26445 * ``` 26446 */ 26447 updateImage(id, image) { 26448 const existingImage = this.style.getImage(id); 26449 if (!existingImage) return this.fire(new ErrorEvent(/* @__PURE__ */ new Error("The map has no image with that id. If you are adding a new image use `map.addImage(...)` instead."))); 26450 const { width, height, data } = image instanceof HTMLImageElement || isImageBitmap(image) ? browser.getImageData(image) : image; 26451 if (width === void 0 || height === void 0) return this.fire(new ErrorEvent(/* @__PURE__ */ 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`"))); 26452 if (width !== existingImage.data.width || height !== existingImage.data.height) return this.fire(new ErrorEvent(/* @__PURE__ */ new Error("The width and height of the updated image must be that same as the previous version of the image"))); 26453 existingImage.isWebGLImage = isStyleImageWebGLData(data); 26454 if (existingImage.isWebGLImage) existingImage.userImage = image; 26455 else { 26456 const copy = !(image instanceof HTMLImageElement || isImageBitmap(image)); 26457 existingImage.data.replace(data, copy); 26458 } 26459 this.style.updateImage(id, existingImage); 26460 return this; 26461 } 26462 /** 26463 * Returns an image, specified by ID, currently available in the map. 26464 * This includes both images from the style's original sprite 26465 * and any images that have been added at runtime using {@link Map.addImage}. 26466 * 26467 * @param id - The ID of the image. 26468 * @returns An image in the map with the specified ID. 26469 * 26470 * @example 26471 * ```ts 26472 * let coffeeShopIcon = map.getImage("coffee_cup"); 26473 * ``` 26474 */ 26475 getImage(id) { 26476 return this.style.getImage(id); 26477 } 26478 /** 26479 * Check whether or not an image with a specific ID exists in the style. This checks both images 26480 * in the style's original sprite and any images 26481 * that have been added at runtime using {@link Map.addImage}. 26482 * 26483 * An {@link ErrorEvent} will be fired if the image ID is missing. 26484 * 26485 * @param id - The ID of the image. 26486 * 26487 * @returns A Boolean indicating whether the image exists. 26488 * @example 26489 * Check if an image with the ID 'cat' exists in the style's sprite. 26490 * ```ts 26491 * let catIconExists = map.hasImage('cat'); 26492 * ``` 26493 */ 26494 hasImage(id) { 26495 if (!id) { 26496 this.fire(new ErrorEvent(/* @__PURE__ */ new Error("Missing required image id"))); 26497 return false; 26498 } 26499 return !!this.style.getImage(id); 26500 } 26501 /** 26502 * Remove an image from a style. This can be an image from the style's original 26503 * sprite or any images 26504 * that have been added at runtime using {@link Map.addImage}. 26505 * 26506 * @param id - The ID of the image. 26507 * 26508 * @example 26509 * ```ts 26510 * // If an image with the ID 'cat' exists in 26511 * // the style's sprite, remove it. 26512 * if (map.hasImage('cat')) map.removeImage('cat'); 26513 * ``` 26514 */ 26515 removeImage(id) { 26516 this.style.removeImage(id); 26517 } 26518 /** 26519 * Load an image from an external URL to be used with {@link Map.addImage}. External 26520 * domains must support [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS). 26521 * 26522 * @param url - The URL of the image file. Image file must be in png, webp, or jpg format. 26523 * @returns a promise that is resolved when the image is loaded, or rejected when the response has no image data (for example an HTTP 204) 26524 * 26525 * @example 26526 * Load an image from an external URL. 26527 * ```ts 26528 * const response = await map.loadImage('https://picsum.photos/50/50'); 26529 * // Add the loaded image to the style's sprite with the ID 'photo'. 26530 * map.addImage('photo', response.data); 26531 * ``` 26532 * @see [Add an icon to the map](https://maplibre.org/maplibre-gl-js/docs/examples/add-an-icon-to-the-map/) 26533 */ 26534 async loadImage(url) { 26535 const response = await ImageRequest.getImage(await this._requestManager.transformRequest(url, "Image"), new AbortController()); 26536 if (!response.data) throw new Error(`Could not load image ${url}: the response is empty`); 26537 return response; 26538 } 26539 /** 26540 * Returns an Array of strings containing the IDs of all images currently available in the map. 26541 * This includes both images from the style's original sprite 26542 * and any images that have been added at runtime using {@link Map.addImage}. 26543 * 26544 * @returns An Array of strings containing the names of all sprites/images currently available in the map. 26545 * 26546 * @example 26547 * ```ts 26548 * let allImages = map.listImages(); 26549 * ``` 26550 */ 26551 listImages() { 26552 return this.style?.listImages() ?? []; 26553 } 26554 /** 26555 * Adds a [MapLibre style layer](https://maplibre.org/maplibre-style-spec/layers) 26556 * to the map's style. 26557 * 26558 * A layer defines how data from a specified source will be styled. Read more about layer types 26559 * and available paint and layout properties in the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/layers). 26560 * 26561 * @param layer - The layer to add, 26562 * conforming to either the MapLibre Style Specification's [layer definition](https://maplibre.org/maplibre-style-spec/layers) or, 26563 * less commonly, the {@link CustomLayerInterface} specification. Can also be a layer definition with an embedded source definition. 26564 * The MapLibre Style Specification's layer definition is appropriate for most layers. 26565 * 26566 * @param beforeId - The ID of an existing layer to insert the new layer before, 26567 * resulting in the new layer appearing visually beneath the existing layer. 26568 * If this argument is not specified, the layer will be appended to the end of the layers array 26569 * and appear visually above all other layers. 26570 * 26571 * @example 26572 * Add a circle layer with a vector source 26573 * ```ts 26574 * map.addLayer({ 26575 * id: 'points-of-interest', 26576 * source: { 26577 * type: 'vector', 26578 * url: 'https://demotiles.maplibre.org/tiles/tiles.json' 26579 * }, 26580 * 'source-layer': 'poi_label', 26581 * type: 'circle', 26582 * paint: { 26583 * // MapLibre Style Specification paint properties 26584 * }, 26585 * layout: { 26586 * // MapLibre Style Specification layout properties 26587 * } 26588 * }); 26589 * ``` 26590 * 26591 * @example 26592 * Define a source before using it to create a new layer 26593 * ```ts 26594 * map.addSource('state-data', { 26595 * type: 'geojson', 26596 * data: 'path/to/data.geojson' 26597 * }); 26598 * 26599 * map.addLayer({ 26600 * id: 'states', 26601 * // References the GeoJSON source defined above 26602 * // and does not require a `source-layer` 26603 * source: 'state-data', 26604 * type: 'symbol', 26605 * layout: { 26606 * // Set the label content to the 26607 * // feature's `name` property 26608 * text-field: ['get', 'name'] 26609 * } 26610 * }); 26611 * ``` 26612 * 26613 * @example 26614 * Add a new symbol layer before an existing layer 26615 * ```ts 26616 * map.addLayer({ 26617 * id: 'states', 26618 * // References a source that's already been defined 26619 * source: 'state-data', 26620 * type: 'symbol', 26621 * layout: { 26622 * // Set the label content to the 26623 * // feature's `name` property 26624 * text-field: ['get', 'name'] 26625 * } 26626 * // Add the layer before the existing `cities` layer 26627 * }, 'cities'); 26628 * ``` 26629 * @see [Create and style clusters](https://maplibre.org/maplibre-gl-js/docs/examples/create-and-style-clusters/) 26630 * @see [Add a vector tile source](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-vector-tile-source/) 26631 * @see [Add a WMS source](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-wms-source/) 26632 */ 26633 addLayer(layer, beforeId) { 26634 this._lazyInitEmptyStyle(); 26635 this.style.addLayer(layer, beforeId); 26636 return this._update(true); 26637 } 26638 /** 26639 * Moves a layer to a different z-position. 26640 * 26641 * @param id - The ID of the layer to move. 26642 * @param beforeId - The ID of an existing layer to insert the new layer before. When viewing the map, the `id` layer will appear beneath the `beforeId` layer. If `beforeId` is omitted, the layer will be appended to the end of the layers array and appear above all other layers on the map. 26643 * 26644 * @example 26645 * Move a layer with ID 'polygon' before the layer with ID 'country-label'. The `polygon` layer will appear beneath the `country-label` layer on the map. 26646 * ```ts 26647 * map.moveLayer('polygon', 'country-label'); 26648 * ``` 26649 */ 26650 moveLayer(id, beforeId) { 26651 this.style.moveLayer(id, beforeId); 26652 return this._update(true); 26653 } 26654 /** 26655 * Removes the layer with the given ID from the map's style. 26656 * 26657 * An {@link ErrorEvent} will be fired if no such layer exists. 26658 * 26659 * @param id - The ID of the layer to remove 26660 * 26661 * @example 26662 * If a layer with ID 'state-data' exists, remove it. 26663 * ```ts 26664 * if (map.getLayer('state-data')) map.removeLayer('state-data'); 26665 * ``` 26666 */ 26667 removeLayer(id) { 26668 this.style.removeLayer(id); 26669 return this._update(true); 26670 } 26671 /** 26672 * Returns the layer with the specified ID in the map's style. 26673 * 26674 * @param id - The ID of the layer to get. 26675 * @returns The layer with the specified ID, or `undefined` 26676 * if the ID corresponds to no existing layers. 26677 * 26678 * @example 26679 * ```ts 26680 * let stateDataLayer = map.getLayer('state-data'); 26681 * ``` 26682 * @see [Filter symbols by toggling a list](https://maplibre.org/maplibre-gl-js/docs/examples/filter-symbols-by-toggling-a-list/) 26683 * @see [Filter symbols by text input](https://maplibre.org/maplibre-gl-js/docs/examples/filter-symbols-by-text-input/) 26684 */ 26685 getLayer(id) { 26686 return this.style?.getLayer(id); 26687 } 26688 /** 26689 * Return the ids of all layers currently in the style, including custom layers, in order. 26690 * 26691 * @returns ids of layers, in order 26692 * 26693 * @example 26694 * ```ts 26695 * const orderedLayerIds = map.getLayersOrder(); 26696 * ``` 26697 */ 26698 getLayersOrder() { 26699 return this.style?.getLayersOrder() ?? []; 26700 } 26701 /** 26702 * Sets the zoom extent for the specified style layer. The zoom extent includes the 26703 * [minimum zoom level](https://maplibre.org/maplibre-style-spec/layers/#minzoom) 26704 * and [maximum zoom level](https://maplibre.org/maplibre-style-spec/layers/#maxzoom)) 26705 * at which the layer will be rendered. 26706 * 26707 * !!! note 26708 * For style layers using vector sources, style layers cannot be rendered at zoom levels lower than the 26709 * minimum zoom level of the _source layer_ because the data does not exist at those zoom levels. If the minimum 26710 * zoom level of the source layer is higher than the minimum zoom level defined in the style layer, the style 26711 * layer will not be rendered at all zoom levels in the zoom range. 26712 * 26713 * @param layerId - The ID of the layer to which the zoom extent will be applied. 26714 * @param minzoom - The minimum zoom to set (0-24). 26715 * @param maxzoom - The maximum zoom to set (0-24). 26716 * 26717 * @example 26718 * ```ts 26719 * map.setLayerZoomRange('my-layer', 2, 5); 26720 * ``` 26721 */ 26722 setLayerZoomRange(layerId, minzoom, maxzoom) { 26723 this.style.setLayerZoomRange(layerId, minzoom, maxzoom); 26724 return this._update(true); 26725 } 26726 /** 26727 * Sets the filter for the specified style layer. 26728 * 26729 * Filters control which features a style layer renders from its source. 26730 * Any feature for which the filter expression evaluates to `true` will be 26731 * rendered on the map. Those that are false will be hidden. 26732 * 26733 * Use `setFilter` to show a subset of your source data. 26734 * 26735 * To clear the filter, pass `null` or `undefined` as the second parameter. 26736 * 26737 * @param layerId - The ID of the layer to which the filter will be applied. 26738 * @param filter - The filter, conforming to the MapLibre Style Specification's 26739 * [filter definition](https://maplibre.org/maplibre-style-spec/layers/#filter). If `null` or `undefined` is provided, the function removes any existing filter from the layer. 26740 * @param options - Options object. 26741 * 26742 * @example 26743 * Display only features with the 'name' property 'USA' 26744 * ```ts 26745 * map.setFilter('my-layer', ['==', ['get', 'name'], 'USA']); 26746 * ``` 26747 * @example 26748 * Display only features with five or more 'available-spots' 26749 * ```ts 26750 * map.setFilter('bike-docks', ['>=', ['get', 'available-spots'], 5]); 26751 * ``` 26752 * @example 26753 * Remove the filter for the 'bike-docks' style layer 26754 * ```ts 26755 * map.setFilter('bike-docks', null); 26756 * ``` 26757 * @see [Create a timeline animation](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-time-slider/) 26758 */ 26759 setFilter(layerId, filter, options = {}) { 26760 this.style?.setFilter(layerId, filter, options); 26761 return this._update(true); 26762 } 26763 /** 26764 * Returns the filter applied to the specified style layer. 26765 * 26766 * @param layerId - The ID of the style layer whose filter to get. 26767 * @returns The layer's filter. 26768 */ 26769 getFilter(layerId) { 26770 return this.style.getFilter(layerId); 26771 } 26772 /** 26773 * Sets the value of a paint property in the specified style layer. 26774 * 26775 * @param layerId - The ID of the layer to set the paint property in. 26776 * @param name - The name of the paint property to set. 26777 * @param value - The value of the paint property to set. 26778 * Must be of a type appropriate for the property, as defined in the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/). 26779 * Pass `null` to unset the existing value. 26780 * @param options - Options object. 26781 * @example 26782 * ```ts 26783 * map.setPaintProperty('my-layer', 'fill-color', '#faafee'); 26784 * ``` 26785 * @see [Change a layer's color with buttons](https://maplibre.org/maplibre-gl-js/docs/examples/change-a-layers-color-with-buttons/) 26786 * @see [Create a draggable point](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-draggable-point/) 26787 */ 26788 setPaintProperty(layerId, name, value, options = {}) { 26789 this.style?.setPaintProperty(layerId, name, value, options); 26790 return this._update(true); 26791 } 26792 /** 26793 * Returns the value of a paint property in the specified style layer. 26794 * 26795 * @param layerId - The ID of the layer to get the paint property from. 26796 * @param name - The name of a paint property to get. 26797 * @returns The value of the specified paint property. 26798 */ 26799 getPaintProperty(layerId, name) { 26800 return this.style.getPaintProperty(layerId, name); 26801 } 26802 /** 26803 * Sets the value of a layout property in the specified style layer. 26804 * 26805 * @param layerId - The ID of the layer to set the layout property in. 26806 * @param name - The name of the layout property to set. 26807 * @param value - The value of the layout property. Must be of a type appropriate for the property, as defined in the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/). 26808 * @param options - The options object. 26809 * @example 26810 * ```ts 26811 * map.setLayoutProperty('my-layer', 'visibility', 'none'); 26812 * ``` 26813 */ 26814 setLayoutProperty(layerId, name, value, options = {}) { 26815 this.style.setLayoutProperty(layerId, name, value, options); 26816 return this._update(true); 26817 } 26818 /** 26819 * Returns the value of a layout property in the specified style layer. 26820 * 26821 * @param layerId - The ID of the layer to get the layout property from. 26822 * @param name - The name of the layout property to get. 26823 * @returns The value of the specified layout property. 26824 */ 26825 getLayoutProperty(layerId, name) { 26826 return this.style.getLayoutProperty(layerId, name); 26827 } 26828 /** 26829 * Sets the value of the style's glyphs property. Pass a falsy value (null or undefined) 26830 * to unset glyphs. 26831 * 26832 * @param glyphsUrl - Glyph URL to set. Must conform to the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/glyphs/). 26833 * @param options - Options object. 26834 * @example 26835 * ```ts 26836 * map.setGlyphs('https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf'); 26837 * ``` 26838 */ 26839 setGlyphs(glyphsUrl, options = {}) { 26840 this._lazyInitEmptyStyle(); 26841 this.style.setGlyphs(glyphsUrl, options); 26842 return this._update(true); 26843 } 26844 /** 26845 * Returns the value of the style's glyphs URL 26846 * 26847 * @returns glyphs Style's glyphs url, or `null` if glyphs are unset. 26848 */ 26849 getGlyphs() { 26850 return this.style.getGlyphsUrl(); 26851 } 26852 /** 26853 * Sets the value of the style's `font-faces` property, which points at the font files used to 26854 * draw text that the style's `glyphs` URL does not cover. Pass a falsy value (null or undefined) 26855 * to unset it. 26856 * 26857 * The files are handed to the browser's CSS Font Loading API, so any format the browser can 26858 * render text with may be used, and requests for them go through `transformRequest` as glyph 26859 * requests do. Text is drawn a grapheme cluster at a time, so a letter and the marks written on 26860 * it are handed to the browser's text engine together and come back as the one shape they are 26861 * written as -- which is what a `glyphs` URL, serving one codepoint at a time, cannot do. 26862 * 26863 * @param fontFaces - The font faces to set. Must conform to the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/root/#font-faces). 26864 * A declaration this cannot make sense of is skipped with a warning, as is a font file that 26865 * fails to load, so the text it would have drawn falls back to the `glyphs` URL. 26866 * @example 26867 * ```ts 26868 * map.setFontFaces({ 26869 * 'Noto Sans Regular': [ 26870 * {url: 'https://example.com/NotoSansKhmer-Regular.ttf', 'unicode-range': ['U+1780-17FF']} 26871 * ] 26872 * }); 26873 * ``` 26874 */ 26875 setFontFaces(fontFaces) { 26876 this._lazyInitEmptyStyle(); 26877 this.style.setFontFaces(fontFaces); 26878 return this._update(true); 26879 } 26880 /** 26881 * Returns the value of the style's `font-faces` property. 26882 * 26883 * @returns The style's font faces, or `null` if it declares none. 26884 */ 26885 getFontFaces() { 26886 return this.style.getFontFaces(); 26887 } 26888 /** 26889 * Adds a sprite to the map's style. Fires the `style` event. 26890 * 26891 * @param id - The ID of the sprite to add. Must not conflict with existing sprites. 26892 * @param url - The URL to load the sprite from 26893 * @param options - Options object. 26894 * @example 26895 * ```ts 26896 * map.addSprite('sprite-two', 'http://example.com/sprite-two'); 26897 * ``` 26898 */ 26899 addSprite(id, url, options = {}) { 26900 this._lazyInitEmptyStyle(); 26901 this.style.addSprite(id, url, options, (err) => { 26902 if (!err) this._update(true); 26903 }); 26904 return this; 26905 } 26906 /** 26907 * Removes the sprite from the map's style. Fires the `style` event. 26908 * 26909 * @param id - The ID of the sprite to remove. If the sprite is declared as a single URL, the ID must be "default". 26910 * @example 26911 * ```ts 26912 * map.removeSprite('sprite-two'); 26913 * map.removeSprite('default'); 26914 * ``` 26915 */ 26916 removeSprite(id) { 26917 this._lazyInitEmptyStyle(); 26918 this.style.removeSprite(id); 26919 return this._update(true); 26920 } 26921 /** 26922 * Returns the as-is value of the style's sprite. 26923 * 26924 * @returns style's sprite list of id-url pairs 26925 */ 26926 getSprite() { 26927 return this.style.getSprite(); 26928 } 26929 /** 26930 * Sets the value of the style's sprite property. 26931 * 26932 * @param spriteUrl - Sprite URL to set. 26933 * @param options - Options object. 26934 * @example 26935 * ```ts 26936 * map.setSprite('YOUR_SPRITE_URL'); 26937 * ``` 26938 */ 26939 setSprite(spriteUrl, options = {}) { 26940 this._lazyInitEmptyStyle(); 26941 this.style.setSprite(spriteUrl, options, (err) => { 26942 if (!err) this._update(true); 26943 }); 26944 return this; 26945 } 26946 /** 26947 * Sets the any combination of light values. 26948 * 26949 * @param light - Light properties to set. Must conform to the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/light). 26950 * @param options - Options object. 26951 * 26952 * @example 26953 * ```ts 26954 * let layerVisibility = map.getLayoutProperty('my-layer', 'visibility'); 26955 * ``` 26956 */ 26957 setLight(light, options = {}) { 26958 this._lazyInitEmptyStyle(); 26959 this.style.setLight(light, options); 26960 return this._update(true); 26961 } 26962 /** 26963 * Returns the value of the light object. 26964 * 26965 * @returns light Light properties of the style. 26966 */ 26967 getLight() { 26968 return this.style.getLight(); 26969 } 26970 /** 26971 * Sets the value of style's sky properties. 26972 * 26973 * @param sky - Sky properties to set. Must conform to the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/sky/). 26974 * @param options - Options object. 26975 * 26976 * @example 26977 * ```ts 26978 * map.setSky({'atmosphere-blend': 1.0}); 26979 * ``` 26980 */ 26981 setSky(sky, options = {}) { 26982 this._lazyInitEmptyStyle(); 26983 this.style.setSky(sky, options); 26984 return this._update(true); 26985 } 26986 /** 26987 * Returns the value of the style's sky. 26988 * 26989 * @returns the sky properties of the style. 26990 * @example 26991 * ```ts 26992 * map.getSky(); 26993 * ``` 26994 */ 26995 getSky() { 26996 return this.style.getSky(); 26997 } 26998 /** 26999 * Sets the `state` of a feature. 27000 * A feature's `state` is a set of user-defined key-value pairs that are assigned to a feature at runtime. 27001 * When using this method, the `state` object is merged with any existing key-value pairs in the feature's state. 27002 * Features are identified by their `feature.id` attribute, which can be any number or string. 27003 * 27004 * This method can only be used with sources that have a `feature.id` attribute. The `feature.id` attribute can be defined in three ways: 27005 * 27006 * - For vector or GeoJSON sources, including an `id` attribute in the original data file. 27007 * - For vector or GeoJSON sources, using the [`promoteId`](https://maplibre.org/maplibre-style-spec/sources/#promoteid) option at the time the source is defined. 27008 * - For GeoJSON sources, using the [`generateId`](https://maplibre.org/maplibre-style-spec/sources/#generateid) option to auto-assign an `id` based on the feature's index in the source data. If you change feature data using `map.getSource('some id').setData(..)`, you may need to re-apply state taking into account updated `id` values. 27009 * 27010 * !!! note 27011 * You can use the [`feature-state` expression](https://maplibre.org/maplibre-style-spec/expressions/#feature-state) to access the values in a feature's state object for the purposes of styling. 27012 * 27013 * @param feature - Feature identifier. Feature objects returned from 27014 * {@link Map.queryRenderedFeatures} or event handlers can be used as feature identifiers. 27015 * @param state - A set of key-value pairs. The values should be valid JSON types. 27016 * 27017 * @example 27018 * ```ts 27019 * // When the mouse moves over the `my-layer` layer, update 27020 * // the feature state for the feature under the mouse 27021 * map.on('mousemove', 'my-layer', (e) => { 27022 * if (e.features.length > 0) { 27023 * map.setFeatureState({ 27024 * source: 'my-source', 27025 * sourceLayer: 'my-source-layer', 27026 * id: e.features[0].id, 27027 * }, { 27028 * hover: true 27029 * }); 27030 * } 27031 * }); 27032 * ``` 27033 * @see [Create a hover effect](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-hover-effect/) 27034 */ 27035 setFeatureState(feature, state) { 27036 this.style.setFeatureState(feature, state); 27037 return this._update(); 27038 } 27039 /** 27040 * Removes the `state` of a feature, setting it back to the default behavior. 27041 * If only a `target.source` is specified, it will remove the state for all features from that source. 27042 * If `target.id` is also specified, it will remove all keys for that feature's state. 27043 * If `key` is also specified, it removes only that key from that feature's state. 27044 * Features are identified by their `feature.id` attribute, which can be any number or string. 27045 * 27046 * @param target - Identifier of where to remove state. It can be a source, a feature, or a specific key of feature. 27047 * Feature objects returned from {@link Map.queryRenderedFeatures} or event handlers can be used as feature identifiers. 27048 * @param key - (optional) The key in the feature state to reset. 27049 * @example 27050 * Reset the entire state object for all features in the `my-source` source 27051 * ```ts 27052 * map.removeFeatureState({ 27053 * source: 'my-source' 27054 * }); 27055 * ``` 27056 * 27057 * @example 27058 * When the mouse leaves the `my-layer` layer, 27059 * reset the entire state object for the 27060 * feature under the mouse 27061 * ```ts 27062 * map.on('mouseleave', 'my-layer', (e) => { 27063 * map.removeFeatureState({ 27064 * source: 'my-source', 27065 * sourceLayer: 'my-source-layer', 27066 * id: e.features[0].id 27067 * }); 27068 * }); 27069 * ``` 27070 * 27071 * @example 27072 * When the mouse leaves the `my-layer` layer, 27073 * reset only the `hover` key-value pair in the 27074 * state for the feature under the mouse 27075 * ```ts 27076 * map.on('mouseleave', 'my-layer', (e) => { 27077 * map.removeFeatureState({ 27078 * source: 'my-source', 27079 * sourceLayer: 'my-source-layer', 27080 * id: e.features[0].id 27081 * }, 'hover'); 27082 * }); 27083 * ``` 27084 */ 27085 removeFeatureState(target, key) { 27086 this.style.removeFeatureState(target, key); 27087 return this._update(); 27088 } 27089 /** 27090 * Gets the `state` of a feature. 27091 * A feature's `state` is a set of user-defined key-value pairs that are assigned to a feature at runtime. 27092 * Features are identified by their `feature.id` attribute, which can be any number or string. 27093 * 27094 * !!! note 27095 * To access the values in a feature's state object for the purposes of styling the feature, use the [`feature-state` expression](https://maplibre.org/maplibre-style-spec/expressions/#feature-state). 27096 * 27097 * @param feature - Feature identifier. Feature objects returned from 27098 * {@link Map.queryRenderedFeatures} or event handlers can be used as feature identifiers. 27099 * @returns The state of the feature: a set of key-value pairs that was assigned to the feature at runtime. 27100 * 27101 * @example 27102 * When the mouse moves over the `my-layer` layer, 27103 * get the feature state for the feature under the mouse 27104 * ```ts 27105 * map.on('mousemove', 'my-layer', (e) => { 27106 * if (e.features.length > 0) { 27107 * map.getFeatureState({ 27108 * source: 'my-source', 27109 * sourceLayer: 'my-source-layer', 27110 * id: e.features[0].id 27111 * }); 27112 * } 27113 * }); 27114 * ``` 27115 */ 27116 getFeatureState(feature) { 27117 return this.style.getFeatureState(feature); 27118 } 27119 /** 27120 * Returns the map's containing HTML element. 27121 * 27122 * @returns The map's container. 27123 */ 27124 getContainer() { 27125 return this._container; 27126 }
vendor: 5,325 bytes, lines 27127-27251
27127 /** 27128 * Returns the HTML element containing the map's `<canvas>` element. 27129 * 27130 * If you want to add non-GL overlays to the map, you should append them to this element. 27131 * 27132 * This is the element to which event bindings for map interactivity (such as panning and zooming) are 27133 * attached. It will receive bubbled events from child elements such as the `<canvas>`, but not from 27134 * map controls. 27135 * 27136 * @returns The container of the map's `<canvas>`. 27137 * @see [Create a draggable point](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-draggable-point/) 27138 */ 27139 getCanvasContainer() { 27140 return this._canvasContainer; 27141 } 27142 /** 27143 * Returns the map's `<canvas>` element. 27144 * 27145 * @returns The map's `<canvas>` element. 27146 * @see [Measure distances](https://maplibre.org/maplibre-gl-js/docs/examples/measure-distances/) 27147 * @see [Display a popup on hover](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup-on-hover/) 27148 * @see [Center the map on a clicked symbol](https://maplibre.org/maplibre-gl-js/docs/examples/center-the-map-on-a-clicked-symbol/) 27149 */ 27150 getCanvas() { 27151 return this._canvas; 27152 } 27153 _containerDimensions() { 27154 let width = 0; 27155 let height = 0; 27156 if (this._container) { 27157 width = this._container.clientWidth || 400; 27158 height = this._container.clientHeight || 300; 27159 } 27160 return [width, height]; 27161 } 27162 /** 27163 * Determines if the initial resize event should be handled based on the container's dimensions. 27164 * 27165 * @returns `true` if the initial resize event should be handled, `false` otherwise. 27166 */ 27167 _shouldHandleInitialResize() { 27168 if (!this._container?.clientWidth || !this._container.clientHeight) return false; 27169 const [width, height] = this._containerDimensions(); 27170 return width !== this._camera.transform.width || height !== this._camera.transform.height; 27171 } 27172 /** 27173 * @internal 27174 * Sets up the ResizeObserver to track container size changes. 27175 * Uses the owning window's ResizeObserver for cross-window support. 27176 */ 27177 _setupResizeObserver() { 27178 let initialResizeEventCaptured = false; 27179 const throttledResizeCallback = throttle((entries) => { 27180 if (this._trackResize && !this._removed) { 27181 this.resize(entries); 27182 this.redraw(); 27183 } 27184 }, 50); 27185 const ResizeObserverClass = this._ownerWindow.ResizeObserver ?? ResizeObserver; 27186 this._resizeObserver = new ResizeObserverClass((entries) => { 27187 if (!initialResizeEventCaptured) { 27188 initialResizeEventCaptured = true; 27189 if (!this._shouldHandleInitialResize()) return; 27190 } 27191 throttledResizeCallback(entries); 27192 }); 27193 this._resizeObserver.observe(this._container); 27194 } 27195 /** 27196 * @internal 27197 * Resolves the container option to an HTMLElement. 27198 * Supports string ID, HTMLElement, or cross-window elements (using nodeType check). 27199 */ 27200 _resolveContainer(container) { 27201 if (typeof container === "string") { 27202 const element = document.getElementById(container); 27203 if (!element) throw new Error(`Container '${container}' not found.`); 27204 return element; 27205 } 27206 if (container instanceof HTMLElement) return container; 27207 if (container && typeof container === "object" && container.nodeType === 1) return container; 27208 throw new Error("Invalid type: 'container' must be a String or HTMLElement."); 27209 } 27210 _setupContainer() { 27211 const dimensions = this._containerDimensions(); 27212 const clampedPixelRatio = this._getClampedPixelRatio(dimensions[0], dimensions[1]); 27213 const container = this._container; 27214 container.classList.add("maplibregl-map"); 27215 const canvasContainer = this._canvasContainer = DOM.create("div", "maplibregl-canvas-container", container); 27216 if (this._interactive) canvasContainer.classList.add("maplibregl-interactive"); 27217 this._canvas = DOM.create("canvas", "maplibregl-canvas", canvasContainer); 27218 this._canvas.addEventListener("webglcontextlost", this._contextLost, false); 27219 this._canvas.addEventListener("webglcontextrestored", this._contextRestored, false); 27220 this._canvas.setAttribute("tabindex", this._interactive ? "0" : "-1"); 27221 this._canvas.setAttribute("aria-label", this._getUIString("Map.Title")); 27222 this._canvas.setAttribute("role", "region"); 27223 this._resizeCanvas(dimensions[0], dimensions[1], clampedPixelRatio); 27224 const controlContainer = this._controlContainer = DOM.create("div", "maplibregl-control-container", container); 27225 const positions = this._controlPositions = {}; 27226 for (const positionName of [ 27227 "top-left", 27228 "top-right", 27229 "bottom-left", 27230 "bottom-right" 27231 ]) positions[positionName] = DOM.create("div", `maplibregl-ctrl-${positionName} `, controlContainer); 27232 this._container.addEventListener("scroll", this._onMapScroll, false); 27233 } 27234 /** 27235 * @internal 27236 * Reverses the DOM mutations of {@link Map._setupContainer}, returning the container element to its pre-construction state. 27237 */ 27238 _cleanupContainer() { 27239 this._canvas.removeEventListener("webglcontextrestored", this._contextRestored, false); 27240 this._canvas.removeEventListener("webglcontextlost", this._contextLost, false); 27241 this._canvasContainer.remove(); 27242 this._controlContainer.remove(); 27243 this._container.removeEventListener("scroll", this._onMapScroll, false); 27244 this._container.classList.remove("maplibregl-map"); 27245 } 27246 _resizeCanvas(width, height, pixelRatio) { 27247 this._canvas.width = Math.floor(pixelRatio * width); 27248 this._canvas.height = Math.floor(pixelRatio * height); 27249 this._canvas.style.width = `${width}px`; 27250 this._canvas.style.height = `${height}px`; 27251 }
vendor: 109,361 bytes, lines 27252-30142
27252 /** 27253 * @internal 27254 * Creates the WebGL2 context and the painter. Throws {@link GPUInitializationError} when the context cannot be created. 27255 */ 27256 _setupPainter() { 27257 const attributes = { 27258 ...this._canvasContextAttributes, 27259 alpha: true, 27260 depth: true, 27261 stencil: true, 27262 premultipliedAlpha: true 27263 }; 27264 let creationEvent = null; 27265 this._canvas.addEventListener("webglcontextcreationerror", (event) => { 27266 creationEvent = event; 27267 }, { once: true }); 27268 const gl = this._canvas.getContext("webgl2", attributes); 27269 if (!gl) throw new GPUInitializationError(attributes, creationEvent); 27270 this.painter = new Painter(gl, this._camera.transform); 27271 } 27272 /** 27273 * @internal 27274 * Creates a new specialized transform instance from a projection instance and migrates 27275 * to this new transform, carrying over all the properties of the old transform (center, pitch, etc.). 27276 * When the style's projection is changed (or first set), this function should be called. 27277 */ 27278 migrateProjection(newTransform, newCameraHelper) { 27279 this._camera.migrateProjection(newTransform, newCameraHelper); 27280 this.painter.transform = newTransform; 27281 this.fire(new MapProjectionEvent({ newProjection: this.style.projection.name })); 27282 } 27283 /** 27284 * Returns a Boolean indicating whether the map is fully loaded. 27285 * 27286 * Returns `false` if the style is not yet fully loaded, 27287 * or if there has been a change to the sources or style that 27288 * has not yet fully loaded. 27289 * 27290 * @returns A Boolean indicating whether the map is fully loaded. 27291 */ 27292 loaded() { 27293 return !this._styleDirty && !this._sourcesDirty && !!this.style && this.style.loaded(); 27294 } 27295 /** 27296 * @internal 27297 * Update this map's style and sources, and re-render the map. 27298 * 27299 * @param updateStyle - mark the map's style for reprocessing as 27300 * well as its sources 27301 */ 27302 _update(updateStyle) { 27303 if (!this.style?._loaded) return this; 27304 this._styleDirty ||= updateStyle; 27305 this._sourcesDirty = true; 27306 this.triggerRepaint(); 27307 return this; 27308 } 27309 /** 27310 * @internal 27311 * Request that the given callback be executed during the next render 27312 * frame. Schedule a render frame if one is not already scheduled. 27313 * 27314 * @returns An id that can be used to cancel the callback 27315 */ 27316 _requestRenderFrame(callback) { 27317 this._update(); 27318 return this._renderTaskQueue.add(callback); 27319 } 27320 _cancelRenderFrame(id) { 27321 this._renderTaskQueue.remove(id); 27322 } 27323 /** 27324 * @internal 27325 * Call when a (re-)render of the map is required: 27326 * 27327 * - The style has changed (`setPaintProperty()`, etc.) 27328 * - Source data has changed (e.g. tiles have finished loading) 27329 * - The map has is moving (or just finished moving) 27330 * - A transition is in progress 27331 * 27332 * @param paintStartTimeStamp - The time when the animation frame began executing. 27333 */ 27334 _render(paintStartTimeStamp) { 27335 const fadeDuration = this._idleTriggered ? this._fadeDuration : 0; 27336 const isGlobeRendering = this.style.projection?.transitionState > 0; 27337 this.painter.context.setDirty(); 27338 this.painter.setBaseState(); 27339 this._renderTaskQueue.run(paintStartTimeStamp); 27340 if (this._removed) return; 27341 let crossFading = false; 27342 if (this.style && this._styleDirty) { 27343 this._styleDirty = false; 27344 const zoom = this._camera.transform.zoom; 27345 const currentTime = now(); 27346 this.style.zoomHistory.update(zoom, currentTime); 27347 const parameters = new EvaluationParameters(zoom, { 27348 now: currentTime, 27349 fadeDuration, 27350 zoomHistory: this.style.zoomHistory, 27351 transition: this.style.getTransition() 27352 }); 27353 const factor = parameters.crossFadingFactor(); 27354 if (factor !== 1 || factor !== this._crossFadingFactor) { 27355 crossFading = true; 27356 this._crossFadingFactor = factor; 27357 } 27358 this.style.update(parameters); 27359 } 27360 const globeRenderingChanged = this.style.projection?.transitionState > 0 !== isGlobeRendering; 27361 this._camera.transform.setTransitionState(this.style.projection?.transitionState); 27362 if (this.style && (this._sourcesDirty || globeRenderingChanged)) { 27363 this._sourcesDirty = false; 27364 this.style._updateSources(this._camera.transform); 27365 } 27366 if (this.terrain) { 27367 if (this.terrain.tileManager.update(this._camera.transform, this.terrain)) this.terrain.resetElevationCache(); 27368 this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom)); 27369 if (!this._camera.elevationFreeze && this.getCenterClampedToGround()) this._camera.transform.setElevation(this.terrain.getElevationForLngLat(this._camera.transform.center, this._camera.transform)); 27370 } else { 27371 this._camera.transform.setMinElevationForCurrentTile(0); 27372 if (this.getCenterClampedToGround()) this._camera.transform.setElevation(0); 27373 } 27374 this._placementDirty = this.style?._updatePlacement(this._camera.transform, this.showCollisionBoxes, fadeDuration, this._crossSourceCollisions, globeRenderingChanged); 27375 this.painter.render(this.style, { 27376 showTileBoundaries: this.showTileBoundaries, 27377 showOverdrawInspector: this._showOverdrawInspector, 27378 rotating: this.isRotating(), 27379 zooming: this.isZooming(), 27380 moving: this.isMoving(), 27381 fadeDuration, 27382 showPadding: this.showPadding, 27383 anisotropicFilterPitch: this.getAnisotropicFilterPitch() 27384 }); 27385 this.fire(new MapLibreEvent("render")); 27386 if (this.loaded() && !this._loaded) { 27387 this._loaded = true; 27388 this.fire(new MapLibreEvent("load")); 27389 } 27390 if (this.style && (this.style.hasTransitions() || crossFading)) this._styleDirty = true; 27391 if (this.style && !this._placementDirty) this.style._releaseSymbolFadeTiles(); 27392 const somethingDirty = this._sourcesDirty || this._styleDirty || this._placementDirty || this.painter.renderToTexture?.needsFollowUpFrame; 27393 if (somethingDirty || this._repaint) this.triggerRepaint(); 27394 else if (!this.isMoving() && this.loaded()) this.fire(new MapLibreEvent("idle")); 27395 if (this._loaded && !this._fullyLoaded && !somethingDirty) this._fullyLoaded = true; 27396 return this; 27397 } 27398 /** 27399 * Force a synchronous redraw of the map. 27400 * @example 27401 * ```ts 27402 * map.redraw(); 27403 * ``` 27404 */ 27405 redraw() { 27406 if (this.style) { 27407 if (this._frameRequest) { 27408 this._frameRequest.abort(); 27409 this._frameRequest = null; 27410 } 27411 this._render(0); 27412 } 27413 return this; 27414 } 27415 /** 27416 * Clean up and release all internal resources associated with this map. 27417 * 27418 * This includes DOM elements, event bindings, web workers, and WebGL resources. 27419 * 27420 * Use this method when you are done using the map and wish to ensure that it no 27421 * longer consumes browser resources. Afterwards, you must not call any other 27422 * methods on the map. 27423 */ 27424 remove() { 27425 if (this._hash) this._hash.remove(); 27426 for (const control of this._controls) control.onRemove(this); 27427 this._controls = []; 27428 if (this._frameRequest) { 27429 this._frameRequest.abort(); 27430 this._frameRequest = null; 27431 } 27432 this._renderTaskQueue.clear(); 27433 this._diffStyleRequest?.abort(); 27434 this.painter.destroy(); 27435 this._handlers.destroy(); 27436 this.setStyle(null); 27437 if (typeof window !== "undefined") this._ownerWindow.removeEventListener("online", this._onWindowOnline, false); 27438 ImageRequest.removeThrottleControl(this._imageQueueHandle); 27439 this._resizeObserver?.disconnect(); 27440 const extension = this.painter.context.gl.getExtension("WEBGL_lose_context"); 27441 if (extension?.loseContext) extension.loseContext(); 27442 this._cleanupContainer(); 27443 this._removed = true; 27444 this.fire(new MapLibreEvent("remove")); 27445 } 27446 /** 27447 * Trigger the rendering of a single frame. Use this method with custom layers to 27448 * repaint the map when the layer changes. Calling this multiple times before the 27449 * next frame is rendered will still result in only a single frame being rendered. 27450 * @example 27451 * ```ts 27452 * map.triggerRepaint(); 27453 * ``` 27454 * @see [Add a 3D model](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-3d-model-using-threejs/) 27455 * @see [Add an animated icon to the map](https://maplibre.org/maplibre-gl-js/docs/examples/add-an-animated-icon-to-the-map/) 27456 */ 27457 triggerRepaint() { 27458 if (this.style && !this._frameRequest) { 27459 this._frameRequest = new AbortController(); 27460 browser.frame(this._frameRequest, (paintStartTimeStamp) => { 27461 this._frameRequest = null; 27462 try { 27463 this._render(paintStartTimeStamp); 27464 } catch (error) { 27465 if (!isAbortError(error)) throw error; 27466 } 27467 }, () => {}, this._ownerWindow); 27468 } 27469 } 27470 /** 27471 * Gets and sets a Boolean indicating whether the map will render an outline 27472 * around each tile and the tile ID. These tile boundaries are useful for 27473 * debugging. 27474 * 27475 * The uncompressed file size of the first vector source is drawn in the top left 27476 * corner of each tile, next to the tile ID. 27477 * 27478 * @example 27479 * ```ts 27480 * map.showTileBoundaries = true; 27481 * ``` 27482 */ 27483 get showTileBoundaries() { 27484 return !!this._showTileBoundaries; 27485 } 27486 set showTileBoundaries(value) { 27487 if (this._showTileBoundaries === value) return; 27488 this._showTileBoundaries = value; 27489 this._update(); 27490 } 27491 /** 27492 * Gets and sets a Boolean indicating whether the map will visualize 27493 * the padding offsets. 27494 */ 27495 get showPadding() { 27496 return !!this._showPadding; 27497 } 27498 set showPadding(value) { 27499 if (this._showPadding === value) return; 27500 this._showPadding = value; 27501 this._update(); 27502 } 27503 /** 27504 * Gets and sets a Boolean indicating whether the map will render boxes 27505 * around all symbols in the data source, revealing which symbols 27506 * were rendered or which were hidden due to collisions. 27507 * This information is useful for debugging. 27508 */ 27509 get showCollisionBoxes() { 27510 return !!this._showCollisionBoxes; 27511 } 27512 set showCollisionBoxes(value) { 27513 if (this._showCollisionBoxes === value) return; 27514 this._showCollisionBoxes = value; 27515 if (value) this.style._generateCollisionBoxes(); 27516 else this._update(); 27517 } 27518 /** 27519 * Gets and sets a Boolean indicating whether the map should color-code 27520 * each fragment to show how many times it has been shaded. 27521 * White fragments have been shaded 8 or more times. 27522 * Black fragments have been shaded 0 times. 27523 * This information is useful for debugging. 27524 */ 27525 get showOverdrawInspector() { 27526 return !!this._showOverdrawInspector; 27527 } 27528 set showOverdrawInspector(value) { 27529 if (this._showOverdrawInspector === value) return; 27530 this._showOverdrawInspector = value; 27531 this._update(); 27532 } 27533 /** 27534 * Gets and sets a Boolean indicating whether the map will 27535 * continuously repaint. This information is useful for analyzing performance. 27536 */ 27537 get repaint() { 27538 return !!this._repaint; 27539 } 27540 set repaint(value) { 27541 if (this._repaint !== value) { 27542 this._repaint = value; 27543 this.triggerRepaint(); 27544 } 27545 } 27546 get vertices() { 27547 return !!this._vertices; 27548 } 27549 set vertices(value) { 27550 this._vertices = value; 27551 this._update(); 27552 } 27553 /** 27554 * Returns the package version of the library 27555 * @returns Package version of the library 27556 */ 27557 get version() { 27558 return version$1; 27559 } 27560 /** 27561 * Returns the elevation for the point where the camera is looking. 27562 * This value corresponds to: 27563 * "meters above sea level" * "exaggeration" 27564 * @returns The elevation. 27565 */ 27566 getCameraTargetElevation() { 27567 return this._camera.transform.elevation; 27568 } 27569 /** 27570 * Gets the {@link ProjectionSpecification}. 27571 * @returns the projection specification. 27572 * @example 27573 * ```ts 27574 * let projection = map.getProjection(); 27575 * ``` 27576 */ 27577 getProjection() { 27578 return this.style.getProjection(); 27579 } 27580 /** 27581 * Sets the {@link ProjectionSpecification}. 27582 * @param projection - the projection specification to set 27583 * @returns 27584 */ 27585 setProjection(projection) { 27586 this._lazyInitEmptyStyle(); 27587 this.style.setProjection(projection); 27588 return this._update(true); 27589 } 27590}; 27591//#endregion 27592//#region src/ui/control/navigation_control.ts 27593const defaultOptions$3 = { 27594 showCompass: true, 27595 showZoom: true, 27596 visualizePitch: false, 27597 visualizeRoll: true 27598}; 27599/** 27600* A `NavigationControl` control contains zoom buttons and a compass. 27601* 27602* @group Markers and Controls 27603* 27604* @example 27605* ```ts 27606* let nav = new NavigationControl(); 27607* map.addControl(nav, 'top-left'); 27608* ``` 27609* @see [Display map navigation controls](https://maplibre.org/maplibre-gl-js/docs/examples/display-map-navigation-controls/) 27610*/ 27611var NavigationControl = class { 27612 /** 27613 * @param options - the control's options 27614 */ 27615 constructor(options) { 27616 this._updateZoomButtons = () => { 27617 const zoom = this._map.getZoom(); 27618 const isMax = zoom === this._map.getMaxZoom(); 27619 const isMin = zoom === this._map.getMinZoom(true); 27620 this._zoomInButton.disabled = isMax; 27621 this._zoomOutButton.disabled = isMin; 27622 this._zoomInButton.setAttribute("aria-disabled", isMax.toString()); 27623 this._zoomOutButton.setAttribute("aria-disabled", isMin.toString()); 27624 }; 27625 this._rotateCompassArrow = () => { 27626 const pitch = this._map.getPitch(); 27627 const roll = this._map.getRoll(); 27628 const bearing = this._map.getBearing(); 27629 const pitchScale = 1 / Math.pow(Math.cos(degreesToRadians(pitch)), .5); 27630 if (this.options.visualizePitch && this.options.visualizeRoll) { 27631 this._compassIcon.style.transform = `scale(${pitchScale}) rotateZ(${-roll}deg) rotateX(${pitch}deg) rotateZ(${-bearing}deg)`; 27632 return; 27633 } 27634 if (this.options.visualizePitch) { 27635 this._compassIcon.style.transform = `scale(${pitchScale}) rotateX(${pitch}deg) rotateZ(${-bearing}deg)`; 27636 return; 27637 } 27638 if (this.options.visualizeRoll) { 27639 this._compassIcon.style.transform = `rotate(${-bearing - roll}deg)`; 27640 return; 27641 } 27642 this._compassIcon.style.transform = `rotate(${-bearing}deg)`; 27643 }; 27644 this._setButtonTitle = (button, title) => { 27645 const str = this._map._getUIString(`NavigationControl.${title}`); 27646 button.title = str; 27647 button.setAttribute("aria-label", str); 27648 }; 27649 this.options = extend({}, defaultOptions$3, options); 27650 this._container = DOM.create("div", "maplibregl-ctrl maplibregl-ctrl-group"); 27651 this._container.addEventListener("contextmenu", (e) => e.preventDefault()); 27652 if (this.options.showZoom) { 27653 this._zoomInButton = this._createButton("maplibregl-ctrl-zoom-in", (e) => this._map.zoomIn({}, { originalEvent: e })); 27654 DOM.create("span", "maplibregl-ctrl-icon", this._zoomInButton).setAttribute("aria-hidden", "true"); 27655 this._zoomOutButton = this._createButton("maplibregl-ctrl-zoom-out", (e) => this._map.zoomOut({}, { originalEvent: e })); 27656 DOM.create("span", "maplibregl-ctrl-icon", this._zoomOutButton).setAttribute("aria-hidden", "true"); 27657 } 27658 if (this.options.showCompass) { 27659 this._compass = this._createButton("maplibregl-ctrl-compass", (e) => { 27660 if (this.options.visualizePitch) this._map.resetNorthPitch({}, { originalEvent: e }); 27661 else this._map.resetNorth({}, { originalEvent: e }); 27662 }); 27663 this._compassIcon = DOM.create("span", "maplibregl-ctrl-icon", this._compass); 27664 this._compassIcon.setAttribute("aria-hidden", "true"); 27665 } 27666 } 27667 /** {@inheritDoc IControl.onAdd} */ 27668 onAdd(map) { 27669 this._map = map; 27670 if (this.options.showZoom) { 27671 this._setButtonTitle(this._zoomInButton, "ZoomIn"); 27672 this._setButtonTitle(this._zoomOutButton, "ZoomOut"); 27673 this._map.on("move", this._updateZoomButtons); 27674 this._updateZoomButtons(); 27675 } 27676 if (this.options.showCompass) { 27677 this._setButtonTitle(this._compass, "ResetBearing"); 27678 if (this.options.visualizePitch) this._map.on("pitch", this._rotateCompassArrow); 27679 if (this.options.visualizeRoll) this._map.on("roll", this._rotateCompassArrow); 27680 this._map.on("rotate", this._rotateCompassArrow); 27681 this._rotateCompassArrow(); 27682 this._handler = new MouseRotateWrapper(this._map, this._compass, this.options.visualizePitch); 27683 } 27684 return this._container; 27685 } 27686 /** {@inheritDoc IControl.onRemove} */ 27687 onRemove() { 27688 this._container.remove(); 27689 if (this.options.showZoom) this._map.off("move", this._updateZoomButtons); 27690 if (this.options.showCompass) { 27691 if (this.options.visualizePitch) this._map.off("pitch", this._rotateCompassArrow); 27692 if (this.options.visualizeRoll) this._map.off("roll", this._rotateCompassArrow); 27693 this._map.off("rotate", this._rotateCompassArrow); 27694 this._handler.off(); 27695 delete this._handler; 27696 } 27697 delete this._map; 27698 } 27699 _createButton(className, fn) { 27700 const a = DOM.create("button", className, this._container); 27701 a.type = "button"; 27702 a.addEventListener("click", fn); 27703 return a; 27704 } 27705}; 27706var MouseRotateWrapper = class { 27707 constructor(map, element, pitch = false) { 27708 this.mousedown = (e) => { 27709 this.startMove(e, DOM.mousePos(this.element, e)); 27710 window.addEventListener("mousemove", this.mousemove); 27711 window.addEventListener("mouseup", this.mouseup); 27712 }; 27713 this.mousemove = (e) => { 27714 this.move(e, DOM.mousePos(this.element, e)); 27715 }; 27716 this.mouseup = (e) => { 27717 this._rotatePitchHandler.dragEnd(e); 27718 this.offTemp(); 27719 }; 27720 this.touchstart = (e) => { 27721 if (e.targetTouches.length !== 1) this.reset(); 27722 else { 27723 this._startPos = this._lastPos = DOM.touchPos(this.element, e.targetTouches)[0]; 27724 this.startMove(e, this._startPos); 27725 window.addEventListener("touchmove", this.touchmove, { passive: false }); 27726 window.addEventListener("touchend", this.touchend); 27727 } 27728 }; 27729 this.touchmove = (e) => { 27730 if (e.targetTouches.length !== 1) this.reset(); 27731 else { 27732 this._lastPos = DOM.touchPos(this.element, e.targetTouches)[0]; 27733 this.move(e, this._lastPos); 27734 } 27735 }; 27736 this.touchend = (e) => { 27737 if (e.targetTouches.length === 0 && this._startPos && this._lastPos && this._startPos.dist(this._lastPos) < this._clickTolerance) this.element.click(); 27738 delete this._startPos; 27739 delete this._lastPos; 27740 this.offTemp(); 27741 }; 27742 this.reset = () => { 27743 this._rotatePitchHandler.reset(); 27744 delete this._startPos; 27745 delete this._lastPos; 27746 this.offTemp(); 27747 }; 27748 this._clickTolerance = 10; 27749 this.element = element; 27750 const moveStateManager = new MouseOrTouchMoveStateManager(); 27751 this._rotatePitchHandler = new DragHandler({ 27752 clickTolerance: 3, 27753 move: (lastPoint, currentPoint) => { 27754 const rect = element.getBoundingClientRect(); 27755 const center = new Point((rect.bottom - rect.top) / 2, (rect.right - rect.left) / 2); 27756 return { 27757 bearingDelta: getAngleDelta(new Point(lastPoint.x, currentPoint.y), currentPoint, center), 27758 pitchDelta: pitch ? (currentPoint.y - lastPoint.y) * -.5 : void 0 27759 }; 27760 }, 27761 moveStateManager, 27762 enable: true, 27763 assignEvents: () => {} 27764 }); 27765 this.map = map; 27766 element.addEventListener("mousedown", this.mousedown); 27767 element.addEventListener("touchstart", this.touchstart, { passive: false }); 27768 element.addEventListener("touchcancel", this.reset); 27769 } 27770 startMove(e, point) { 27771 this._rotatePitchHandler.dragStart(e, point); 27772 DOM.disableDrag(); 27773 } 27774 move(e, point) { 27775 const map = this.map; 27776 const { bearingDelta, pitchDelta } = this._rotatePitchHandler.dragMove(e, point) || {}; 27777 if (bearingDelta) map.setBearing(map.getBearing() + bearingDelta); 27778 if (pitchDelta) map.setPitch(map.getPitch() + pitchDelta); 27779 } 27780 off() { 27781 const element = this.element; 27782 element.removeEventListener("mousedown", this.mousedown); 27783 element.removeEventListener("touchstart", this.touchstart); 27784 window.removeEventListener("touchmove", this.touchmove); 27785 window.removeEventListener("touchend", this.touchend); 27786 element.removeEventListener("touchcancel", this.reset); 27787 this.offTemp(); 27788 } 27789 offTemp() { 27790 DOM.enableDrag(); 27791 window.removeEventListener("mousemove", this.mousemove); 27792 window.removeEventListener("mouseup", this.mouseup); 27793 window.removeEventListener("touchmove", this.touchmove); 27794 window.removeEventListener("touchend", this.touchend); 27795 } 27796}; 27797//#endregion 27798//#region src/util/geolocation_support.ts 27799let supportsGeolocation; 27800async function checkGeolocationSupport(forceRecalculation = false) { 27801 if (supportsGeolocation !== void 0 && !forceRecalculation) return supportsGeolocation; 27802 if (window.navigator.permissions === void 0) { 27803 supportsGeolocation = !!window.navigator.geolocation; 27804 return supportsGeolocation; 27805 } 27806 try { 27807 supportsGeolocation = (await window.navigator.permissions.query({ name: "geolocation" })).state !== "denied"; 27808 } catch { 27809 supportsGeolocation = !!window.navigator.geolocation; 27810 } 27811 return supportsGeolocation; 27812} 27813//#endregion 27814//#region src/util/smart_wrap.ts 27815/** 27816* Given a LngLat, prior projected position, and a transform, return a new LngLat shifted 27817* n à 360° east or west for some n ⥠0 such that: 27818* 27819* * the projected location of the result is on screen, if possible, and secondarily: 27820* * the difference between the projected location of the result and the prior position 27821* is minimized. 27822* 27823* The object is to preserve perceived object constancy for Popups and Markers as much as 27824* possible; they should avoid shifting large distances across the screen, even when the 27825* map center changes by ±360° due to automatic wrapping, and when about to go off screen, 27826* should wrap just enough to avoid doing so. 27827*/ 27828function smartWrap(lngLat, priorPos, transform, useNormalWrap = false) { 27829 if (useNormalWrap || !transform.getCoveringTilesDetailsProvider().allowWorldCopies()) return lngLat?.wrap(); 27830 const originalLngLat = new LngLat(lngLat.lng, lngLat.lat); 27831 lngLat = new LngLat(lngLat.lng, lngLat.lat); 27832 if (priorPos) { 27833 const left = new LngLat(lngLat.lng - 360, lngLat.lat); 27834 const right = new LngLat(lngLat.lng + 360, lngLat.lat); 27835 const delta = transform.locationToScreenPoint(lngLat).distSqr(priorPos); 27836 if (transform.locationToScreenPoint(left).distSqr(priorPos) < delta) lngLat = left; 27837 else if (transform.locationToScreenPoint(right).distSqr(priorPos) < delta) lngLat = right; 27838 } 27839 while (Math.abs(lngLat.lng - transform.center.lng) > 180) { 27840 const pos = transform.locationToScreenPoint(lngLat); 27841 if (pos.x >= 0 && pos.y >= 0 && pos.x <= transform.width && pos.y <= transform.height) break; 27842 if (lngLat.lng > transform.center.lng) lngLat.lng -= 360; 27843 else lngLat.lng += 360; 27844 } 27845 if (lngLat.lng !== originalLngLat.lng && transform.isPointOnMapSurface(transform.locationToScreenPoint(lngLat))) return lngLat; 27846 return originalLngLat; 27847} 27848//#endregion 27849//#region src/ui/anchor.ts 27850const anchorTranslate = { 27851 "center": "translate(-50%,-50%)", 27852 "top": "translate(-50%,0)", 27853 "top-left": "translate(0,0)", 27854 "top-right": "translate(-100%,0)", 27855 "bottom": "translate(-50%,-100%)", 27856 "bottom-left": "translate(0,-100%)", 27857 "bottom-right": "translate(-100%,-100%)", 27858 "left": "translate(0,-50%)", 27859 "right": "translate(-100%,-50%)" 27860}; 27861function applyAnchorClass(element, anchor, prefix) { 27862 const classList = element.classList; 27863 for (const key in anchorTranslate) classList.remove(`maplibregl-${prefix}-anchor-${key}`); 27864 classList.add(`maplibregl-${prefix}-anchor-${anchor}`); 27865} 27866//#endregion 27867//#region src/ui/marker.ts 27868/** 27869* Screen-pixel deltas applied when a focused draggable marker is moved with the arrow keys. 27870*/ 27871const ARROW_KEY_DELTAS = { 27872 ArrowLeft: [-1, 0], 27873 ArrowRight: [1, 0], 27874 ArrowUp: [0, -1], 27875 ArrowDown: [0, 1] 27876}; 27877const KEYBOARD_DRAG_SMALL_STEP = 1; 27878const KEYBOARD_DRAG_LARGE_STEP = 10; 27879/** Fill color of the default marker when {@link MarkerOptions.color} is not given. */ 27880const DEFAULT_MARKER_COLOR = "#3FB1CE"; 27881/** Height in CSS pixels of the default marker SVG at scale 1. */ 27882const DEFAULT_MARKER_HEIGHT = 41; 27883/** Width in CSS pixels of the default marker SVG at scale 1. */ 27884const DEFAULT_MARKER_WIDTH = 27; 27885const SVG_NS = "http://www.w3.org/2000/svg"; 27886/** Creates an SVG element with the given attributes, set in the order given, and children. */ 27887function svgElement(tag, attributes, children = []) { 27888 const element = DOM.createNS(SVG_NS, tag); 27889 for (const name in attributes) element.setAttributeNS(null, name, attributes[name]); 27890 for (const child of children) element.appendChild(child); 27891 return element; 27892} 27893/** The default marker SVG at scale 1 in the default color, built on first use and cloned per marker. */ 27894let defaultMarkerTemplate; 27895/** 27896* Returns the shared default marker SVG, building it the first time it is asked for. Assembling 27897* the pin takes about fifty DOM calls, so each default {@link Marker} deep-clones this template 27898* and overrides only its size and its fill, see {@link defaultMarkerFillGroup}. 27899*/ 27900function getDefaultMarkerTemplate() { 27901 if (defaultMarkerTemplate) return defaultMarkerTemplate; 27902 const shadow = svgElement("g", { 27903 transform: "translate(3.0, 29.0)", 27904 fill: "#000000" 27905 }, [ 27906 ["10.5", "5.25002273"], 27907 ["10.5", "5.25002273"], 27908 ["9.5", "4.77275007"], 27909 ["8.5", "4.29549936"], 27910 ["7.5", "3.81822308"], 27911 ["6.5", "3.34094679"], 27912 ["5.5", "2.86367051"], 27913 ["4.5", "2.38636864"] 27914 ].map(([rx, ry]) => svgElement("ellipse", { 27915 opacity: "0.04", 27916 cx: "10.5", 27917 cy: "5.80029008", 27918 rx, 27919 ry 27920 }))); 27921 const background = svgElement("g", { fill: DEFAULT_MARKER_COLOR }, [svgElement("path", { 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" })]); 27922 const border = svgElement("g", { 27923 opacity: "0.25", 27924 fill: "#000000" 27925 }, [svgElement("path", { 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" })]); 27926 const maki = svgElement("g", { 27927 transform: "translate(6.0, 7.0)", 27928 fill: "#FFFFFF" 27929 }); 27930 const circles = svgElement("g", { transform: "translate(8.0, 8.0)" }, [svgElement("circle", { 27931 fill: "#000000", 27932 opacity: "0.25", 27933 cx: "5.5", 27934 cy: "5.5", 27935 r: "5.4999962" 27936 }), svgElement("circle", { 27937 fill: "#FFFFFF", 27938 cx: "5.5", 27939 cy: "5.5", 27940 r: "5.4999962" 27941 })]); 27942 defaultMarkerTemplate = svgElement("svg", { 27943 display: "block", 27944 height: `${DEFAULT_MARKER_HEIGHT}px`, 27945 width: `${DEFAULT_MARKER_WIDTH}px`, 27946 viewBox: `0 0 ${DEFAULT_MARKER_WIDTH} ${DEFAULT_MARKER_HEIGHT}` 27947 }, [svgElement("g", { "fill-rule": "nonzero" }, [ 27948 shadow, 27949 background, 27950 border, 27951 maki, 27952 circles 27953 ])]); 27954 return defaultMarkerTemplate; 27955} 27956/** The group of a default marker SVG that carries the pin's fill color: the second child of its single top-level group. */ 27957function defaultMarkerFillGroup(svg) { 27958 return svg.firstElementChild.children[1]; 27959} 27960/** 27961* The event class for marker drag events (`dragstart`, `drag` and `dragend`). 27962* 27963* @group Event Related 27964*/ 27965var MarkerDragEvent = class extends Event {}; 27966/** 27967* The event class for the marker `click` event. 27968* 27969* @group Event Related 27970*/ 27971var MarkerClickEvent = class extends Event {}; 27972/** 27973* Creates a marker component 27974* 27975* @group Markers and Controls 27976* 27977* @example 27978* ```ts 27979* let marker = new Marker() 27980* .setLngLat([30.5, 50.5]) 27981* .addTo(map); 27982* ``` 27983* 27984* @example 27985* Set options 27986* ```ts 27987* let marker = new Marker({ 27988* color: "#FFFFFF", 27989* draggable: true 27990* }).setLngLat([30.5, 50.5]) 27991* .addTo(map); 27992* ``` 27993* @see [Add a default marker](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-default-marker/) 27994* @see [Add custom icons with Markers](https://maplibre.org/maplibre-gl-js/docs/examples/add-custom-icons-with-markers/) 27995* @see [Create a draggable Marker](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-draggable-marker/) 27996* @see [Animate a marker](https://maplibre.org/maplibre-gl-js/docs/examples/animate-a-marker/) 27997* @see [Attach a popup to a marker instance](https://maplibre.org/maplibre-gl-js/docs/examples/attach-a-popup-to-a-marker-instance/) 27998* 27999* ## Events 28000* 28001* **Event** `dragstart` of type {@link MarkerDragEvent} will be fired when dragging starts. 28002* 28003* **Event** `drag` of type {@link MarkerDragEvent} will be fired while dragging. 28004* 28005* **Event** `dragend` of type {@link MarkerDragEvent} will be fired when the marker is finished being dragged. 28006* 28007* **Event** `click` of type {@link MarkerClickEvent} will be fired when the marker is clicked. 28008* 28009* ## CSS Classes 28010* 28011* **CSS class** `maplibregl-marker-covered` is toggled on the marker element when the marker 28012* is hidden behind 3D terrain or on the back of a globe. 28013* Use this class to apply custom styles to covered markers. 28014* 28015* @example 28016* ```css 28017* .maplibregl-marker-covered { 28018* pointer-events: none; 28019* cursor: default; 28020* } 28021* ``` 28022*/ 28023var Marker = class extends Evented { 28024 /** 28025 * @param options - the options 28026 */ 28027 constructor(options) { 28028 super(); 28029 this._onClick = (e) => { 28030 this.fire(new MarkerClickEvent("click", { originalEvent: e })); 28031 }; 28032 this._onKeyPress = (e) => { 28033 if (e.code === "Space" || e.code === "Enter") this.togglePopup(); 28034 }; 28035 this._onKeyDown = (e) => { 28036 if (!this._defaultMarker || !this._draggable || !this._map || !this._lngLat) return; 28037 if (e.composedPath()[0] !== this._element) return; 28038 if (e.altKey || e.ctrlKey || e.metaKey) return; 28039 const delta = ARROW_KEY_DELTAS[e.key]; 28040 if (!delta) return; 28041 e.preventDefault(); 28042 e.stopPropagation(); 28043 const step = e.shiftKey ? KEYBOARD_DRAG_LARGE_STEP : KEYBOARD_DRAG_SMALL_STEP; 28044 const pos = this._map.project(this._lngLat); 28045 this.setLngLat(this._map.unproject(new Point(pos.x + delta[0] * step, pos.y + delta[1] * step))); 28046 if (!this._keyboardDragActive) { 28047 this._keyboardDragActive = true; 28048 this.fire(new MarkerDragEvent("dragstart")); 28049 } 28050 this.fire(new MarkerDragEvent("drag")); 28051 }; 28052 this._onKeyUp = (e) => { 28053 if (!ARROW_KEY_DELTAS[e.key]) return; 28054 this._endKeyboardDrag(); 28055 }; 28056 this._onBlur = () => { 28057 this._endKeyboardDrag(); 28058 }; 28059 this._onMapClick = (e) => { 28060 const targetElement = e.originalEvent.target; 28061 const element = this._element; 28062 if (this._popup && (targetElement === element || element.contains(targetElement))) this.togglePopup(); 28063 }; 28064 this._updateCoveredUnthrottled = () => { 28065 const terrain = this._map?.terrain; 28066 if (!terrain || !this._isInViewport()) return; 28067 const covered = this._isCovered(terrain); 28068 if (covered && this._popup?.isOpen()) this._popup.remove(); 28069 this._element.style.opacity = covered ? this._opacityWhenCovered : this._opacity; 28070 this._element.classList.toggle("maplibregl-marker-covered", covered); 28071 }; 28072 this._updateCovered = throttle(this._updateCoveredUnthrottled, 100); 28073 this._update = (e) => { 28074 if (!this._map) return; 28075 this._lngLat = smartWrap(this._lngLat, this._flatPos, this._map._camera.transform); 28076 this._flatPos = this._pos = this._map.project(this._lngLat)._add(this._offset); 28077 if (this._map.terrain) this._flatPos = this._map._camera.transform.locationToScreenPoint(this._lngLat)._add(this._offset); 28078 let rotation = ""; 28079 if (this._rotationAlignment === "viewport" || this._rotationAlignment === "auto") rotation = `rotateZ(${this._rotation}deg)`; 28080 else if (this._rotationAlignment === "map") rotation = `rotateZ(${this._rotation - this._map.getBearing()}deg)`; 28081 let pitch = ""; 28082 if (this._pitchAlignment === "viewport" || this._pitchAlignment === "auto") pitch = "rotateX(0deg)"; 28083 else if (this._pitchAlignment === "map") pitch = `rotateX(${this._map.getPitch()}deg)`; 28084 if (!this._subpixelPositioning && e?.type !== "move") this._pos = this._pos.round(); 28085 this._element.style.transform = `${anchorTranslate[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px) ${pitch} ${rotation}`; 28086 this._updateOpacity(); 28087 }; 28088 this._onMove = (e) => { 28089 if (!this._isDragging) { 28090 const clickTolerance = this._clickTolerance || this._map._clickTolerance; 28091 this._isDragging = e.point.dist(this._pointerdownPos) >= clickTolerance; 28092 } 28093 if (!this._isDragging) return; 28094 this._pos = e.point.sub(this._positionDelta); 28095 this._lngLat = this._map.unproject(this._pos); 28096 this.setLngLat(this._lngLat); 28097 this._element.style.pointerEvents = "none"; 28098 if (this._state === "pending") { 28099 this._state = "active"; 28100 this.fire(new MarkerDragEvent("dragstart")); 28101 } 28102 this.fire(new MarkerDragEvent("drag")); 28103 }; 28104 this._onUp = () => { 28105 this._element.style.pointerEvents = "auto"; 28106 this._positionDelta = null; 28107 this._pointerdownPos = null; 28108 this._isDragging = false; 28109 this._map.off("mousemove", this._onMove); 28110 this._map.off("touchmove", this._onMove); 28111 if (this._state === "active") this.fire(new MarkerDragEvent("dragend")); 28112 this._state = "inactive"; 28113 }; 28114 this._addDragHandler = (e) => { 28115 if (this._element.contains(e.originalEvent.target)) { 28116 e.preventDefault(); 28117 this._positionDelta = e.point.sub(this._pos).add(this._offset); 28118 this._pointerdownPos = e.point; 28119 this._state = "pending"; 28120 this._map.on("mousemove", this._onMove); 28121 this._map.on("touchmove", this._onMove); 28122 this._map.once("mouseup", this._onUp); 28123 this._map.once("touchend", this._onUp); 28124 } 28125 }; 28126 this._anchor = options?.anchor || "center"; 28127 this._color = options?.color || DEFAULT_MARKER_COLOR; 28128 this._scale = options?.scale || 1; 28129 this._draggable = options?.draggable || false; 28130 this._clickTolerance = options?.clickTolerance || 0; 28131 this._subpixelPositioning = options?.subpixelPositioning || false; 28132 this._isDragging = false; 28133 this._roleManaged = false; 28134 this._tabIndexManaged = false; 28135 this._keyboardDragActive = false; 28136 this._state = "inactive"; 28137 this._rotation = options?.rotation || 0; 28138 this._rotationAlignment = options?.rotationAlignment || "auto"; 28139 this._pitchAlignment = options?.pitchAlignment && options.pitchAlignment !== "auto" ? options.pitchAlignment : this._rotationAlignment; 28140 this.setOpacity(options?.opacity, options?.opacityWhenCovered); 28141 if (!options?.element) { 28142 this._defaultMarker = true; 28143 this._element = DOM.create("div"); 28144 const svg = getDefaultMarkerTemplate().cloneNode(true); 28145 svg.setAttributeNS(null, "height", `${DEFAULT_MARKER_HEIGHT * this._scale}px`); 28146 svg.setAttributeNS(null, "width", `${DEFAULT_MARKER_WIDTH * this._scale}px`); 28147 defaultMarkerFillGroup(svg).setAttributeNS(null, "fill", this._color); 28148 this._element.appendChild(svg); 28149 this._offset = Point.convert(options?.offset || [0, -14]); 28150 } else { 28151 this._element = options.element; 28152 this._offset = Point.convert(options?.offset || [0, 0]); 28153 } 28154 this._element.classList.add("maplibregl-marker"); 28155 this._element.addEventListener("dragstart", (e) => { 28156 e.preventDefault(); 28157 }); 28158 this._element.addEventListener("mousedown", (e) => { 28159 e.preventDefault(); 28160 }); 28161 applyAnchorClass(this._element, this._anchor, "marker"); 28162 if (options?.className) for (const name of options.className.split(" ")) this._element.classList.add(name); 28163 this._popup = null; 28164 } 28165 /** 28166 * Attaches the `Marker` to a `Map` object. 28167 * @param map - The MapLibre GL JS map to add the marker to. 28168 * @example 28169 * ```ts 28170 * let marker = new Marker() 28171 * .setLngLat([30.5, 50.5]) 28172 * .addTo(map); // add the marker to the map 28173 * ``` 28174 */ 28175 addTo(map) { 28176 this.remove(); 28177 this._map = map; 28178 if (this._defaultMarker && !this._element.hasAttribute("aria-label")) this._element.setAttribute("aria-label", map._getUIString("Marker.Title")); 28179 this._updateAccessibilityRole(); 28180 map.getCanvasContainer().appendChild(this._element); 28181 map.on("move", this._update); 28182 map.on("moveend", this._update); 28183 map.on("terrain", this._update); 28184 map.on("projectiontransition", this._update); 28185 map.on("idle", this._update); 28186 this._element.addEventListener("click", this._onClick); 28187 this.setDraggable(this._draggable); 28188 this._update(); 28189 this._map.on("click", this._onMapClick); 28190 return this; 28191 } 28192 /** 28193 * Removes the marker from a map 28194 * @example 28195 * ```ts 28196 * let marker = new Marker().addTo(map); 28197 * marker.remove(); 28198 * ``` 28199 */ 28200 remove() { 28201 if (this._map) { 28202 this._map.off("click", this._onMapClick); 28203 this._map.off("move", this._update); 28204 this._map.off("moveend", this._update); 28205 this._map.off("terrain", this._update); 28206 this._map.off("projectiontransition", this._update); 28207 this._map.off("idle", this._update); 28208 this._map.off("mousedown", this._addDragHandler); 28209 this._map.off("touchstart", this._addDragHandler); 28210 this._map.off("mouseup", this._onUp); 28211 this._map.off("touchend", this._onUp); 28212 this._map.off("mousemove", this._onMove); 28213 this._map.off("touchmove", this._onMove); 28214 delete this._map; 28215 } 28216 this._element.removeEventListener("click", this._onClick); 28217 this._element.removeEventListener("keydown", this._onKeyDown); 28218 this._element.removeEventListener("keyup", this._onKeyUp); 28219 this._element.removeEventListener("blur", this._onBlur); 28220 this._element.removeEventListener("keypress", this._onKeyPress); 28221 this._keyboardDragActive = false; 28222 this._element.remove(); 28223 if (this._popup) this._popup.remove(); 28224 return this; 28225 } 28226 /** 28227 * Get the marker's geographical location. 28228 * 28229 * The longitude of the result may differ by a multiple of 360 degrees from the longitude previously 28230 * set by `setLngLat` because `Marker` wraps the anchor longitude across copies of the world to keep 28231 * the marker on screen. 28232 * 28233 * @returns A {@link LngLat} describing the marker's location. 28234 * @example 28235 * ```ts 28236 * // Store the marker's longitude and latitude coordinates in a variable 28237 * let lngLat = marker.getLngLat(); 28238 * // Print the marker's longitude and latitude values in the console 28239 * console.log('Longitude: ' + lngLat.lng + ', Latitude: ' + lngLat.lat ) 28240 * ``` 28241 * @see [Create a draggable Marker](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-draggable-marker/) 28242 */ 28243 getLngLat() { 28244 return this._lngLat; 28245 } 28246 /** 28247 * Set the marker's geographical position and move it. 28248 * @param lnglat - A {@link LngLat} describing where the marker should be located. 28249 * @example 28250 * Create a new marker, set the longitude and latitude, and add it to the map 28251 * ```ts 28252 * new Marker() 28253 * .setLngLat([-65.017, -16.457]) 28254 * .addTo(map); 28255 * ``` 28256 * @see [Add custom icons with Markers](https://maplibre.org/maplibre-gl-js/docs/examples/add-custom-icons-with-markers/) 28257 * @see [Create a draggable Marker](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-draggable-marker/) 28258 */ 28259 setLngLat(lnglat) { 28260 this._lngLat = LngLat.convert(lnglat); 28261 this._pos = null; 28262 this._update(); 28263 if (this._popup) this._popup.setLngLat(this._lngLat); 28264 return this; 28265 } 28266 /** 28267 * Returns the `Marker`'s HTML element. 28268 * @returns element 28269 */ 28270 getElement() { 28271 return this._element; 28272 } 28273 /** 28274 * Binds a {@link Popup} to the {@link Marker}. 28275 * @param popup - An instance of the {@link Popup} class. If undefined or null, any popup 28276 * set on this {@link Marker} instance is unset. 28277 * @example 28278 * ```ts 28279 * let marker = new Marker() 28280 * .setLngLat([0, 0]) 28281 * .setPopup(new Popup().setHTML("<h1>Hello World!</h1>")) // add popup 28282 * .addTo(map); 28283 * ``` 28284 * @see [Attach a popup to a marker instance](https://maplibre.org/maplibre-gl-js/docs/examples/attach-a-popup-to-a-marker-instance/) 28285 */ 28286 setPopup(popup) { 28287 if (this._popup) { 28288 this._popup.remove(); 28289 this._popup = null; 28290 this._element.removeEventListener("keypress", this._onKeyPress); 28291 } 28292 if (popup) { 28293 if (!("offset" in popup.options)) { 28294 const markerRadius = 13.5; 28295 const linearOffset = Math.abs(markerRadius) / Math.SQRT2; 28296 popup.options.offset = this._defaultMarker ? { 28297 "top": [0, 0], 28298 "top-left": [0, 0], 28299 "top-right": [0, 0], 28300 "bottom": [0, -38.1], 28301 "bottom-left": [linearOffset, (24.6 + linearOffset) * -1], 28302 "bottom-right": [-linearOffset, (24.6 + linearOffset) * -1], 28303 "left": [markerRadius, -24.6], 28304 "right": [-13.5, -24.6] 28305 } : this._offset; 28306 } 28307 this._popup = popup; 28308 this._element.addEventListener("keypress", this._onKeyPress); 28309 } 28310 this._updateTabIndex(); 28311 this._updateAccessibilityRole(); 28312 return this; 28313 } 28314 /** 28315 * Set the option to allow subpixel positioning of the marker by passing a boolean 28316 * 28317 * @param value - when set to `true`, subpixel positioning is enabled for the marker. 28318 * 28319 * @example 28320 * ```ts 28321 * let marker = new Marker() 28322 * marker.setSubpixelPositioning(true); 28323 * ``` 28324 */ 28325 setSubpixelPositioning(value) { 28326 this._subpixelPositioning = value; 28327 return this; 28328 } 28329 _endKeyboardDrag() { 28330 if (this._keyboardDragActive) { 28331 this._keyboardDragActive = false; 28332 this.fire(new MarkerDragEvent("dragend")); 28333 } 28334 } 28335 /** 28336 * Returns the {@link Popup} instance that is bound to the {@link Marker}. 28337 * @returns popup 28338 * @example 28339 * ```ts 28340 * let marker = new Marker() 28341 * .setLngLat([0, 0]) 28342 * .setPopup(new Popup().setHTML("<h1>Hello World!</h1>")) 28343 * .addTo(map); 28344 * 28345 * console.log(marker.getPopup()); // return the popup instance 28346 * ``` 28347 */ 28348 getPopup() { 28349 return this._popup; 28350 } 28351 /** 28352 * Opens or closes the {@link Popup} instance that is bound to the {@link Marker}, depending on the current state of the {@link Popup}. 28353 * @example 28354 * ```ts 28355 * let marker = new Marker() 28356 * .setLngLat([0, 0]) 28357 * .setPopup(new Popup().setHTML("<h1>Hello World!</h1>")) 28358 * .addTo(map); 28359 * 28360 * marker.togglePopup(); // toggle popup open or closed 28361 * ``` 28362 */ 28363 togglePopup() { 28364 const popup = this._popup; 28365 if (this._element.style.opacity === this._opacityWhenCovered) return this; 28366 if (!popup) return this; 28367 else if (popup.isOpen()) popup.remove(); 28368 else { 28369 popup.setLngLat(this._lngLat); 28370 popup.addTo(this._map); 28371 } 28372 return this; 28373 } 28374 _updateOpacity() { 28375 if (!this._isInViewport()) return; 28376 const terrain = this._map?.terrain; 28377 const occluded = this._map._camera.transform.isLocationOccluded(this._lngLat); 28378 if (!terrain || occluded) { 28379 const targetOpacity = occluded ? this._opacityWhenCovered : this._opacity; 28380 if (this._element.style.opacity !== targetOpacity) { 28381 this._element.style.opacity = targetOpacity; 28382 this._element.classList.toggle("maplibregl-marker-covered", occluded); 28383 } 28384 return; 28385 } 28386 this._updateCovered(); 28387 } 28388 /** 28389 * @internal 28390 * Whether the viewport shows the marker's position. There is nothing to compute for one it does not. 28391 */ 28392 _isInViewport() { 28393 const { width, height } = this._map._camera.transform; 28394 return !!this._pos && this._pos.x >= 0 && this._pos.y >= 0 && this._pos.x <= width && this._pos.y <= height; 28395 } 28396 /** 28397 * @internal 28398 * Whether the terrain covers the marker's center: the location raised by the height `offset` lifts the element 28399 * above it on screen, or the location itself when the offset lowers the element. 28400 */ 28401 _isCovered(terrain) { 28402 const transform = this._map._camera.transform; 28403 const elevation = terrain.getElevationForLngLat(this._lngLat, transform); 28404 const metersToCenter = Math.max(0, -this._offset.y) / transform.pixelsPerMeter; 28405 const elevationToCenter = Math.sin(this._map.getPitch() * Math.PI / 180) * metersToCenter; 28406 return transform.isLocationOccluded(this._lngLat, terrain, elevation + elevationToCenter); 28407 } 28408 /** 28409 * Get the marker's offset. 28410 * @returns The marker's screen coordinates in pixels. 28411 */ 28412 getOffset() { 28413 return this._offset; 28414 } 28415 /** 28416 * Sets the offset of the marker 28417 * @param offset - The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. 28418 */ 28419 setOffset(offset) { 28420 this._offset = Point.convert(offset); 28421 this._update(); 28422 return this; 28423 } 28424 /** 28425 * Adds a CSS class to the marker element. 28426 * 28427 * @param className - on-empty string with CSS class name to add to marker element 28428 * 28429 * @example 28430 * ``` 28431 * let marker = new Marker() 28432 * marker.addClassName('some-class') 28433 * ``` 28434 */ 28435 addClassName(className) { 28436 this._element.classList.add(className); 28437 } 28438 /** 28439 * Removes a CSS class from the marker element. 28440 * 28441 * @param className - Non-empty string with CSS class name to remove from marker element 28442 * 28443 * @example 28444 * ```ts 28445 * let marker = new Marker() 28446 * marker.removeClassName('some-class') 28447 * ``` 28448 */ 28449 removeClassName(className) { 28450 this._element.classList.remove(className); 28451 } 28452 /** 28453 * Add or remove the given CSS class on the marker element, depending on whether the element currently has that class. 28454 * 28455 * @param className - Non-empty string with CSS class name to add/remove 28456 * 28457 * @returns if the class was removed return false, if class was added, then return true 28458 * 28459 * @example 28460 * ```ts 28461 * let marker = new Marker() 28462 * marker.toggleClassName('toggleClass') 28463 * ``` 28464 */ 28465 toggleClassName(className) { 28466 return this._element.classList.toggle(className); 28467 } 28468 /** 28469 * Sets the `draggable` property and functionality of the marker. 28470 * A draggable default marker is also keyboard focusable and movable with the 28471 * arrow keys (see {@link MarkerOptions.draggable}); custom marker elements keep 28472 * their focusability and keyboard behavior application-owned. 28473 * @param shouldBeDraggable - Turns drag functionality on/off 28474 */ 28475 setDraggable(shouldBeDraggable) { 28476 this._draggable = !!shouldBeDraggable; 28477 this._element.classList.toggle("maplibregl-marker-draggable", this._draggable); 28478 if (this._map) { 28479 if (shouldBeDraggable) { 28480 this._map.on("mousedown", this._addDragHandler); 28481 this._map.on("touchstart", this._addDragHandler); 28482 } else { 28483 this._map.off("mousedown", this._addDragHandler); 28484 this._map.off("touchstart", this._addDragHandler); 28485 } 28486 } 28487 if (this._defaultMarker) { 28488 if (this._draggable) { 28489 this._element.addEventListener("keydown", this._onKeyDown); 28490 this._element.addEventListener("keyup", this._onKeyUp); 28491 this._element.addEventListener("blur", this._onBlur); 28492 } else { 28493 this._element.removeEventListener("keydown", this._onKeyDown); 28494 this._element.removeEventListener("keyup", this._onKeyUp); 28495 this._element.removeEventListener("blur", this._onBlur); 28496 this._endKeyboardDrag(); 28497 } 28498 } 28499 this._updateTabIndex(); 28500 this._updateAccessibilityRole(); 28501 return this; 28502 } 28503 /** 28504 * Returns true if the marker can be dragged 28505 * @returns True if the marker is draggable. 28506 */ 28507 isDraggable() { 28508 return this._draggable; 28509 } 28510 /** 28511 * Keep the marker element focusable while it has built-in keyboard behavior. 28512 * A popup makes any marker element interactive; dragging only manages 28513 * focusability for the default marker so custom elements stay 28514 * application-owned (#7790). A tabindex supplied by the application is 28515 * never added, changed, or removed here. 28516 */ 28517 _updateTabIndex() { 28518 if (!!this._popup || this._defaultMarker && this._draggable) { 28519 if (!this._element.hasAttribute("tabindex")) { 28520 this._element.setAttribute("tabindex", "0"); 28521 this._tabIndexManaged = true; 28522 } 28523 } else if (this._tabIndexManaged) { 28524 if (this._element.getAttribute("tabindex") === "0") this._element.removeAttribute("tabindex"); 28525 this._tabIndexManaged = false; 28526 } 28527 } 28528 /** 28529 * Keep the default marker role aligned with interactivity. 28530 * Default markers need a role because `aria-label` is set in {@link Marker.addTo}. 28531 * Non-interactive markers use `role=img`; interactive ones (draggable or with a popup) use `role=button`. 28532 * Click listeners are application-owned and do not automatically change the role. 28533 * Custom marker elements are left alone so applications own their a11y tree. 28534 * Explicit roles set by the application are preserved. 28535 */ 28536 _updateAccessibilityRole() { 28537 if (!this._defaultMarker) return; 28538 if (this._element.hasAttribute("role") && !this._roleManaged) return; 28539 const role = this._draggable || !!this._popup ? "button" : "img"; 28540 this._element.setAttribute("role", role); 28541 this._roleManaged = true; 28542 } 28543 /** 28544 * Sets the `rotation` property of the marker. 28545 * @param rotation - The rotation angle of the marker (clockwise, in degrees), relative to its respective {@link Marker.setRotationAlignment} setting. 28546 */ 28547 setRotation(rotation) { 28548 this._rotation = rotation || 0; 28549 this._update(); 28550 return this; 28551 } 28552 /** 28553 * Returns the current rotation angle of the marker (in degrees). 28554 * @returns The current rotation angle of the marker. 28555 */ 28556 getRotation() { 28557 return this._rotation; 28558 } 28559 /** 28560 * Sets the `rotationAlignment` property of the marker. 28561 * @param alignment - Sets the `rotationAlignment` property of the marker. defaults to 'auto' 28562 */ 28563 setRotationAlignment(alignment) { 28564 this._rotationAlignment = alignment || "auto"; 28565 this._update(); 28566 return this; 28567 } 28568 /** 28569 * Returns the current `rotationAlignment` property of the marker. 28570 * @returns The current rotational alignment of the marker. 28571 */ 28572 getRotationAlignment() { 28573 return this._rotationAlignment; 28574 } 28575 /** 28576 * Sets the `pitchAlignment` property of the marker. 28577 * @param alignment - Sets the `pitchAlignment` property of the marker. If alignment is 'auto', it will automatically match `rotationAlignment`. 28578 */ 28579 setPitchAlignment(alignment) { 28580 this._pitchAlignment = alignment && alignment !== "auto" ? alignment : this._rotationAlignment; 28581 this._update(); 28582 return this; 28583 } 28584 /** 28585 * Returns the current `pitchAlignment` property of the marker. 28586 * @returns The current pitch alignment of the marker in degrees. 28587 */ 28588 getPitchAlignment() { 28589 return this._pitchAlignment; 28590 } 28591 /** 28592 * Sets the `opacity` and `opacityWhenCovered` properties of the marker. 28593 * When called without arguments, resets opacity and opacityWhenCovered to defaults 28594 * @param opacity - Sets the `opacity` property of the marker. 28595 * @param opacityWhenCovered - Sets the `opacityWhenCovered` property of the marker. 28596 */ 28597 setOpacity(opacity, opacityWhenCovered) { 28598 if (this._opacity === void 0 || opacity === void 0 && opacityWhenCovered === void 0) { 28599 this._opacity = "1"; 28600 this._opacityWhenCovered = "0.2"; 28601 } 28602 if (opacity !== void 0) this._opacity = String(opacity); 28603 if (opacityWhenCovered !== void 0) this._opacityWhenCovered = String(opacityWhenCovered); 28604 if (this._map) this._updateOpacity(); 28605 return this; 28606 } 28607}; 28608//#endregion 28609//#region src/ui/control/geolocate_control.ts 28610const defaultOptions$2 = { 28611 positionOptions: { 28612 enableHighAccuracy: false, 28613 maximumAge: 0, 28614 timeout: 6e3 28615 }, 28616 fitBoundsOptions: { maxZoom: 15 }, 28617 trackUserLocation: false, 28618 showAccuracyCircle: true, 28619 showUserLocation: true 28620}; 28621let numberOfWatches = 0; 28622let noTimeout = false; 28623/** 28624* The event class for geolocate control state events 28625* (`trackuserlocationstart`, `trackuserlocationend`, `userlocationfocus` and `userlocationlostfocus`). 28626* 28627* @group Event Related 28628*/ 28629var GeolocateEvent = class extends Event {}; 28630/** 28631* The event class for the geolocate control `geolocate` and `outofmaxbounds` events. 28632* Carries the [Position](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPosition) returned by the Geolocation API. 28633* 28634* @group Event Related 28635*/ 28636var GeolocatePositionEvent = class extends Event {}; 28637/** 28638* The event class for the geolocate control `error` event. 28639* Carries the [PositionError](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError) returned by the Geolocation API. 28640* 28641* @group Event Related 28642*/ 28643var GeolocateErrorEvent = class extends Event {}; 28644/** 28645* A `GeolocateControl` control provides a button that uses the browser's geolocation 28646* API to locate the user on the map. 28647* 28648* Not all browsers support geolocation, 28649* and some users may disable the feature. Geolocation support for modern 28650* browsers including Chrome requires sites to be served over HTTPS. If 28651* geolocation support is not available, the `GeolocateControl` will show 28652* as disabled. 28653* 28654* The zoom level applied will depend on the accuracy of the geolocation provided by the device. 28655* 28656* The `GeolocateControl` has two modes. If `trackUserLocation` is `false` (default) the control acts as a button, which when pressed will set the map's camera to target the user location. If the user moves, the map won't update. This is most suited for the desktop. If `trackUserLocation` is `true` the control acts as a toggle button that when active the user's location is actively monitored for changes. In this mode the `GeolocateControl` has three interaction states: 28657* * active - the map's camera automatically updates as the user's location changes, keeping the location dot in the center. Initial state and upon clicking the `GeolocateControl` button. 28658* * passive - the user's location dot automatically updates, but the map's camera does not. Occurs upon the user initiating a map movement. 28659* * disabled - occurs if Geolocation is not available, disabled or denied. 28660* 28661* These interaction states can't be controlled programmatically, rather they are set based on user interactions. 28662* 28663* ## State Diagram 28664*  28665* 28666* @group Markers and Controls 28667* 28668* @example 28669* ```ts 28670* map.addControl(new GeolocateControl({ 28671* positionOptions: { 28672* enableHighAccuracy: true 28673* }, 28674* trackUserLocation: true 28675* })); 28676* ``` 28677* @see [Locate the user](https://maplibre.org/maplibre-gl-js/docs/examples/locate-the-user/) 28678* 28679* ## Events 28680* 28681* **Event** `trackuserlocationend` of type {@link Event} will be fired when the `GeolocateControl` changes to the background state, which happens when a user changes the camera during an active position lock. This only applies when `trackUserLocation` is `true`. In the background state, the dot on the map will update with location updates but the camera will not. 28682* 28683* **Event** `trackuserlocationstart` of type {@link Event} will be fired when the `GeolocateControl` changes to the active lock state, which happens either upon first obtaining a successful Geolocation API position for the user (a `geolocate` event will follow), or the user clicks the geolocate button when in the background state which uses the last known position to recenter the map and enter active lock state (no `geolocate` event will follow unless the users's location changes). 28684* 28685* **Event** `userlocationlostfocus` of type {@link Event} will be fired when the `GeolocateControl` changes to the background state, which happens when a user changes the camera during an active position lock. This only applies when `trackUserLocation` is `true`. In the background state, the dot on the map will update with location updates but the camera will not. 28686* 28687* **Event** `userlocationfocus` of type {@link Event} will be fired when the `GeolocateControl` changes to the active lock state, which happens upon the user clicks the geolocate button when in the background state which uses the last known position to recenter the map and enter active lock state. 28688* 28689* **Event** `geolocate` of type {@link Event} will be fired on each Geolocation API position update which returned as success. 28690* `data` - The returned [Position](https://developer.mozilla.org/en-US/docs/Web/API/Position) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) or [Geolocation.watchPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). 28691* 28692* **Event** `error` of type {@link Event} will be fired on each Geolocation API position update which returned as an error. 28693* `data` - The returned [PositionError](https://developer.mozilla.org/en-US/docs/Web/API/PositionError) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) or [Geolocation.watchPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). 28694* 28695* **Event** `outofmaxbounds` of type {@link Event} will be fired on each Geolocation API position update which returned as success but user position is out of map `maxBounds`. 28696* `data` - The returned [Position](https://developer.mozilla.org/en-US/docs/Web/API/Position) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) or [Geolocation.watchPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). 28697* 28698* @example 28699* ```ts 28700* // Initialize the geolocate control. 28701* let geolocate = new GeolocateControl({ 28702* positionOptions: { 28703* enableHighAccuracy: true 28704* }, 28705* trackUserLocation: true 28706* }); 28707* // Add the control to the map. 28708* map.addControl(geolocate); 28709* // Set an event listener that fires 28710* // when a trackuserlocationend event occurs. 28711* geolocate.on('trackuserlocationend', () => { 28712* console.log('A trackuserlocationend event has occurred.') 28713* }); 28714* ``` 28715* 28716* @example 28717* ```ts 28718* // Initialize the geolocate control. 28719* let geolocate = new GeolocateControl({ 28720* positionOptions: { 28721* enableHighAccuracy: true 28722* }, 28723* trackUserLocation: true 28724* }); 28725* // Add the control to the map. 28726* map.addControl(geolocate); 28727* // Set an event listener that fires 28728* // when a trackuserlocationstart event occurs. 28729* geolocate.on('trackuserlocationstart', () => { 28730* console.log('A trackuserlocationstart event has occurred.') 28731* }); 28732* ``` 28733* 28734* @example 28735* ```ts 28736* // Initialize the geolocate control. 28737* let geolocate = new GeolocateControl({ 28738* positionOptions: { 28739* enableHighAccuracy: true 28740* }, 28741* trackUserLocation: true 28742* }); 28743* // Add the control to the map. 28744* map.addControl(geolocate); 28745* // Set an event listener that fires 28746* // when an userlocationlostfocus event occurs. 28747* geolocate.on('userlocationlostfocus', function() { 28748* console.log('An userlocationlostfocus event has occurred.') 28749* }); 28750* ``` 28751* 28752* @example 28753* ```ts 28754* // Initialize the geolocate control. 28755* let geolocate = new GeolocateControl({ 28756* positionOptions: { 28757* enableHighAccuracy: true 28758* }, 28759* trackUserLocation: true 28760* }); 28761* // Add the control to the map. 28762* map.addControl(geolocate); 28763* // Set an event listener that fires 28764* // when an userlocationfocus event occurs. 28765* geolocate.on('userlocationfocus', function() { 28766* console.log('An userlocationfocus event has occurred.') 28767* }); 28768* ``` 28769* 28770* @example 28771* ```ts 28772* // Initialize the geolocate control. 28773* let geolocate = new GeolocateControl({ 28774* positionOptions: { 28775* enableHighAccuracy: true 28776* }, 28777* trackUserLocation: true 28778* }); 28779* // Add the control to the map. 28780* map.addControl(geolocate); 28781* // Set an event listener that fires 28782* // when a geolocate event occurs. 28783* geolocate.on('geolocate', () => { 28784* console.log('A geolocate event has occurred.') 28785* }); 28786* ``` 28787* 28788* @example 28789* ```ts 28790* // Initialize the geolocate control. 28791* let geolocate = new GeolocateControl({ 28792* positionOptions: { 28793* enableHighAccuracy: true 28794* }, 28795* trackUserLocation: true 28796* }); 28797* // Add the control to the map. 28798* map.addControl(geolocate); 28799* // Set an event listener that fires 28800* // when an error event occurs. 28801* geolocate.on('error', () => { 28802* console.log('An error event has occurred.') 28803* }); 28804* ``` 28805* 28806* @example 28807* ```ts 28808* // Initialize the geolocate control. 28809* let geolocate = new GeolocateControl({ 28810* positionOptions: { 28811* enableHighAccuracy: true 28812* }, 28813* trackUserLocation: true 28814* }); 28815* // Add the control to the map. 28816* map.addControl(geolocate); 28817* // Set an event listener that fires 28818* // when an outofmaxbounds event occurs. 28819* geolocate.on('outofmaxbounds', () => { 28820* console.log('An outofmaxbounds event has occurred.') 28821* }); 28822* ``` 28823*/ 28824var GeolocateControl = class extends Evented { 28825 /** 28826 * @param options - the control's options 28827 */ 28828 constructor(options) { 28829 super(); 28830 this._onSuccess = (position) => { 28831 if (!this._map) return; 28832 if (this._isOutOfMapMaxBounds(position)) { 28833 this._setErrorState(); 28834 this.fire(new GeolocatePositionEvent("outofmaxbounds", position)); 28835 this._updateMarker(); 28836 this._finish(); 28837 return; 28838 } 28839 if (this.options.trackUserLocation) { 28840 this._lastKnownPosition = position; 28841 switch (this._watchState) { 28842 case "WAITING_ACTIVE": 28843 case "ACTIVE_LOCK": 28844 case "ACTIVE_ERROR": 28845 this._watchState = "ACTIVE_LOCK"; 28846 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"); 28847 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active-error"); 28848 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active"); 28849 break; 28850 case "BACKGROUND": 28851 case "BACKGROUND_ERROR": 28852 this._watchState = "BACKGROUND"; 28853 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"); 28854 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background-error"); 28855 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-background"); 28856 break; 28857 default: throw new Error(`Unexpected watchState ${this._watchState}`); 28858 } 28859 } 28860 if (this.options.showUserLocation && this._watchState !== "OFF") this._updateMarker(position); 28861 if (!this.options.trackUserLocation || this._watchState === "ACTIVE_LOCK") this._updateCamera(position); 28862 if (this.options.showUserLocation) this._dotElement.classList.remove("maplibregl-user-location-dot-stale"); 28863 this.fire(new GeolocatePositionEvent("geolocate", position)); 28864 this._finish(); 28865 }; 28866 this._updateCamera = (position) => { 28867 const center = new LngLat(position.coords.longitude, position.coords.latitude); 28868 const radius = position.coords.accuracy; 28869 const bearing = this._map.getBearing(); 28870 const options = extend({ bearing }, this.options.fitBoundsOptions); 28871 const newBounds = LngLatBounds.fromLngLat(center, radius); 28872 this._map.fitBounds(newBounds, options, { geolocateSource: true }); 28873 }; 28874 this._updateMarker = (position) => { 28875 if (position) { 28876 const center = new LngLat(position.coords.longitude, position.coords.latitude); 28877 this._accuracyCircleMarker.setLngLat(center).addTo(this._map); 28878 this._userLocationDotMarker.setLngLat(center).addTo(this._map); 28879 this._accuracy = position.coords.accuracy; 28880 this._updateCircleRadiusIfNeeded(); 28881 } else { 28882 this._userLocationDotMarker.remove(); 28883 this._accuracyCircleMarker.remove(); 28884 } 28885 }; 28886 this._onUpdate = () => { 28887 this._updateCircleRadiusIfNeeded(); 28888 }; 28889 this._onError = (error) => { 28890 if (!this._map) return; 28891 if (error.code === 1) { 28892 this._watchState = "OFF"; 28893 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"); 28894 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"); 28895 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active-error"); 28896 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background"); 28897 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background-error"); 28898 this._geolocateButton.disabled = true; 28899 const title = this._map._getUIString("GeolocateControl.LocationNotAvailable"); 28900 this._geolocateButton.title = title; 28901 this._geolocateButton.setAttribute("aria-label", title); 28902 if (this._geolocationWatchID !== void 0) this._clearWatch(); 28903 } else if (error.code === 3 && noTimeout) return; 28904 else this._setErrorState(); 28905 if (this._watchState !== "OFF" && this.options.showUserLocation) this._dotElement.classList.add("maplibregl-user-location-dot-stale"); 28906 this.fire(new GeolocateErrorEvent("error", error)); 28907 this._finish(); 28908 }; 28909 this._finish = () => { 28910 if (this._timeoutId) clearTimeout(this._timeoutId); 28911 this._timeoutId = void 0; 28912 }; 28913 this._onMoveStart = (event) => { 28914 if (!this._map) return; 28915 const fromResize = event?.[0] instanceof ResizeObserverEntry; 28916 if (!event.geolocateSource && this._watchState === "ACTIVE_LOCK" && !fromResize && !this._map.isZooming()) { 28917 this._watchState = "BACKGROUND"; 28918 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-background"); 28919 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"); 28920 this.fire(new GeolocateEvent("trackuserlocationend")); 28921 this.fire(new GeolocateEvent("userlocationlostfocus")); 28922 } 28923 }; 28924 this._setupUI = () => { 28925 if (!this._map) return; 28926 this._container.addEventListener("contextmenu", (e) => { 28927 e.preventDefault(); 28928 }); 28929 this._geolocateButton = DOM.create("button", "maplibregl-ctrl-geolocate", this._container); 28930 DOM.create("span", "maplibregl-ctrl-icon", this._geolocateButton).setAttribute("aria-hidden", "true"); 28931 this._geolocateButton.type = "button"; 28932 this._geolocateButton.disabled = true; 28933 }; 28934 this._finishSetupUI = (supported) => { 28935 if (!this._map) return; 28936 if (supported === false) { 28937 warnOnce("Geolocation support is not available so the GeolocateControl will be disabled."); 28938 const title = this._map._getUIString("GeolocateControl.LocationNotAvailable"); 28939 this._geolocateButton.disabled = true; 28940 this._geolocateButton.title = title; 28941 this._geolocateButton.setAttribute("aria-label", title); 28942 } else { 28943 const title = this._map._getUIString("GeolocateControl.FindMyLocation"); 28944 this._geolocateButton.disabled = false; 28945 this._geolocateButton.title = title; 28946 this._geolocateButton.setAttribute("aria-label", title); 28947 } 28948 if (this.options.trackUserLocation) { 28949 this._geolocateButton.setAttribute("aria-pressed", "false"); 28950 this._watchState = "OFF"; 28951 } 28952 if (this.options.showUserLocation) { 28953 this._dotElement = DOM.create("div", "maplibregl-user-location-dot"); 28954 this._userLocationDotMarker = new Marker({ element: this._dotElement }); 28955 this._circleElement = DOM.create("div", "maplibregl-user-location-accuracy-circle"); 28956 this._accuracyCircleMarker = new Marker({ 28957 element: this._circleElement, 28958 pitchAlignment: "map" 28959 }); 28960 if (this.options.trackUserLocation) this._watchState = "OFF"; 28961 this._map.on("zoom", this._onUpdate); 28962 this._map.on("move", this._onUpdate); 28963 this._map.on("rotate", this._onUpdate); 28964 this._map.on("pitch", this._onUpdate); 28965 } 28966 this._geolocateButton.addEventListener("click", () => this.trigger()); 28967 this._setup = true; 28968 if (this.options.trackUserLocation) this._map.on("movestart", this._onMoveStart); 28969 }; 28970 this.options = extend({}, defaultOptions$2, options); 28971 } 28972 /** {@inheritDoc IControl.onAdd} */ 28973 onAdd(map) { 28974 this._map = map; 28975 this._container = DOM.create("div", "maplibregl-ctrl maplibregl-ctrl-group"); 28976 this._setupUI(); 28977 checkGeolocationSupport().then((supported) => this._finishSetupUI(supported)); 28978 return this._container; 28979 } 28980 /** {@inheritDoc IControl.onRemove} */ 28981 onRemove() { 28982 if (this._geolocationWatchID !== void 0) { 28983 window.navigator.geolocation.clearWatch(this._geolocationWatchID); 28984 this._geolocationWatchID = void 0; 28985 } 28986 if (this.options.showUserLocation && this._userLocationDotMarker) this._userLocationDotMarker.remove(); 28987 if (this.options.showAccuracyCircle && this._accuracyCircleMarker) this._accuracyCircleMarker.remove(); 28988 this._container.remove(); 28989 this._map.off("movestart", this._onMoveStart); 28990 this._map.off("zoom", this._onUpdate); 28991 this._map.off("move", this._onUpdate); 28992 this._map.off("rotate", this._onUpdate); 28993 this._map.off("pitch", this._onUpdate); 28994 this._map = void 0; 28995 numberOfWatches = 0; 28996 noTimeout = false; 28997 } 28998 /** 28999 * Check if the Geolocation API Position is outside the map's `maxBounds`. 29000 * 29001 * @param position - the Geolocation API Position 29002 * @returns `true` if position is outside the map's `maxBounds`, otherwise returns `false`. 29003 */ 29004 _isOutOfMapMaxBounds(position) { 29005 const bounds = this._map.getMaxBounds(); 29006 const coordinates = position.coords; 29007 return bounds && (coordinates.longitude < bounds.getWest() || coordinates.longitude > bounds.getEast() || coordinates.latitude < bounds.getSouth() || coordinates.latitude > bounds.getNorth()); 29008 } 29009 _setErrorState() { 29010 switch (this._watchState) { 29011 case "WAITING_ACTIVE": 29012 this._watchState = "ACTIVE_ERROR"; 29013 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"); 29014 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active-error"); 29015 break; 29016 case "ACTIVE_LOCK": 29017 this._watchState = "ACTIVE_ERROR"; 29018 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"); 29019 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active-error"); 29020 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-waiting"); 29021 break; 29022 case "BACKGROUND": 29023 this._watchState = "BACKGROUND_ERROR"; 29024 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background"); 29025 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-background-error"); 29026 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-waiting"); 29027 break; 29028 case "ACTIVE_ERROR": 29029 case "BACKGROUND_ERROR": break; 29030 case "OFF": 29031 case void 0: break; 29032 default: throw new Error(`Unexpected watchState ${this._watchState}`); 29033 } 29034 } 29035 _updateCircleRadiusIfNeeded() { 29036 const userLocation = this._userLocationDotMarker.getLngLat(); 29037 if (!this.options.showUserLocation || !this.options.showAccuracyCircle || !this._accuracy || !userLocation) return; 29038 const screenPosition = this._map.project(userLocation); 29039 const userLocationWith100Px = this._map.unproject([screenPosition.x + 100, screenPosition.y]); 29040 const pixelsToMeters = userLocation.distanceTo(userLocationWith100Px) / 100; 29041 const circleDiameter = 2 * this._accuracy / pixelsToMeters; 29042 this._circleElement.style.width = `${circleDiameter.toFixed(2)}px`; 29043 this._circleElement.style.height = `${circleDiameter.toFixed(2)}px`; 29044 } 29045 /** 29046 * Programmatically request and move the map to the user's location. 29047 * 29048 * @returns `false` if called before control was added to a map, otherwise returns `true`. 29049 * @example 29050 * ```ts 29051 * // Initialize the geolocate control. 29052 * let geolocate = new GeolocateControl({ 29053 * positionOptions: { 29054 * enableHighAccuracy: true 29055 * }, 29056 * trackUserLocation: true 29057 * }); 29058 * // Add the control to the map. 29059 * map.addControl(geolocate); 29060 * map.on('load', () => { 29061 * geolocate.trigger(); 29062 * }); 29063 * ``` 29064 */ 29065 trigger() { 29066 if (!this._setup) { 29067 warnOnce("Geolocate control triggered before added to a map"); 29068 return false; 29069 } 29070 if (this.options.trackUserLocation) { 29071 switch (this._watchState) { 29072 case "OFF": 29073 this._watchState = "WAITING_ACTIVE"; 29074 this.fire(new GeolocateEvent("trackuserlocationstart")); 29075 break; 29076 case "WAITING_ACTIVE": 29077 case "ACTIVE_LOCK": 29078 case "ACTIVE_ERROR": 29079 case "BACKGROUND_ERROR": 29080 numberOfWatches--; 29081 noTimeout = false; 29082 this._watchState = "OFF"; 29083 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"); 29084 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active"); 29085 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-active-error"); 29086 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background"); 29087 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background-error"); 29088 this.fire(new GeolocateEvent("trackuserlocationend")); 29089 break; 29090 case "BACKGROUND": 29091 this._watchState = "ACTIVE_LOCK"; 29092 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-background"); 29093 if (this._lastKnownPosition) this._updateCamera(this._lastKnownPosition); 29094 this.fire(new GeolocateEvent("trackuserlocationstart")); 29095 this.fire(new GeolocateEvent("userlocationfocus")); 29096 break; 29097 default: throw new Error(`Unexpected watchState ${this._watchState}`); 29098 } 29099 switch (this._watchState) { 29100 case "WAITING_ACTIVE": 29101 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-waiting"); 29102 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active"); 29103 break; 29104 case "ACTIVE_LOCK": 29105 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-active"); 29106 break; 29107 case "OFF": break; 29108 default: throw new Error(`Unexpected watchState ${this._watchState}`); 29109 } 29110 if (this._watchState === "OFF" && this._geolocationWatchID !== void 0) this._clearWatch(); 29111 else if (this._geolocationWatchID === void 0) { 29112 this._geolocateButton.classList.add("maplibregl-ctrl-geolocate-waiting"); 29113 this._geolocateButton.setAttribute("aria-pressed", "true"); 29114 numberOfWatches++; 29115 let positionOptions; 29116 if (numberOfWatches > 1) { 29117 positionOptions = { 29118 maximumAge: 6e5, 29119 timeout: 0 29120 }; 29121 noTimeout = true; 29122 } else { 29123 positionOptions = this.options.positionOptions; 29124 noTimeout = false; 29125 } 29126 this._geolocationWatchID = window.navigator.geolocation.watchPosition(this._onSuccess, this._onError, positionOptions); 29127 } 29128 } else { 29129 window.navigator.geolocation.getCurrentPosition(this._onSuccess, this._onError, this.options.positionOptions); 29130 this._timeoutId = setTimeout(this._finish, 1e4); 29131 } 29132 return true; 29133 } 29134 _clearWatch() { 29135 window.navigator.geolocation.clearWatch(this._geolocationWatchID); 29136 this._geolocationWatchID = void 0; 29137 this._geolocateButton.classList.remove("maplibregl-ctrl-geolocate-waiting"); 29138 this._geolocateButton.setAttribute("aria-pressed", "false"); 29139 if (this.options.showUserLocation) this._updateMarker(null); 29140 } 29141}; 29142//#endregion 29143//#region src/ui/control/scale_control.ts 29144const defaultOptions$1 = { 29145 maxWidth: 100, 29146 unit: "metric" 29147}; 29148/** 29149* A `ScaleControl` control displays the ratio of a distance on the map to the corresponding distance on the ground. 29150* 29151* @group Markers and Controls 29152* 29153* @example 29154* ```ts 29155* let scale = new ScaleControl({ 29156* maxWidth: 80, 29157* unit: 'imperial' 29158* }); 29159* map.addControl(scale); 29160* 29161* scale.setUnit('metric'); 29162* ``` 29163*/ 29164var ScaleControl = class { 29165 /** 29166 * @param options - the control's options 29167 */ 29168 constructor(options) { 29169 this._onMove = () => { 29170 updateScale(this._map, this._container, this.options); 29171 }; 29172 this.setUnit = (unit) => { 29173 this.options.unit = unit; 29174 updateScale(this._map, this._container, this.options); 29175 }; 29176 this.options = { 29177 ...defaultOptions$1, 29178 ...options 29179 }; 29180 } 29181 getDefaultPosition() { 29182 return "bottom-left"; 29183 } 29184 /** {@inheritDoc IControl.onAdd} */ 29185 onAdd(map) { 29186 this._map = map; 29187 this._container = DOM.create("div", "maplibregl-ctrl maplibregl-ctrl-scale", map.getContainer()); 29188 this._map.on("move", this._onMove); 29189 this._onMove(); 29190 return this._container; 29191 } 29192 /** {@inheritDoc IControl.onRemove} */ 29193 onRemove() { 29194 this._container.remove(); 29195 this._map.off("move", this._onMove); 29196 this._map = void 0; 29197 } 29198}; 29199function updateScale(map, container, options) { 29200 const optWidth = options?.maxWidth || 100; 29201 const { width: containerWidth, height: containerHeight } = map._camera.transform; 29202 const y = containerHeight / 2; 29203 const x = containerWidth / 2; 29204 const left = map.unproject([x - optWidth / 2, y]); 29205 const right = map.unproject([x + optWidth / 2, y]); 29206 const globeWidth = Math.round(map.project(right).x - map.project(left).x); 29207 const maxWidth = Math.min(optWidth, globeWidth, containerWidth); 29208 const maxMeters = left.distanceTo(right); 29209 if (options?.unit === "imperial") { 29210 const maxFeet = 3.2808 * maxMeters; 29211 if (maxFeet > 5280) setScale(container, maxWidth, maxFeet / 5280, map._getUIString("ScaleControl.Miles")); 29212 else setScale(container, maxWidth, maxFeet, map._getUIString("ScaleControl.Feet")); 29213 } else if (options?.unit === "nautical") setScale(container, maxWidth, maxMeters / 1852, map._getUIString("ScaleControl.NauticalMiles")); 29214 else if (maxMeters >= 1e3) setScale(container, maxWidth, maxMeters / 1e3, map._getUIString("ScaleControl.Kilometers")); 29215 else setScale(container, maxWidth, maxMeters, map._getUIString("ScaleControl.Meters")); 29216} 29217function setScale(container, maxWidth, maxDistance, unit) { 29218 const distance = getRoundNum(maxDistance); 29219 const ratio = distance / maxDistance; 29220 container.style.width = `${maxWidth * ratio}px`; 29221 const label = `${distance}\u00a0${unit}`; 29222 if (container.textContent !== label) container.textContent = label; 29223} 29224function getDecimalRoundNum(d) { 29225 const multiplier = Math.pow(10, Math.ceil(-Math.log(d) / Math.LN10)); 29226 return Math.round(d * multiplier) / multiplier; 29227} 29228function getRoundNum(num) { 29229 const pow10 = Math.pow(10, `${Math.floor(num)}`.length - 1); 29230 let d = num / pow10; 29231 d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : d >= 1 ? 1 : getDecimalRoundNum(d); 29232 return pow10 * d; 29233} 29234//#endregion 29235//#region src/ui/control/fullscreen_control.ts 29236/** 29237* The event class for fullscreen control events (`fullscreenstart` and `fullscreenend`). 29238* 29239* @group Event Related 29240*/ 29241var FullscreenEvent = class extends Event {}; 29242/** 29243* A `FullscreenControl` control contains a button for toggling the map in and out of fullscreen mode. 29244* When [requestFullscreen](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen) is not supported, fullscreen is handled via CSS properties. 29245* The map's `cooperativeGestures` option is temporarily disabled while the map 29246* is in fullscreen mode, and is restored when the map exist fullscreen mode. 29247* 29248* @group Markers and Controls 29249* @param options - the full screen control options 29250* 29251* @example 29252* ```ts 29253* map.addControl(new FullscreenControl({container: document.querySelector('body')})); 29254* ``` 29255* @see [View a fullscreen map](https://maplibre.org/maplibre-gl-js/docs/examples/view-a-fullscreen-map/) 29256* 29257* ## Events 29258* 29259* **Event** `fullscreenstart` of type {@link FullscreenEvent} will be fired when fullscreen mode has started. 29260* 29261* **Event** `fullscreenend` of type {@link FullscreenEvent} will be fired when fullscreen mode has ended. 29262*/ 29263var FullscreenControl = class extends Evented { 29264 /** 29265 * @param options - the control's options 29266 */ 29267 constructor(options = {}) { 29268 super(); 29269 this._onFullscreenChange = () => { 29270 let fullscreenElement = window.document.fullscreenElement || window.document.webkitFullscreenElement; 29271 while (fullscreenElement?.shadowRoot?.fullscreenElement) fullscreenElement = fullscreenElement.shadowRoot.fullscreenElement; 29272 if (fullscreenElement === this._container !== this._fullscreen) this._handleFullscreenChange(); 29273 }; 29274 this._onClickFullscreen = () => { 29275 if (this._isFullscreen()) this._exitFullscreen(); 29276 else this._requestFullscreen(); 29277 }; 29278 this._fullscreen = false; 29279 this._pseudo = options.pseudo ?? false; 29280 if (options?.container) { 29281 if (options.container instanceof HTMLElement) this._container = options.container; 29282 else warnOnce("Full screen control 'container' must be a DOM element."); 29283 } 29284 if ("onfullscreenchange" in document) this._fullscreenchange = "fullscreenchange"; 29285 else if ("onmozfullscreenchange" in document) this._fullscreenchange = "mozfullscreenchange"; 29286 else if ("onwebkitfullscreenchange" in document) this._fullscreenchange = "webkitfullscreenchange"; 29287 else if ("onmsfullscreenchange" in document) this._fullscreenchange = "MSFullscreenChange"; 29288 } 29289 /** {@inheritDoc IControl.onAdd} */ 29290 onAdd(map) { 29291 this._map = map; 29292 this._container ||= this._map.getContainer(); 29293 this._controlContainer = DOM.create("div", "maplibregl-ctrl maplibregl-ctrl-group"); 29294 this._setupUI(); 29295 return this._controlContainer; 29296 } 29297 /** {@inheritDoc IControl.onRemove} */ 29298 onRemove() { 29299 this._controlContainer.remove(); 29300 this._map = null; 29301 window.document.removeEventListener(this._fullscreenchange, this._onFullscreenChange); 29302 } 29303 _setupUI() { 29304 const button = this._fullscreenButton = DOM.create("button", "maplibregl-ctrl-fullscreen", this._controlContainer); 29305 DOM.create("span", "maplibregl-ctrl-icon", button).setAttribute("aria-hidden", "true"); 29306 button.type = "button"; 29307 this._updateTitle(); 29308 this._fullscreenButton.addEventListener("click", this._onClickFullscreen); 29309 window.document.addEventListener(this._fullscreenchange, this._onFullscreenChange); 29310 } 29311 _updateTitle() { 29312 const title = this._getTitle(); 29313 this._fullscreenButton.setAttribute("aria-label", title); 29314 this._fullscreenButton.title = title; 29315 } 29316 _getTitle() { 29317 return this._map._getUIString(this._isFullscreen() ? "FullscreenControl.Exit" : "FullscreenControl.Enter"); 29318 } 29319 _isFullscreen() { 29320 return this._fullscreen; 29321 } 29322 _handleFullscreenChange() { 29323 this._fullscreen = !this._fullscreen; 29324 this._fullscreenButton.classList.toggle("maplibregl-ctrl-shrink"); 29325 this._fullscreenButton.classList.toggle("maplibregl-ctrl-fullscreen"); 29326 this._updateTitle(); 29327 if (this._fullscreen) { 29328 this.fire(new FullscreenEvent("fullscreenstart")); 29329 this._prevCooperativeGesturesEnabled = this._map.cooperativeGestures.isEnabled(); 29330 this._map.cooperativeGestures.disable(); 29331 } else { 29332 this.fire(new FullscreenEvent("fullscreenend")); 29333 if (this._prevCooperativeGesturesEnabled) this._map.cooperativeGestures.enable(); 29334 } 29335 } 29336 _exitFullscreen() { 29337 if (this._pseudo) this._togglePseudoFullScreen(); 29338 else if (window.document.exitFullscreen) window.document.exitFullscreen(); 29339 else if (window.document.webkitCancelFullScreen) window.document.webkitCancelFullScreen(); 29340 else this._togglePseudoFullScreen(); 29341 } 29342 _requestFullscreen() { 29343 if (this._pseudo) this._togglePseudoFullScreen(); 29344 else if (this._container.requestFullscreen) this._container.requestFullscreen(); 29345 else if (this._container.webkitRequestFullscreen) this._container.webkitRequestFullscreen(); 29346 else this._togglePseudoFullScreen(); 29347 } 29348 _togglePseudoFullScreen() { 29349 this._container.classList.toggle("maplibregl-pseudo-fullscreen"); 29350 this._handleFullscreenChange(); 29351 this._map.resize(); 29352 } 29353}; 29354//#endregion 29355//#region src/ui/control/terrain_control.ts 29356/** 29357* A `TerrainControl` control contains a button for turning the terrain on and off. 29358* 29359* @group Markers and Controls 29360* 29361* @example 29362* ```ts 29363* let map = new Map({TerrainControl: false}) 29364* .addControl(new TerrainControl({ 29365* source: "terrain" 29366* })); 29367* ``` 29368* @see [3D Terrain](https://maplibre.org/maplibre-gl-js/docs/examples/3d-terrain/) 29369* @see [Create a Heatmap layer on a globe with terrain elevation](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-heatmap-layer-on-a-globe-with-terrain-elevation/) 29370* @see [Display a hybrid satellite map with terrain elevation](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-hybrid-satellite-map-with-terrain-elevation/) 29371* @see [Sky, Fog, Terrain](https://maplibre.org/maplibre-gl-js/docs/examples/sky-fog-terrain/) 29372*/ 29373var TerrainControl = class { 29374 /** 29375 * @param options - the control's options 29376 */ 29377 constructor(options) { 29378 this._toggleTerrain = () => { 29379 if (this._map.getTerrain()) this._map.setTerrain(null); 29380 else this._map.setTerrain(this.options); 29381 this._updateTerrainIcon(); 29382 }; 29383 this._updateTerrainIcon = () => { 29384 this._terrainButton.classList.remove("maplibregl-ctrl-terrain"); 29385 this._terrainButton.classList.remove("maplibregl-ctrl-terrain-enabled"); 29386 if (this._map.terrain) { 29387 this._terrainButton.classList.add("maplibregl-ctrl-terrain-enabled"); 29388 this._terrainButton.title = this._map._getUIString("TerrainControl.Disable"); 29389 } else { 29390 this._terrainButton.classList.add("maplibregl-ctrl-terrain"); 29391 this._terrainButton.title = this._map._getUIString("TerrainControl.Enable"); 29392 } 29393 }; 29394 this.options = options; 29395 } 29396 /** {@inheritDoc IControl.onAdd} */ 29397 onAdd(map) { 29398 this._map = map; 29399 this._container = DOM.create("div", "maplibregl-ctrl maplibregl-ctrl-group"); 29400 this._terrainButton = DOM.create("button", "maplibregl-ctrl-terrain", this._container); 29401 DOM.create("span", "maplibregl-ctrl-icon", this._terrainButton).setAttribute("aria-hidden", "true"); 29402 this._terrainButton.type = "button"; 29403 this._terrainButton.addEventListener("click", this._toggleTerrain); 29404 this._updateTerrainIcon(); 29405 this._map.on("terrain", this._updateTerrainIcon); 29406 return this._container; 29407 } 29408 /** {@inheritDoc IControl.onRemove} */ 29409 onRemove() { 29410 this._container.remove(); 29411 this._map.off("terrain", this._updateTerrainIcon); 29412 this._map = void 0; 29413 } 29414}; 29415//#endregion 29416//#region src/ui/control/globe_control.ts 29417/** 29418* A `GlobeControl` control contains a button for toggling the map projection between "mercator" and "globe". 29419* 29420* @group Markers and Controls 29421* 29422* @example 29423* ```ts 29424* let map = new Map() 29425* .addControl(new GlobeControl()); 29426* ``` 29427* 29428* @see [Display a globe with a fill extrusion layer](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-globe-with-a-fill-extrusion-layer/) 29429* @see [Sky, Fog, Terrain](https://maplibre.org/maplibre-gl-js/docs/examples/sky-fog-terrain/) 29430*/ 29431var GlobeControl = class { 29432 constructor() { 29433 this._toggleProjection = () => { 29434 const currentProjection = this._map.getProjection()?.type; 29435 if (currentProjection === "mercator" || !currentProjection) this._map.setProjection({ type: "globe" }); 29436 else this._map.setProjection({ type: "mercator" }); 29437 this._updateGlobeIcon(); 29438 }; 29439 this._updateGlobeIcon = () => { 29440 this._globeButton.classList.remove("maplibregl-ctrl-globe"); 29441 this._globeButton.classList.remove("maplibregl-ctrl-globe-enabled"); 29442 if (this._map.getProjection()?.type === "globe") { 29443 this._globeButton.classList.add("maplibregl-ctrl-globe-enabled"); 29444 this._globeButton.title = this._map._getUIString("GlobeControl.Disable"); 29445 } else { 29446 this._globeButton.classList.add("maplibregl-ctrl-globe"); 29447 this._globeButton.title = this._map._getUIString("GlobeControl.Enable"); 29448 } 29449 }; 29450 } 29451 /** {@inheritDoc IControl.onAdd} */ 29452 onAdd(map) { 29453 this._map = map; 29454 this._container = DOM.create("div", "maplibregl-ctrl maplibregl-ctrl-group"); 29455 this._globeButton = DOM.create("button", "maplibregl-ctrl-globe", this._container); 29456 DOM.create("span", "maplibregl-ctrl-icon", this._globeButton).setAttribute("aria-hidden", "true"); 29457 this._globeButton.type = "button"; 29458 this._globeButton.addEventListener("click", this._toggleProjection); 29459 this._updateGlobeIcon(); 29460 this._map.on("styledata", this._updateGlobeIcon); 29461 this._map.on("projectiontransition", this._updateGlobeIcon); 29462 return this._container; 29463 } 29464 /** {@inheritDoc IControl.onRemove} */ 29465 onRemove() { 29466 this._container.remove(); 29467 this._map.off("styledata", this._updateGlobeIcon); 29468 this._map.off("projectiontransition", this._updateGlobeIcon); 29469 this._globeButton.removeEventListener("click", this._toggleProjection); 29470 this._map = void 0; 29471 } 29472}; 29473//#endregion 29474//#region src/ui/popup.ts 29475const defaultOptions = { 29476 closeButton: true, 29477 closeOnClick: true, 29478 focusAfterOpen: true, 29479 className: "", 29480 maxWidth: "240px", 29481 subpixelPositioning: false, 29482 locationOccludedOpacity: void 0, 29483 padding: void 0 29484}; 29485const focusQuerySelector = [ 29486 "a[href]", 29487 "[tabindex]:not([tabindex='-1'])", 29488 "[contenteditable]:not([contenteditable='false'])", 29489 "button:not([disabled])", 29490 "input:not([disabled])", 29491 "select:not([disabled])", 29492 "textarea:not([disabled])" 29493].join(", "); 29494/** 29495* The event class for popup events (`open` and `close`). 29496* 29497* @group Event Related 29498*/ 29499var PopupEvent = class extends Event {}; 29500/** 29501* A popup component. 29502* 29503* @group Markers and Controls 29504* 29505* 29506* @example 29507* Create a popup 29508* ```ts 29509* let popup = new Popup(); 29510* // Set an event listener that will fire 29511* // any time the popup is opened 29512* popup.on('open', () => { 29513* console.log('popup was opened'); 29514* }); 29515* ``` 29516* 29517* @example 29518* Create a popup 29519* ```ts 29520* let popup = new Popup(); 29521* // Set an event listener that will fire 29522* // any time the popup is closed 29523* popup.on('close', () => { 29524* console.log('popup was closed'); 29525* }); 29526* ``` 29527* 29528* @example 29529* ```ts 29530* let markerHeight = 50, markerRadius = 10, linearOffset = 25; 29531* let popupOffsets = { 29532* 'top': [0, 0], 29533* 'top-left': [0,0], 29534* 'top-right': [0,0], 29535* 'bottom': [0, -markerHeight], 29536* 'bottom-left': [linearOffset, (markerHeight - markerRadius + linearOffset) * -1], 29537* 'bottom-right': [-linearOffset, (markerHeight - markerRadius + linearOffset) * -1], 29538* 'left': [markerRadius, (markerHeight - markerRadius) * -1], 29539* 'right': [-markerRadius, (markerHeight - markerRadius) * -1] 29540* }; 29541* let popup = new Popup({offset: popupOffsets, className: 'my-class'}) 29542* .setLngLat(e.lngLat) 29543* .setHTML("<h1>Hello World!</h1>") 29544* .setMaxWidth("300px") 29545* .addTo(map); 29546* ``` 29547* @see [Display a popup](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup/) 29548* @see [Display a popup on hover](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup-on-hover/) 29549* @see [Display a popup on click](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup-on-click/) 29550* @see [Attach a popup to a marker instance](https://maplibre.org/maplibre-gl-js/docs/examples/attach-a-popup-to-a-marker-instance/) 29551* @see [Show polygon information on click](https://maplibre.org/maplibre-gl-js/docs/examples/show-polygon-information-on-click/) 29552* 29553* ## Events 29554* 29555* **Event** `open` of type {@link PopupEvent} will be fired when the popup is opened manually or programmatically. 29556* 29557* **Event** `close` of type {@link PopupEvent} will be fired when the popup is closed manually or programmatically. 29558*/ 29559var Popup = class extends Evented { 29560 /** 29561 * @param options - the options 29562 */ 29563 constructor(options) { 29564 super(); 29565 this._updateOpacity = () => { 29566 if (this.options.locationOccludedOpacity === void 0) return; 29567 if (this._map._camera.transform.isLocationOccluded(this.getLngLat())) this._container.style.opacity = `${this.options.locationOccludedOpacity}`; 29568 else this._container.style.opacity = ""; 29569 }; 29570 this.remove = () => { 29571 if (this._content) this._content.remove(); 29572 if (this._container) { 29573 this._container.remove(); 29574 delete this._container; 29575 } 29576 if (this._map) { 29577 this._map.off("move", this._update); 29578 this._map.off("move", this._onClose); 29579 this._map.off("click", this._onClose); 29580 this._map.off("remove", this.remove); 29581 this._map.off("terrain", this._update); 29582 this._map.off("projectiontransition", this._update); 29583 this._map.off("idle", this._update); 29584 this._map.off("mousemove", this._update); 29585 this._map.off("mouseup", this._update); 29586 this._map.off("drag", this._update); 29587 this._map._canvasContainer.classList.remove("maplibregl-track-pointer"); 29588 delete this._map; 29589 this.fire(new PopupEvent("close")); 29590 } 29591 return this; 29592 }; 29593 this._update = (event) => { 29594 const hasPosition = this._lngLat || this._trackPointer; 29595 if (!this._map || !hasPosition || !this._content) return; 29596 if (!this._container) { 29597 this._container = DOM.create("div", "maplibregl-popup", this._map.getContainer()); 29598 this._tip = DOM.create("div", "maplibregl-popup-tip", this._container); 29599 this._container.appendChild(this._content); 29600 if (this.options.className) for (const name of this.options.className.split(" ")) this._container.classList.add(name); 29601 if (this._closeButton) this._closeButton.setAttribute("aria-label", this._map._getUIString("Popup.Close")); 29602 if (this._trackPointer) this._container.classList.add("maplibregl-popup-track-pointer"); 29603 } 29604 if (this.options.maxWidth && this._container.style.maxWidth !== this.options.maxWidth) this._container.style.maxWidth = this.options.maxWidth; 29605 this._lngLat = smartWrap(this._lngLat, this._flatPos, this._map._camera.transform, this._trackPointer); 29606 let cursor; 29607 if (event && "point" in event && event.point) cursor = event.point; 29608 if (this._trackPointer && !cursor) return; 29609 const pos = this._flatPos = this._pos = this._trackPointer && cursor ? cursor : this._map.project(this._lngLat); 29610 if (this._map.terrain) this._flatPos = this._trackPointer && cursor ? cursor : this._map._camera.transform.locationToScreenPoint(this._lngLat); 29611 let anchor = this.options.anchor; 29612 const offset = normalizeOffset(this.options.offset); 29613 if (!anchor) { 29614 const width = this._container.offsetWidth; 29615 const height = this._container.offsetHeight; 29616 const padding = normalizePadding(this.options.padding); 29617 let anchorComponents; 29618 if (pos.y + offset.bottom.y < height + padding.top) anchorComponents = ["top"]; 29619 else if (pos.y > this._map._camera.transform.height - height - padding.bottom) anchorComponents = ["bottom"]; 29620 else anchorComponents = []; 29621 if (pos.x < width / 2 + padding.left) anchorComponents.push("left"); 29622 else if (pos.x > this._map._camera.transform.width - width / 2 - padding.right) anchorComponents.push("right"); 29623 if (anchorComponents.length === 0) anchor = "bottom"; 29624 else anchor = anchorComponents.join("-"); 29625 } 29626 let offsetedPos = pos.add(offset[anchor]); 29627 if (!this.options.subpixelPositioning) offsetedPos = offsetedPos.round(); 29628 this._container.style.transform = `${anchorTranslate[anchor]} translate(${offsetedPos.x}px,${offsetedPos.y}px)`; 29629 applyAnchorClass(this._container, anchor, "popup"); 29630 this._updateOpacity(); 29631 }; 29632 this._onClose = () => { 29633 this.remove(); 29634 }; 29635 this.options = extend(Object.create(defaultOptions), options); 29636 } 29637 /** 29638 * Adds the popup to a map. 29639 * 29640 * @param map - The MapLibre GL JS map to add the popup to. 29641 * @example 29642 * ```ts 29643 * new Popup() 29644 * .setLngLat([0, 0]) 29645 * .setHTML("<h1>Null Island</h1>") 29646 * .addTo(map); 29647 * ``` 29648 * @see [Display a popup](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup/) 29649 * @see [Display a popup on hover](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup-on-hover/) 29650 * @see [Display a popup on click](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup-on-click/) 29651 * @see [Show polygon information on click](https://maplibre.org/maplibre-gl-js/docs/examples/show-polygon-information-on-click/) 29652 */ 29653 addTo(map) { 29654 if (this._map) this.remove(); 29655 this._map = map; 29656 if (this.options.closeOnClick) this._map.on("click", this._onClose); 29657 if (this.options.closeOnMove) this._map.on("move", this._onClose); 29658 this._map.on("remove", this.remove); 29659 this._map.on("terrain", this._update); 29660 this._map.on("projectiontransition", this._update); 29661 this._map.on("idle", this._update); 29662 this._update(); 29663 this._focusFirstElement(); 29664 if (this._trackPointer) { 29665 this._map.on("mousemove", this._update); 29666 this._map.on("mouseup", this._update); 29667 if (this._container) this._container.classList.add("maplibregl-popup-track-pointer"); 29668 this._map._canvasContainer.classList.add("maplibregl-track-pointer"); 29669 } else this._map.on("move", this._update); 29670 this.fire(new PopupEvent("open")); 29671 return this; 29672 } 29673 /** 29674 * @returns `true` if the popup is open, `false` if it is closed. 29675 */ 29676 isOpen() { 29677 return !!this._map; 29678 } 29679 /** 29680 * Returns the geographical location of the popup's anchor. 29681 * 29682 * The longitude of the result may differ by a multiple of 360 degrees from the longitude previously 29683 * set by `setLngLat` because `Popup` wraps the anchor longitude across copies of the world to keep 29684 * the popup on screen. 29685 * 29686 * @returns The geographical location of the popup's anchor. 29687 */ 29688 getLngLat() { 29689 return this._lngLat; 29690 } 29691 /** 29692 * Sets the geographical location of the popup's anchor, and moves the popup to it. Replaces trackPointer() behavior. 29693 * 29694 * @param lnglat - The geographical location to set as the popup's anchor. 29695 */ 29696 setLngLat(lnglat) { 29697 this._lngLat = LngLat.convert(lnglat); 29698 this._pos = null; 29699 this._flatPos = null; 29700 this._trackPointer = false; 29701 this._update(); 29702 if (this._map) { 29703 this._map.on("move", this._update); 29704 this._map.off("mousemove", this._update); 29705 if (this._container) this._container.classList.remove("maplibregl-popup-track-pointer"); 29706 this._map._canvasContainer.classList.remove("maplibregl-track-pointer"); 29707 } 29708 return this; 29709 } 29710 /** 29711 * Tracks the popup anchor to the cursor position on screens with a pointer device (it will be hidden on touchscreens). Replaces the `setLngLat` behavior. 29712 * For most use cases, set `closeOnClick` and `closeButton` to `false`. 29713 * @example 29714 * ```ts 29715 * let popup = new Popup({ closeOnClick: false, closeButton: false }) 29716 * .setHTML("<h1>Hello World!</h1>") 29717 * .trackPointer() 29718 * .addTo(map); 29719 * ``` 29720 */ 29721 trackPointer() { 29722 this._trackPointer = true; 29723 this._pos = null; 29724 this._flatPos = null; 29725 this._update(); 29726 if (this._map) { 29727 this._map.off("move", this._update); 29728 this._map.on("mousemove", this._update); 29729 this._map.on("drag", this._update); 29730 if (this._container) this._container.classList.add("maplibregl-popup-track-pointer"); 29731 this._map._canvasContainer.classList.add("maplibregl-track-pointer"); 29732 } 29733 return this; 29734 } 29735 /** 29736 * Returns the `Popup`'s HTML element. 29737 * @example 29738 * Change the `Popup` element's font size 29739 * ```ts 29740 * let popup = new Popup() 29741 * .setLngLat([-96, 37.8]) 29742 * .setHTML("<p>Hello World!</p>") 29743 * .addTo(map); 29744 * let popupElem = popup.getElement(); 29745 * popupElem.style.fontSize = "25px"; 29746 * ``` 29747 * @returns element 29748 */ 29749 getElement() { 29750 return this._container; 29751 } 29752 /** 29753 * Sets the popup's content to a string of text. 29754 * 29755 * This function creates a [Text](https://developer.mozilla.org/en-US/docs/Web/API/Text) node in the DOM, 29756 * so it cannot insert raw HTML. Use this method for security against XSS 29757 * if the popup content is user-provided. 29758 * 29759 * @param text - Textual content for the popup. 29760 * @example 29761 * ```ts 29762 * let popup = new Popup() 29763 * .setLngLat(e.lngLat) 29764 * .setText('Hello, world!') 29765 * .addTo(map); 29766 * ``` 29767 */ 29768 setText(text) { 29769 return this.setDOMContent(document.createTextNode(text)); 29770 } 29771 /** 29772 * Sets the popup's content to the HTML provided as a string. 29773 * 29774 * This method does not perform HTML filtering or sanitization, and must be 29775 * used only with trusted content. Consider {@link Popup.setText} if 29776 * the content is an untrusted text string. 29777 * 29778 * @param html - A string representing HTML content for the popup. 29779 * @example 29780 * ```ts 29781 * let popup = new Popup() 29782 * .setLngLat(e.lngLat) 29783 * .setHTML("<h1>Hello World!</h1>") 29784 * .addTo(map); 29785 * ``` 29786 * @see [Display a popup](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup/) 29787 * @see [Display a popup on hover](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup-on-hover/) 29788 * @see [Display a popup on click](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup-on-click/) 29789 * @see [Attach a popup to a marker instance](https://maplibre.org/maplibre-gl-js/docs/examples/attach-a-popup-to-a-marker-instance/) 29790 */ 29791 setHTML(html) { 29792 const frag = document.createDocumentFragment(); 29793 const temp = document.createElement("body"); 29794 let child; 29795 temp.innerHTML = html; 29796 while (true) { 29797 child = temp.firstChild; 29798 if (!child) break; 29799 frag.appendChild(child); 29800 } 29801 return this.setDOMContent(frag); 29802 } 29803 /** 29804 * Returns the popup's maximum width. 29805 * 29806 * @returns The maximum width of the popup. 29807 */ 29808 getMaxWidth() { 29809 return this._container?.style.maxWidth; 29810 } 29811 /** 29812 * Sets the popup's maximum width. This is setting the CSS property `max-width`. 29813 * Available values can be found here: https://developer.mozilla.org/en-US/docs/Web/CSS/max-width 29814 * 29815 * @param maxWidth - A string representing the value for the maximum width. 29816 */ 29817 setMaxWidth(maxWidth) { 29818 this.options.maxWidth = maxWidth; 29819 this._update(); 29820 return this; 29821 } 29822 /** 29823 * Sets the popup's content to the element provided as a DOM node. 29824 * 29825 * @param htmlNode - A DOM node to be used as content for the popup. 29826 * @example 29827 * Create an element with the popup content 29828 * ```ts 29829 * let div = document.createElement('div'); 29830 * div.innerHTML = 'Hello, world!'; 29831 * let popup = new Popup() 29832 * .setLngLat(e.lngLat) 29833 * .setDOMContent(div) 29834 * .addTo(map); 29835 * ``` 29836 */ 29837 setDOMContent(htmlNode) { 29838 if (this._content) { 29839 while (this._content.hasChildNodes()) if (this._content.firstChild) this._content.removeChild(this._content.firstChild); 29840 } else this._content = DOM.create("div", "maplibregl-popup-content", this._container); 29841 this._content.appendChild(htmlNode); 29842 this._createCloseButton(); 29843 this._update(); 29844 this._focusFirstElement(); 29845 return this; 29846 } 29847 /** 29848 * Adds a CSS class to the popup container element. 29849 * 29850 * @param className - Non-empty string with CSS class name to add to popup container 29851 * 29852 * @example 29853 * ```ts 29854 * let popup = new Popup() 29855 * popup.addClassName('some-class') 29856 * ``` 29857 */ 29858 addClassName(className) { 29859 if (this._container) this._container.classList.add(className); 29860 return this; 29861 } 29862 /** 29863 * Removes a CSS class from the popup container element. 29864 * 29865 * @param className - Non-empty string with CSS class name to remove from popup container 29866 * 29867 * @example 29868 * ```ts 29869 * let popup = new Popup() 29870 * popup.removeClassName('some-class') 29871 * ``` 29872 */ 29873 removeClassName(className) { 29874 if (this._container) this._container.classList.remove(className); 29875 return this; 29876 } 29877 /** 29878 * Sets the popup's offset. 29879 * 29880 * @param offset - Sets the popup's offset. 29881 */ 29882 setOffset(offset) { 29883 this.options.offset = offset; 29884 this._update(); 29885 return this; 29886 } 29887 /** 29888 * Add or remove the given CSS class on the popup container, depending on whether the container currently has that class. 29889 * 29890 * @param className - Non-empty string with CSS class name to add/remove 29891 * 29892 * @returns if the class was removed return false, if class was added, then return true, undefined if there is no container 29893 * 29894 * @example 29895 * ```ts 29896 * let popup = new Popup() 29897 * popup.toggleClassName('toggleClass') 29898 * ``` 29899 */ 29900 toggleClassName(className) { 29901 if (this._container) return this._container.classList.toggle(className); 29902 } 29903 /** 29904 * Set the option to allow subpixel positioning of the popup by passing a boolean 29905 * 29906 * @param value - When boolean is true, subpixel positioning is enabled for the popup. 29907 * 29908 * @example 29909 * ```ts 29910 * let popup = new Popup() 29911 * popup.setSubpixelPositioning(true); 29912 * ``` 29913 */ 29914 setSubpixelPositioning(value) { 29915 this.options.subpixelPositioning = value; 29916 } 29917 /** 29918 * Sets the popup's padding constraints for positioning. 29919 * 29920 * @param padding - The padding to apply as a {@link PaddingOptions} object. 29921 * @example 29922 * ```ts 29923 * popup.setPadding({ top: 10, right: 20, bottom: 30, left: 40 }); 29924 * ``` 29925 */ 29926 setPadding(padding) { 29927 this.options.padding = padding; 29928 this._update(); 29929 } 29930 _createCloseButton() { 29931 if (this.options.closeButton) { 29932 this._closeButton = DOM.create("button", "maplibregl-popup-close-button", this._content); 29933 this._closeButton.type = "button"; 29934 this._closeButton.innerHTML = "×"; 29935 this._closeButton.addEventListener("click", this._onClose); 29936 } 29937 } 29938 _focusFirstElement() { 29939 if (!this.options.focusAfterOpen || !this._container) return; 29940 const firstFocusable = this._container.querySelector(focusQuerySelector); 29941 if (firstFocusable) firstFocusable.focus(); 29942 } 29943}; 29944function normalizeOffset(offset) { 29945 if (!offset) return normalizeOffset(new Point(0, 0)); 29946 else if (typeof offset === "number") { 29947 const cornerOffset = Math.round(Math.abs(offset) / Math.SQRT2); 29948 return { 29949 "center": new Point(0, 0), 29950 "top": new Point(0, offset), 29951 "top-left": new Point(cornerOffset, cornerOffset), 29952 "top-right": new Point(-cornerOffset, cornerOffset), 29953 "bottom": new Point(0, -offset), 29954 "bottom-left": new Point(cornerOffset, -cornerOffset), 29955 "bottom-right": new Point(-cornerOffset, -cornerOffset), 29956 "left": new Point(offset, 0), 29957 "right": new Point(-offset, 0) 29958 }; 29959 } else if (offset instanceof Point || Array.isArray(offset)) { 29960 const convertedOffset = Point.convert(offset); 29961 return { 29962 "center": convertedOffset, 29963 "top": convertedOffset, 29964 "top-left": convertedOffset, 29965 "top-right": convertedOffset, 29966 "bottom": convertedOffset, 29967 "bottom-left": convertedOffset, 29968 "bottom-right": convertedOffset, 29969 "left": convertedOffset, 29970 "right": convertedOffset 29971 }; 29972 } else return { 29973 "center": Point.convert(offset["center"] || [0, 0]), 29974 "top": Point.convert(offset["top"] || [0, 0]), 29975 "top-left": Point.convert(offset["top-left"] || [0, 0]), 29976 "top-right": Point.convert(offset["top-right"] || [0, 0]), 29977 "bottom": Point.convert(offset["bottom"] || [0, 0]), 29978 "bottom-left": Point.convert(offset["bottom-left"] || [0, 0]), 29979 "bottom-right": Point.convert(offset["bottom-right"] || [0, 0]), 29980 "left": Point.convert(offset["left"] || [0, 0]), 29981 "right": Point.convert(offset["right"] || [0, 0]) 29982 }; 29983} 29984function normalizePadding(padding) { 29985 if (!padding) return { 29986 top: 0, 29987 right: 0, 29988 bottom: 0, 29989 left: 0 29990 }; 29991 return { 29992 top: padding.top ?? 0, 29993 right: padding.right ?? 0, 29994 bottom: padding.bottom ?? 0, 29995 left: padding.left ?? 0 29996 }; 29997} 29998//#endregion 29999//#region src/index.ts 30000const version = version$2; 30001/** 30002* Sets the map's [RTL text plugin](https://www.mapbox.com/mapbox-gl-js/plugins/#mapbox-gl-rtl-text). 30003* 30004* @deprecated MapLibre shapes Arabic and reorders bidirectional text itself, so nothing has to be 30005* loaded for right-to-left languages to be drawn correctly. A plugin set here still replaces the 30006* built-in implementation, but this will be removed in a future release. 30007* @param pluginURL - URL pointing to the Mapbox RTL text plugin source. 30008* @param lazy - If set to `true`, maplibre will defer loading the plugin until rtl text is encountered, 30009* rtl text will then be rendered only after the plugin finishes loading. 30010* @example 30011* ```ts 30012* setRTLTextPlugin('https://unpkg.com/@mapbox/[email protected]/dist/mapbox-gl-rtl-text.js', false); 30013* ``` 30014* @see [Add support for right-to-left scripts](https://maplibre.org/maplibre-gl-js/docs/examples/add-support-for-right-to-left-scripts/) 30015* @see [Display and style rich text labels](https://maplibre.org/maplibre-gl-js/docs/examples/display-and-style-rich-text-labels/) 30016*/ 30017function setRTLTextPlugin(pluginURL, lazy) { 30018 return rtlMainThreadPluginFactory().setRTLTextPlugin(pluginURL, lazy); 30019} 30020/** 30021* Gets the map's [RTL text plugin](https://www.mapbox.com/mapbox-gl-js/plugins/#mapbox-gl-rtl-text) status. 30022* The status can be `unavailable` (i.e. not requested or removed), `loading`, `loaded` or `error`. 30023* If the status is `loaded` and the plugin is requested again, an error will be thrown. 30024* 30025* @deprecated The status says nothing about whether right-to-left text can be drawn, which it always 30026* can be. It reports only on a plugin set through the deprecated {@link setRTLTextPlugin}. 30027* @example 30028* ```ts 30029* const pluginStatus = getRTLTextPluginStatus(); 30030* ``` 30031*/ 30032function getRTLTextPluginStatus() { 30033 return rtlMainThreadPluginFactory().getRTLTextPluginStatus(); 30034} 30035/** 30036* Returns the package version of the library 30037* @returns Package version of the library 30038*/ 30039function getVersion() { 30040 return version; 30041} 30042/** 30043* Gets the number of web workers instantiated on a page with GL JS maps. 30044* By default, workerCount is 1 except for Safari browser where it is set to half the number of CPU cores (capped at 3). 30045* Make sure to set this property before creating any map instances for it to have effect. 30046* 30047* @returns Number of workers currently configured. 30048* @example 30049* ```ts 30050* const workerCount = getWorkerCount() 30051* ``` 30052*/ 30053function getWorkerCount() { 30054 return WorkerPool.workerCount; 30055} 30056/** 30057* Sets the number of web workers instantiated on a page with GL JS maps. 30058* By default, workerCount is 1 except for Safari browser where it is set to half the number of CPU cores (capped at 3). 30059* Make sure to set this property before creating any map instances for it to have effect. 30060* 30061* @example 30062* ```ts 30063* setWorkerCount(2); 30064* ``` 30065*/ 30066function setWorkerCount(count) { 30067 WorkerPool.workerCount = count; 30068} 30069/** 30070* Gets and sets the maximum number of images (raster tiles, sprites, icons) to load in parallel, 30071* which affects performance in raster-heavy maps. 16 by default. 30072* 30073* @returns Number of parallel requests currently configured. 30074* @example 30075* ```ts 30076* getMaxParallelImageRequests(); 30077* ``` 30078*/ 30079function getMaxParallelImageRequests() { 30080 return config.MAX_PARALLEL_IMAGE_REQUESTS; 30081} 30082/** 30083* Sets the maximum number of images (raster tiles, sprites, icons) to load in parallel, 30084* which affects performance in raster-heavy maps. 16 by default. 30085* 30086* @example 30087* ```ts 30088* setMaxParallelImageRequests(10); 30089* ``` 30090*/ 30091function setMaxParallelImageRequests(numRequests) { 30092 config.MAX_PARALLEL_IMAGE_REQUESTS = numRequests; 30093} 30094/** 30095* Gets the worker url 30096* @returns The worker url 30097*/ 30098function getWorkerUrl() { 30099 return config.WORKER_URL; 30100} 30101/** 30102* Sets the worker url 30103*/ 30104function setWorkerUrl(value) { 30105 config.WORKER_URL = value; 30106} 30107/** 30108* Allows loading javascript code in the worker thread. 30109* *Note* that since this is using some very internal classes and flows it is considered experimental and can break at any point. 30110* 30111* It can be useful for the following examples: 30112* 1. Using `self.addProtocol` in the worker thread - note that you might need to also register the protocol on the main thread. 30113* 2. Using `self.registerWorkerSource(workerSource: WorkerSource)` to register a worker source, which should come with `addSourceType` usually. 30114* 3. using `self.actor.registerMessageHandler` to override some internal worker operations 30115* @param workerUrl - the worker url e.g. a url of a javascript file to load in the worker 30116* @returns 30117* 30118* @example 30119* ```ts 30120* // below is an example of sending a js file to the worker to load the method there 30121* // Note that you'll need to call the global function `addProtocol` in the worker to register the protocol there. 30122* // add-protocol-worker.js 30123* async function loadFn(params, abortController) { 30124* const t = await fetch(`https://${params.url.split("://")[1]}`); 30125* if (t.status == 200) { 30126* const buffer = await t.arrayBuffer(); 30127* return {data: buffer} 30128* } else { 30129* throw new Error(`Tile fetch error: ${t.statusText}`); 30130* } 30131* } 30132* self.addProtocol('custom', loadFn); 30133* 30134* // main.js 30135* importScriptInWorkers('add-protocol-worker.js'); 30136* ``` 30137*/ 30138async function importScriptInWorkers(workerUrl) { 30139 await getGlobalDispatcher().broadcast("IS", workerUrl); 30140} 30141//#endregion 30142export { AJAXError, AttributionControl, BoxZoomHandler, CanvasSource, CooperativeGesturesHandler, DoubleClickZoomHandler, DragPanHandler, DragRotateHandler, EXTENT, EdgeInsets, ErrorEvent, Event, Evented, FullscreenControl, FullscreenEvent, GPUInitializationError, GeoJSONSource, GeolocateControl, GeolocateErrorEvent, GeolocateEvent, GeolocatePositionEvent, GlobeControl, Hash, ImageSource, KeyboardHandler, LngLat, LngLatBounds, LogoControl, Map$1 as Map, Map$1 as MapLibreMap, MapBoxZoomEvent, MapContextEvent, MapLibreEvent, MapMouseEvent, MapMovementEvent, MapProjectionEvent, MapSourceDataEvent, MapStyleDataEvent, MapStyleImageMissingEvent, MapStyleLoadEvent, MapTerrainEvent, MapTouchEvent, MapWheelEvent, Marker, MarkerClickEvent, MarkerDragEvent, MercatorCoordinate, NavigationControl, Point, Popup, PopupEvent, RasterDEMTileSource, RasterTileSource, ScaleControl, ScrollZoomHandler, Style, TerrainControl, TwoFingersTouchPitchHandler, TwoFingersTouchRotateHandler, TwoFingersTouchZoomHandler, TwoFingersTouchZoomRotateHandler, VectorTileSource, VideoSource, addProtocol, addSourceType, clearPrewarmedResources, config, createTileMesh, getGlobalDispatcher, getMaxParallelImageRequests, getRTLTextPluginStatus, getVersion, getWorkerCount, getWorkerUrl, importScriptInWorkers, isTimeFrozen, now, prewarm, removeProtocol, restoreNow, setMaxParallelImageRequests, setNow, setRTLTextPlugin, setWorkerCount, setWorkerUrl };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.