maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
1,427 lines • 1.15 MB
JavaScript
/**
* MapLibre GL JS
* @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v6.2.0/LICENSE.txt
*/
import { $ as tileCoordinatesToMercatorCoordinates, $n as degreesToRadians, $r as clone, $t as EvaluationParameters, A as getOverlapMode, Ai as scale, An as sameOrigin, Ar as rollPitchBearingToQuat, At as Uniform1i, Bn as angleToRotateBetweenVectors2D, Br as pixelsToTileUnits, Bt as CollisionCircleLayoutArray, C as createStyleLayer, Ci as invert$1, Cn as AJAXError, Cr as pick, Ct as renderColorRamp, D as isSymbolStyleLayer, Di as rotateX, Dn as getReferrer, Dr as readImageUsingVideoFrame, Dt as polygonIntersectsPolygon, E as isBackgroundStyleLayer, Ei as perspective, En as getJSON, Er as rayPlaneIntersection, Et as isCircleStyleLayer, Fi as invert, Fn as AbortError, Fr as translatePosition, Ft as UniformColorArray, G as isFillExtrusionStyleLayer, Gn as clone$2, Gr as sqrLen, Gt as RasterBoundsArray, H as isLineStyleLayer, Hn as arrayBufferToImageBitmap, Hr as create$3, Ht as Pos3dArray, I as ImagePosition, Ii as rotate, In as isAbortError, Ir as uniqueId, It as UniformFloatArray, J as cameraDirectionFromPitchBearing, Jn as createMat4f64, Jr as slerp, Jt as isRasterStyleLayer, Kn as createIdentityMat4f32, Kr as zero, Kt as TriangleIndexArray, L as potpack, Li as isOffscreenCanvasDistorted, Ln as throwIfAborted, Lr as warnOnce, Lt as UniformMatrix4f, M as evaluateSizeForZoom, Mi as create$4, Mn as getProtocol, Mr as sphericalToCartesian, Mt as Uniform3f, Ni as fromRotation, Nn as removeProtocol, Nr as subscribe, Nt as Uniform4f, O as SymbolBucket, Oi as rotateY$1, On as getVideo, Or as remapSaturate, Ot as toEvaluationFeature, P as getAnchorAlignment, Pi as create$2, Pn as config, Pr as threePlaneIntersection, Pt as UniformColor, Q as projectToWorldCoordinates, Qn as defaultEasing, Qr as add, Qt as Transitionable, R as renderStyleImage, Ri as offscreenCanvasSupported, Rr as wrap, Rt as SegmentVector, S as Actor, Si as identity, Sn as Evented, Sr as parseCacheControl, St as isHeatmapStyleLayer, T as validateCustomStyleLayer, Ti as ortho, Tn as getArrayBuffer, Tr as radiansToDegrees, Tt as RGBAImage, Un as bezier, Ur as length, Ut as PosArray, V as collisionCircleLayout, Vn as arrayBufferToImage, Vr as EXTENT, Vt as LineStripIndexArray, Wn as clamp, Wr as scale$1, Wt as QuadTriangleArray, X as getMercatorHorizon, Xn as createVec4f64, Xr as scale$3, Xt as Properties, Y as cameraMercatorCoordinateFromCenterAndRotation, Yn as createVec3f64, Yr as mul, Yt as DataConstantProperty, Z as maxMercatorHorizonAngle, Zn as deepEqual, Zr as transformMat4, Zt as TRANSITION_SUFFIX, _ as OverscaledTileID, _i as copy, _r as isWorker, _t as isColorReliefStyleLayer, a as clipLine, ai as normalize, an as emitValidationErrors, ar as filterObject, at as mercatorXfromLng, b as compareTileId, bi as exactEquals, bn as ErrorEvent, br as mod, bt as isHillshadeStyleLayer, ci as rotateZ$1, cn as validateStyleAndEmit, cr as getAngleDelta, ct as LngLat, di as sub, dn as ValidationError, dr as getRollPitchBearing, dt as isFillStyleLayer, ei as cross, er as differenceOfAnglesDegrees, et as unprojectFromWorldCoordinates, f as GEOJSON_TILE_LAYER_NAME, fi as transformMat3, fr as isImageBitmap, g as CanonicalTileID, gi as clone$1, gn as featureFilter, gr as isTouchableOrPointableType, gt as SubdivisionGranularitySetting, h as Bounds, hi as zero$1, hn as emptyStyle, hr as isTouchableEvent, ht as SubdivisionGranularityExpression, ii as negate, in as SPEC_SOURCE_TYPES, ir as extend, it as lngFromMercatorX, j as evaluateSizeForFeature, ji as translate, jn as addProtocol, jr as scaleZoom, jt as Uniform2f, k as addDynamicAttributes, ki as rotateZ, kn as makeRequest, kr as rollPitchBearingEqual, kt as Uniform1f, li as scale$2, ln as Color, lr as getEdgeTiles, lt as earthRadius, mi as transformQuat, mn as diff, mr as isSafari, mt as SOUTH_POLE_Y, ni as len, nn as ZoomHistory, nr as ensureError, nt as altitudeFromMercatorZ, oi as rotateX$1, on as validateAndEmit, or as findLineIntersection, ot as mercatorYfromLat, pi as transformMat4$1, pn as derefLayers, pr as isPointableEvent, pt as NORTH_POLE_Y, q as calculateTileMatrix, qn as createIdentityMat4f64, qr as fromEuler, qt as createLayout, r as TextAnchorEnum, ri as length$1, rr as evaluateZoomSnap, rt as latFromMercatorY, s as TileCache, si as rotateY, sn as validateStyle, sr as getAABB, st as mercatorZfromAltitude, t as getAnchorJustification, ti as dot, tn as codePointUsesLocalIdeographFontFamily, tr as distanceOfAnglesRadians, tt as MercatorCoordinate, u as GeoJSONFeature, ui as scaleAndAdd, un as ProjectionDefinition, v as UnwrappedTileID, vi as create$1, vn as interpolateFactory, vr as lerp, w as isCustomStyleLayer, wi as multiply, wn as GLOBAL_DISPATCHER_ID, wr as pointPlaneSignedDistance, wt as AlphaImage, x as isInBoundsForZoomLngLat, xi as fromScaling, xn as Event, xr as nextPowerOfTwo, xt as HEATMAP_FULL_RENDER_FBO_KEY, y as calculateTileKey, yi as equals, yn as latest, yr as mapObject, yt as Texture, z as parseGlyphPbf, zi as Point, zn as MAX_VALID_LATITUDE, zr as zoomScale, zt as CollisionBoxArray } from "./maplibre-gl-shared-dev.mjs";
//#region package.json
var version$2 = "6.2.0";
//#endregion
//#region src/util/browser.ts
let linkEl;
let reducedMotionQuery;
let reducedMotionOverride;
/** */
const browser = {
/**
* Schedules a callback to be invoked on the next animation frame.
* @param abortController - Controller to abort the scheduled frame.
* @param fn - Callback to invoke with the paint start timestamp.
* @param reject - Callback to invoke if the frame is aborted.
* @param targetWindow - Optional window to use for requestAnimationFrame.
* When the map is rendered in a popup window or iframe, pass the owning
* window to ensure animation frames continue even when the main window
* is not focused.
*/
frame(abortController, fn, reject, targetWindow) {
const win = targetWindow || window;
const frameId = win.requestAnimationFrame((paintStartTimestamp) => {
unsubscribe();
fn(paintStartTimestamp);
});
const { unsubscribe } = subscribe(abortController.signal, "abort", () => {
unsubscribe();
win.cancelAnimationFrame(frameId);
reject(new AbortError(abortController.signal.reason));
}, false);
},
/**
* Returns a promise that resolves on the next animation frame.
* @param abortController - Controller to abort the scheduled frame.
* @param targetWindow - Optional window to use for requestAnimationFrame.
* @see {@link browser.frame}
*/
frameAsync(abortController, targetWindow) {
return new Promise((resolve, reject) => {
this.frame(abortController, resolve, reject, targetWindow);
});
},
getImageData(img, padding = 0) {
return this.getImageCanvasContext(img).getImageData(-padding, -padding, img.width + 2 * padding, img.height + 2 * padding);
},
getImageCanvasContext(img) {
const canvas = window.document.createElement("canvas");
const context = canvas.getContext("2d", { willReadFrequently: true });
if (!context) throw new Error("failed to create canvas 2d context");
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0, img.width, img.height);
return context;
},
resolveURL(path) {
linkEl ||= document.createElement("a");
linkEl.href = path;
return linkEl.href;
},
get hardwareConcurrency() {
return typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4;
},
get prefersReducedMotion() {
if (reducedMotionOverride !== void 0) return reducedMotionOverride;
if (!matchMedia) return false;
reducedMotionQuery ??= matchMedia("(prefers-reduced-motion: reduce)");
return reducedMotionQuery.matches;
},
set prefersReducedMotion(value) {
reducedMotionOverride = value;
}
};
//#endregion
//#region src/util/time_control.ts
/**
* Manages time flow with optional freezing capability for deterministic rendering.
*/
var TimeManager = class {
constructor() {
this._frozenAt = null;
}
/**
* Gets the current time, either real or frozen.
* @returns Current time in milliseconds
*/
getCurrentTime() {
return this._frozenAt !== null ? this._frozenAt : performance.now();
}
/**
* Sets time at a specific timestamp.
* @param timestamp - Time in milliseconds to set
*/
setNow(timestamp) {
this._frozenAt = timestamp;
}
/**
* Restores normal time flow.
*/
restoreNow() {
this._frozenAt = null;
}
/**
* Returns whether time is currently frozen.
* @returns True if time is frozen, false otherwise
*/
isFrozen() {
return this._frozenAt !== null;
}
};
const timeManager = new TimeManager();
/**
* Returns the current time in milliseconds.
* When time is frozen via setNow(), returns the frozen timestamp.
* Otherwise returns real browser time via performance.now().
*
* @returns Current time in milliseconds
* @example
* ```ts
* // Measure elapsed time
* const start = maplibregl.now();
* // ... later ...
* const elapsed = maplibregl.now() - start;
*
* // During frozen time
* maplibregl.setNow(16.67);
* console.log(maplibregl.now()); // 16.67
* maplibregl.restoreNow();
* console.log(maplibregl.now()); // real time
* ```
*/
function now() {
return timeManager.getCurrentTime();
}
/**
* Freezes time at a specific timestamp for deterministic rendering.
* Useful for frame-by-frame video capture where each frame needs
* a consistent time value.
*
* @param timestamp - Time in milliseconds to freeze at
* @example
* ```ts
* // Freeze time for video export at 60fps
* setNow(0); // First frame
* // ... render frame ...
* setNow(16.67); // Second frame
* // ... render frame ...
* setNow(33.34); // Third frame
* // ... done ...
* restoreNow(); // Resume normal time
* ```
*/
function setNow(timestamp) {
timeManager.setNow(timestamp);
}
/**
* Restores normal time flow after freezing with setNow().
* Call this after finishing deterministic rendering operations.
*
* @example
* ```ts
* // After video export, resume normal time
* setNow(0);
* // ... export frames ...
* restoreNow(); // Map animations resume normally
* ```
*/
function restoreNow() {
timeManager.restoreNow();
}
/**
* Returns whether time is currently frozen.
* @returns True if time is frozen via setNow(), false otherwise
* @example
* ```ts
* setNow(1000);
* console.log(isTimeFrozen()); // true
* restoreNow();
* console.log(isTimeFrozen()); // false
* ```
*/
function isTimeFrozen() {
return timeManager.isFrozen();
}
//#endregion
//#region src/util/dom.ts
var DOM = class DOM {
static {
this.docStyle = typeof window !== "undefined" && window.document?.documentElement.style;
}
static {
this.selectProp = !DOM.docStyle || "userSelect" in DOM.docStyle ? "userSelect" : "webkitUserSelect";
}
static create(tagName, className, container) {
const el = window.document.createElement(tagName);
if (className !== void 0) el.className = className;
if (container) container.appendChild(el);
return el;
}
static createNS(namespaceURI, tagName) {
return window.document.createElementNS(namespaceURI, tagName);
}
static disableDrag() {
if (DOM.docStyle && DOM.selectProp) {
DOM.userSelect = DOM.docStyle[DOM.selectProp];
DOM.docStyle[DOM.selectProp] = "none";
}
}
static enableDrag() {
if (DOM.docStyle && DOM.selectProp) DOM.docStyle[DOM.selectProp] = DOM.userSelect;
}
static suppressClickInternal(e) {
e.preventDefault();
e.stopPropagation();
window.removeEventListener("click", DOM.suppressClickInternal, true);
}
static suppressClick() {
window.addEventListener("click", DOM.suppressClickInternal, true);
window.setTimeout(() => {
window.removeEventListener("click", DOM.suppressClickInternal, true);
}, 0);
}
static getScale(element) {
const rect = element.getBoundingClientRect();
return {
x: rect.width / element.offsetWidth || 1,
y: rect.height / element.offsetHeight || 1,
boundingClientRect: rect
};
}
static getPoint(el, scale, e) {
const rect = scale.boundingClientRect;
return new Point((e.clientX - rect.left) / scale.x - el.clientLeft, (e.clientY - rect.top) / scale.y - el.clientTop);
}
static mousePos(el, e) {
const scale = DOM.getScale(el);
return DOM.getPoint(el, scale, e);
}
static touchPos(el, touches) {
const points = [];
const scale = DOM.getScale(el);
for (const touch of touches) points.push(DOM.getPoint(el, scale, touch));
return points;
}
/**
* Sanitize an HTML string - this might not be enough to prevent all XSS attacks
* Base on https://javascriptsource.com/sanitize-an-html-string-to-reduce-the-risk-of-xss-attacks/
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
*/
static sanitize(str) {
const html = new DOMParser().parseFromString(str, "text/html").body || document.createElement("body");
const scripts = html.querySelectorAll("script");
for (const script of scripts) script.remove();
DOM.clean(html);
return html.innerHTML;
}
/**
* Check if the attribute is potentially dangerous
*/
static isPossiblyDangerous(name, value) {
const val = value.replace(/\s+/g, "").toLowerCase();
if ([
"src",
"href",
"xlink:href"
].includes(name)) {
if (val.includes("javascript:") || val.includes("data:")) return true;
}
if (name.startsWith("on")) return true;
}
/**
* Remove dangerous stuff from the HTML document's nodes
* @param html - The HTML document
*/
static clean(html) {
const nodes = html.children;
for (const node of nodes) {
DOM.removeAttributes(node);
DOM.clean(node);
}
}
/**
* Remove potentially dangerous attributes from an element
* @param elem - The element
*/
static removeAttributes(elem) {
for (const { name, value } of elem.attributes) {
if (!DOM.isPossiblyDangerous(name, value)) continue;
elem.removeAttribute(name);
}
}
};
//#endregion
//#region src/util/image_request.ts
let ImageRequest;
(function(_ImageRequest) {
let imageRequestQueue;
let currentParallelImageRequests;
let throttleControlCallbackHandleCounter;
let throttleControlCallbacks;
_ImageRequest.resetRequestQueue = () => {
imageRequestQueue = [];
currentParallelImageRequests = 0;
throttleControlCallbackHandleCounter = 0;
throttleControlCallbacks = {};
};
_ImageRequest.addThrottleControl = (callback) => {
const handle = throttleControlCallbackHandleCounter++;
throttleControlCallbacks[handle] = callback;
return handle;
};
_ImageRequest.removeThrottleControl = (callbackHandle) => {
delete throttleControlCallbacks[callbackHandle];
processQueue();
};
/**
* Check to see if any of the installed callbacks are requesting the queue
* to be throttled.
* @returns `true` if any callback is causing the queue to be throttled.
*/
const isThrottled = () => {
for (const key of Object.keys(throttleControlCallbacks)) if (throttleControlCallbacks[key]()) return true;
return false;
};
_ImageRequest.getImage = (requestParameters, abortController, supportImageRefresh = true, imageBitmapOptions) => {
return new Promise((resolve, reject) => {
requestParameters.headers ||= {};
requestParameters.headers.accept = "image/webp,*/*";
extend(requestParameters, { type: "image" });
const request = {
abortController,
requestParameters,
supportImageRefresh,
imageBitmapOptions,
state: "queued",
onError: (error) => {
reject(error);
},
onSuccess: (response) => {
resolve(response);
}
};
imageRequestQueue.push(request);
processQueue();
});
};
const arrayBufferToCanvasImageSource = (data, imageBitmapOptions) => {
if (typeof createImageBitmap === "function") return arrayBufferToImageBitmap(data, imageBitmapOptions);
else return arrayBufferToImage(data);
};
const doImageRequest = async (itemInQueue) => {
itemInQueue.state = "running";
const { requestParameters, supportImageRefresh, imageBitmapOptions, onError, onSuccess, abortController } = itemInQueue;
const canUseHTMLImageElement = supportImageRefresh === false && !imageBitmapOptions && !isWorker(self) && !getProtocol(requestParameters.url) && (!requestParameters.headers || Object.keys(requestParameters.headers).reduce((acc, item) => acc && item === "accept", true));
currentParallelImageRequests++;
const getImagePromise = canUseHTMLImageElement ? getImageUsingHtmlImage(requestParameters, abortController) : makeRequest(requestParameters, abortController);
try {
const response = await getImagePromise;
delete itemInQueue.abortController;
itemInQueue.state = "completed";
if (response.data instanceof HTMLImageElement || isImageBitmap(response.data)) onSuccess(response);
else if (response.data) onSuccess({
data: await arrayBufferToCanvasImageSource(response.data, imageBitmapOptions),
cacheControl: response.cacheControl,
expires: response.expires
});
} catch (err) {
delete itemInQueue.abortController;
onError(ensureError(err));
} finally {
currentParallelImageRequests--;
processQueue();
}
};
/**
* Process some number of items in the image request queue.
*/
const processQueue = () => {
const maxImageRequests = isThrottled() ? config.MAX_PARALLEL_IMAGE_REQUESTS_PER_FRAME : config.MAX_PARALLEL_IMAGE_REQUESTS;
for (let numImageRequests = currentParallelImageRequests; numImageRequests < maxImageRequests && imageRequestQueue.length > 0; numImageRequests++) {
const topItemInQueue = imageRequestQueue.shift();
if (topItemInQueue.abortController.signal.aborted) {
numImageRequests--;
continue;
}
doImageRequest(topItemInQueue);
}
};
const getImageUsingHtmlImage = (requestParameters, abortController) => {
return new Promise((resolve, reject) => {
const image = new Image();
const url = requestParameters.url;
const credentials = requestParameters.credentials;
if (credentials && credentials === "include") image.crossOrigin = "use-credentials";
else if (credentials && credentials === "same-origin" || !sameOrigin(url)) image.crossOrigin = "anonymous";
abortController.signal.addEventListener("abort", () => {
image.src = "";
reject(new AbortError(abortController.signal.reason));
});
image.fetchPriority = "high";
image.onload = () => {
image.onerror = image.onload = null;
resolve({ data: image });
};
image.onerror = () => {
image.onerror = image.onload = null;
if (abortController.signal.aborted) return;
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."));
};
image.src = url;
});
};
})(ImageRequest || (ImageRequest = {}));
ImageRequest.resetRequestQueue();
var RequestManager = class {
constructor(transformRequestFn) {
this._transformRequestFn = transformRequestFn ?? null;
}
transformRequest(url, type) {
if (this._transformRequestFn) return this._transformRequestFn(url, type) || { url };
return { url };
}
setTransformRequest(transformRequest) {
this._transformRequestFn = transformRequest;
}
};
//#endregion
//#region src/ui/events.ts
/**
* The base event for MapLibre
*
* @group Event Related
*/
var MapLibreEvent = class extends Event {};
/**
* `MapMovementEvent` is the event type for the camera-transition map events:
* `movestart`, `move`, `moveend`, `zoomstart`, `zoom`, `zoomend`, `rotatestart`, `rotate`, `rotateend`,
* `dragstart`, `drag`, `dragend`, `pitchstart`, `pitch`, `pitchend`, `rollstart`, `roll` and `rollend`.
* These are fired as the map's view changes, as a result of either user interaction or methods such
* as {@link Map.jumpTo} / {@link Map.flyTo}.
*
* @group Event Related
*/
var MapMovementEvent = class extends MapLibreEvent {};
/**
* The `style.load` event, fired once the map's style has fully loaded or changed.
*
* @group Event Related
*/
var MapStyleLoadEvent = class extends MapLibreEvent {
constructor(data = {}) {
super("style.load", data);
}
};
/**
* The style data event
*
* @group Event Related
*/
var MapStyleDataEvent = class extends MapLibreEvent {
constructor(type, data = {}) {
super(type, data);
this.dataType = "style";
}
};
/**
* A `MapSourceDataEvent` is emitted with the source-related `data`, `dataloading`, `dataabort`,
* `sourcedata`, `sourcedataloading` and `sourcedataabort` events. Its `dataType` is always `'source'`.
*
* Possible values for `sourceDataType`s are:
*
* - `'metadata'`: indicates that any necessary source metadata has been loaded (such as TileJSON) and it is ok to start loading tiles
* - `'content'`: indicates the source data has changed (such as when source.setData() has been called on GeoJSONSource)
* - `'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')
* - `'idle'`: indicates that no new source data has been fetched (but the source has done loading)
*
* @group Event Related
*
* @example
* ```ts
* // The sourcedata event is an example of a MapSourceDataEvent.
* // Set up an event listener on the map.
* map.on('sourcedata', (e) => {
* if (e.isSourceLoaded) {
* // Do something when the source has finished loading
* }
* });
* ```
*/
var MapSourceDataEvent = class extends MapLibreEvent {
constructor(type, data = {}) {
super(type, data);
this.dataType = "source";
}
};
/**
* `MapMouseEvent` is the event type for mouse-related map events.
*
* @group Event Related
*
* @example
* ```ts
* // The `click` event is an example of a `MapMouseEvent`.
* // Set up an event listener on the map.
* map.on('click', (e) => {
* // The event object (e) contains information like the
* // coordinates of the point on the map that was clicked.
* console.log('A click event has occurred at ' + e.lngLat);
* });
* ```
*/
var MapMouseEvent = class extends MapLibreEvent {
/**
* Prevents subsequent default processing of the event by the map.
*
* Calling this method will prevent the following default map behaviors:
*
* * On `mousedown` events, the behavior of {@link DragPanHandler}
* * On `mousedown` events, the behavior of {@link DragRotateHandler}
* * On `mousedown` events, the behavior of {@link BoxZoomHandler}
* * On `dblclick` events, the behavior of {@link DoubleClickZoomHandler}
*
*/
preventDefault() {
this._defaultPrevented = true;
}
/**
* `true` if `preventDefault` has been called.
*/
get defaultPrevented() {
return this._defaultPrevented;
}
constructor(type, map, originalEvent, data = {}) {
originalEvent = originalEvent instanceof MouseEvent ? originalEvent : new MouseEvent(type, originalEvent);
const point = DOM.mousePos(map.getCanvas(), originalEvent);
const lngLat = map.unproject(point);
super(type, extend({
point,
lngLat,
originalEvent
}, data));
this._defaultPrevented = false;
this.target = map;
}
};
/**
* `MapTouchEvent` is the event type for touch-related map events.
*
* @group Event Related
*/
var MapTouchEvent = class extends MapLibreEvent {
/**
* Prevents subsequent default processing of the event by the map.
*
* Calling this method will prevent the following default map behaviors:
*
* * On `touchstart` events, the behavior of {@link DragPanHandler}
* * On `touchstart` events, the behavior of {@link TwoFingersTouchZoomRotateHandler}
*
*/
preventDefault() {
this._defaultPrevented = true;
}
/**
* `true` if `preventDefault` has been called.
*/
get defaultPrevented() {
return this._defaultPrevented;
}
constructor(type, map, originalEvent) {
const touches = type === "touchend" ? originalEvent.changedTouches : originalEvent.touches;
const points = DOM.touchPos(map.getCanvasContainer(), touches);
const lngLats = points.map((t) => map.unproject(t));
const point = points.reduce((prev, curr, i, arr) => {
return prev.add(curr.div(arr.length));
}, new Point(0, 0));
const lngLat = map.unproject(point);
super(type, {
points,
point,
lngLats,
lngLat,
originalEvent
});
this._defaultPrevented = false;
}
};
/**
* `MapWheelEvent` is the event type for the `wheel` map event.
*
* @group Event Related
*/
var MapWheelEvent = class extends MapLibreEvent {
/**
* Prevents subsequent default processing of the event by the map.
*
* Calling this method will prevent the behavior of {@link ScrollZoomHandler}.
*/
preventDefault() {
this._defaultPrevented = true;
}
/**
* `true` if `preventDefault` has been called.
*/
get defaultPrevented() {
return this._defaultPrevented;
}
/** */
constructor(map, originalEvent) {
super("wheel", { originalEvent });
this._defaultPrevented = false;
}
};
/**
* A `MapBoxZoomEvent` is the event type for the boxzoom-related map events emitted by the {@link BoxZoomHandler}.
*
* @group Event Related
*/
var MapBoxZoomEvent = class extends MapLibreEvent {};
/**
* The terrain event
*
* @group Event Related
*/
var MapTerrainEvent = class extends MapLibreEvent {
constructor(data = {}) {
super("terrain", data);
}
};
/**
* The map projection event
*
* @group Event Related
*/
var MapProjectionEvent = class extends MapLibreEvent {
constructor(data = {}) {
super("projectiontransition", data);
}
};
/**
* An event related to the web gl context
*
* @group Event Related
*/
var MapContextEvent = class extends MapLibreEvent {};
/**
* The style image missing event, fired when an image is still missing after the missing style image
* resolver has been given a chance to supply it. To load or generate images on demand,
* use {@link Map.setMissingStyleImageResolver}.
* Event listeners cannot resolve the missing image for the current request.
*
* @group Event Related
*
* @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/)
*/
var MapStyleImageMissingEvent = class extends MapLibreEvent {
constructor(data = {}) {
super("styleimagemissing", data);
}
};
//#endregion
//#region src/util/style.ts
/**
* Takes a SpriteSpecification value and returns it in its array form. If `undefined` is passed as an input value, an
* empty array is returned.
* duplicated entries with identical id/url will be removed in returned array
* @param sprite - optional sprite to coerce
* @returns an empty array in case `undefined` is passed; id-url pairs otherwise
*/
function coerceSpriteToArray(sprite) {
const resultArray = [];
if (typeof sprite === "string") resultArray.push({
id: "default",
url: sprite
});
else if (sprite && sprite.length > 0) {
const dedupArray = [];
for (const { id, url } of sprite) {
const key = `${id}${url}`;
if (!dedupArray.includes(key)) {
dedupArray.push(key);
resultArray.push({
id,
url
});
}
}
}
return resultArray;
}
//#endregion
//#region src/style/load_sprite.ts
function normalizeSpriteURL(url, format, extension) {
try {
const parsed = new URL(url);
parsed.pathname += `${format}${extension}`;
return parsed.toString();
} catch {
throw new Error(`Invalid sprite URL "${url}", must be absolute. Modify style specification directly or use TransformStyleFunction to correct the issue dynamically`);
}
}
async function loadSprite(originalSprite, requestManager, pixelRatio, abortController) {
const spriteArray = coerceSpriteToArray(originalSprite);
const format = pixelRatio > 1 ? "@2x" : "";
const jsonsMap = {};
const imagesMap = {};
for (const { id, url } of spriteArray) {
const jsonRequestParameters = await requestManager.transformRequest(normalizeSpriteURL(url, format, ".json"), "SpriteJSON");
jsonsMap[id] = getJSON(jsonRequestParameters, abortController);
const imageRequestParameters = await requestManager.transformRequest(normalizeSpriteURL(url, format, ".png"), "SpriteImage");
imagesMap[id] = ImageRequest.getImage(imageRequestParameters, abortController);
}
await Promise.all([...Object.values(jsonsMap), ...Object.values(imagesMap)]);
return doOnceCompleted(jsonsMap, imagesMap);
}
/**
* @param jsonsMap - JSON data map
* @param imagesMap - image data map
*/
async function doOnceCompleted(jsonsMap, imagesMap) {
const result = {};
for (const spriteName in jsonsMap) {
result[spriteName] = {};
const context = browser.getImageCanvasContext((await imagesMap[spriteName]).data);
const json = (await jsonsMap[spriteName]).data;
for (const id in json) {
const { width, height, x, y, sdf, pixelRatio, stretchX, stretchY, content, textFitWidth, textFitHeight } = json[id];
const spriteData = {
width,
height,
x,
y,
context
};
result[spriteName][id] = {
data: null,
pixelRatio,
sdf,
stretchX,
stretchY,
content,
textFitWidth,
textFitHeight,
spriteData
};
}
}
return result;
}
//#endregion
//#region src/render/image_manager.ts
/**
* When copied into the atlas texture, image data is padded by one pixel on each side. Icon
* images are padded with fully transparent pixels, while pattern images are padded with a
* copy of the image data wrapped from the opposite side. In both cases, this ensures the
* correct behavior of GL_LINEAR texture sampling mode.
*/
const padding = 1;
/**
* ImageManager does three things:
*
* 1. Tracks requests for icon images from tile workers and sends responses when the requests are fulfilled.
* 2. Builds a texture atlas for pattern images.
* 3. Rerenders renderable images once per frame
*
* These are disparate responsibilities and should eventually be handled by different classes. When we implement
* data-driven support for `*-pattern`, we'll likely use per-bucket pattern atlases, and that would be a good time
* to refactor this.
*/
var ImageManager = class extends Evented {
constructor() {
super();
this.images = {};
this.updatedImages = {};
this.callbackDispatchedThisFrame = {};
this.loaded = false;
this.requestors = [];
this.missingImageResolver = null;
this.patterns = {};
this.atlasImage = new RGBAImage({
width: 1,
height: 1
});
this.dirty = true;
}
destroy() {
if (this.atlasTexture) {
this.atlasTexture.destroy();
this.atlasTexture = null;
}
for (const id of Object.keys(this.images)) this.removeImage(id);
this.patterns = {};
this.atlasImage = new RGBAImage({
width: 1,
height: 1
});
this.dirty = true;
}
isLoaded() {
return this.loaded;
}
setLoaded(loaded) {
if (this.loaded === loaded) return;
this.loaded = loaded;
if (loaded) {
for (const { ids, promiseResolve } of this.requestors) promiseResolve(this._getImagesForIds(ids));
this.requestors = [];
}
}
getImage(id) {
const image = this.images[id];
if (image && !image.data && image.spriteData) {
const spriteData = image.spriteData;
image.data = new RGBAImage({
width: spriteData.width,
height: spriteData.height
}, spriteData.context.getImageData(spriteData.x, spriteData.y, spriteData.width, spriteData.height).data);
image.spriteData = null;
}
return image;
}
addImage(id, image) {
if (this.images[id]) throw new Error(`Image id ${id} already exist, use updateImage instead`);
if (this._validate(id, image)) this.images[id] = image;
}
_validate(id, image) {
let valid = true;
const data = image.data || image.spriteData;
if (!this._validateStretch(image.stretchX, data?.width)) {
this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Image "${id}" has invalid "stretchX" value`)));
valid = false;
}
if (!this._validateStretch(image.stretchY, data?.height)) {
this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Image "${id}" has invalid "stretchY" value`)));
valid = false;
}
if (!this._validateContent(image.content, image)) {
this.fire(new ErrorEvent(/* @__PURE__ */ new Error(`Image "${id}" has invalid "content" value`)));
valid = false;
}
return valid;
}
_validateStretch(stretch, size) {
if (!stretch) return true;
let last = 0;
for (const part of stretch) {
if (part[0] < last || part[1] < part[0] || size < part[1]) return false;
last = part[1];
}
return true;
}
_validateContent(content, image) {
if (!content) return true;
if (content.length !== 4) return false;
const spriteData = image.spriteData;
const width = spriteData?.width || image.data.width;
const height = spriteData?.height || image.data.height;
if (content[0] < 0 || width < content[0]) return false;
if (content[1] < 0 || height < content[1]) return false;
if (content[2] < 0 || width < content[2]) return false;
if (content[3] < 0 || height < content[3]) return false;
if (content[2] < content[0]) return false;
return content[3] >= content[1];
}
updateImage(id, image, validate = true) {
const oldImage = this.getImage(id);
if (validate && (oldImage.data.width !== image.data.width || oldImage.data.height !== image.data.height)) throw new Error(`size mismatch between old image (${oldImage.data.width}x${oldImage.data.height}) and new image (${image.data.width}x${image.data.height}).`);
image.version = oldImage.version + 1;
this.images[id] = image;
this.updatedImages[id] = true;
}
removeImage(id) {
const image = this.images[id];
delete this.images[id];
delete this.patterns[id];
if (image.userImage?.onRemove) image.userImage.onRemove();
}
listImages() {
return Object.keys(this.images);
}
setMissingImageResolver(resolver) {
this.missingImageResolver = resolver;
}
getImages(ids) {
return new Promise((resolve, _reject) => {
let hasAllDependencies = true;
if (!this.isLoaded()) {
for (const id of ids) if (!this.images[id]) hasAllDependencies = false;
}
if (this.isLoaded() || hasAllDependencies) resolve(this._getImagesForIds(ids));
else this.requestors.push({
ids,
promiseResolve: resolve
});
});
}
async _getImagesForIds(ids) {
const unresolvedIds = new Set(ids.filter((id) => !this.getImage(id)));
const resolver = this.missingImageResolver;
if (resolver) await Promise.all(Array.from(unresolvedIds, (id) => resolver(id)));
const response = {};
for (const id of ids) {
const image = this.getImage(id);
if (image) {
unresolvedIds.delete(id);
response[id] = {
data: image.data.clone(),
pixelRatio: image.pixelRatio,
sdf: image.sdf,
version: image.version,
stretchX: image.stretchX,
stretchY: image.stretchY,
content: image.content,
textFitWidth: image.textFitWidth,
textFitHeight: image.textFitHeight,
hasRenderCallback: Boolean(image.userImage?.render)
};
}
}
for (const id of unresolvedIds) {
this.fire(new MapStyleImageMissingEvent({ id }));
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.`);
}
return response;
}
getPixelSize() {
const { width, height } = this.atlasImage;
return {
width,
height
};
}
getPattern(id) {
const pattern = this.patterns[id];
const image = this.getImage(id);
if (!image) return null;
if (pattern && pattern.position.version === image.version) return pattern.position;
if (!pattern) {
const bin = {
w: image.data.width + 2,
h: image.data.height + 2,
x: 0,
y: 0
};
const position = new ImagePosition(bin, image);
this.patterns[id] = {
bin,
position
};
} else pattern.position.version = image.version;
this._updatePatternAtlas();
return this.patterns[id].position;
}
bind(context) {
const gl = context.gl;
if (!this.atlasTexture) this.atlasTexture = new Texture(context, this.atlasImage, gl.RGBA);
else if (this.dirty) {
this.atlasTexture.update(this.atlasImage);
this.dirty = false;
}
this.atlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
}
_updatePatternAtlas() {
const bins = [];
for (const id in this.patterns) bins.push(this.patterns[id].bin);
const { w, h } = potpack(bins);
const dst = this.atlasImage;
dst.resize({
width: w || 1,
height: h || 1
});
for (const id in this.patterns) {
const { bin } = this.patterns[id];
const x = bin.x + padding;
const y = bin.y + padding;
const src = this.getImage(id).data;
const w = src.width;
const h = src.height;
RGBAImage.copy(src, dst, {
x: 0,
y: 0
}, {
x,
y
}, {
width: w,
height: h
});
RGBAImage.copy(src, dst, {
x: 0,
y: h - 1
}, {
x,
y: y - 1
}, {
width: w,
height: 1
});
RGBAImage.copy(src, dst, {
x: 0,
y: 0
}, {
x,
y: y + h
}, {
width: w,
height: 1
});
RGBAImage.copy(src, dst, {
x: w - 1,
y: 0
}, {
x: x - 1,
y
}, {
width: 1,
height: h
});
RGBAImage.copy(src, dst, {
x: 0,
y: 0
}, {
x: x + w,
y
}, {
width: 1,
height: h
});
}
this.dirty = true;
}
beginFrame() {
this.callbackDispatchedThisFrame = {};
}
dispatchRenderCallbacks(ids) {
for (const id of ids) {
if (this.callbackDispatchedThisFrame[id]) continue;
this.callbackDispatchedThisFrame[id] = true;
const image = this.getImage(id);
if (!image) warnOnce(`Image with ID: "${id}" was not found`);
if (renderStyleImage(image)) this.updateImage(id, image);
}
}
cloneImages() {
const clonedImages = {};
for (const id in this.images) {
const image = this.images[id];
clonedImages[id] = {
...image,
data: image.data ? image.data.clone() : null
};
}
return clonedImages;
}
};
//#endregion
//#region src/style/load_glyph_range.ts
async function loadGlyphRange(fontstack, range, urlTemplate, requestManager) {
const begin = range * 256;
const end = begin + 255;
const request = await requestManager.transformRequest(urlTemplate.replace("{fontstack}", fontstack).replace("{range}", `${begin}-${end}`), "Glyphs");
const response = await getArrayBuffer(request, new AbortController());
if (!response?.data) throw new Error(`Could not load glyph range. range: ${range}, ${begin}-${end}`);
const glyphs = {};
for (const glyph of parseGlyphPbf(response.data)) glyphs[glyph.id] = glyph;
return glyphs;
}
//#endregion
//#region node_modules/@mapbox/tiny-sdf/index.js
const INF = 0x56bc75e2d63100000;
const alphaTable = /* @__PURE__ */ new Float64Array(256);
for (let i = 0; i < 256; i++) {
const d = .5 - Math.pow(i / 255, 1 / 2.2);
alphaTable[i] = d * Math.abs(d);
}
alphaTable[255] = -0x56bc75e2d63100000;
var TinySDF = class {
constructor({ fontSize = 24, buffer = 3, radius = 8, cutoff = .25, fontFamily = "sans-serif", fontWeight = "normal", fontStyle = "normal", lang = null } = {}) {
this.buffer = buffer;
this.radius = radius;
this.cutoff = cutoff;
this.lang = lang;
const size = this.size = fontSize + buffer * 4;
const canvas = this._createCanvas(size);
const ctx = this.ctx = canvas.getContext("2d", { willReadFrequently: true });
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
ctx.textBaseline = "alphabetic";
ctx.textAlign = "left";
ctx.fillStyle = "black";
this.gridOuter = new Float64Array(size * size);
this.gridInner = new Float64Array(size * size);
this.f = new Float64Array(size);
this.z = new Float64Array(size + 1);
this.v = new Uint16Array(size);
}
_createCanvas(size) {
if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(size, size);
const canvas = document.createElement("canvas");
canvas.width = canvas.height = size;
return canvas;
}
draw(char) {
const { width: glyphAdvance, actualBoundingBoxAscent, actualBoundingBoxDescent, actualBoundingBoxLeft, actualBoundingBoxRight } = this.ctx.measureText(char);
const glyphTop = Math.ceil(actualBoundingBoxAscent);
const glyphLeft = Math.floor(-actualBoundingBoxLeft);
const glyphWidth = Math.max(0, Math.min(this.size - this.buffer, Math.ceil(actualBoundingBoxRight) - glyphLeft));
const glyphHeight = Math.max(0, Math.min(this.size - this.buffer, glyphTop + Math.ceil(actualBoundingBoxDescent)));
const width = glyphWidth + 2 * this.buffer;
const height = glyphHeight + 2 * this.buffer;
const len = Math.max(width * height, 0);
const data = new Uint8ClampedArray(len);
const glyph = {
data,
width,
height,
glyphWidth,
glyphHeight,
glyphTop,
glyphLeft,
glyphAdvance
};
if (glyphWidth === 0 || glyphHeight === 0) return glyph;
const { ctx, buffer, gridInner, gridOuter } = this;
if (this.lang) ctx.lang = this.lang;
ctx.clearRect(buffer, buffer, glyphWidth, glyphHeight);
ctx.fillText(char, buffer - glyphLeft, buffer + glyphTop);
const imgData = ctx.getImageData(buffer, buffer, glyphWidth, glyphHeight);
gridOuter.fill(INF, 0, len);
gridInner.fill(0, 0, len);
let imgIdx = 3;
for (let y = 0; y < glyphHeight; y++) {
let j = (y + buffer) * width + buffer;
for (let x = 0; x < glyphWidth; x++, imgIdx += 4, j++) {
const a = imgData.data[imgIdx];
if (a === 0) continue;
const t = alphaTable[a];
gridOuter[j] = Math.max(0, t);
gridInner[j] = Math.max(0, -t);
}
}
edt(gridOuter, 0, 0, width, height, width, this.f, this.v, this.z);
const pad = Math.min(buffer, 1);
edt(gridInner, buffer - pad, buffer - pad, glyphWidth + 2 * pad, glyphHeight + 2 * pad, width, this.f, this.v, this.z);
const scale = 255 / this.radius;
const base = 255 * (1 - this.cutoff);
for (let i = 0; i < len; i++) {
const d = Math.sqrt(gridOuter[i]) - Math.sqrt(gridInner[i]);
data[i] = Math.round(base - scale * d);
}
return glyph;
}
};
function edt(data, x0, y0, width, height, gridSize, f, v, z) {
for (let x = x0; x < x0 + width; x++) edt1d(data, y0 * gridSize + x, gridSize, height, f, v, z);
for (let y = y0; y < y0 + height; y++) edt1d(data, y * gridSize + x0, 1, width, f, v, z);
}
function edt1d(grid, offset, stride, length, f, v, z) {
v[0] = 0;
z[0] = -0x56bc75e2d63100000;
z[1] = INF;
f[0] = grid[offset];
for (let q = 1, k = 0, s = 0; q < length; q++) {
f[q] = grid[offset + q * stride];
const q2 = q * q;
do {
const r = v[k];
s = (f[q] - f[r] + q2 - r * r) / (q - r) / 2;
} while (s <= z[k] && --k > -1);
k++;
v[k] = q;
z[k] = s;
z[k + 1] = INF;
}
for (let q = 0, k = 0; q < length; q++) {
while (z[k + 1] < q) k++;
const r = v[k];
const qr = q - r;
grid[offset + q * stride] = f[r] + qr * qr;
}
}
//#endregion
//#region src/render/glyph_manager.ts
/**
* The style specification hard-codes some last resort fonts as a default fontstack.
*/
const defaultStack = latest.layout_symbol["text-font"].default.join(",");
/**
* The CSS generic font family closest to `defaultStack`.
*/
const defaultGenericFontFamily = "sans-serif";
/**
* Scale factor for client-generated glyphs.
*
* Client-generated glyphs are rendered at 2× because CJK glyphs are more detailed than others.
*/
const textureScale = 2;
var GlyphManager = class GlyphManager {
static {
this.loadGlyphRange = loadGlyphRange;
}
static {
this.TinySDF = TinySDF;
}
constructor(requestManager, localIdeographFontFamily, lang) {
this.requestManager = requestManager;
this.localIdeographFontFamily = localIdeographFontFamily;
this.entries = {};
this.lang = lang;
}
setURL(url) {
this.url = url;
}
async getGlyphs(glyphs) {
const glyphsPromises = [];
for (const stack in glyphs) for (const id of glyphs[stack]) glyphsPromises.push(this._getAndCacheGlyphsPromise(stack, id));
const updatedGlyphs = await Promise.all(glyphsPromises);
const result = {};
for (const { stack, id, glyph } of updatedGlyphs) {
result[stack] ||= {};
result[stack][id] = glyph && {
id: glyph.id,
bitmap: glyph.bitmap.clone(),
metrics: glyph.metrics
};
}
return result;
}
async _getAndCacheGlyphsPromise(stack, id) {
this.entries[stack] ??= {
glyphs: {},
requests: {},
ranges: {}
};
const entry = this.entries[stack];
let glyph = entry.glyphs[id];
if (glyph !== void 0) return {
stack,
id,
glyph
};
if (!this.url || this._charUsesLocalIdeographFontFamily(id)) {
glyph = entry.glyphs[id] = await this._drawGlyph(entry, stack, id);
return {
stack,
id,
glyph
};
}
return await this._downloadAndCacheRangePromise(stack, id);
}
async _downloadAndCacheRangePromise(stack, id) {
const entry = this.entries[stack];
const range = Math.floor(id / 256);
if (entry.ranges[range]) return {
stack,
id,
glyph: null
};
entry.requests[range] ||= GlyphManager.loadGlyphRange(stack, range, this.url, this.requestManager);
try {
const response = await entry.requests[range];
for (const id in response) entry.glyphs[+id] = response[+id];
entry.ranges[range] = true;
return {
stack,
id,
glyph: response[id] || null
};
} catch (e) {
const glyph = entry.glyphs[id] = await this._drawGlyph(entry, stack, id);
this._warnOnMissingGlyphRange(glyph, range, id, ensureError(e));
return {
stack,
id,
glyph
};
}
}
_warnOnMissingGlyphRange(glyph, range, id, err) {
const begin = range * 256;
const end = begin + 255;
const codePoint = id.toString(16).padStart(4, "0").toUpperCase();
warnOnce(`Unable to load glyph range ${range}, ${begin}-${end}. Rendering codepoint U+${codePoint} locally instead. ${err}`);
}
/**
* Returns whether the given codepoint should be rendered locally.
*/
_charUsesLocalIdeographFontFamily(id) {
return !!this.localIdeographFontFamily && codePointUsesLocalIdeographFontFamily(id);
}
/**
* Draws a glyph offscreen using TinySDF, creating a TinySDF instance lazily.
*/
async _drawGlyph(entry, stack, id) {
const usesLocalIdeographFontFamily = stack === defaultStack && this.localIdeographFontFamily !== "" && this._charUsesLocalIdeographFontFamily(id);
const tinySDFKey = usesLocalIdeographFontFamily ? "ideographTinySDF" : "tinySDF";
entry[tinySDFKey] ||= this._createTinySDF(usesLocalIdeographFontFamily ? this.localIdeographFontFamily : stack);
const char = (await entry[tinySDFKey]).draw(String.fromCodePoint(id));
/**
* TinySDF's "top" is the distance from the alphabetic baseline to the top of the glyph.
* Server-generated fonts specify "top" relative to an origin above the em box (the origin
* comes from FreeType, but I'm unclear on exactly how it's derived)
* ref: https://github.com/mapbox/sdf-glyph-foundry
*
* Server fonts don't yet include baseline information, so we can't line up exactly with them
* (and they don't line up with each other)
* ref: https://github.com/mapbox/node-fontnik/pull/160
*
* To approximately align TinySDF glyphs with server-provided glyphs, we use this baseline adjustment
* factor calibrated to be in between DIN Pro and Arial Unicode (but closer to Arial Unicode)
*/
const topAdjustment = 27.5;
const leftAdjustment = .5;
const isControl = /^\p{gc=Cf}+$/u.test(String.fromCodePoint(id));
return {
id,
bitmap: new AlphaImage({
width: char.width || 60,
height: char.height || 60
}, char.data),
metrics: {
width: isControl ? 0 : char.glyphWidth / textureScale || 24,
height: char.glyphHeight / textureScale || 24,
left: char.glyphLeft / textureScale + leftAdjustment || 0,
top: char.glyphTop / textureScale - topAdjustment || -8,
advance: isControl ? 0 : char.glyphAdvance / textureScale || 24,
isDoubleResolution: true
}
};
}
async _createTinySDF(stack) {
const fontFamilies = stack ? stack.split(",") : [];
fontFamilies.push(defaultGenericFontFamily);
const fontFamily = fontFamilies.map((fontName) => /[-\w]+/.test(fontName) ? fontName : `'${CSS.escape(fontName)}'`).join(",");
const fontSize = 48;
const fontWeight = this._fontWeight(fontFamilies[0]);
const fontStyle = this._fontStyle(fontFamilies[0]);
if (typeof document !== "undefined" && document.fonts?.load) try {
await document.fonts.load(`${fontStyle} ${fontWeight || "normal"} ${fontSize}px ${fontFamily}`);
} catch (e) {
warnOnce(`Failed to load font "${fontFamily}": ${ensureError(e).message}`);
}
return new GlyphManager.TinySDF({
fontSize,
buffer: 6,
radius: 16,
cutoff: .25,
fontFamily,
fontWeight,
fontStyle,
lang: this.lang
});
}
/**
* Sniffs the font style out of a font family name.
*/
_fontStyle(fontFamily) {
if (/italic/i.test(fontFamily)) return "italic";
else if (/oblique/i.test(fontFamily)) return "oblique";
return "normal";
}
/**
* Sniffs the font weight out of a font family name.
*/
_fontWeight(fontFamily) {
const weightsByName = {
thin: 100,
hairline: 100,
"extra light": 200,
"ultra light": 200,
light: 300,
normal: 400,
regular: 400,
medium: 500,
semibold: 600,
demibold: 600,
bold: 700,
"extra bold": 800,
"ultra bold": 800,
black: 900,
heavy: 900,
"extra black": 950,
"ultra black": 950
};
let match;
for (const [name, weight] of Object.entries(weightsByName)) if (new RegExp(`\\b${name}\\b`, "i").test(fontFamily)) match = `${weight}`;
return match;
}
destroy() {
for (const stack in this.entries) {
const entry = this.entries[stack];
entry.tinySDF = null;
entry.ideographTinySDF = null;
entry.glyphs = {};
entry.requests = {};
entry.ranges = {};
}
this.entries = {};
}
};
//#endregion
//#region src/style/light_properties.g.ts
let properties$2;
const getProperties$2 = () => properties$2 = properties$2 || new Properties({
"anchor": new DataConstantProperty(latest["light"]["anchor"], "anchor"),
"position": new DataConstantProperty(latest["light"]["position"], "position"),
"color": new DataConstantProperty(latest["light"]["color"], "color"),
"intensity": new DataConstantProperty(latest["light"]["intensity"], "intensity")
});
//#endregion
//#region src/style/light.ts
var Light = class extends Evented {
constructor(lightOptions, globalState) {
super();
this._transitionable = new Transitionable(getProperties$2(), "light", globalState);
this.setLight(lightOptions);
this._transitioning = this._transitionable.untransitioned();
}
getLight() {
return this._transitionable.serialize();
}
/**
* Get