maplair
Version:
JavaScript library for mobile-friendly interactive maps
6,810 lines • 229 kB
JavaScript
/* @preserve
* Maplair 4.0.0-alpha.1, a JS library for interactive maps. https://maplair.org
* (c) 2026 Maplair contributors, (c) 2010-2026 Volodymyr Agafonkin, (c) 2010-2011 CloudMade
*/
(function (previousL) {
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.L = {}));
})(this, function(exports) {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region \0rolldown/runtime.js
var __defProp = Object.defineProperty;
var __exportAll = (all, no_symbols) => {
let target = {};
for (var name in all) __defProp(target, name, {
get: all[name],
enumerable: true
});
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
return target;
};
//#endregion
//#region package.json
var version$1 = "4.0.0-alpha.1";
//#endregion
//#region src/core/Util.js
var Util_exports = /* @__PURE__ */ __exportAll({
falseFn: () => falseFn,
formatNum: () => formatNum,
lastId: () => lastId,
setOptions: () => setOptions,
splitWords: () => splitWords,
stamp: () => stamp,
template: () => template,
throttle: () => throttle,
wrapNum: () => wrapNum
});
let lastId = 0;
function stamp(obj) {
if (!("_maplair_id" in obj)) obj["_maplair_id"] = ++lastId;
return obj._maplair_id;
}
function throttle(fn, time, context) {
let lock, queuedArgs;
function later() {
lock = false;
if (queuedArgs) {
wrapperFn.apply(context, queuedArgs);
queuedArgs = false;
}
}
function wrapperFn(...args) {
if (lock) queuedArgs = args;
else {
fn.apply(context, args);
setTimeout(later, time);
lock = true;
}
}
return wrapperFn;
}
function wrapNum(x, range, includeMax) {
const max = range[1], min = range[0], d = max - min;
return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
}
function falseFn() {
return false;
}
function formatNum(num, precision) {
if (precision === false) return num;
const pow = 10 ** (precision === void 0 ? 6 : precision);
return Math.round(num * pow) / pow;
}
const WHITESPACE_RE = /\s+/;
function splitWords(str) {
return str.trim().split(WHITESPACE_RE);
}
function setOptions(obj, options) {
if (!Object.hasOwn(obj, "options")) obj.options = obj.options ? Object.create(obj.options) : {};
for (const i in options) if (Object.hasOwn(options, i)) obj.options[i] = options[i];
return obj.options;
}
const templateRe = /\{ *([\w_ -]+) *\}/g;
function template(str, data) {
return str.replace(templateRe, (str, key) => {
let value = data[key];
if (value === void 0) throw new Error(`No value provided for variable ${str}`);
else if (typeof value === "function") value = value(data);
return value;
});
}
//#endregion
//#region src/core/Class.js
var Class = class {
static include(props) {
const parentOptions = this.prototype.options;
for (const k of getAllMethodNames(props)) this.prototype[k] = props[k];
if (props.options) {
this.prototype.options = parentOptions;
this.mergeOptions(props.options);
}
return this;
function* getAllMethodNames(obj) {
do {
if (obj === Object || obj === Object.prototype) break;
for (const k of Object.getOwnPropertyNames(obj)) yield k;
} while ((obj = Object.getPrototypeOf(obj)) !== void 0);
}
}
static setDefaultOptions(options) {
setOptions(this.prototype, options);
return this;
}
static mergeOptions(options) {
this.prototype.options ??= {};
Object.assign(this.prototype.options, options);
return this;
}
static addInitHook(fn, ...args) {
const init = typeof fn === "function" ? fn : function() {
this[fn](...args);
};
if (!Object.hasOwn(this, "_initHooks")) this._initHooks = [];
this._initHooks.push(init);
return this;
}
constructor() {
setOptions(this);
}
callInitHooks(cls = this.constructor) {
const done = this._initHooksCalledFor;
if (done && (done === cls || done.prototype instanceof cls)) return this;
const classes = [];
for (let c = cls; c && c !== Function.prototype && c !== done; c = Object.getPrototypeOf(c)) classes.unshift(c);
this._initHooksCalledFor = cls;
for (const c of classes) if (Object.hasOwn(c, "_initHooks")) for (const hook of c._initHooks) hook.call(this);
return this;
}
};
//#endregion
//#region src/geometry/Point.js
var Point = class Point {
constructor(x, y, round) {
if (!Point.validate(x, y)) throw new Error(`Invalid Point object: (${x}, ${y})`);
let _x, _y;
if (x instanceof Point) {
_x = x.x;
_y = x.y;
} else if (Array.isArray(x)) {
_x = x[0];
_y = x[1];
} else if (typeof x === "object" && "x" in x && "y" in x) {
_x = x.x;
_y = x.y;
} else {
_x = x;
_y = y;
}
this.x = round ? Math.round(_x) : _x;
this.y = round ? Math.round(_y) : _y;
}
static validate(x, y) {
if (x instanceof Point || Array.isArray(x)) return true;
else if (x && typeof x === "object" && "x" in x && "y" in x) return true;
else if ((x || x === 0) && (y || y === 0)) return true;
return false;
}
add(point) {
return new Point(this)._add(new Point(point));
}
_add(point) {
this.x += point.x;
this.y += point.y;
return this;
}
subtract(point) {
return new Point(this)._subtract(new Point(point));
}
_subtract(point) {
this.x -= point.x;
this.y -= point.y;
return this;
}
divideBy(num) {
return new Point(this)._divideBy(num);
}
_divideBy(num) {
this.x /= num;
this.y /= num;
return this;
}
multiplyBy(num) {
return new Point(this)._multiplyBy(num);
}
_multiplyBy(num) {
this.x *= num;
this.y *= num;
return this;
}
scaleBy(point) {
return new Point(this.x * point.x, this.y * point.y);
}
unscaleBy(point) {
return new Point(this.x / point.x, this.y / point.y);
}
round() {
return new Point(this)._round();
}
_round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
}
floor() {
return new Point(this)._floor();
}
_floor() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
}
ceil() {
return new Point(this)._ceil();
}
_ceil() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
}
trunc() {
return new Point(this)._trunc();
}
_trunc() {
this.x = Math.trunc(this.x);
this.y = Math.trunc(this.y);
return this;
}
distanceTo(point) {
point = new Point(point);
const x = point.x - this.x, y = point.y - this.y;
return Math.sqrt(x * x + y * y);
}
equals(point) {
point = new Point(point);
return point.x === this.x && point.y === this.y;
}
contains(point) {
point = new Point(point);
return Math.abs(point.x) <= Math.abs(this.x) && Math.abs(point.y) <= Math.abs(this.y);
}
toString() {
return `Point(${formatNum(this.x)}, ${formatNum(this.y)})`;
}
};
//#endregion
//#region src/core/Browser.js
const chrome = userAgentContains("chrome");
const safari = !chrome && userAgentContains("safari");
const mobile = typeof orientation !== "undefined";
const reducedMotion = navigator.preferences?.reducedMotion?.value ? navigator.preferences.reducedMotion.value === "reduce" : typeof window === "undefined" ? true : window?.matchMedia("(prefers-reduced-motion: reduce)").matches;
const pointer = typeof window === "undefined" ? false : !!window.PointerEvent;
const touchNative = typeof window === "undefined" ? false : "ontouchstart" in window || !!window.TouchEvent;
const touch = touchNative || pointer;
const retina = typeof window === "undefined" || typeof window.devicePixelRatio === "undefined" ? false : window.devicePixelRatio > 1;
const mac = typeof navigator === "undefined" || typeof navigator.platform === "undefined" ? false : navigator.platform.startsWith("Mac");
const linux = typeof navigator === "undefined" || typeof navigator.platform === "undefined" ? false : navigator.platform.startsWith("Linux");
function userAgentContains(str) {
if (typeof navigator === "undefined" || typeof navigator.userAgent === "undefined") return false;
return navigator.userAgent.toLowerCase().includes(str);
}
var Browser_default = {
chrome,
safari,
mobile,
reducedMotion,
pointer,
touch,
touchNative,
retina,
mac,
linux
};
//#endregion
//#region src/dom/DomEvent.DoubleTap.js
function makeDblclick(ev) {
let init = {
bubbles: ev.bubbles,
cancelable: ev.cancelable,
composed: ev.composed,
detail: 2,
view: ev.view,
screenX: ev.screenX,
screenY: ev.screenY,
clientX: ev.clientX,
clientY: ev.clientY,
ctrlKey: ev.ctrlKey,
shiftKey: ev.shiftKey,
altKey: ev.altKey,
metaKey: ev.metaKey,
button: ev.button,
buttons: ev.buttons,
relatedTarget: ev.relatedTarget,
region: ev.region
};
let newEvent;
if (ev instanceof PointerEvent) {
init = {
...init,
pointerId: ev.pointerId,
width: ev.width,
height: ev.height,
pressure: ev.pressure,
tangentialPressure: ev.tangentialPressure,
tiltX: ev.tiltX,
tiltY: ev.tiltY,
twist: ev.twist,
pointerType: ev.pointerType,
isPrimary: ev.isPrimary
};
newEvent = new PointerEvent("dblclick", init);
} else newEvent = new MouseEvent("dblclick", init);
return newEvent;
}
const delay = 200;
function addDoubleTapListener(obj, handler) {
obj.addEventListener("dblclick", handler);
let last = 0, detail;
function simDblclick(ev) {
if (ev.detail !== 1) {
detail = ev.detail;
return;
}
if (ev.pointerType === "mouse" || ev.sourceCapabilities && !ev.sourceCapabilities.firesTouchEvents) return;
const path = getPropagationPath(ev);
if (path.some((el) => el instanceof HTMLLabelElement && el.attributes.for) && !path.some((el) => el instanceof HTMLInputElement || el instanceof HTMLSelectElement)) return;
const now = Date.now();
if (now - last <= delay) {
detail++;
if (detail === 2) ev.target.dispatchEvent(makeDblclick(ev));
} else detail = 1;
last = now;
}
obj.addEventListener("click", simDblclick);
return {
dblclick: handler,
simDblclick
};
}
function removeDoubleTapListener(obj, handlers) {
obj.removeEventListener("dblclick", handlers.dblclick);
obj.removeEventListener("click", handlers.simDblclick);
}
//#endregion
//#region src/dom/DomEvent.PointerEvents.js
var DomEvent_PointerEvents_exports = /* @__PURE__ */ __exportAll({
cleanupPointers: () => cleanupPointers,
disablePointerDetection: () => disablePointerDetection,
enablePointerDetection: () => enablePointerDetection,
getPointers: () => getPointers
});
const activePointers = /* @__PURE__ */ new Map();
function enablePointerDetection(el) {
el.addEventListener("pointerdown", _onSet, { capture: true });
el.addEventListener("pointermove", _onUpdate, { capture: true });
el.addEventListener("pointerup", _onDelete, { capture: true });
el.addEventListener("pointercancel", _onDelete, { capture: true });
activePointers.clear();
}
function disablePointerDetection(el) {
el.removeEventListener("pointerdown", _onSet, { capture: true });
el.removeEventListener("pointermove", _onUpdate, { capture: true });
el.removeEventListener("pointerup", _onDelete, { capture: true });
el.removeEventListener("pointercancel", _onDelete, { capture: true });
}
function _onSet(e) {
e.target.setPointerCapture(e.pointerId);
activePointers.set(e.pointerId, e);
}
function _onUpdate(e) {
if (activePointers.has(e.pointerId)) activePointers.set(e.pointerId, e);
}
function _onDelete(e) {
e.target.releasePointerCapture(e.pointerId);
activePointers.delete(e.pointerId);
}
function getPointers() {
return [...activePointers.values()];
}
function cleanupPointers() {
for (const e of activePointers.values()) try {
e.target.releasePointerCapture(e.pointerId);
} catch {}
activePointers.clear();
}
//#endregion
//#region src/dom/DomEvent.js
var DomEvent_exports = /* @__PURE__ */ __exportAll({
PointerEvents: () => DomEvent_PointerEvents_exports,
disableClickPropagation: () => disableClickPropagation,
disableScrollPropagation: () => disableScrollPropagation,
getPointerPosition: () => getPointerPosition,
getPropagationPath: () => getPropagationPath,
getWheelDelta: () => getWheelDelta,
getWheelPxFactor: () => getWheelPxFactor,
isExternalTarget: () => isExternalTarget,
off: () => off,
on: () => on,
preventDefault: () => preventDefault,
stop: () => stop,
stopPropagation: () => stopPropagation
});
function on(obj, types, fn, context) {
if (types && typeof types === "object") for (const [type, listener] of Object.entries(types)) addOne(obj, type, listener, fn);
else for (const type of splitWords(types)) addOne(obj, type, fn, context);
return this;
}
const eventsKey = "_maplair_events";
function off(obj, types, fn, context) {
if (arguments.length === 1) {
batchRemove(obj);
delete obj[eventsKey];
} else if (types && typeof types === "object") for (const [type, listener] of Object.entries(types)) removeOne(obj, type, listener, fn);
else {
types = splitWords(types);
if (arguments.length === 2) batchRemove(obj, (type) => types.includes(type));
else for (const type of types) removeOne(obj, type, fn, context);
}
return this;
}
const DIGIT_RE = /\d/;
function batchRemove(obj, filterFn) {
for (const id of Object.keys(obj[eventsKey] ?? {})) {
const type = id.split(DIGIT_RE)[0];
if (!filterFn || filterFn(type)) removeOne(obj, type, null, null, id);
}
}
const pointerSubst = {
pointerenter: "pointerover",
pointerleave: "pointerout",
wheel: typeof window === "undefined" ? false : !("onwheel" in window) && "mousewheel"
};
function addOne(obj, type, fn, context) {
const id = type + stamp(fn) + (context ? `_${stamp(context)}` : "");
if (obj[eventsKey] && obj[eventsKey][id]) return this;
let handler = function(e) {
return fn.call(context || obj, e);
};
const originalHandler = handler;
if (Browser_default.touch && type === "dblclick") handler = addDoubleTapListener(obj, handler);
else if ("addEventListener" in obj) {
if (type === "wheel" || type === "mousewheel") obj.addEventListener(pointerSubst[type] || type, handler, { passive: false });
else if (type === "pointerenter" || type === "pointerleave") {
handler = function(e) {
if (isExternalTarget(obj, e)) originalHandler(e);
};
obj.addEventListener(pointerSubst[type], handler, false);
} else obj.addEventListener(type, originalHandler, false);
} else obj.attachEvent(`on${type}`, handler);
obj[eventsKey] ??= {};
obj[eventsKey][id] = handler;
}
function removeOne(obj, type, fn, context, id) {
id ??= type + stamp(fn) + (context ? `_${stamp(context)}` : "");
const handler = obj[eventsKey] && obj[eventsKey][id];
if (!handler) return this;
if (Browser_default.touch && type === "dblclick") removeDoubleTapListener(obj, handler);
else if ("removeEventListener" in obj) obj.removeEventListener(pointerSubst[type] || type, handler, false);
else obj.detachEvent(`on${type}`, handler);
obj[eventsKey][id] = null;
}
function stopPropagation(e) {
if (e.stopPropagation) e.stopPropagation();
else if (e.originalEvent) e.originalEvent._stopped = true;
else console.debug("Unsupported event", e);
return this;
}
function disableScrollPropagation(el) {
addOne(el, "wheel", stopPropagation);
return this;
}
function disableClickPropagation(el) {
on(el, "pointerdown dblclick contextmenu", stopPropagation);
el["_maplair_disable_click"] = true;
return this;
}
function preventDefault(e) {
e.preventDefault?.();
return this;
}
function stop(e) {
preventDefault(e);
stopPropagation(e);
return this;
}
function getPropagationPath(ev) {
return ev.composedPath();
}
function getPointerPosition(e, container) {
if (!container) return new Point(e.clientX, e.clientY);
const scale = getScale(container), offset = scale.boundingClientRect;
return new Point((e.clientX - offset.left) / scale.x - container.clientLeft, (e.clientY - offset.top) / scale.y - container.clientTop);
}
function getWheelPxFactor() {
const ratio = window.devicePixelRatio;
return Browser_default.linux && Browser_default.chrome ? ratio : Browser_default.mac ? ratio * 3 : ratio > 0 ? 2 * ratio : 1;
}
function getWheelDelta(e) {
return e.deltaY && e.deltaMode === 0 ? -e.deltaY / getWheelPxFactor() : e.deltaY && e.deltaMode === 1 ? -e.deltaY * 20 : e.deltaY && e.deltaMode === 2 ? -e.deltaY * 60 : e.deltaX || e.deltaZ ? 0 : 0;
}
function isExternalTarget(el, e) {
let related = e.relatedTarget;
if (!related) return true;
try {
while (related && related !== el) related = related.parentNode;
} catch (err) {
return false;
}
return related !== el;
}
//#endregion
//#region src/dom/DomUtil.js
var DomUtil_exports = /* @__PURE__ */ __exportAll({
create: () => create,
disableImageDrag: () => disableImageDrag,
disableTextSelection: () => disableTextSelection,
enableImageDrag: () => enableImageDrag,
enableTextSelection: () => enableTextSelection,
get: () => get,
getPosition: () => getPosition,
getScale: () => getScale,
getSizedParentNode: () => getSizedParentNode,
preventOutline: () => preventOutline,
restoreOutline: () => restoreOutline,
setPosition: () => setPosition,
setTransform: () => setTransform,
toBack: () => toBack,
toFront: () => toFront
});
function get(id) {
return typeof id === "string" ? document.getElementById(id) : id;
}
function create(tagName, className, container) {
const el = document.createElement(tagName);
el.className = className ?? "";
container?.appendChild(el);
return el;
}
function toFront(el) {
const parent = el.parentNode;
if (parent && parent.lastChild !== el) parent.appendChild(el);
}
function toBack(el) {
const parent = el.parentNode;
if (parent && parent.firstChild !== el) parent.insertBefore(el, parent.firstChild);
}
function setTransform(el, offset, scale) {
const pos = offset ?? new Point(0, 0);
const transform = `translate3d(${pos.x}px, ${pos.y}px, 0px)${scale ? ` scale(${scale})` : ""}`;
if (el.style.transform !== transform) el.style.transform = transform;
}
const positions = /* @__PURE__ */ new WeakMap();
function setPosition(el, point) {
positions.set(el, point);
setTransform(el, point);
}
function getPosition(el) {
return positions.get(el) ?? new Point(0, 0);
}
const documentStyle = typeof document === "undefined" ? {} : document.documentElement.style;
const userSelectProp = ["userSelect", "WebkitUserSelect"].find((prop) => prop in documentStyle);
let prevUserSelect;
function disableTextSelection() {
const value = documentStyle[userSelectProp];
if (value === "none") return;
prevUserSelect = value;
documentStyle[userSelectProp] = "none";
}
function enableTextSelection() {
if (typeof prevUserSelect === "undefined") return;
documentStyle[userSelectProp] = prevUserSelect;
prevUserSelect = void 0;
}
function disableImageDrag() {
on(window, "dragstart", preventDefault);
}
function enableImageDrag() {
off(window, "dragstart", preventDefault);
}
let _outlineElement;
let _outlineStyle;
function preventOutline(element) {
while (element.tabIndex === -1) element = element.parentNode;
if (!element.style) return;
restoreOutline();
_outlineElement = element;
_outlineStyle = element.style.outlineStyle;
element.style.outlineStyle = "none";
on(window, "keydown", restoreOutline);
}
function restoreOutline() {
if (!_outlineElement) return;
_outlineElement.style.outlineStyle = _outlineStyle;
_outlineElement = void 0;
_outlineStyle = void 0;
off(window, "keydown", restoreOutline);
}
function getSizedParentNode(element) {
do
element = element.parentNode;
while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
return element;
}
function getScale(element) {
const rect = element.getBoundingClientRect();
return {
x: rect.width / element.offsetWidth || 1,
y: rect.height / element.offsetHeight || 1,
boundingClientRect: rect
};
}
//#endregion
//#region src/control/Control.js
var Control = class Control extends Class {
static {
this.setDefaultOptions({ position: "topright" });
}
constructor(options) {
super();
setOptions(this, options);
this.callInitHooks(Control);
}
getPosition() {
return this.options.position;
}
setPosition(position) {
const map = this._map;
map?.removeControl(this);
this.options.position = position;
map?.addControl(this);
return this;
}
getContainer() {
return this._container;
}
addTo(map) {
this.remove();
this._map = map;
const container = this._container = this.onAdd(map), pos = this.getPosition(), corner = map._controlCorners[pos];
container.classList.add("maplair-control");
if (pos.includes("bottom")) corner.insertBefore(container, corner.firstChild);
else corner.appendChild(container);
this._map.on("unload", this.remove, this);
return this;
}
remove() {
if (!this._map) return this;
this._container.remove();
if (this.onRemove) this.onRemove(this._map);
this._map.off("unload", this.remove, this);
this._map = null;
return this;
}
_refocusOnMap(e) {
if (this._map && e && !(e.screenX === 0 && e.screenY === 0)) this._map.getContainer().focus();
}
static register(MaplairMap) {
MaplairMap.include({
addControl(control) {
control.addTo(this);
return this;
},
removeControl(control) {
control.remove();
return this;
},
_initControlPos() {
const corners = this._controlCorners = {}, l = "maplair-", container = this._controlContainer = create("div", `${l}control-container`, this._container);
function createCorner(vSide, hSide) {
const className = `${l + vSide} ${l}${hSide}`;
corners[vSide + hSide] = create("div", className, container);
}
createCorner("top", "left");
createCorner("top", "right");
createCorner("bottom", "left");
createCorner("bottom", "right");
},
_clearControlPos() {
for (const c of Object.values(this._controlCorners)) c.remove();
this._controlContainer.remove();
delete this._controlCorners;
delete this._controlContainer;
}
});
}
};
//#endregion
//#region src/core/I18n.js
var I18n = class {
static #messages = {};
static #locale = null;
static registerLocale(locale, messages) {
this.#messages[locale] = {
...this.#messages[locale],
...messages
};
}
static setLocale(locale) {
this.#locale = locale;
}
static getLocale() {
return this.#locale;
}
static translate(string, data = {}) {
if (this.#locale === null) return template(string, data);
const translation = this.#messages[this.#locale]?.[string];
if (!translation) {
console.warn(`No translation found for "${string}", falling back to the original string.`);
return template(string, data);
}
try {
return template(translation, data);
} catch (err) {
console.error(`Error while translating "${translation}".`, err);
return translation;
}
}
};
//#endregion
//#region src/control/AttributionControl.js
const mapleLeaf = "<span aria-hidden=\"true\" class=\"maplair-attribution-leaf\">🍁</span>";
var AttributionControl = class AttributionControl extends Control {
static {
this.setDefaultOptions({
position: "bottomright",
prefix: null
});
}
constructor(options) {
super(options);
if (this.options.prefix === null) {
const title = I18n.translate("A JavaScript library for interactive maps");
this.options.prefix = `<a target="_blank" href="https://maplair.org" title="${title}">${mapleLeaf}Maplair</a>`;
}
this._attributions = {};
this.callInitHooks(AttributionControl);
}
onAdd(map) {
map.attributionControl = this;
this._container = create("div", "maplair-control-attribution");
disableClickPropagation(this._container);
for (const layer of Object.values(map._layers)) if (layer.getAttribution) this.addAttribution(layer.getAttribution());
this._update();
map.on("layeradd", this._addAttribution, this);
return this._container;
}
onRemove(map) {
map.off("layeradd", this._addAttribution, this);
}
_addAttribution(ev) {
if (ev.layer.getAttribution) {
this.addAttribution(ev.layer.getAttribution());
ev.layer.once("remove", () => this.removeAttribution(ev.layer.getAttribution()));
}
}
setPrefix(prefix) {
this.options.prefix = prefix;
this._update();
return this;
}
addAttribution(text) {
if (!text) return this;
if (!this._attributions[text]) this._attributions[text] = 0;
this._attributions[text]++;
this._update();
return this;
}
removeAttribution(text) {
if (!text) return this;
if (this._attributions[text]) {
this._attributions[text]--;
this._update();
}
return this;
}
_update() {
if (!this._map) return;
const attribs = Object.keys(this._attributions).filter((i) => this._attributions[i]);
const prefixAndAttribs = [];
if (this.options.prefix) prefixAndAttribs.push(this.options.prefix);
if (attribs.length) prefixAndAttribs.push(attribs.join(", "));
this._container.innerHTML = prefixAndAttribs.join(" <span aria-hidden=\"true\">|</span> ");
}
static register(MaplairMap) {
MaplairMap.mergeOptions({ attributionControl: true });
MaplairMap.addInitHook(function() {
if (this.options.attributionControl) new AttributionControl().addTo(this);
});
}
};
//#endregion
//#region src/control/LayersControl.js
var LayersControl = class LayersControl extends Control {
static {
this.setDefaultOptions({
collapsed: true,
collapseDelay: 0,
position: "topright",
autoZIndex: true,
hideSingleBase: false,
sortLayers: false,
sortFunction(layerA, layerB, nameA, nameB) {
return nameA < nameB ? -1 : nameB < nameA ? 1 : 0;
}
});
}
constructor(baseLayers, overlays, options) {
super(options);
this._layerControlInputs = [];
this._layers = [];
this._lastZIndex = 0;
this._handlingClick = false;
this._preventClick = false;
for (const [name, layer] of Object.entries(baseLayers ?? {})) this._addLayer(layer, name);
for (const [name, layer] of Object.entries(overlays ?? {})) this._addLayer(layer, name, true);
this.callInitHooks(LayersControl);
}
onAdd(map) {
this._initLayout();
this._update();
this._map = map;
map.on("zoomend", this._checkDisabledLayers, this);
for (const layer of this._layers) layer.layer.on("add remove", this._onLayerChange, this);
if (!this.options.collapsed) map.on("resize", this._expandIfNotCollapsed, this);
return this._container;
}
addTo(map) {
super.addTo(map);
return this._expandIfNotCollapsed();
}
onRemove() {
this._map.off("zoomend", this._checkDisabledLayers, this);
for (const layer of this._layers) layer.layer.off("add remove", this._onLayerChange, this);
this._map.off("resize", this._expandIfNotCollapsed, this);
}
addBaseLayer(layer, name) {
this._addLayer(layer, name);
return this._map ? this._update() : this;
}
addOverlay(layer, name) {
this._addLayer(layer, name, true);
return this._map ? this._update() : this;
}
removeLayer(layer) {
layer.off("add remove", this._onLayerChange, this);
const obj = this._getLayer(stamp(layer));
if (obj) this._layers.splice(this._layers.indexOf(obj), 1);
return this._map ? this._update() : this;
}
expand() {
clearTimeout(this._collapseDelayTimeout);
this._container.classList.add("maplair-control-layers-expanded");
this._section.style.height = null;
const acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
if (acceptableHeight < this._section.clientHeight) {
this._section.classList.add("maplair-control-layers-scrollbar");
this._section.style.height = `${acceptableHeight}px`;
} else this._section.classList.remove("maplair-control-layers-scrollbar");
this._checkDisabledLayers();
return this;
}
collapse(ev) {
if (!ev || !((ev.type === "pointerleave" || ev.type === "pointerout") && ev.pointerType === "touch")) {
if (this.options.collapseDelay > 0) {
this._collapseDelayTimeout = setTimeout(() => {
this._container.classList.remove("maplair-control-layers-expanded");
}, this.options.collapseDelay);
return this;
}
this._container.classList.remove("maplair-control-layers-expanded");
}
return this;
}
_initLayout() {
const className = "maplair-control-layers", container = this._container = create("div", className), collapsed = this.options.collapsed;
disableClickPropagation(container);
disableScrollPropagation(container);
const section = this._section = create("fieldset", `${className}-list`);
if (collapsed) {
this._map.on("click", this.collapse, this);
on(container, {
pointerenter: this._expandSafely,
pointerleave: this.collapse
}, this);
}
const link = this._layersLink = create("a", `${className}-toggle`, container);
link.href = "#";
link.title = I18n.translate("Layers");
link.setAttribute("role", "button");
on(link, {
keydown(e) {
if (e.code === "Enter") this._expandSafely();
},
click(e) {
e.preventDefault();
this._expandSafely();
}
}, this);
if (!collapsed) this.expand();
this._baseLayersList = create("div", `${className}-base`, section);
this._separator = create("div", `${className}-separator`, section);
this._overlaysList = create("div", `${className}-overlays`, section);
container.appendChild(section);
}
_getLayer(id) {
for (const layer of this._layers) if (layer && stamp(layer.layer) === id) return layer;
}
_addLayer(layer, name, overlay) {
if (this._map) layer.on("add remove", this._onLayerChange, this);
this._layers.push({
layer,
name,
overlay
});
if (this.options.sortLayers) this._layers.sort(((a, b) => this.options.sortFunction(a.layer, b.layer, a.name, b.name)));
if (this.options.autoZIndex && layer.setZIndex) {
this._lastZIndex++;
layer.setZIndex(this._lastZIndex);
}
this._expandIfNotCollapsed();
}
_update() {
if (!this._container) return this;
this._baseLayersList.replaceChildren();
this._overlaysList.replaceChildren();
this._layerControlInputs = [];
let baseLayersPresent, overlaysPresent, baseLayersCount = 0;
for (const obj of this._layers) {
this._addItem(obj);
overlaysPresent ||= obj.overlay;
baseLayersPresent ||= !obj.overlay;
baseLayersCount += !obj.overlay ? 1 : 0;
}
if (this.options.hideSingleBase) {
baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
this._baseLayersList.style.display = baseLayersPresent ? "" : "none";
}
this._separator.style.display = overlaysPresent && baseLayersPresent ? "" : "none";
return this;
}
_onLayerChange(e) {
if (!this._handlingClick) this._update();
const obj = this._getLayer(stamp(e.target));
const type = obj.overlay ? e.type === "add" ? "overlayadd" : "overlayremove" : e.type === "add" ? "baselayerchange" : null;
if (type) this._map.fire(type, obj);
}
_addItem(obj) {
const label = document.createElement("label"), checked = this._map.hasLayer(obj.layer);
const input = document.createElement("input");
input.type = obj.overlay ? "checkbox" : "radio";
input.className = "maplair-control-layers-selector";
input.defaultChecked = checked;
if (!obj.overlay) input.name = `maplair-base-layers_${stamp(this)}`;
this._layerControlInputs.push(input);
input.layerId = stamp(obj.layer);
on(input, "click", this._onInputClick, this);
const name = document.createElement("span");
name.innerHTML = ` ${obj.name}`;
const holder = document.createElement("span");
label.appendChild(holder);
holder.appendChild(input);
holder.appendChild(name);
(obj.overlay ? this._overlaysList : this._baseLayersList).appendChild(label);
this._checkDisabledLayers();
return label;
}
_onInputClick(e) {
if (this._preventClick) return;
const inputs = this._layerControlInputs, addedLayers = [], removedLayers = [];
this._handlingClick = true;
for (const input of inputs) {
const layer = this._getLayer(input.layerId).layer;
if (input.checked) addedLayers.push(layer);
else if (!input.checked) removedLayers.push(layer);
}
for (const layer of removedLayers) if (this._map.hasLayer(layer)) this._map.removeLayer(layer);
for (const layer of addedLayers) if (!this._map.hasLayer(layer)) this._map.addLayer(layer);
this._handlingClick = false;
this._refocusOnMap(e);
}
_checkDisabledLayers() {
const inputs = this._layerControlInputs, zoom = this._map.getZoom();
for (const input of inputs) {
const layer = this._getLayer(input.layerId).layer;
input.disabled = layer.options.minZoom !== void 0 && zoom < layer.options.minZoom || layer.options.maxZoom !== void 0 && zoom > layer.options.maxZoom;
}
}
_expandIfNotCollapsed() {
if (this._map && !this.options.collapsed) this.expand();
return this;
}
_expandSafely() {
const section = this._section;
this._preventClick = true;
on(section, "click", preventDefault);
this.expand();
setTimeout(() => {
off(section, "click", preventDefault);
this._preventClick = false;
});
}
};
//#endregion
//#region src/control/ScaleControl.js
var ScaleControl = class ScaleControl extends Control {
static {
this.setDefaultOptions({
position: "bottomleft",
maxWidth: 100,
metric: true,
imperial: true,
updateWhenIdle: false
});
}
constructor(...args) {
super(...args);
this.callInitHooks(ScaleControl);
}
onAdd(map) {
const className = "maplair-control-scale", container = create("div", className), options = this.options;
this._addScales(options, `${className}-line`, container);
map.on(options.updateWhenIdle ? "moveend" : "move", this._update, this);
map.whenReady(this._update, this);
return container;
}
onRemove(map) {
map.off(this.options.updateWhenIdle ? "moveend" : "move", this._update, this);
}
_addScales(options, className, container) {
if (options.metric) this._mScale = create("div", className, container);
if (options.imperial) this._iScale = create("div", className, container);
}
_update() {
const map = this._map, y = map.getSize().y / 2;
const maxMeters = map.distance(map.containerPointToLatLng([0, y]), map.containerPointToLatLng([this.options.maxWidth, y]));
this._updateScales(maxMeters);
}
_updateScales(maxMeters) {
if (this.options.metric && maxMeters) this._updateMetric(maxMeters);
if (this.options.imperial && maxMeters) this._updateImperial(maxMeters);
}
_updateMetric(maxMeters) {
const meters = this._getRoundNum(maxMeters), label = meters < 1e3 ? I18n.translate("{meters} m", { meters }) : I18n.translate("{kilometers} km", { kilometers: meters / 1e3 });
this._updateScale(this._mScale, label, meters / maxMeters);
}
_updateImperial(maxMeters) {
const maxFeet = maxMeters * 3.2808399;
let maxMiles, miles, feet;
if (maxFeet > 5280) {
maxMiles = maxFeet / 5280;
miles = this._getRoundNum(maxMiles);
this._updateScale(this._iScale, I18n.translate("{miles} mi", { miles }), miles / maxMiles);
} else {
feet = this._getRoundNum(maxFeet);
this._updateScale(this._iScale, I18n.translate("{feet} ft", { feet }), feet / maxFeet);
}
}
_updateScale(scale, text, ratio) {
scale.style.width = `${Math.round(this.options.maxWidth * ratio)}px`;
scale.innerHTML = text;
}
_getRoundNum(num) {
const pow10 = 10 ** (`${Math.floor(num)}`.length - 1);
let d = num / pow10;
d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
return pow10 * d;
}
};
//#endregion
//#region src/control/ZoomControl.js
var ZoomControl = class ZoomControl extends Control {
static {
this.setDefaultOptions({
position: "topleft",
zoomInText: "<span aria-hidden=\"true\">+</span>",
zoomOutText: "<span aria-hidden=\"true\">−</span>"
});
}
constructor(...args) {
super(...args);
this.callInitHooks(ZoomControl);
}
onAdd(map) {
const zoomName = "maplair-control-zoom", container = create("div", `${zoomName} maplair-bar`), options = this.options;
this._zoomInButton = this._createButton(options.zoomInText, I18n.translate("Zoom in"), `${zoomName}-in`, container, this._zoomIn);
this._zoomOutButton = this._createButton(options.zoomOutText, I18n.translate("Zoom out"), `${zoomName}-out`, container, this._zoomOut);
this._updateDisabled();
map.on("zoomend zoomlevelschange", this._updateDisabled, this);
return container;
}
onRemove(map) {
map.off("zoomend zoomlevelschange", this._updateDisabled, this);
}
disable() {
this._disabled = true;
this._updateDisabled();
return this;
}
enable() {
this._disabled = false;
this._updateDisabled();
return this;
}
_zoomIn(e) {
if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
}
_zoomOut(e) {
if (!this._disabled && this._map._zoom > this._map.getMinZoom()) this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
}
_createButton(html, title, className, container, fn) {
const link = create("a", className, container);
link.innerHTML = html;
link.href = "#";
link.title = title;
link.setAttribute("role", "button");
link.setAttribute("aria-label", title);
disableClickPropagation(link);
on(link, "click", stop);
on(link, "click", fn, this);
on(link, "click", this._refocusOnMap, this);
return link;
}
_updateDisabled() {
const map = this._map, className = "maplair-disabled";
this._zoomInButton.classList.remove(className);
this._zoomOutButton.classList.remove(className);
this._zoomInButton.setAttribute("aria-disabled", "false");
this._zoomOutButton.setAttribute("aria-disabled", "false");
if (this._disabled || map._zoom === map.getMinZoom()) {
this._zoomOutButton.classList.add(className);
this._zoomOutButton.setAttribute("aria-disabled", "true");
}
if (this._disabled || map._zoom === map.getMaxZoom()) {
this._zoomInButton.classList.add(className);
this._zoomInButton.setAttribute("aria-disabled", "true");
}
}
static register(MaplairMap) {
MaplairMap.mergeOptions({ zoomControl: true });
MaplairMap.addInitHook(function() {
if (this.options.zoomControl) {
this.zoomControl = new ZoomControl();
this.addControl(this.zoomControl);
}
});
}
};
//#endregion
//#region src/core/Events.js
var Evented = class Evented extends Class {
on(types, fn, context) {
if (typeof types === "object") for (const [type, f] of Object.entries(types)) this._on(type, f, fn);
else for (const type of splitWords(types)) this._on(type, fn, context);
return this;
}
off(types, fn, context) {
if (!arguments.length) {
delete this._events;
delete this._listenerIndex;
} else if (typeof types === "object") for (const [type, f] of Object.entries(types)) this._off(type, f, fn);
else {
const removeAll = arguments.length === 1;
for (const type of splitWords(types)) if (removeAll) this._off(type);
else this._off(type, fn, context);
}
return this;
}
static __REMOVED_EVENTS = [
"mousedown",
"mouseup",
"mouseover",
"mouseout",
"mousemove"
];
_on(type, fn, context, _once) {
if (Evented.__REMOVED_EVENTS.includes(type)) console.error(`The event ${type} has been removed. Use the PointerEvent variant instead.`);
if (typeof fn !== "function") {
console.warn(`wrong listener type: ${typeof fn}`);
return;
}
if (context === this) context = void 0;
const contexts = this._getListenerIndex(type).get(fn);
if (contexts?.has(context)) return;
const newListener = {
fn,
ctx: context
};
if (_once) newListener.once = true;
this._events ??= {};
this._events[type] ??= /* @__PURE__ */ new Set();
this._events[type].add(newListener);
if (contexts) contexts.set(context, newListener);
else this._listenerIndex[type].set(fn, /* @__PURE__ */ new Map([[context, newListener]]));
}
_getListenerIndex(type) {
this._listenerIndex ??= {};
return this._listenerIndex[type] ??= /* @__PURE__ */ new Map();
}
_off(type, fn, context) {
if (!this._events) return;
let listeners = this._events[type];
if (!listeners) return;
if (arguments.length === 1) {
if (this._firingCount) for (const listener of listeners) listener.fn = falseFn;
delete this._events[type];
delete this._listenerIndex?.[type];
return;
}
if (typeof fn !== "function") {
console.warn(`wrong listener type: ${typeof fn}`);
return;
}
if (context === this) context = void 0;
const contexts = this._listenerIndex?.[type]?.get(fn), listener = contexts?.get(context);
if (listener) {
contexts.delete(context);
if (!contexts.size) this._listenerIndex[type].delete(fn);
if (this._firingCount) {
listener.fn = falseFn;
this._events[type] = listeners = new Set(listeners);
}
listeners.delete(listener);
}
}
fire(type, data, propagate) {
if (!this.listens(type, propagate)) return this;
const event = {
...data,
type,
target: this,
sourceTarget: data?.sourceTarget || this
};
if (this._events) {
const listeners = this._events[type];
if (listeners) {
this._firingCount = this._firingCount + 1 || 1;
for (const l of listeners) {
const fn = l.fn;
if (l.once) this.off(type, fn, l.ctx);
fn.call(l.ctx || this, event);
}
this._firingCount--;
}
}
if (propagate) this._propagateEvent(event);
return this;
}
listens(type, fn, context, propagate) {
if (typeof type !== "string") console.warn("\"string\" type argument expected");
let _fn = fn;
if (typeof fn !== "function") {
propagate = !!fn;
_fn = void 0;
context = void 0;
}
if (this._events?.[type]?.size) {
if (this._listens(type, _fn, context) !== false) return true;
}
if (propagate) {
for (const p of Object.values(this._eventParents ?? {})) if (p.listens(type, fn, context, propagate)) return true;
}
return false;
}
_listens(type, fn, context) {
if (!this._events) return false;
const listeners = this._events[type];
if (!fn) return !!listeners?.size;
if (context === this) context = void 0;
return !!this._listenerIndex?.[type]?.get(fn)?.has(context);
}
once(types, fn, context) {
if (typeof types === "object") for (const [type, f] of Object.entries(types)) this._on(type, f, fn, true);
else for (const type of splitWords(types)) this._on(type, fn, context, true);
return this;
}
addEventParent(obj) {
this._eventParents ??= {};
this._eventParents[stamp(obj)] = obj;
return this;
}
removeEventParent(obj) {
if (this._eventParents) delete this._eventParents[stamp(obj)];
return this;
}
_propagateEvent(e) {
for (const p of Object.values(this._eventParents ?? {})) p.fire(e.type, {
propagatedFrom: e.target,
...e
}, true);
}
};
//#endregion
//#region src/core/Handler.js
var Handler = class Handler extends Class {
constructor(map) {
super();
this._map = map;
this.callInitHooks(Handler);
}
enable() {
if (this._enabled) return this;
this._enabled = true;
this.addHooks();
return this;
}
disable() {
if (!this._enabled) return this;
this._enabled = false;
this.removeHooks();
return this;
}
enabled() {
return !!this._enabled;
}
};
Handler.addTo = function(map, name) {
map.addHandler(name, this);
return this;
};
//#endregion
//#region src/dom/PosAnimation.js
var PosAnimation = class PosAnimation extends Evented {
constructor(...args) {
super(...args);
this.callInitHooks(PosAnimation);
}
run(el, newPos, duration, easeLinearity) {
this.stop();
this._el = el;
this._inProgress = true;
this._duration = duration ?? .25;
this._easeOutPower = 1 / Math.max(easeLinearity ?? .5, .2);
this._startPos = getPosition(el);
this._offset = newPos.subtract(this._startPos);
this._startTime = Date.now();
this.fire("start");
this._animate();
}
stop() {
if (!this._inProgress) return;
this._step(true);
this._complete();
}
_animate() {
this._animId = requestAnimationFrame(this._animate.bind(this));
this._step();
}
_step(round) {
const elapsed = Date.now() - this._startTime, duration = this._duration * 1e3;
if (elapsed < duration) this._runFrame(this._easeOut(elapsed / duration), round);
else {
this._runFrame(1);
this._complete();
}
}
_runFrame(progress, round) {
const pos = this._startPos.add(this._offset.multiplyBy(progress));
if (round) pos._round();
setPosition(this._el, pos);
this.fire("step");
}
_complete() {
cancelAnimationFrame(this._animId);
this._inProgress = false;
this.fire("end");
}
_easeOut(t) {
return 1 - (1 - t) ** this._easeOutPower;
}
};
//#endregion
//#region src/dom/Draggable.js
var Draggable = class Draggable extends Evented {
static {
this.setDefaultOptions({ clickTolerance: 3 });
}
constructor(element, dragStartTarget, preventOutline, options) {
super();
setOptions(this, options);
this._element = element;
this._dragStartTarget = dragStartTarget ?? element;
this._preventOutline = preventOutline;
this.callInitHooks(Draggable);
}
enable() {
if (this._enabled) return;
on(this._dragStartTarget, "pointerdown", this._onDown, this);
this._enabled = true;
}
disable() {
if (!this._enabled) return;
if (Draggable._dragging === this) this.finishDrag(true);
off(this._dragStartTarget, "pointerdown", this._onDown, this);
this._enabled = false;
this._moved = false;
}
_onDown(e) {
this._moved = false;
if (this._element.classList.contains("maplair-zoom-anim")) return;
if (getPointers().length !== 1) {
if (Draggable._dragging === this) this.finishDrag();
return;
}
if (Draggable._dragging || e.shiftKey || e.button !== 0 && e.pointerType !== "touch") return;
Draggable._dragging = this;
if (this._preventOutline) preventOutline(this._element);
disableImageDrag();
disableTextSelection();
if (this._moving) return;
this.fire("down");
const sizedParent = getSizedParentNode(this._element);
this._startPoint = new Point(e.clientX, e.clientY);
this._startPos = getPosition(this._element);
this._parentScale = getScale(sizedParent);
on(this._dragStartTarget, "pointermove", this._onMove, this);
on(this._dragStartTarget, "pointerup pointercancel", this._onUp, this);
}
_onMove(e) {
if (getPointers().length > 1) {
this._moved = true;
return;
}
const offset = new Point(e.clientX, e.clientY)._subtract(this._startPoint);
if (!offset.x && !offset.y) return;
if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) return;
offset.x /= this._parentScale.x;
offset.y /= this._parentScale.y;
if (e.cancelable) e.preventDefault();
if (!this._moved) {
this.fire("dragstart");
this._moved = true;
this._element.ownerDocument.body.classList.add("maplair-dragging");
this._lastTarget = e.target ?? e.srcElement;
this._lastTarget.classList.add("maplair-drag-target");
}
this._newPos = this._startPos.add(offset);
this._moving = true;
this._lastEvent = e;
this._updatePosition();
}
_updatePosition() {
const e = { originalEvent: this._lastEvent };
this.fire("predrag", e);
setPosition(this._element, this._newPos);
this.fire("drag", e);
}
_onUp() {
this.finishDrag();
}
finishDrag(noInertia) {
this._element.ownerDocument.body.classList.remove("maplair-dragging");
if (this._lastTarget) {
this._lastTarget.classList.remove("maplair-drag-target");
this._lastTarget = null;
}
off(this._dragStartTarget, "pointermove", this._onMove, this);
off(this._dragStartTarget, "pointerup pointercancel", this._onUp, this);
enableImageDrag();
enableTextSelection();
const fireDragend = this._moved && this._moving;
this._moving = false;
Draggable._dragging = false;
if (fireDragend) this.fire("dragend", {
noInertia,
distance: this._newPos.distanceTo(this._startPos)
});
}
};
//#endregion
//#region src/geometry/Bounds.js
var Bounds = class Bounds {
constructor(a, b) {
if (!a) return;
if (a instanceof Bounds) {
if (a.isValid()) {
this.min = new Point(a.min);
this.max = new Point(a.max);
}
return;
}
const points = b ? [a, b] : a;
for (const point of points) this.extend(point);
}
extend(obj) {
let min2, max2;
if (!obj) return this;
if (obj instanceof Point || typeof obj[0] === "number" || "x" in obj) min2 = max2 = new Point(obj);
else {
obj = new Bounds(obj);
min2 = obj.min;
max2 = obj.max;
if (!min2 || !max2) return this;
}
if (!this.min && !this.max) {
this.min = new Point(min2);
this.max = new Point(max2);
} else {
this.min.x = Math.min(min2.x, this.min.x);
this.max.x = Math.max(max2.x, this.max.x);
this.min.y = Math.min(min2.y, this.min.y);
this.max.y = Math.max(max2.y, this.max.y);
}
return this;
}
getCenter(round) {
return new Point((this.min.x + this.max.x) / 2, (this.min.y + this.max.y) / 2, round);
}
getBottomLeft() {
return new Point(this.min.x, this.max.y);
}
getTopRight() {
return new Point(this.max.x, this.min.y);
}
getTopLeft() {
return this.min;
}
getBottomRight() {
return this.max;
}
getSize() {
return this.max.subtract(this.min);
}
contains(obj) {
let min, max;
if (typeof obj[0] === "number" || obj instanceof Point) obj = new Point(obj);
else obj = new Bounds(obj);
if (obj instanceof Bounds) {
min = obj.min;
max = obj.max;
} else min = max = obj;
return min.x >= this.min.x && max.x <= this.max.x && min.y >= this.min.y && max.y <= this.max.y;
}
intersects(bounds) {
bounds = new Bounds(bounds);
const min = this.min, max = this.max, min2 = bounds.min, max2 = bounds.max, xIntersects = max2.x >= min.x && min2.x <= max.x, yIntersects = max2.y >= min.y && min2.y <= max.y;
return xIntersects && yIntersects;
}
overlaps(bounds) {
bounds = new Bounds(bounds);
const min = this.min, max = this.max, min2 = bounds.min, max2 = bounds.max, xOverlaps = max2.x > min.x && min2.x < max.x, yOverlaps = max2.y > min.y && min2.y < max.y;
return xOverlaps && yOverlaps;
}
isValid() {
return !!(this.min && this.max);
}
pad(bufferRatio) {
const min = this.min, max = this.max, heightBuffer = Math.abs(min.x - max.x) * bufferRatio, widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
return new Bounds(new Point(min.x - heightBuffer, min.y - widthBuffer), new Point(max.x + heightBuffer, max.y + widthBuffer));
}
equals(bounds) {
if (!bounds) return false;
bounds = new Bounds(bounds);
return this.min.equals(bounds.getTopLeft()) && this.max.equals(bounds.getBottomRight());
}
};
//#endregion
//#region src/geometry/Transformation.js
var Transformation = class {
constructor(a, b, c, d) {
if (Array.isArray(a)) {
this._a = a[0];
this._b = a[1];
this._c = a[2];
this._d = a[3];
return;
}
this._a = a;
this._b = b;
this._c = c;
this._d = d;
}
transform(point, scale) {
return this._transform(new Point(point), scale);
}
_transform(point, scale) {
scale ||= 1;
point.x = scale * (this._a * point.x + this._b);
point.y = scale * (this._c * point.y + this._d);
return point;
}
untransform(point, scale) {
scale ||= 1;
return new Point((point.x / scale - this._b) / this._a, (point.y / scale - this._d) / this._c);
}
};
//#endregion
//#region src/geo/LatLngBounds.js
var LatLngBounds = class LatLngBounds {
constructor(corner1, corner2) {
if (!corner1) return;
if (corner1 instanceof LatLngBounds) {
if (corner1.isValid()) {
this._southWest = new LatLng(corner1._southWest);
this._northEast = new LatLng(corner1._northEast);
}
return;
}
const latlngs = corner2 ? [corner1, corner2] : corner1;
for (const latlng of latlngs) this.extend(latlng);
}
extend(obj) {
const sw = this._southWest, ne = this._northEast;
let sw2, ne2;
if (obj instanceof LatLng) {
sw2 = obj;
ne2 = obj;
} else if (obj instanceof LatLngBounds) {
sw2 = obj._southWest;
ne2 = obj._northEast;
if (!sw2 || !ne2) return this;
} else {
if (!obj) return this;
if (LatLng.validate(obj)) return this.extend(new LatLng(obj));
return this.extend(new LatLngBounds(obj));
}
if (!sw && !ne) {
this._southWest = new LatLng(sw2.lat, sw2.lng);
this._northEast = new LatLng(ne2.lat, ne2.lng);
} else {
sw.lat = Math.min(sw2.lat, sw.lat);
sw.lng = Math.min(sw2.lng, sw.lng);
ne.lat = Math.max(ne2.lat, ne.lat);
ne.lng = Math.max(ne2.lng, ne.lng);
}
return this;
}
pad(bufferRatio) {
const sw = this._southWest, ne = this._northEast, heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new LatLngBounds(new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
}
getCenter() {
return new LatLng((this._southWest.lat + this._northEast.lat) / 2, (this._southWest.lng + this._northEast.lng) / 2);
}
getSouthWest() {
return this._southWest;
}
getNorthEast() {
return this._northEast;
}
getNorthWest() {
return new LatLng(this.getNorth(), this.getWest());
}
getSouthEast() {
return new LatLng(this.getSouth(), this.getEast());
}
getWest() {
return this._southWest.lng;
}
getSouth() {
return this._southWest.lat;
}
getEast() {
return this._northEast.lng;
}
getNorth() {
return this._northEast.lat;
}
contains(obj) {
if (LatLng.validate(obj)) obj = new LatLng(obj);
else obj = new LatLngBounds(obj);
const sw = this._southWest, ne = this._northEast;
let sw2, ne2;
if (obj instanceof LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else sw2 = ne2 = obj;
return sw2.lat >= sw.lat && ne2.lat <= ne.lat && sw2.lng >= sw.lng && ne2.lng <= ne.lng;
}
intersects(bounds) {
bounds = new LatLngBounds(bounds);
const sw = this._southWest, ne = this._northEast, sw2 = bounds.getSouthWest(), ne2 = bounds.getNorthEast(), latIntersects = ne2.lat >= sw.lat && sw2.lat <= ne.lat, lngIntersects = ne2.lng >= sw.lng && sw2.lng <= ne.lng;
return latIntersects && lngIntersects;
}
overlaps(bounds) {
bounds = new LatLngBounds(bounds);
const sw = this._southWest, ne = this._northEast, sw2 = bounds.getSouthWest(), ne2 = bounds.getNorthEast(), latOverlaps = ne2.lat > sw.lat && sw2.lat < ne.lat, lngOverlaps = ne2.lng > sw.lng && sw2.lng < ne.lng;
return latOverlaps && lngOverlaps;
}
toBBoxString() {
return [
this.getWest(),
this.getSouth(),
this.getEast(),
this.getNorth()
].join(",");
}
equals(bounds, maxMargin) {
if (!bounds) return false;
bounds = new LatLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest(), maxMargin) && this._northEast.equals(bounds.getNorthEast(), maxMargin);
}
isValid() {
return !!(this._southWest && this._northEast);
}
};
//#endregion
//#region src/geo/crs/CRS.js
var CRS = class {
static projection = void 0;
static transformation = void 0;
static latLngToPoint(latlng, zoom) {
const projectedPoint = this.projection.project(latlng), scale = this.scale(zoom);
return this.transformation._transform(projectedPoint, scale);
}
static pointToLatLng(point, zoom) {
const scale = this.scale(zoom), untransformedPoint = this.transformation.untransform(point, scale);
return this.projection.unproject(untransformedPoint);
}
static project(latlng) {
return this.projection.project(latlng);
}
static unproject(point) {
return this.projection.unproject(point);
}
static scale(zoom) {
return 256 * 2 ** zoom;
}
static zoom(scale) {
return Math.log(scale / 256) / Math.LN2;
}
static getProjectedBounds(zoom) {
if (this.infinite) return null;
const b = this.projection.bounds, s = this.scale(zoom);
return new Bounds(this.transformation.transform(b.min, s), this.transformation.transform(b.max, s));
}
static infinite = false;
static wrapLatLng(latlng) {
latlng = new LatLng(latlng);
const lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, alt = latlng.alt;
return new LatLng(lat, lng, alt);
}
static wrapLatLngBounds(bounds) {
bounds = new LatLngBounds(bounds);
const center = bounds.getCenter(), newCenter = this.wrapLatLng(center), latShift = center.lat - newCenter.lat, lngShift = center.lng - newCenter.lng;
if (latShift === 0 && lngShift === 0) return bounds;
const sw = bounds.getSouthWest(), ne = bounds.getNorthEast();
return new LatLngBounds(new LatLng(sw.lat - latShift, sw.lng - lngShift), new LatLng(ne.lat - latShift, ne.lng - lngShift));
}
};
//#endregion
//#region src/geo/crs/EarthCRS.js
var EarthCRS = class extends CRS {
static wrapLng = [-180, 180];
static R = 6371e3;
static distance(latlng1, latlng2) {
const rad = Math.PI / 180, lat1 = latlng1.lat * rad, lat2 = latlng2.lat * rad, sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return this.R * c;
}
};
//#endregion
//#region src/geo/LatLng.js
var LatLng = class LatLng {
constructor(lat, lng, alt) {
if (!LatLng.validate(lat, lng, alt)) throw new Error(`Invalid LatLng object: (${lat}, ${lng})`);
let _lat, _lng, _alt;
if (lat instanceof LatLng) {
_lat = lat.lat;
_lng = lat.lng;
_alt = lat.alt;
} else if (Array.isArray(lat) && typeof lat[0] !== "object") {
if (lat.length === 3) {
_lat = lat[0];
_lng = lat[1];
_alt = lat[2];
} else if (lat.length === 2) {
_lat = lat[0];
_lng = lat[1];
}
} else if (typeof lat === "object" && "lat" in lat) {
_lat = lat.lat;
_lng = "lng" in lat ? lat.lng : lat.lon;
_alt = lat.alt;
} else {
_lat = lat;
_lng = lng;
_alt = alt;
}
this.lat = +_lat;
this.lng = +_lng;
if (_alt !== void 0) this.alt = +_alt;
}
static validate(lat, lng, alt) {
if (lat instanceof LatLng || typeof lat === "object" && lat !== null && "lat" in lat) return true;
else if (lat && Array.isArray(lat) && typeof lat[0] !== "object") {
if (lat.length === 3 || lat.length === 2) return true;
return false;
} else if ((lat || lat === 0) && (lng || lng === 0)) return true;
return false;
}
equals(obj, maxMargin) {
if (!obj) return false;
obj = new LatLng(obj);
return Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng)) <= (maxMargin ?? 1e-9);
}
toString(precision) {
return `LatLng(${formatNum(this.lat, precision)}, ${formatNum(this.lng, precision)})`;
}
distanceTo(other) {
return EarthCRS.distance(this, new LatLng(other));
}
wrap() {
return EarthCRS.wrapLatLng(this);
}
toBounds(sizeInMeters) {
const latAccuracy = 180 * sizeInMeters / 40075017, lngAccuracy = latAccuracy / Math.cos(Math.PI / 180 * this.lat);
return new LatLngBounds([this.lat - latAccuracy, this.lng - lngAccuracy], [this.lat + latAccuracy, this.lng + lngAccuracy]);
}
};
//#endregion
//#region src/geometry/PolyUtil.js
var PolyUtil_exports = /* @__PURE__ */ __exportAll({
centroid: () => centroid,
clipPolygon: () => clipPolygon,
polygonCenter: () => polygonCenter
});
function clipPolygon(points, bounds, round) {
let clippedPoints, i, j, k, a, b, len, edge, p;
const edges = [
1,
4,
2,
8
];
for (i = 0, len = points.length; i < len; i++) points[i]._code = _getBitCode(points[i], bounds);
for (k = 0; k < 4; k++) {
edge = edges[k];
clippedPoints = [];
for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
a = points[i];
b = points[j];
if (!(a._code & edge)) {
if (b._code & edge) {
p = _getEdgeIntersection(b, a, edge, bounds, round);
p._code = _getBitCode(p, bounds);
clippedPoints.push(p);
}
clippedPoints.push(a);
} else if (!(b._code & edge)) {
p = _getEdgeIntersection(b, a, edge, bounds, round);
p._code = _getBitCode(p, bounds);
clippedPoints.push(p);
}
}
points = clippedPoints;
}
return points;
}
function polygonCenter(latlngs, crs) {
let i, j, p1, p2, f, area, x, y, center;
if (!latlngs || latlngs.length === 0) throw new Error("latlngs not passed");
if (!isFlat(latlngs)) {
console.warn("latlngs are not flat! Only the first ring will be used");
latlngs = latlngs[0];
}
let centroidLatLng = new LatLng([0, 0]);
const bounds = new LatLngBounds(latlngs);
if (bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()) < 1700) centroidLatLng = centroid(latlngs);
const len = latlngs.length;
const points = [];
for (i = 0; i < len; i++) {
const latlng = new LatLng(latlngs[i]);
points.push(crs.project(new LatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
}
area = x = y = 0;
for (i = 0, j = len - 1; i < len; j = i++) {
p1 = points[i];
p2 = points[j];
f = p1.y * p2.x - p2.y * p1.x;
x += (p1.x + p2.x) * f;
y += (p1.y + p2.y) * f;
area += f * 3;
}
if (area === 0) center = points[0];
else center = [x / area, y / area];
const latlngCenter = crs.unproject(new Point(center));
return new LatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
}
function centroid(coords) {
let latSum = 0;
let lngSum = 0;
let len = 0;
for (const coord of coords) {
const latlng = new LatLng(coord);
latSum += latlng.lat;
lngSum += latlng.lng;
len++;
}
return new LatLng([latSum / len, lngSum / len]);
}
//#endregion
//#region src/geometry/LineUtil.js
var LineUtil_exports = /* @__PURE__ */ __exportAll({
_getBitCode: () => _getBitCode,
_getEdgeIntersection: () => _getEdgeIntersection,
_sqClosestPointOnSegment: () => _sqClosestPointOnSegment,
clipSegment: () => clipSegment,
closestPointOnSegment: () => closestPointOnSegment,
isFlat: () => isFlat,
pointToSegmentDistance: () => pointToSegmentDistance,
polylineCenter: () => polylineCenter,
simplify: () => simplify
});
function simplify(points, tolerance) {
if (!tolerance || !points.length) return points.slice();
const sqTolerance = tolerance * tolerance;
points = _reducePoints(points, sqTolerance);
points = _simplifyDP(points, sqTolerance);
return points;
}
function pointToSegmentDistance(p, p1, p2) {
return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
}
function closestPointOnSegment(p, p1, p2) {
return _sqClosestPointOnSegment(p, p1, p2);
}
function _simplifyDP(points, sqTolerance) {
const len = points.length, markers = new Uint8Array(len);
markers[0] = markers[len - 1] = 1;
_simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
let i;
const newPoints = [];
for (i = 0; i < len; i++) if (markers[i]) newPoints.push(points[i]);
return newPoints;
}
function _simplifyDPStep(points, markers, sqTolerance, first, last) {
let maxSqDist = 0, index, i, sqDist;
for (i = first + 1; i <= last - 1; i++) {
sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
_simplifyDPStep(points, markers, sqTolerance, first, index);
_simplifyDPStep(points, markers, sqTolerance, index, last);
}
}
function _reducePoints(points, sqTolerance) {
const reducedPoints = [points[0]];
let prev = 0;
for (let i = 1; i < points.length; i++) if (_sqDist(points[i], points[prev]) > sqTolerance) {
reducedPoints.push(points[i]);
prev = i;
}
if (prev < points.length - 1) reducedPoints.push(points[points.length - 1]);
return reducedPoints;
}
let _lastCode;
function clipSegment(a, b, bounds, useLastCode, round) {
let codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), codeB = _getBitCode(b, bounds), codeOut, p, newCode;
_lastCode = codeB;
while (true) {
if (!(codeA | codeB)) return [a, b];
if (codeA & codeB) return false;
codeOut = codeA || codeB;
p = _getEdgeIntersection(a, b, codeOut, bounds, round);
newCode = _getBitCode(p, bounds);
if (codeOut === codeA) {
a = p;
codeA = newCode;
} else {
b = p;
codeB = newCode;
}
}
}
function _getEdgeIntersection(a, b, code, bounds, round) {
const dx = b.x - a.x, dy = b.y - a.y, min = bounds.min, max = bounds.max;
let x, y;
if (code & 8) {
x = a.x + dx * (max.y - a.y) / dy;
y = max.y;
} else if (code & 4) {
x = a.x + dx * (min.y - a.y) / dy;
y = min.y;
} else if (code & 2) {
x = max.x;
y = a.y + dy * (max.x - a.x) / dx;
} else if (code & 1) {
x = min.x;
y = a.y + dy * (min.x - a.x) / dx;
}
return new Point(x, y, round);
}
function _getBitCode(p, bounds) {
let code = 0;
if (p.x < bounds.min.x) code |= 1;
else if (p.x > bounds.max.x) code |= 2;
if (p.y < bounds.min.y) code |= 4;
else if (p.y > bounds.max.y) code |= 8;
return code;
}
function _sqDist(p1, p2) {
const dx = p2.x - p1.x, dy = p2.y - p1.y;
return dx * dx + dy * dy;
}
function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
let x = p1.x, y = p1.y, dx = p2.x - x, dy = p2.y - y, t;
const dot = dx * dx + dy * dy;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new Point(x, y);
}
function isFlat(latlngs) {
return !Array.isArray(latlngs[0]) || typeof latlngs[0][0] !== "object" && typeof latlngs[0][0] !== "undefined";
}
function polylineCenter(latlngs, crs) {
let i, halfDist, segDist, dist, p1, p2, ratio, center;
if (!latlngs || latlngs.length === 0) throw new Error("latlngs not passed");
if (!isFlat(latlngs)) {
console.warn("latlngs are not flat! Only the first ring will be used");
latlngs = latlngs[0];
}
let centroidLatLng = new LatLng([0, 0]);
const bounds = new LatLngBounds(latlngs);
if (bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()) < 1700) centroidLatLng = centroid(latlngs);
const len = latlngs.length;
const points = [];
for (i = 0; i < len; i++) {
const latlng = new LatLng(latlngs[i]);
points.push(crs.project(new LatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
}
for (i = 0, halfDist = 0; i < len - 1; i++) halfDist += points[i].distanceTo(points[i + 1]) / 2;
if (halfDist === 0) center = points[0];
else for (i = 0, dist = 0; i < len - 1; i++) {
p1 = points[i];
p2 = points[i + 1];
segDist = p1.distanceTo(p2);
dist += segDist;
if (dist > halfDist) {
ratio = (dist - halfDist) / segDist;
center = [p2.x - ratio * (p2.x - p1.x), p2.y - ratio * (p2.y - p1.y)];
break;
}
}
const latlngCenter = crs.unproject(new Point(center));
return new LatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
}
//#endregion
//#region src/geo/projection/Projection.LonLat.js
const LonLat = {
project(latlng) {
latlng = new LatLng(latlng);
return new Point(latlng.lng, latlng.lat);
},
unproject(point) {
point = new Point(point);
return new LatLng(point.y, point.x);
},
bounds: new Bounds([-180, -90], [180, 90])
};
const Mercator = {
R: 6378137,
R_MINOR: 6356752.314245179,
bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
project(latlng) {
latlng = new LatLng(latlng);
const d = Math.PI / 180, r = this.R, tmp = this.R_MINOR / r, e = Math.sqrt(1 - tmp * tmp);
let y = latlng.lat * d;
const con = e * Math.sin(y);
const ts = Math.tan(Math.PI / 4 - y / 2) / ((1 - con) / (1 + con)) ** (e / 2);
y = -r * Math.log(Math.max(ts, 1e-10));
return new Point(latlng.lng * d * r, y);
},
unproject(point) {
point = new Point(point);
const d = 180 / Math.PI, r = this.R, tmp = this.R_MINOR / r, e = Math.sqrt(1 - tmp * tmp), ts = Math.exp(-point.y / r);
let phi = Math.PI / 2 - 2 * Math.atan(ts);
for (let i = 0, dphi = .1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
con = e * Math.sin(phi);
con = ((1 - con) / (1 + con)) ** (e / 2);
dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
phi += dphi;
}
return new LatLng(phi * d, point.x * d / r);
}
};
//#endregion
//#region src/geo/projection/Projection.SphericalMercator.js
const earthRadius = 6378137;
const SphericalMercator = {
R: earthRadius,
MAX_LATITUDE: 85.0511287798,
project(latlng) {
latlng = new LatLng(latlng);
const d = Math.PI / 180, max = this.MAX_LATITUDE, lat = Math.max(Math.min(max, latlng.lat), -max), sin = Math.sin(lat * d);
return new Point(this.R * latlng.lng * d, this.R * Math.log((1 + sin) / (1 - sin)) / 2);
},
unproject(point) {
point = new Point(point);
const d = 180 / Math.PI;
return new LatLng((2 * Math.atan(Math.exp(point.y / this.R)) - Math.PI / 2) * d, point.x * d / this.R);
},
bounds: (() => {
const d = earthRadius * Math.PI;
return new Bounds([-d, -d], [d, d]);
})()
};
//#endregion
//#region src/geo/projection/index.js
var projection_exports = /* @__PURE__ */ __exportAll({
LonLat: () => LonLat,
Mercator: () => Mercator,
SphericalMercator: () => SphericalMercator
});
//#endregion
//#region src/geo/crs/EPSG3395.js
var EPSG3395 = class extends EarthCRS {
static code = "EPSG:3395";
static projection = Mercator;
static transformation = (() => {
const scale = .5 / (Math.PI * Mercator.R);
return new Transformation(scale, .5, -scale, .5);
})();
};
//#endregion
//#region src/geo/crs/EPSG3857.js
var EPSG3857 = class extends EarthCRS {
static code = "EPSG:3857";
static projection = SphericalMercator;
static transformation = (() => {
const scale = .5 / (Math.PI * SphericalMercator.R);
return new Transformation(scale, .5, -scale, .5);
})();
};
var EPSG900913 = class extends EPSG3857 {
static code = "EPSG:900913";
};
//#endregion
//#region src/geo/crs/EPSG4326.js
var EPSG4326 = class extends EarthCRS {
static code = "EPSG:4326";
static projection = LonLat;
static transformation = new Transformation(1 / 180, 1, -1 / 180, .5);
};
//#endregion
//#region src/geo/crs/SimpleCRS.js
var SimpleCRS = class extends CRS {
static projection = LonLat;
static transformation = new Transformation(1, 0, -1, 0);
static scale(zoom) {
return 2 ** zoom;
}
static zoom(scale) {
return Math.log(scale) / Math.LN2;
}
static distance(latlng1, latlng2) {
const dx = latlng2.lng - latlng1.lng, dy = latlng2.lat - latlng1.lat;
return Math.sqrt(dx * dx + dy * dy);
}
static infinite = true;
};
//#endregion
//#region src/layer/Layer.js
var Layer = class extends Evented {
static {
this.setDefaultOptions({
pane: "overlayPane",
attribution: null,
bubblingPointerEvents: true
});
}
addTo(map) {
map.addLayer(this);
return this;
}
remove() {
return this.removeFrom(this._map || this._mapToAdd);
}
removeFrom(obj) {
obj?.removeLayer(this);
return this;
}
getPane(name) {
return this._map.getPane(name ? this.options[name] || name : this.options.pane);
}
addInteractiveTarget(targetEl) {
this._map._targets[stamp(targetEl)] = this;
return this;
}
removeInteractiveTarget(targetEl) {
delete this._map._targets[stamp(targetEl)];
return this;
}
getAttribution() {
return this.options.attribution;
}
_layerAdd(e) {
const map = e.target;
if (!map.hasLayer(this)) return;
this._map = map;
this._zoomAnimated = map._zoomAnimated;
if (this.getEvents) {
const events = this.getEvents();
map.on(events, this);
this.once("remove", () => map.off(events, this));
}
this.onAdd(map);
this.fire("add");
map.fire("layeradd", { layer: this });
}
static register(MaplairMap) {
MaplairMap.include({
addLayer(layer) {
if (!layer._layerAdd) throw new Error("The provided object is not a Layer.");
const id = stamp(layer);
if (this._layers[id]) return this;
this._layers[id] = layer;
layer._mapToAdd = this;
if (layer.beforeAdd) layer.beforeAdd(this);
this.whenReady(layer._layerAdd, layer);
return this;
},
removeLayer(layer) {
const id = stamp(layer);
if (!this._layers[id]) return this;
if (this._loaded) layer.onRemove(this);
delete this._layers[id];
if (this._loaded) {
this.fire("layerremove", { layer });
layer.fire("remove");
}
layer._map = layer._mapToAdd = null;
return this;
},
hasLayer(layer) {
return stamp(layer) in this._layers;
},
eachLayer(method, context) {
for (const layer of Object.values(this._layers)) method.call(context, layer);
return this;
},
_addLayers(layers) {
layers = layers ? Array.isArray(layers) ? layers : [layers] : [];
for (const layer of layers) this.addLayer(layer);
},
_addZoomLimit(layer) {
if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
this._zoomBoundLayers[stamp(layer)] = layer;
this._updateZoomLevels();
}
},
_removeZoomLimit(layer) {
const id = stamp(layer);
if (this._zoomBoundLayers[id]) {
delete this._zoomBoundLayers[id];
this._updateZoomLevels();
}
},
_updateZoomLevels() {
let minZoom = Infinity, maxZoom = -Infinity;
const oldZoomSpan = this._getZoomSpan();
for (const l of Object.values(this._zoomBoundLayers)) {
const options = l.options;
minZoom = Math.min(minZoom, options.minZoom ?? Infinity);
maxZoom = Math.max(maxZoom, options.maxZoom ?? -Infinity);
}
this._layersMaxZoom = maxZoom === -Infinity ? void 0 : maxZoom;
this._layersMinZoom = minZoom === Infinity ? void 0 : minZoom;
if (oldZoomSpan !== this._getZoomSpan()) this.fire("zoomlevelschange");
if (this.options.maxZoom === void 0 && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) this.setZoom(this._layersMaxZoom);
if (this.options.minZoom === void 0 && this._layersMinZoom && this.getZoom() < this._layersMinZoom) this.setZoom(this._layersMinZoom);
}
});
}
};
//#endregion
//#region src/layer/LayerGroup.js
var LayerGroup = class LayerGroup extends Layer {
constructor(layers, options) {
super();
setOptions(this, options);
this._layers = {};
for (const layer of layers ?? []) this.addLayer(layer);
this.callInitHooks(LayerGroup);
}
addLayer(layer) {
const id = this.getLayerId(layer);
this._layers[id] = layer;
this._map?.addLayer(layer);
return this;
}
removeLayer(layer) {
const id = layer in this._layers ? layer : this.getLayerId(layer);
if (this._map && this._layers[id]) this._map.removeLayer(this._layers[id]);
delete this._layers[id];
return this;
}
hasLayer(layer) {
return (typeof layer === "number" ? layer : this.getLayerId(layer)) in this._layers;
}
clearLayers() {
return this.eachLayer(this.removeLayer, this);
}
onAdd(map) {
this.eachLayer(map.addLayer, map);
}
onRemove(map) {
this.eachLayer(map.removeLayer, map);
}
eachLayer(method, context) {
for (const layer of Object.values(this._layers)) method.call(context, layer);
return this;
}
getLayer(id) {
return this._layers[id];
}
getLayers() {
const layers = [];
this.eachLayer(layers.push, layers);
return layers;
}
setZIndex(zIndex) {
return this.eachLayer((l) => l.setZIndex?.(zIndex));
}
getLayerId(layer) {
return stamp(layer);
}
};
//#endregion
//#region src/layer/FeatureGroup.js
var FeatureGroup = class FeatureGroup extends LayerGroup {
constructor(...args) {
super(...args);
this.callInitHooks(FeatureGroup);
}
addLayer(layer) {
if (this.hasLayer(layer)) return this;
layer.addEventParent(this);
super.addLayer(layer);
return this.fire("layeradd", { layer });
}
removeLayer(layer) {
if (!this.hasLayer(layer)) return this;
if (layer in this._layers) layer = this._layers[layer];
layer.removeEventParent(this);
super.removeLayer(layer);
return this.fire("layerremove", { layer });
}
setStyle(style) {
return this.eachLayer((l) => l.setStyle?.(style));
}
bringToFront() {
return this.eachLayer((l) => l.bringToFront?.());
}
bringToBack() {
return this.eachLayer((l) => l.bringToBack?.());
}
getBounds() {
const bounds = new LatLngBounds();
for (const layer of Object.values(this._layers)) bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
return bounds;
}
};
//#endregion
//#region src/layer/marker/Icon.js
var Icon = class Icon extends Class {
static {
this.setDefaultOptions({
popupAnchor: [0, 0],
tooltipAnchor: [0, 0],
crossOrigin: false
});
}
constructor(options) {
super();
setOptions(this, options);
this.callInitHooks(Icon);
}
createIcon(oldIcon) {
return this._createIcon("icon", oldIcon);
}
createShadow(oldIcon) {
return this._createIcon("shadow", oldIcon);
}
_createIcon(name, oldIcon) {
const src = this._getIconUrl(name);
if (!src) {
if (name === "icon") throw new Error("iconUrl not set in Icon options (see the docs).");
return null;
}
const img = this._createImg(src, oldIcon && oldIcon.tagName === "IMG" ? oldIcon : null);
this._setIconStyles(img, name);
if (this.options.crossOrigin || this.options.crossOrigin === "") img.crossOrigin = this.options.crossOrigin === true ? "" : this.options.crossOrigin;
return img;
}
_setIconStyles(img, name) {
const options = this.options;
let sizeOption = options[`${name}Size`];
if (typeof sizeOption === "number") sizeOption = [sizeOption, sizeOption];
const size = Point.validate(sizeOption) && new Point(sizeOption);
const anchorPosition = name === "shadow" && options.shadowAnchor || options.iconAnchor || size && size.divideBy(2, true);
const anchor = Point.validate(anchorPosition) && new Point(anchorPosition);
img.className = `maplair-marker-${name} ${options.className || ""}`;
if (anchor) {
img.style.marginLeft = `${-anchor.x}px`;
img.style.marginTop = `${-anchor.y}px`;
}
if (size) {
img.style.width = `${size.x}px`;
img.style.height = `${size.y}px`;
}
}
_createImg(src, el) {
el ??= document.createElement("img");
el.src = src;
return el;
}
_getIconUrl(name) {
return Browser_default.retina && this.options[`${name}RetinaUrl`] || this.options[`${name}Url`];
}
};
//#endregion
//#region src/layer/marker/DefaultIcon.js
const URL_RE = /^url\((['"])?(.+)\1\)$/;
const MARKER_ICON_RE = /^(.*)marker-icon\.svg$/;
var DefaultIcon = class DefaultIcon extends Icon {
static {
this.setDefaultOptions({
iconUrl: "marker-icon.svg",
iconRetinaUrl: "marker-icon.svg",
shadowUrl: "marker-shadow.svg",
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41]
});
}
constructor(...args) {
super(...args);
this.callInitHooks(DefaultIcon);
}
_getIconUrl(name) {
if (!DefaultIcon.imagePath) DefaultIcon.imagePath = this._detectIconPath();
const url = super._getIconUrl(name);
if (!url) return null;
return (this.options.imagePath ?? DefaultIcon.imagePath) + url;
}
_stripUrl(path) {
const strip = function(str, re, idx) {
const match = re.exec(str);
return match && match[idx];
};
path = strip(path, URL_RE, 2);
return path && strip(path, MARKER_ICON_RE, 1);
}
_detectIconPath() {
const el = create("div", "maplair-default-icon-path", document.body);
const path = this._stripUrl(getComputedStyle(el).backgroundImage);
document.body.removeChild(el);
if (path) return path;
const link = document.querySelector("link[href$=\"maplair.css\"]");
if (!link) return "";
return link.href.substring(0, link.href.length - 11 - 1);
}
};
//#endregion
//#region src/layer/marker/Marker.Drag.js
var MarkerDrag = class MarkerDrag extends Handler {
constructor(marker) {
super(marker._map);
this._marker = marker;
this.callInitHooks(MarkerDrag);
}
addHooks() {
const icon = this._marker._icon;
if (!this._draggable) this._draggable = new Draggable(icon, icon, true);
this._draggable.on({
dragstart: this._onDragStart,
predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).enable();
icon.classList.add("maplair-marker-draggable");
}
removeHooks() {
this._draggable.off({
dragstart: this._onDragStart,
predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).disable();
this._marker._icon?.classList.remove("maplair-marker-draggable");
}
moved() {
return this._draggable?._moved;
}
_adjustPan(e) {
const marker = this._marker, map = marker._map, speed = this._marker.options.autoPanSpeed, padding = this._marker.options.autoPanPadding, iconPos = getPosition(marker._icon), bounds = map.getPixelBounds(), origin = map.getPixelOrigin();
const panBounds = new Bounds(bounds.min._subtract(origin).add(padding), bounds.max._subtract(origin).subtract(padding));
if (!panBounds.contains(iconPos)) {
const movement = new Point((Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)).multiplyBy(speed);
map.panBy(movement, { animate: false });
this._draggable._newPos._add(movement);
this._draggable._startPos._add(movement);
setPosition(marker._icon, this._draggable._newPos);
this._onDrag(e);
this._panRequest = requestAnimationFrame(this._adjustPan.bind(this, e));
}
}
_onDragStart() {
this._oldLatLng = this._marker.getLatLng();
this._marker.closePopup?.();
this._marker.fire("movestart").fire("dragstart");
}
_onPreDrag(e) {
if (this._marker.options.autoPan) {
cancelAnimationFrame(this._panRequest);
this._panRequest = requestAnimationFrame(this._adjustPan.bind(this, e));
}
}
_onDrag(e) {
const marker = this._marker, shadow = marker._shadow, iconPos = getPosition(marker._icon), latlng = marker._map.layerPointToLatLng(iconPos);
if (shadow) setPosition(shadow, iconPos);
marker._latlng = latlng;
e.latlng = latlng;
e.oldLatLng = this._oldLatLng;
marker.fire("move", e).fire("drag", e);
}
_onDragEnd(e) {
cancelAnimationFrame(this._panRequest);
delete this._oldLatLng;
this._marker.fire("moveend").fire("dragend", e);
}
};
//#endregion
//#region src/layer/marker/Marker.js
var Marker = class Marker extends Layer {
static {
this.setDefaultOptions({
icon: new DefaultIcon(),
interactive: true,
keyboard: true,
title: "",
alt: "Marker",
zIndexOffset: 0,
opacity: 1,
riseOnHover: false,
riseOffset: 250,
pane: "markerPane",
shadowPane: "shadowPane",
shadow: true,
bubblingPointerEvents: false,
autoPanOnFocus: true,
draggable: false,
autoPan: false,
autoPanPadding: [50, 50],
autoPanSpeed: 10
});
}
constructor(latlng, options) {
super();
setOptions(this, options);
this._latlng = new LatLng(latlng);
this.callInitHooks(Marker);
}
onAdd(map) {
this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
if (this._zoomAnimated) map.on("zoomanim", this._animateZoom, this);
this._initIcon();
this.update();
if (map.options.markerCulling) this._updateCulling();
}
onRemove(map) {
if (this.dragging?.enabled()) {
this.options.draggable = true;
this.dragging.removeHooks();
}
delete this.dragging;
if (this._zoomAnimated) map.off("zoomanim", this._animateZoom, this);
this._removeIcon();
this._removeShadow();
this._culled = false;
}
getEvents() {
const events = {
zoom: this.update,
viewreset: this.update
};
if (this._map?.options.markerCulling) events.moveend = this._updateCulling;
return events;
}
getLatLng() {
return this._latlng;
}
setLatLng(latlng) {
const oldLatLng = this._latlng;
this._latlng = new LatLng(latlng);
if (this._map?.options.markerCulling) this._updateCulling();
this.update();
return this.fire("move", {
oldLatLng,
latlng: this._latlng
});
}
setZIndexOffset(offset) {
this.options.zIndexOffset = offset;
return this.update();
}
getIcon() {
return this.options.icon;
}
setIcon(icon) {
this.options.icon = icon;
if (this._map) {
this._initIcon();
this.update();
}
if (this._popup) this.bindPopup(this._popup, this._popup.options);
return this;
}
getElement() {
return this._icon;
}
update() {
if (this._icon && this._map && !this._culled) {
const pos = this._map.latLngToLayerPoint(this._latlng).round();
this._setPos(pos);
}
return this;
}
_initIcon() {
const options = this.options, classToAdd = `maplair-zoom-${this._zoomAnimated ? "animated" : "hide"}`;
const icon = options.icon.createIcon(this._icon);
let addIcon = false;
if (icon !== this._icon) {
if (this._icon) this._removeIcon();
addIcon = true;
if (options.title) icon.title = options.title;
if (icon.tagName === "IMG") icon.alt = options.alt ?? "";
}
icon.classList.add(classToAdd);
if (options.keyboard) {
icon.tabIndex = "0";
icon.setAttribute("role", "button");
}
this._icon = icon;
if (options.riseOnHover) this.on({
pointerover: this._bringToFront,
pointerout: this._resetZIndex
});
if (this.options.autoPanOnFocus) on(icon, "focus", this._panOnFocus, this);
const newShadow = options.shadow ? options.icon.createShadow(this._shadow) : null;
let addShadow = false;
if (newShadow !== this._shadow) {
this._removeShadow();
addShadow = true;
}
if (newShadow) {
newShadow.classList.add(classToAdd);
newShadow.alt = "";
}
this._shadow = newShadow;
if (options.opacity < 1) this._updateOpacity();
if (addIcon) this.getPane().appendChild(this._icon);
this._initInteraction();
if (newShadow && addShadow) this.getPane(options.shadowPane).appendChild(this._shadow);
}
_removeIcon() {
if (this.options.riseOnHover) this.off({
pointerover: this._bringToFront,
pointerout: this._resetZIndex
});
if (this.options.autoPanOnFocus) off(this._icon, "focus", this._panOnFocus, this);
this._icon.remove();
this.removeInteractiveTarget(this._icon);
this._icon = null;
}
_updateCulling() {
if (!this._icon || !this._map) return;
const visible = this._map.getBounds().pad(.5).contains(this._latlng) || !!this.dragging?._draggable?._moving || this._icon.contains(document.activeElement);
if (visible === !this._culled) return;
this._culled = !visible;
if (visible) {
this.getPane().appendChild(this._icon);
if (this._shadow) this.getPane(this.options.shadowPane).appendChild(this._shadow);
this.update();
} else {
this._icon.remove();
this._shadow?.remove();
}
}
_removeShadow() {
this._shadow?.remove();
this._shadow = null;
}
_setPos(pos) {
if (this._icon) setPosition(this._icon, pos);
if (this._shadow) setPosition(this._shadow, pos);
this._zIndex = pos.y + this.options.zIndexOffset;
this._resetZIndex();
}
_updateZIndex(offset) {
const zIndex = String(this._zIndex + offset);
if (this._icon && this._icon.style.zIndex !== zIndex) this._icon.style.zIndex = zIndex;
}
_animateZoom(opt) {
if (this._culled) return;
const pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
this._setPos(pos);
}
_initInteraction() {
if (!this.options.interactive) return;
this._icon.classList.add("maplair-interactive");
this.addInteractiveTarget(this._icon);
if (MarkerDrag) {
let draggable = this.options.draggable;
if (this.dragging) {
draggable = this.dragging.enabled();
this.dragging.disable();
}
this.dragging = new MarkerDrag(this);
if (draggable) this.dragging.enable();
}
}
setOpacity(opacity) {
this.options.opacity = opacity;
if (this._map) this._updateOpacity();
return this;
}
_updateOpacity() {
const opacity = this.options.opacity;
if (this._icon) this._icon.style.opacity = opacity;
if (this._shadow) this._shadow.style.opacity = opacity;
}
_bringToFront() {
this._updateZIndex(this.options.riseOffset);
}
_resetZIndex() {
this._updateZIndex(0);
}
_panOnFocus() {
const map = this._map;
if (!map) return;
const iconOpts = this.options.icon.options;
const size = iconOpts.iconSize ? new Point(iconOpts.iconSize) : new Point(0, 0);
const anchor = iconOpts.iconAnchor ? new Point(iconOpts.iconAnchor) : new Point(0, 0);
map.panInside(this._latlng, {
paddingTopLeft: anchor,
paddingBottomRight: size.subtract(anchor)
});
}
_getPopupAnchor() {
return this.options.icon.options.popupAnchor;
}
_getTooltipAnchor() {
return this.options.icon.options.tooltipAnchor;
}
};
//#endregion
//#region src/layer/vector/Path.js
var Path = class extends Layer {
static {
this.setDefaultOptions({
stroke: true,
color: "#3388ff",
weight: 3,
opacity: 1,
lineCap: "round",
lineJoin: "round",
dashArray: null,
dashOffset: null,
fill: false,
fillColor: null,
fillOpacity: .2,
fillRule: "evenodd",
interactive: true,
bubblingPointerEvents: true
});
}
beforeAdd(map) {
this._renderer = map.getRenderer(this);
}
onAdd() {
this._renderer._initPath(this);
this._reset();
this._renderer._addPath(this);
}
onRemove() {
this._renderer._removePath(this);
}
redraw() {
if (this._map) this._renderer._updatePath(this);
return this;
}
setStyle(style) {
setOptions(this, style);
if (this._renderer) {
this._renderer._updateStyle(this);
if (this.options.stroke && style && Object.hasOwn(style, "weight")) this._updateBounds();
}
return this;
}
bringToFront() {
this._renderer?._bringToFront(this);
return this;
}
bringToBack() {
this._renderer?._bringToBack(this);
return this;
}
getElement() {
return this._path;
}
_reset() {
this._project();
this._update();
}
_clickTolerance() {
return (this.options.stroke ? this.options.weight / 2 : 0) + (this._renderer.options.tolerance || 0);
}
};
//#endregion
//#region src/layer/vector/CircleMarker.js
var CircleMarker = class CircleMarker extends Path {
static {
this.setDefaultOptions({
fill: true,
radius: 10
});
}
constructor(latlng, options) {
super();
setOptions(this, options);
this._latlng = new LatLng(latlng);
this._radius = this.options.radius;
this.callInitHooks(CircleMarker);
}
setLatLng(latlng) {
const oldLatLng = this._latlng;
this._latlng = new LatLng(latlng);
this.redraw();
return this.fire("move", {
oldLatLng,
latlng: this._latlng
});
}
getLatLng() {
return this._latlng;
}
setRadius(radius) {
this.options.radius = this._radius = radius;
return this.redraw();
}
getRadius() {
return this._radius;
}
setStyle(options) {
super.setStyle(options);
if (options?.radius !== void 0) this.setRadius(options.radius);
return this;
}
_project() {
this._point = this._map.latLngToLayerPoint(this._latlng);
this._pxRadius = this._radius;
this._updateBounds();
}
_updateBounds() {
const r = this._pxRadius, r2 = this._pxRadiusY ?? r, w = this._clickTolerance(), p = [r + w, r2 + w];
this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
}
_update() {
if (this._map) this._updatePath();
}
_updatePath() {
this._renderer._updateCircle(this);
}
_empty() {
return this._pxRadius && !this._renderer._bounds.intersects(this._pxBounds);
}
_containsPoint(p) {
return p.distanceTo(this._point) <= this._pxRadius + this._clickTolerance();
}
};
//#endregion
//#region src/layer/vector/Circle.js
var Circle = class Circle extends CircleMarker {
constructor(latlng, options) {
super(latlng, options);
if (isNaN(this.options.radius)) throw new Error("Circle radius cannot be NaN");
this.callInitHooks(Circle);
}
getBounds() {
const half = [this._pxRadius, this._pxRadiusY ?? this._pxRadius];
return new LatLngBounds(this._map.layerPointToLatLng(this._point.subtract(half)), this._map.layerPointToLatLng(this._point.add(half)));
}
_project() {
const lng = this._latlng.lng, lat = this._latlng.lat, map = this._map, crs = map.options.crs;
if (crs.distance === EarthCRS.distance) {
const d = Math.PI / 180, latR = this._radius / EarthCRS.R / d, top = map.project([lat + latR, lng]), bottom = map.project([lat - latR, lng]), p = top.add(bottom).divideBy(2), lat2 = map.unproject(p).lat;
let lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
if (isNaN(lngR) || lngR === 0) lngR = latR / Math.cos(Math.PI / 180 * lat);
this._point = p.subtract(map.getPixelOrigin());
this._pxRadius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
this._pxRadiusY = p.y - top.y;
} else {
const latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._radius, 0]));
this._point = map.latLngToLayerPoint(this._latlng);
this._pxRadius = Math.abs(this._point.x - map.latLngToLayerPoint(latlng2).x);
}
this._updateBounds();
}
};
//#endregion
//#region src/layer/vector/Polyline.js
var Polyline = class Polyline extends Path {
static {
this.setDefaultOptions({
smoothFactor: .5,
noClip: false
});
}
constructor(latlngs, options) {
super();
setOptions(this, options);
this._setLatLngs(latlngs);
this.callInitHooks(Polyline);
}
getLatLngs() {
return this._latlngs;
}
setLatLngs(latlngs) {
this._setLatLngs(latlngs);
return this.redraw();
}
isEmpty() {
return !this._latlngs.length;
}
closestLayerPoint(p) {
p = new Point(p);
let minDistance = Infinity, minPoint = null, p1, p2;
const closest = _sqClosestPointOnSegment;
for (const points of this._parts) for (let i = 1, len = points.length; i < len; i++) {
p1 = points[i - 1];
p2 = points[i];
const sqDist = closest(p, p1, p2, true);
if (sqDist < minDistance) {
minDistance = sqDist;
minPoint = closest(p, p1, p2);
}
}
if (minPoint) minPoint.distance = Math.sqrt(minDistance);
return minPoint;
}
getCenter() {
if (!this._map) throw new Error("Must add layer to map before using getCenter()");
return polylineCenter(this._defaultShape(), this._map.options.crs);
}
getBounds() {
return this._bounds;
}
addLatLng(latlng, latlngs) {
latlngs ??= this._defaultShape();
latlng = new LatLng(latlng);
latlngs.push(latlng);
this._bounds.extend(latlng);
return this.redraw();
}
_setLatLngs(latlngs) {
this._bounds = new LatLngBounds();
this._latlngs = this._convertLatLngs(latlngs);
}
_defaultShape() {
return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
}
_convertLatLngs(latlngs) {
const result = [], flat = isFlat(latlngs);
for (let i = 0, len = latlngs.length; i < len; i++) if (flat) {
result[i] = new LatLng(latlngs[i]);
this._bounds.extend(result[i]);
} else result[i] = this._convertLatLngs(latlngs[i]);
return result;
}
_project() {
const pxBounds = new Bounds();
this._rings = [];
this._projectLatlngs(this._latlngs, this._rings, pxBounds);
if (this._bounds.isValid() && pxBounds.isValid()) {
this._rawPxBounds = pxBounds;
this._updateBounds();
}
}
_updateBounds() {
const w = this._clickTolerance(), p = new Point(w, w);
if (!this._rawPxBounds) return;
this._pxBounds = new Bounds([this._rawPxBounds.min.subtract(p), this._rawPxBounds.max.add(p)]);
}
_projectLatlngs(latlngs, result, projectedBounds) {
if (latlngs[0] instanceof LatLng) {
const ring = latlngs.map((latlng) => this._map.latLngToLayerPoint(latlng));
ring.forEach((r) => projectedBounds.extend(r));
result.push(ring);
} else latlngs.forEach((latlng) => this._projectLatlngs(latlng, result, projectedBounds));
}
_clipPoints() {
const bounds = this._renderer._bounds;
this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) return;
if (this.options.noClip) {
this._parts = this._rings;
return;
}
const parts = this._parts;
let i, j, k, len, len2, segment, points;
for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
points = this._rings[i];
for (j = 0, len2 = points.length; j < len2 - 1; j++) {
segment = clipSegment(points[j], points[j + 1], bounds, j, true);
if (!segment) continue;
parts[k] ??= [];
parts[k].push(segment[0]);
if (segment[1] !== points[j + 1] || j === len2 - 2) {
parts[k].push(segment[1]);
k++;
}
}
}
}
_simplifyPoints() {
const parts = this._parts, tolerance = this.options.smoothFactor;
for (let i = 0, len = parts.length; i < len; i++) parts[i] = simplify(parts[i], tolerance);
}
_update() {
if (!this._map) return;
this._clipPoints();
this._simplifyPoints();
this._updatePath();
}
_updatePath() {
this._renderer._updatePoly(this);
}
_containsPoint(p, closed) {
let i, j, k, len, len2, part;
const w = this._clickTolerance();
if (!this._pxBounds || !this._pxBounds.contains(p)) return false;
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
if (!closed && j === 0) continue;
if (pointToSegmentDistance(p, part[k], part[j]) <= w) return true;
}
}
return false;
}
};
//#endregion
//#region src/layer/vector/Polygon.js
var Polygon = class Polygon extends Polyline {
static {
this.setDefaultOptions({ fill: true });
}
constructor(...args) {
super(...args);
this.callInitHooks(Polygon);
}
isEmpty() {
return !this._latlngs.length || !this._latlngs[0].length;
}
getCenter() {
if (!this._map) throw new Error("Must add layer to map before using getCenter()");
return polygonCenter(this._defaultShape(), this._map.options.crs);
}
_convertLatLngs(latlngs) {
const result = super._convertLatLngs(latlngs), len = result.length;
if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) result.pop();
return result;
}
_setLatLngs(latlngs) {
super._setLatLngs(latlngs);
if (isFlat(this._latlngs)) this._latlngs = [this._latlngs];
}
_defaultShape() {
return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
}
_clipPoints() {
let bounds = this._renderer._bounds;
const w = this.options.weight, p = new Point(w, w);
bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) return;
if (this.options.noClip) {
this._parts = this._rings;
return;
}
for (const ring of this._rings) {
const clipped = clipPolygon(ring, bounds, true);
if (clipped.length) this._parts.push(clipped);
}
}
_updatePath() {
this._renderer._updatePoly(this, true);
}
_containsPoint(p) {
let inside = false, part, p1, p2, i, j, k, len, len2;
if (!this._pxBounds || !this._pxBounds.contains(p)) return false;
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
p1 = part[j];
p2 = part[k];
if (p1.y > p.y !== p2.y > p.y && p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x) inside = !inside;
}
}
return inside || super._containsPoint(p, true);
}
};
//#endregion
//#region src/layer/GeoJSON.js
var GeoJSON = class GeoJSON extends FeatureGroup {
constructor(geojson, options) {
super(void 0, options);
if (geojson) this.addData(geojson);
this.callInitHooks(GeoJSON);
}
addData(geojson) {
const features = Array.isArray(geojson) ? geojson : geojson.features;
if (features) {
for (const feature of features) if (feature.geometries || feature.geometry || feature.features || feature.coordinates) this.addData(feature);
return this;
}
const options = this.options;
if (options.filter && !options.filter(geojson)) return this;
const layer = GeoJSON.geometryToLayer(geojson, options);
if (!layer) return this;
layer.feature = GeoJSON.asFeature(geojson);
layer.defaultOptions = layer.options;
this.resetStyle(layer);
if (options.onEachFeature) options.onEachFeature(geojson, layer);
return this.addLayer(layer);
}
resetStyle(layer) {
if (layer === void 0) return this.eachLayer(this.resetStyle, this);
layer.options = Object.create(layer.defaultOptions);
this._setLayerStyle(layer, this.options.style);
return this;
}
setStyle(style) {
return this.eachLayer((layer) => this._setLayerStyle(layer, style));
}
_setLayerStyle(layer, style) {
if (layer.setStyle) {
if (typeof style === "function") style = style(layer.feature);
layer.setStyle(style);
}
}
static geometryToLayer(geojson, options) {
const geometry = geojson.type === "Feature" ? geojson.geometry : geojson, coords = geometry?.coordinates, layers = [], pointToLayer = options?.pointToLayer, _coordsToLatLng = options?.coordsToLatLng ?? GeoJSON.coordsToLatLng;
let latlng, latlngs;
if (!coords && !geometry) return null;
switch (geometry.type) {
case "Point":
latlng = _coordsToLatLng(coords);
return GeoJSON._pointToLayer(pointToLayer, geojson, latlng, options);
case "MultiPoint":
for (const coord of coords) {
latlng = _coordsToLatLng(coord);
layers.push(GeoJSON._pointToLayer(pointToLayer, geojson, latlng, options));
}
return new FeatureGroup(layers);
case "LineString":
case "MultiLineString":
latlngs = GeoJSON.coordsToLatLngs(coords, geometry.type === "LineString" ? 0 : 1, _coordsToLatLng);
return new Polyline(latlngs, options);
case "Polygon":
case "MultiPolygon":
latlngs = GeoJSON.coordsToLatLngs(coords, geometry.type === "Polygon" ? 1 : 2, _coordsToLatLng);
return new Polygon(latlngs, options);
case "GeometryCollection":
for (const g of geometry.geometries) {
const geoLayer = GeoJSON.geometryToLayer({
geometry: g,
type: "Feature",
properties: geojson.properties
}, options);
if (geoLayer) layers.push(geoLayer);
}
return new FeatureGroup(layers);
case "FeatureCollection":
for (const f of geometry.features) {
const featureLayer = GeoJSON.geometryToLayer(f, options);
if (featureLayer) layers.push(featureLayer);
}
return new FeatureGroup(layers);
default: throw new Error("Invalid GeoJSON object.");
}
}
static _pointToLayer(pointToLayerFn, geojson, latlng, options) {
return pointToLayerFn ? pointToLayerFn(geojson, latlng) : new Marker(latlng, options?.markersInheritOptions && options);
}
static coordsToLatLng(coords) {
return new LatLng(coords[1], coords[0], coords[2]);
}
static coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
return coords.map((coord) => levelsDeep ? GeoJSON.coordsToLatLngs(coord, levelsDeep - 1, _coordsToLatLng) : (_coordsToLatLng || GeoJSON.coordsToLatLng)(coord));
}
static latLngToCoords(latlng, precision) {
latlng = new LatLng(latlng);
return latlng.alt !== void 0 ? [
formatNum(latlng.lng, precision),
formatNum(latlng.lat, precision),
formatNum(latlng.alt, precision)
] : [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
}
static latLngsToCoords(latlngs, levelsDeep, close, precision) {
const coords = latlngs.map((latlng) => levelsDeep ? GeoJSON.latLngsToCoords(latlng, isFlat(latlng) ? 0 : levelsDeep - 1, close, precision) : GeoJSON.latLngToCoords(latlng, precision));
if (!levelsDeep && close && coords.length > 0) coords.push(coords[0].slice());
return coords;
}
static getFeature(layer, newGeometry) {
return layer.feature ? {
...layer.feature,
geometry: newGeometry
} : GeoJSON.asFeature(newGeometry);
}
static asFeature(geojson) {
if (geojson.type === "Feature" || geojson.type === "FeatureCollection") return geojson;
return {
type: "Feature",
properties: {},
geometry: geojson
};
}
};
const PointToGeoJSON = { toGeoJSON(precision) {
return GeoJSON.getFeature(this, {
type: "Point",
coordinates: GeoJSON.latLngToCoords(this.getLatLng(), precision)
});
} };
Marker.include(PointToGeoJSON);
Circle.include(PointToGeoJSON);
CircleMarker.include(PointToGeoJSON);
Polyline.include({ toGeoJSON(precision) {
const multi = !isFlat(this._latlngs);
const coords = GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
return GeoJSON.getFeature(this, {
type: `${multi ? "Multi" : ""}LineString`,
coordinates: coords
});
} });
Polygon.include({ toGeoJSON(precision) {
const holes = !isFlat(this._latlngs), multi = holes && !isFlat(this._latlngs[0]);
let coords = GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
if (!holes) coords = [coords];
return GeoJSON.getFeature(this, {
type: `${multi ? "Multi" : ""}Polygon`,
coordinates: coords
});
} });
LayerGroup.include({
toMultiPoint(precision) {
const coords = [];
this.eachLayer((layer) => {
coords.push(layer.toGeoJSON(precision).geometry.coordinates);
});
return GeoJSON.getFeature(this, {
type: "MultiPoint",
coordinates: coords
});
},
toGeoJSON(precision) {
const type = this.feature?.geometry?.type;
if (type === "MultiPoint") return this.toMultiPoint(precision);
const isGeometryCollection = type === "GeometryCollection", jsons = [];
this.eachLayer((layer) => {
if (layer.toGeoJSON) {
const json = layer.toGeoJSON(precision);
if (isGeometryCollection) jsons.push(json.geometry);
else {
const feature = GeoJSON.asFeature(json);
if (feature.type === "FeatureCollection") jsons.push(...feature.features);
else jsons.push(feature);
}
}
});
if (isGeometryCollection) return GeoJSON.getFeature(this, {
geometries: jsons,
type: "GeometryCollection"
});
return {
type: "FeatureCollection",
features: jsons
};
}
});
//#endregion
//#region src/layer/BlanketOverlay.js
var BlanketOverlay = class BlanketOverlay extends Layer {
static {
this.setDefaultOptions({
padding: .1,
continuous: false
});
}
constructor(options) {
super();
setOptions(this, options);
this.callInitHooks(BlanketOverlay);
}
onAdd() {
if (!this._container) {
this._initContainer();
this._container.classList.add("maplair-zoom-animated");
}
this.getPane().appendChild(this._container);
this._resizeContainer();
this._onMoveEnd();
}
onRemove() {
this._destroyContainer();
}
getEvents() {
const events = {
viewreset: this._reset,
zoom: this._onZoom,
moveend: this._onMoveEnd,
resize: this._resizeContainer,
zoomend: this._onZoomEnd
};
if (this._zoomAnimated) events.zoomanim = this._onAnimZoom;
if (this.options.continuous) events.move = this._onMoveEnd;
return events;
}
_onAnimZoom(ev) {
this._updateTransform(ev.center, ev.zoom);
}
_onZoom() {
this._updateTransform(this._map.getCenter(), this._map.getZoom());
}
_updateTransform(center, zoom) {
const scale = this._map.getZoomScale(zoom, this._zoom), viewHalf = this._map.getSize().multiplyBy(.5 + this.options.padding), currentCenterPoint = this._map.project(this._center, zoom), topLeftOffset = viewHalf.multiplyBy(-scale)._add(currentCenterPoint)._subtract(this._map._getNewPixelOrigin(center, zoom))._round();
setTransform(this._container, topLeftOffset, scale);
}
_onMoveEnd(ev) {
const p = this.options.padding, size = this._map.getSize(), min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
this._center = this._map.getCenter();
this._zoom = this._map.getZoom();
this._updateTransform(this._center, this._zoom);
this._onSettled(ev);
}
_reset() {
this._onSettled();
this._updateTransform(this._center, this._zoom);
this._onViewReset();
}
_initContainer() {
this._container = create("div");
}
_destroyContainer() {
off(this._container);
this._container.remove();
delete this._container;
}
_resizeContainer() {
const p = this.options.padding, size = this._map.getSize().multiplyBy(1 + p * 2).round();
this._container.style.width = `${size.x}px`;
this._container.style.height = `${size.y}px`;
return size;
}
_onZoomEnd() {}
_onViewReset() {}
_onSettled() {}
};
//#endregion
//#region src/layer/ImageOverlay.js
var ImageOverlay = class ImageOverlay extends Layer {
static {
this.setDefaultOptions({
opacity: 1,
alt: "",
interactive: false,
crossOrigin: false,
errorOverlayUrl: "",
zIndex: 1,
className: "",
decoding: "auto"
});
}
constructor(url, bounds, options) {
super();
this._url = url;
this._bounds = new LatLngBounds(bounds);
setOptions(this, options);
this.callInitHooks(ImageOverlay);
}
onAdd() {
if (!this._image) {
this._initImage();
if (this.options.opacity < 1) this._updateOpacity();
}
if (this.options.interactive) {
this._image.classList.add("maplair-interactive");
this.addInteractiveTarget(this._image);
}
this.getPane().appendChild(this._image);
this._reset();
}
onRemove() {
this._image.remove();
if (this.options.interactive) this.removeInteractiveTarget(this._image);
}
setOpacity(opacity) {
this.options.opacity = opacity;
if (this._image) this._updateOpacity();
return this;
}
setStyle(styleOpts) {
if (styleOpts.opacity) this.setOpacity(styleOpts.opacity);
return this;
}
bringToFront() {
if (this._map) toFront(this._image);
return this;
}
bringToBack() {
if (this._map) toBack(this._image);
return this;
}
setUrl(url) {
this._url = url;
if (this._image) this._image.src = url;
return this;
}
setBounds(bounds) {
this._bounds = new LatLngBounds(bounds);
if (this._map) this._reset();
return this;
}
getEvents() {
const events = {
zoom: this._reset,
viewreset: this._reset
};
if (this._zoomAnimated) events.zoomanim = this._animateZoom;
return events;
}
setZIndex(value) {
this.options.zIndex = value;
this._updateZIndex();
return this;
}
getBounds() {
return this._bounds;
}
getElement() {
return this._image;
}
_initImage() {
const wasElementSupplied = this._url.tagName === "IMG";
const img = this._image = wasElementSupplied ? this._url : create("img");
img.classList.add("maplair-image-layer");
if (this._zoomAnimated) img.classList.add("maplair-zoom-animated");
if (this.options.className) img.classList.add(...splitWords(this.options.className));
img.onselectstart = falseFn;
img.onpointermove = falseFn;
img.onload = this.fire.bind(this, "load");
img.onerror = this._overlayOnError.bind(this);
if (this.options.crossOrigin || this.options.crossOrigin === "") img.crossOrigin = this.options.crossOrigin === true ? "" : this.options.crossOrigin;
img.decoding = this.options.decoding;
if (this.options.zIndex) this._updateZIndex();
if (wasElementSupplied) {
this._url = img.src;
return;
}
img.src = this._url;
img.alt = this.options.alt;
}
_animateZoom(e) {
const scale = this._map.getZoomScale(e.zoom), offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
setTransform(this._image, offset, scale);
}
_reset() {
const image = this._image, bounds = new Bounds(this._map.latLngToLayerPoint(this._bounds.getNorthWest()), this._map.latLngToLayerPoint(this._bounds.getSouthEast())), size = bounds.getSize();
setPosition(image, bounds.min);
image.style.width = `${size.x}px`;
image.style.height = `${size.y}px`;
}
_updateOpacity() {
this._image.style.opacity = this.options.opacity;
}
_updateZIndex() {
if (this._image && this.options.zIndex !== void 0 && this.options.zIndex !== null) this._image.style.zIndex = this.options.zIndex;
}
_overlayOnError() {
this.fire("error");
const errorUrl = this.options.errorOverlayUrl;
if (errorUrl && this._url !== errorUrl) {
this._url = errorUrl;
this._image.src = errorUrl;
}
}
getCenter() {
return this._bounds.getCenter();
}
};
//#endregion
//#region src/layer/VideoOverlay.js
var VideoOverlay = class VideoOverlay extends ImageOverlay {
static {
this.setDefaultOptions({
autoplay: true,
controls: false,
loop: true,
keepAspectRatio: true,
muted: false,
playsInline: true
});
}
constructor(...args) {
super(...args);
this.callInitHooks(VideoOverlay);
}
_initImage() {
const wasElementSupplied = this._url.tagName === "VIDEO";
const vid = this._image = wasElementSupplied ? this._url : create("video");
vid.classList.add("maplair-image-layer");
if (this._zoomAnimated) vid.classList.add("maplair-zoom-animated");
if (this.options.className) vid.classList.add(...splitWords(this.options.className));
on(vid, "pointerdown", (e) => {
if (vid.controls) stopPropagation(e);
});
vid.onloadeddata = this.fire.bind(this, "load");
if (wasElementSupplied) {
const sourceElements = vid.getElementsByTagName("source");
const sources = sourceElements.map((e) => e.src);
this._url = sourceElements.length > 0 ? sources : [vid.src];
return;
}
if (!Array.isArray(this._url)) this._url = [this._url];
if (!this.options.keepAspectRatio && Object.hasOwn(vid.style, "objectFit")) vid.style["objectFit"] = "fill";
vid.autoplay = !!this.options.autoplay;
vid.controls = !!this.options.controls;
vid.loop = !!this.options.loop;
vid.muted = !!this.options.muted;
vid.playsInline = !!this.options.playsInline;
for (const url of this._url) {
const source = create("source");
source.src = url;
vid.appendChild(source);
}
}
};
//#endregion
//#region src/layer/SVGOverlay.js
var SVGOverlay = class SVGOverlay extends ImageOverlay {
constructor(...args) {
super(...args);
this.callInitHooks(SVGOverlay);
}
_initImage() {
const el = this._image = this._url;
el.classList.add("maplair-image-layer");
if (this._zoomAnimated) el.classList.add("maplair-zoom-animated");
if (this.options.className) el.classList.add(...splitWords(this.options.className));
el.onselectstart = falseFn;
el.onpointermove = falseFn;
}
};
//#endregion
//#region src/layer/DivOverlay.js
var DivOverlay = class DivOverlay extends Layer {
static {
this.setDefaultOptions({
interactive: false,
offset: [0, 0],
className: "",
pane: void 0,
content: ""
});
}
constructor(options, source) {
super();
if (options instanceof LatLng || Array.isArray(options)) {
this._latlng = new LatLng(options);
setOptions(this, source);
} else {
setOptions(this, options);
this._source = source;
}
if (this.options.content) this._content = this.options.content;
this.callInitHooks(DivOverlay);
}
openOn(map) {
map = arguments.length ? map : this._source._map;
if (!map.hasLayer(this)) map.addLayer(this);
return this;
}
close() {
this._map?.removeLayer(this);
return this;
}
toggle(layer) {
if (this._map) this.close();
else {
if (arguments.length) this._source = layer;
else layer = this._source;
this._prepareOpen();
this.openOn(layer._map);
}
return this;
}
onAdd(map) {
this._zoomAnimated = map._zoomAnimated;
if (!this._container) this._initLayout();
if (map._fadeAnimated) this._container.style.opacity = 0;
clearTimeout(this._removeTimeout);
this.getPane().appendChild(this._container);
this.update();
if (map._fadeAnimated) this._container.style.opacity = 1;
this.bringToFront();
if (this.options.interactive) {
this._container.classList.add("maplair-interactive");
this.addInteractiveTarget(this._container);
}
}
onRemove(map) {
if (map._fadeAnimated) {
this._container.style.opacity = 0;
this._removeTimeout = setTimeout(() => this._container.remove(), 200);
} else this._container.remove();
if (this.options.interactive) {
this._container.classList.remove("maplair-interactive");
this.removeInteractiveTarget(this._container);
}
}
getLatLng() {
return this._latlng;
}
setLatLng(latlng) {
this._latlng = new LatLng(latlng);
if (this._map) {
this._updatePosition();
this._adjustPan();
}
return this;
}
getContent() {
return this._content;
}
setContent(content) {
this._content = content;
this.update();
return this;
}
getElement() {
return this._container;
}
update() {
if (!this._map) return;
this._container.style.visibility = "hidden";
this._updateContent();
this._updateLayout();
this._updatePosition();
this._container.style.visibility = "";
this._adjustPan();
}
getEvents() {
const events = {
zoom: this._updatePosition,
viewreset: this._updatePosition
};
if (this._zoomAnimated) events.zoomanim = this._animateZoom;
return events;
}
isOpen() {
return !!this._map && this._map.hasLayer(this);
}
bringToFront() {
if (this._map) toFront(this._container);
return this;
}
bringToBack() {
if (this._map) toBack(this._container);
return this;
}
_prepareOpen(latlng) {
let source = this._source;
if (!source._map) return false;
if (source instanceof FeatureGroup) {
source = null;
for (const layer of Object.values(this._source._layers)) if (layer._map) {
source = layer;
break;
}
if (!source) return false;
this._source = source;
}
if (!latlng) {
if (source.getCenter) latlng = source.getCenter();
else if (source.getLatLng) latlng = source.getLatLng();
else if (source.getBounds) latlng = source.getBounds().getCenter();
else throw new Error("Unable to get source layer LatLng.");
}
this.setLatLng(latlng);
if (this._map) this.update();
return true;
}
_updateContent() {
if (!this._content) return;
const node = this._contentNode;
const content = typeof this._content === "function" ? this._content(this._source ?? this) : this._content;
if (typeof content === "string") node.innerHTML = content;
else {
while (node.hasChildNodes()) node.removeChild(node.firstChild);
node.appendChild(content);
}
this.fire("contentupdate");
}
_updatePosition() {
if (!this._map) return;
const pos = this._map.latLngToLayerPoint(this._latlng), anchor = this._getAnchor();
let offset = new Point(this.options.offset);
if (this._zoomAnimated) setPosition(this._container, pos.add(anchor));
else offset = offset.add(pos).add(anchor);
const bottom = this._containerBottom = -offset.y, left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
this._container.style.bottom = `${bottom}px`;
this._container.style.left = `${left}px`;
}
_getAnchor() {
return [0, 0];
}
static register(MaplairMap) {
MaplairMap.include({ _initOverlay(OverlayClass, content, latlng, options) {
let overlay = content;
if (!(overlay instanceof OverlayClass)) overlay = new OverlayClass(options).setContent(content);
if (latlng) overlay.setLatLng(latlng);
return overlay;
} });
Layer.include({ _initOverlay(OverlayClass, old, content, options) {
let overlay = content;
if (overlay instanceof OverlayClass) {
setOptions(overlay, options);
overlay._source = this;
} else {
overlay = old && !options ? old : new OverlayClass(options, this);
overlay.setContent(content);
}
return overlay;
} });
}
};
//#endregion
//#region src/layer/Popup.js
var Popup = class Popup extends DivOverlay {
static {
this.setDefaultOptions({
pane: "popupPane",
offset: [0, 7],
maxWidth: 300,
minWidth: 50,
maxHeight: null,
autoPan: !Browser_default.reducedMotion,
autoPanPaddingTopLeft: null,
autoPanPaddingBottomRight: null,
autoPanPadding: [5, 5],
keepInView: false,
closeButton: true,
autoClose: true,
closeOnEscapeKey: true,
className: "",
trackResize: true
});
}
constructor(...args) {
super(...args);
this.callInitHooks(Popup);
}
openOn(map) {
map = arguments.length ? map : this._source._map;
if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) map.removeLayer(map._popup);
map._popup = this;
return super.openOn(map);
}
onAdd(map) {
super.onAdd(map);
map.fire("popupopen", { popup: this });
if (this._source) {
this._source.fire("popupopen", { popup: this }, true);
if (!(this._source instanceof Path)) this._source.on("preclick", stopPropagation);
}
}
onRemove(map) {
super.onRemove(map);
map.fire("popupclose", { popup: this });
if (this._source) {
this._source.fire("popupclose", { popup: this }, true);
if (!(this._source instanceof Path)) this._source.off("preclick", stopPropagation);
}
}
getEvents() {
const events = super.getEvents();
if (this.options.closeOnClick ?? this._map.options.closePopupOnClick) events.preclick = this.close;
if (this.options.keepInView) events.moveend = this._adjustPan;
return events;
}
_initLayout() {
const prefix = "maplair-popup", container = this._container = create("div", `${prefix} ${this.options.className || ""} maplair-zoom-animated`);
const wrapper = this._wrapper = create("div", `${prefix}-content-wrapper`, container);
this._contentNode = create("div", `${prefix}-content`, wrapper);
disableClickPropagation(container);
disableScrollPropagation(this._contentNode);
on(container, "contextmenu", stopPropagation);
this._tipContainer = create("div", `${prefix}-tip-container`, container);
this._tip = create("div", `${prefix}-tip`, this._tipContainer);
if (this.options.closeButton) {
const closeButton = this._closeButton = create("a", `${prefix}-close-button`, container);
closeButton.setAttribute("role", "button");
closeButton.setAttribute("aria-label", I18n.translate("Close popup"));
closeButton.href = "#close";
closeButton.innerHTML = "<span aria-hidden=\"true\">×</span>";
on(closeButton, "click", (ev) => {
ev.preventDefault();
this.close();
});
}
if (this.options.trackResize) {
this._resizeObserver = new ResizeObserver(((entries) => {
if (!this._map) return;
this._containerWidth = entries[0]?.contentRect?.width;
this._containerHeight = entries[0]?.contentRect?.height;
this._updateLayout();
this._updatePosition();
this._adjustPan();
}));
this._resizeObserver.observe(this._contentNode);
}
}
_updateLayout() {
const container = this._contentNode, style = container.style;
style.maxWidth = `${this.options.maxWidth}px`;
style.minWidth = `${this.options.minWidth}px`;
const maxHeight = this.options.maxHeight, scrolledClass = "maplair-popup-scrolled";
if (maxHeight && (this._containerHeight ?? container.offsetHeight) > maxHeight) {
style.height = `${maxHeight}px`;
container.classList.add(scrolledClass);
} else container.classList.remove(scrolledClass);
this._containerWidth = this._container.offsetWidth;
this._containerHeight = this._container.offsetHeight;
this._containerMarginBottom = parseInt(getComputedStyle(this._container).marginBottom, 10) || 0;
}
_animateZoom(e) {
const pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), anchor = this._getAnchor();
setPosition(this._container, pos.add(anchor));
}
_adjustPan() {
if (!this.options.autoPan) return;
this._map._panAnim?.stop();
if (this._autopanning) {
this._autopanning = false;
return;
}
const map = this._map, marginBottom = this._containerMarginBottom ?? (parseInt(getComputedStyle(this._container).marginBottom, 10) || 0), containerHeight = this._containerHeight + marginBottom, containerWidth = this._containerWidth, layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
layerPos._add(getPosition(this._container));
const containerPos = map.layerPointToContainerPoint(layerPos), padding = new Point(this.options.autoPanPadding), paddingTL = new Point(this.options.autoPanPaddingTopLeft ?? padding), paddingBR = new Point(this.options.autoPanPaddingBottomRight ?? padding), size = map.getSize();
let dx = 0, dy = 0;
if (containerPos.x + containerWidth + paddingBR.x > size.x) dx = containerPos.x + containerWidth - size.x + paddingBR.x;
if (containerPos.x - dx - paddingTL.x < 0) dx = containerPos.x - paddingTL.x;
if (containerPos.y + containerHeight + paddingBR.y > size.y) dy = containerPos.y + containerHeight - size.y + paddingBR.y;
if (containerPos.y - dy - paddingTL.y < 0) dy = containerPos.y - paddingTL.y;
if (dx || dy) {
if (this.options.keepInView) this._autopanning = true;
map.fire("autopanstart").panBy([dx, dy]);
}
}
_getAnchor() {
return new Point(this._source?._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
}
static register(MaplairMap) {
MaplairMap.mergeOptions({ closePopupOnClick: true });
MaplairMap.include({
openPopup(popup, latlng, options) {
this._initOverlay(Popup, popup, latlng, options).openOn(this);
return this;
},
closePopup(popup) {
popup = arguments.length ? popup : this._popup;
popup?.close();
return this;
}
});
Layer.include({
bindPopup(content, options) {
this._popup = this._initOverlay(Popup, this._popup, content, options);
if (!this._popupHandlersAdded) {
this.on({
click: this._openPopup,
keypress: this._onKeyPress,
remove: this.closePopup,
move: this._movePopup
});
this._popupHandlersAdded = true;
}
return this;
},
unbindPopup() {
if (this._popup) {
this.off({
click: this._openPopup,
keypress: this._onKeyPress,
remove: this.closePopup,
move: this._movePopup
});
this._popupHandlersAdded = false;
this._popup = null;
}
return this;
},
openPopup(latlng) {
if (this._popup) {
if (!(this instanceof FeatureGroup)) this._popup._source = this;
if (this._popup._prepareOpen(latlng || this._latlng)) this._popup.openOn(this._map);
}
return this;
},
closePopup() {
this._popup?.close();
return this;
},
togglePopup() {
this._popup?.toggle(this);
return this;
},
isPopupOpen() {
return this._popup?.isOpen() ?? false;
},
setPopupContent(content) {
this._popup?.setContent(content);
return this;
},
getPopup() {
return this._popup;
},
_openPopup(e) {
if (!this._popup || !this._map) return;
stop(e);
const target = e.propagatedFrom ?? e.target;
if (this._popup._source === target && !(target instanceof Path)) {
if (this._map.hasLayer(this._popup)) this.closePopup();
else this.openPopup(e.latlng);
return;
}
this._popup._source = target;
this.openPopup(e.latlng);
},
_movePopup(e) {
this._popup.setLatLng(e.latlng);
},
_onKeyPress(e) {
if (e.originalEvent.code === "Enter") this._openPopup(e);
}
});
}
};
//#endregion
//#region src/layer/Tooltip.js
var Tooltip = class Tooltip extends DivOverlay {
static {
this.setDefaultOptions({
pane: "tooltipPane",
offset: [0, 0],
direction: "auto",
permanent: false,
sticky: false,
opacity: .9
});
}
constructor(...args) {
super(...args);
this.callInitHooks(Tooltip);
}
onAdd(map) {
super.onAdd(map);
this.setOpacity(this.options.opacity);
map.fire("tooltipopen", { tooltip: this });
if (this._source) {
this.addEventParent(this._source);
this._source.fire("tooltipopen", { tooltip: this }, true);
}
}
onRemove(map) {
super.onRemove(map);
map.fire("tooltipclose", { tooltip: this });
if (this._source) {
this.removeEventParent(this._source);
this._source.fire("tooltipclose", { tooltip: this }, true);
}
}
getEvents() {
const events = super.getEvents();
if (!this.options.permanent) events.preclick = this.close;
return events;
}
_initLayout() {
const className = `maplair-tooltip ${this.options.className || ""} maplair-zoom-${this._zoomAnimated ? "animated" : "hide"}`;
this._contentNode = this._container = create("div", className);
this._container.setAttribute("role", "tooltip");
this._container.setAttribute("id", `maplair-tooltip-${stamp(this)}`);
}
_updateLayout() {}
_adjustPan() {}
_setPosition(pos) {
let subX, subY, direction = this.options.direction;
const map = this._map, container = this._container, centerPoint = map.latLngToContainerPoint(map.getCenter()), tooltipPoint = map.layerPointToContainerPoint(pos), tooltipWidth = container.offsetWidth, tooltipHeight = container.offsetHeight, offset = new Point(this.options.offset), anchor = this._getAnchor();
if (direction === "top") {
subX = tooltipWidth / 2;
subY = tooltipHeight;
} else if (direction === "bottom") {
subX = tooltipWidth / 2;
subY = 0;
} else if (direction === "center") {
subX = tooltipWidth / 2;
subY = tooltipHeight / 2;
} else if (direction === "right") {
subX = 0;
subY = tooltipHeight / 2;
} else if (direction === "left") {
subX = tooltipWidth;
subY = tooltipHeight / 2;
} else if (tooltipPoint.x < centerPoint.x) {
direction = "right";
subX = 0;
subY = tooltipHeight / 2;
} else {
direction = "left";
subX = tooltipWidth + (offset.x + anchor.x) * 2;
subY = tooltipHeight / 2;
}
pos = pos.subtract(new Point(subX, subY, true)).add(offset).add(anchor);
container.classList.remove("maplair-tooltip-right", "maplair-tooltip-left", "maplair-tooltip-top", "maplair-tooltip-bottom");
container.classList.add(`maplair-tooltip-${direction}`);
setPosition(container, pos);
}
_updatePosition() {
const pos = this._map.latLngToLayerPoint(this._latlng);
this._setPosition(pos);
}
setOpacity(opacity) {
this.options.opacity = opacity;
if (this._container) this._container.style.opacity = opacity;
}
_animateZoom(e) {
const pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
this._setPosition(pos);
}
_getAnchor() {
return new Point(this._source?._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
}
static register(MaplairMap) {
MaplairMap.include({
openTooltip(tooltip, latlng, options) {
this._initOverlay(Tooltip, tooltip, latlng, options).openOn(this);
return this;
},
closeTooltip(tooltip) {
tooltip.close();
return this;
}
});
Layer.include({
bindTooltip(content, options) {
if (this._tooltip && this.isTooltipOpen()) this.unbindTooltip();
this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
this._initTooltipInteractions();
if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) this.openTooltip();
return this;
},
unbindTooltip() {
if (this._tooltip) {
this._initTooltipInteractions(true);
this.closeTooltip();
this._tooltip = null;
}
return this;
},
_initTooltipInteractions(remove) {
if (!remove && this._tooltipHandlersAdded) return;
const onOff = remove ? "off" : "on", events = {
remove: this.closeTooltip,
move: this._moveTooltip
};
if (!this._tooltip.options.permanent) {
events.pointerover = this._openTooltip;
events.pointerout = this.closeTooltip;
events.click = this._openTooltip;
if (this._map) this._addFocusListeners(remove);
else events.add = () => this._addFocusListeners(remove);
} else events.add = this._openTooltip;
if (this._tooltip.options.sticky) events.pointermove = this._moveTooltip;
this[onOff](events);
this._tooltipHandlersAdded = !remove;
},
openTooltip(latlng) {
if (this._tooltip) {
if (!(this instanceof FeatureGroup)) this._tooltip._source = this;
if (this._tooltip._prepareOpen(latlng)) {
this._tooltip.openOn(this._map);
if (this.getElement) this._setAriaDescribedByOnLayer(this);
else if (this.eachLayer) this.eachLayer(this._setAriaDescribedByOnLayer, this);
}
}
return this;
},
closeTooltip() {
if (this._tooltip) return this._tooltip.close();
},
toggleTooltip() {
this._tooltip?.toggle(this);
return this;
},
isTooltipOpen() {
return this._tooltip.isOpen();
},
setTooltipContent(content) {
this._tooltip?.setContent(content);
return this;
},
getTooltip() {
return this._tooltip;
},
_addFocusListeners(remove) {
if (this.getElement) this._addFocusListenersOnLayer(this, remove);
else if (this.eachLayer) this.eachLayer((layer) => this._addFocusListenersOnLayer(layer, remove), this);
},
_addFocusListenersOnLayer(layer, remove) {
const el = typeof layer.getElement === "function" && layer.getElement();
if (el) {
const toggle = remove ? off : on;
if (!remove) {
el._maplair_focus_handler && off(el, "focus", el._maplair_focus_handler, this);
el._maplair_focus_handler = () => {
if (this._tooltip) {
this._tooltip._source = layer;
this.openTooltip();
}
};
}
el._maplair_focus_handler && toggle(el, "focus", el._maplair_focus_handler, this);
toggle(el, "blur", this.closeTooltip, this);
if (remove) delete el._maplair_focus_handler;
}
},
_setAriaDescribedByOnLayer(layer) {
(typeof layer.getElement === "function" && layer.getElement())?.setAttribute?.("aria-describedby", this._tooltip._container.id);
},
_openTooltip(e) {
if (!this._tooltip || !this._map) return;
if (this._map.dragging?.moving()) {
if (e.type === "add" && !this._moveEndOpensTooltip) {
this._moveEndOpensTooltip = true;
this._map.once("moveend", () => {
this._moveEndOpensTooltip = false;
this._openTooltip(e);
});
}
return;
}
this._tooltip._source = e.propagatedFrom ?? e.target;
this.openTooltip(this._tooltip.options.sticky ? e.latlng : void 0);
},
_moveTooltip(e) {
let latlng = e.latlng, containerPoint, layerPoint;
if (this._tooltip.options.sticky && e.originalEvent) {
containerPoint = this._map.pointerEventToContainerPoint(e.originalEvent);
layerPoint = this._map.containerPointToLayerPoint(containerPoint);
latlng = this._map.layerPointToLatLng(layerPoint);
}
this._tooltip.setLatLng(latlng);
}
});
}
};
//#endregion
//#region src/layer/marker/DivIcon.js
var DivIcon = class DivIcon extends Icon {
static {
this.setDefaultOptions({
iconSize: [12, 12],
html: false,
bgPos: null,
className: "maplair-div-icon"
});
}
constructor(...args) {
super(...args);
this.callInitHooks(DivIcon);
}
createIcon(oldIcon) {
const div = oldIcon && oldIcon.tagName === "DIV" ? oldIcon : document.createElement("div"), options = this.options;
if (options.html instanceof Element) {
div.replaceChildren();
div.appendChild(options.html);
} else div.innerHTML = options.html !== false ? options.html : "";
if (options.bgPos) {
const bgPos = new Point(options.bgPos);
div.style.backgroundPosition = `${-bgPos.x}px ${-bgPos.y}px`;
}
this._setIconStyles(div, "icon");
return div;
}
createShadow() {
return null;
}
};
//#endregion
//#region src/layer/tile/GridLayer.js
var GridLayer = class GridLayer extends Layer {
static {
this.setDefaultOptions({
tileSize: 256,
opacity: 1,
updateWhenIdle: !Browser_default.reducedMotion,
updateWhenZooming: true,
updateInterval: Browser_default.reducedMotion ? 5e3 : 200,
zIndex: 1,
bounds: null,
minZoom: 0,
maxZoom: void 0,
maxNativeZoom: void 0,
minNativeZoom: void 0,
noWrap: false,
pane: "tilePane",
className: "",
keepBuffer: 2
});
}
constructor(options) {
super();
setOptions(this, options);
this.callInitHooks(GridLayer);
}
onAdd() {
this._initContainer();
this._levels = {};
this._tiles = {};
this._resetView();
}
beforeAdd(map) {
map._addZoomLimit(this);
}
onRemove(map) {
this._removeAllTiles();
this._container.remove();
map._removeZoomLimit(this);
this._container = null;
this._tileZoom = void 0;
clearTimeout(this._pruneTimeout);
}
bringToFront() {
if (this._map) {
toFront(this._container);
this._setAutoZIndex(Math.max);
}
return this;
}
bringToBack() {
if (this._map) {
toBack(this._container);
this._setAutoZIndex(Math.min);
}
return this;
}
getContainer() {
return this._container;
}
setOpacity(opacity) {
this.options.opacity = opacity;
this._updateOpacity();
return this;
}
setZIndex(zIndex) {
this.options.zIndex = zIndex;
this._updateZIndex();
return this;
}
isLoading() {
return this._loading;
}
redraw() {
if (this._map) {
this._removeAllTiles();
const tileZoom = this._clampZoom(this._map.getZoom());
if (tileZoom !== this._tileZoom) {
this._tileZoom = tileZoom;
this._updateLevels();
}
this._update();
}
return this;
}
getEvents() {
const events = {
viewprereset: this._invalidateAll,
viewreset: this._resetView,
zoom: this._resetView,
moveend: this._onMoveEnd
};
if (!this.options.updateWhenIdle) {
if (!this._onMove) this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
events.move = this._onMove;
}
if (this._zoomAnimated) events.zoomanim = this._animateZoom;
return events;
}
createTile() {
return document.createElement("div");
}
getTileSize() {
const s = this.options.tileSize;
return s instanceof Point ? s : new Point(s, s);
}
_updateZIndex() {
if (this._container && this.options.zIndex !== void 0 && this.options.zIndex !== null) this._container.style.zIndex = this.options.zIndex;
}
_setAutoZIndex(compare) {
const layers = this.getPane().children;
let edgeZIndex = -compare(-Infinity, Infinity);
for (const layer of layers) {
const zIndex = layer.style.zIndex;
if (layer !== this._container && zIndex) edgeZIndex = compare(edgeZIndex, +zIndex);
}
if (isFinite(edgeZIndex)) {
this.options.zIndex = edgeZIndex + compare(-1, 1);
this._updateZIndex();
}
}
_updateOpacity() {
if (!this._map) return;
this._container.style.opacity = this.options.opacity;
const now = Date.now();
let nextFrame = false, willPrune = false;
for (const tile of Object.values(this._tiles ?? {})) {
if (!tile.current || !tile.loaded) continue;
const fade = Math.min(1, (now - tile.loaded) / 200);
tile.el.style.opacity = fade;
if (fade < 1) nextFrame = true;
else {
if (tile.active) willPrune = true;
else this._onOpaqueTile(tile);
tile.active = true;
}
}
if (willPrune && !this._noPrune) this._pruneTiles();
if (nextFrame) {
cancelAnimationFrame(this._fadeFrame);
this._fadeFrame = requestAnimationFrame(this._updateOpacity.bind(this));
}
}
_onOpaqueTile() {}
_initContainer() {
if (this._container) return;
this._container = create("div", `maplair-layer ${this.options.className ?? ""}`);
this._updateZIndex();
if (this.options.opacity < 1) this._updateOpacity();
this.getPane().appendChild(this._container);
}
_updateLevels() {
const zoom = this._tileZoom, maxZoom = this.options.maxZoom;
if (zoom === void 0) return;
for (let z of Object.keys(this._levels)) {
z = Number(z);
if (this._levels[z].el.children.length || z === zoom) {
this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
this._onUpdateLevel(z);
} else {
this._levels[z].el.remove();
this._removeTilesAtZoom(z);
this._onRemoveLevel(z);
delete this._levels[z];
}
}
let level = this._levels[zoom];
const map = this._map;
if (!level) {
level = this._levels[zoom] = {};
level.el = create("div", "maplair-tile-container maplair-zoom-animated", this._container);
level.el.style.zIndex = maxZoom;
level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
level.zoom = zoom;
this._setZoomTransform(level, map.getCenter(), map.getZoom());
level.el.offsetWidth;
this._onCreateLevel(level);
}
this._level = level;
return level;
}
_onUpdateLevel() {}
_onRemoveLevel() {}
_onCreateLevel() {}
_pruneTiles() {
if (!this._map) return;
const zoom = this._map.getZoom();
if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
this._removeAllTiles();
return;
}
for (const tile of Object.values(this._tiles)) tile.retain = tile.current;
for (const tile of Object.values(this._tiles)) if (tile.current && !tile.active) {
const coords = tile.coords;
if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
}
for (const [key, tile] of Object.entries(this._tiles)) if (!tile.retain) this._removeTile(key);
}
_removeTilesAtZoom(zoom) {
for (const [key, tile] of Object.entries(this._tiles)) if (tile.coords.z === zoom) this._removeTile(key);
}
_removeAllTiles() {
for (const key of Object.keys(this._tiles)) this._removeTile(key);
}
_invalidateAll() {
for (const z of Object.keys(this._levels)) {
this._levels[z].el.remove();
this._onRemoveLevel(Number(z));
delete this._levels[z];
}
this._removeAllTiles();
this._tileZoom = void 0;
}
_retainParent(x, y, z, minZoom) {
const x2 = Math.floor(x / 2), y2 = Math.floor(y / 2), z2 = z - 1, coords2 = new Point(+x2, +y2);
coords2.z = +z2;
const key = this._tileCoordsToKey(coords2), tile = this._tiles[key];
if (tile?.active) {
tile.retain = true;
return true;
} else if (tile?.loaded) tile.retain = true;
if (z2 > minZoom) return this._retainParent(x2, y2, z2, minZoom);
return false;
}
_retainChildren(x, y, z, maxZoom) {
for (let i = 2 * x; i < 2 * x + 2; i++) for (let j = 2 * y; j < 2 * y + 2; j++) {
const coords = new Point(i, j);
coords.z = z + 1;
const key = this._tileCoordsToKey(coords), tile = this._tiles[key];
if (tile?.active) {
tile.retain = true;
continue;
} else if (tile?.loaded) tile.retain = true;
if (z + 1 < maxZoom) this._retainChildren(i, j, z + 1, maxZoom);
}
}
_resetView(e) {
const animating = e && (e.pinch || e.flyTo);
this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
}
_animateZoom(e) {
this._setView(e.center, e.zoom, true, e.noUpdate);
}
_clampZoom(zoom) {
const options = this.options;
if (void 0 !== options.minNativeZoom && zoom < options.minNativeZoom) return options.minNativeZoom;
if (void 0 !== options.maxNativeZoom && options.maxNativeZoom < zoom) return options.maxNativeZoom;
return zoom;
}
_setView(center, zoom, noPrune, noUpdate) {
let tileZoom = Math.round(zoom);
if (this.options.maxZoom !== void 0 && tileZoom > this.options.maxZoom || this.options.minZoom !== void 0 && tileZoom < this.options.minZoom) tileZoom = void 0;
else tileZoom = this._clampZoom(tileZoom);
const tileZoomChanged = this.options.updateWhenZooming && tileZoom !== this._tileZoom;
if (!noUpdate || tileZoomChanged) {
this._tileZoom = tileZoom;
if (this._abortLoading) this._abortLoading();
this._updateLevels();
this._resetGrid();
if (tileZoom !== void 0) this._update(center);
if (!noPrune) this._pruneTiles();
this._noPrune = !!noPrune;
}
this._setZoomTransforms(center, zoom);
}
_setZoomTransforms(center, zoom) {
for (const level of Object.values(this._levels)) this._setZoomTransform(level, center, zoom);
}
_setZoomTransform(level, center, zoom) {
const scale = this._map.getZoomScale(zoom, level.zoom), translate = level.origin.multiplyBy(scale).subtract(this._map._getNewPixelOrigin(center, zoom)).round();
setTransform(level.el, translate, scale);
}
_resetGrid() {
const map = this._map, crs = map.options.crs, tileSize = this._tileSize = this.getTileSize(), tileZoom = this._tileZoom;
const bounds = this._map.getPixelWorldBounds(this._tileZoom);
if (bounds) this._globalTileRange = this._pxBoundsToTileRange(bounds);
this._wrapX = crs.wrapLng && !this.options.noWrap && [Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)];
this._wrapY = crs.wrapLat && !this.options.noWrap && [Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)];
}
_onMoveEnd() {
if (!this._map || this._map._animatingZoom) return;
this._update();
}
_getTiledPixelBounds(center) {
const map = this._map, mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), scale = map.getZoomScale(mapZoom, this._tileZoom), pixelCenter = map.project(center, this._tileZoom).floor(), halfSize = map.getSize().divideBy(scale * 2);
return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
}
_update(center) {
const map = this._map;
if (!map) return;
const zoom = this._clampZoom(map.getZoom());
if (center === void 0) center = map.getCenter();
if (this._tileZoom === void 0) return;
const pixelBounds = this._getTiledPixelBounds(center), tileRange = this._pxBoundsToTileRange(pixelBounds), tileCenter = tileRange.getCenter(), queue = [], margin = this.options.keepBuffer, noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), tileRange.getTopRight().add([margin, -margin]));
if (!(isFinite(tileRange.min.x) && isFinite(tileRange.min.y) && isFinite(tileRange.max.x) && isFinite(tileRange.max.y))) throw new Error("Attempted to load an infinite number of tiles");
for (const tile of Object.values(this._tiles)) {
const c = tile.coords;
if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) tile.current = false;
}
if (Math.abs(zoom - this._tileZoom) > 1) {
this._setView(center, zoom);
return;
}
for (let j = tileRange.min.y; j <= tileRange.max.y; j++) for (let i = tileRange.min.x; i <= tileRange.max.x; i++) {
const coords = new Point(i, j);
coords.z = this._tileZoom;
if (!this._isValidTile(coords)) continue;
const tile = this._tiles[this._tileCoordsToKey(coords)];
if (tile) tile.current = true;
else queue.push(coords);
}
queue.sort((a, b) => a.distanceTo(tileCenter) - b.distanceTo(tileCenter));
if (queue.length !== 0) {
if (!this._loading) {
this._loading = true;
this.fire("loading");
}
const fragment = document.createDocumentFragment();
for (const q of queue) this._addTile(q, fragment);
this._level.el.appendChild(fragment);
}
}
_isValidTile(coords) {
const crs = this._map.options.crs;
if (!crs.infinite) {
const bounds = this._globalTileRange;
if (!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x) || !crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y)) return false;
}
if (!this.options.bounds) return true;
const tileBounds = this._tileCoordsToBounds(coords);
return new LatLngBounds(this.options.bounds).overlaps(tileBounds);
}
_keyToBounds(key) {
return this._tileCoordsToBounds(this._keyToTileCoords(key));
}
_tileCoordsToNwSe(coords) {
const map = this._map, tileSize = this.getTileSize(), nwPoint = coords.scaleBy(tileSize), sePoint = nwPoint.add(tileSize);
return [map.unproject(nwPoint, coords.z), map.unproject(sePoint, coords.z)];
}
_tileCoordsToBounds(coords) {
const bp = this._tileCoordsToNwSe(coords);
let bounds = new LatLngBounds(bp[0], bp[1]);
if (!this.options.noWrap) bounds = this._map.wrapLatLngBounds(bounds);
return bounds;
}
_tileCoordsToKey(coords) {
return `${coords.x}:${coords.y}:${coords.z}`;
}
_keyToTileCoords(key) {
const k = key.split(":"), coords = new Point(+k[0], +k[1]);
coords.z = +k[2];
return coords;
}
_removeTile(key) {
const tile = this._tiles[key];
if (!tile) return;
tile.el.remove();
delete this._tiles[key];
this.fire("tileunload", {
tile: tile.el,
coords: this._keyToTileCoords(key)
});
}
_initTile(tile) {
tile.classList.add("maplair-tile");
const tileSize = this.getTileSize();
tile.style.width = `${tileSize.x}px`;
tile.style.height = `${tileSize.y}px`;
tile.onselectstart = falseFn;
tile.onpointermove = falseFn;
}
_addTile(coords, container) {
const tilePos = this._getTilePos(coords), key = this._tileCoordsToKey(coords);
const tile = this.createTile(this._wrapCoords(coords), this._tileReady.bind(this, coords));
this._initTile(tile);
if (this.createTile.length < 2) requestAnimationFrame(this._tileReady.bind(this, coords, null, tile));
setPosition(tile, tilePos);
this._tiles[key] = {
el: tile,
coords,
current: true
};
container.appendChild(tile);
this.fire("tileloadstart", {
tile,
coords
});
}
_tileReady(coords, err, tile) {
if (err) this.fire("tileerror", {
error: err,
tile,
coords
});
const key = this._tileCoordsToKey(coords);
tile = this._tiles[key];
if (!tile) return;
tile.loaded = Date.now();
if (this._map._fadeAnimated) {
tile.el.style.opacity = 0;
cancelAnimationFrame(this._fadeFrame);
this._fadeFrame = requestAnimationFrame(this._updateOpacity.bind(this));
} else {
tile.active = true;
this._pruneTiles();
}
if (!err) {
tile.el.classList.add("maplair-tile-loaded");
this.fire("tileload", {
tile: tile.el,
coords
});
}
if (this._noTilesToLoad()) {
this._loading = false;
this.fire("load");
if (!this._map._fadeAnimated) requestAnimationFrame(this._pruneTiles.bind(this));
else this._pruneTimeout = setTimeout(this._pruneTiles.bind(this), 250);
}
}
_getTilePos(coords) {
return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
}
_wrapCoords(coords) {
const newCoords = new Point(this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
newCoords.z = coords.z;
return newCoords;
}
_pxBoundsToTileRange(bounds) {
const tileSize = this.getTileSize();
return new Bounds(bounds.min.unscaleBy(tileSize).floor(), bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
}
_noTilesToLoad() {
return Object.values(this._tiles).every((t) => t.loaded);
}
};
//#endregion
//#region src/layer/tile/TileLayer.js
var TileLayer = class TileLayer extends GridLayer {
static {
this.setDefaultOptions({
minZoom: 0,
maxZoom: 18,
subdomains: "abc",
errorTileUrl: "",
zoomOffset: 0,
tms: false,
zoomReverse: false,
detectRetina: false,
crossOrigin: false,
referrerPolicy: "strict-origin-when-cross-origin"
});
}
constructor(url, options) {
super(options);
this._url = url;
options = setOptions(this, options);
const tileUrl = new URL(this._url, location.href);
const urlHostname = tileUrl.hostname;
if (["tile.openstreetmap.org", "tile.osm.org"].some((host) => urlHostname.endsWith(host))) {
if (options.attribution === null) options.attribution = `© <a href="https://www.openstreetmap.org/copyright">${I18n.translate("OpenStreetMap contributors")}</a>`;
if (tileUrl.protocol === "http:") {
tileUrl.protocol = "https:";
this._url = tileUrl.toString();
}
}
if (this.options.detectRetina && Browser_default.retina && this.options.maxZoom > 0) {
this.options.tileSize = Math.floor(this.options.tileSize / 2);
if (!this.options.zoomReverse) {
this.options.zoomOffset++;
this.options.maxZoom = Math.max(this.options.minZoom, this.options.maxZoom - 1);
} else {
this.options.zoomOffset--;
this.options.minZoom = Math.min(this.options.maxZoom, this.options.minZoom + 1);
}
this.options.minZoom = Math.max(0, this.options.minZoom);
} else if (!this.options.zoomReverse) this.options.maxZoom = Math.max(this.options.minZoom, this.options.maxZoom);
else this.options.minZoom = Math.min(this.options.maxZoom, this.options.minZoom);
if (typeof this.options.subdomains === "string") this.options.subdomains = this.options.subdomains.split("");
this.on("tileunload", this._onTileRemove);
this.callInitHooks(TileLayer);
}
setUrl(url, noRedraw) {
if (this._url === url && noRedraw === void 0) noRedraw = true;
this._url = url;
if (!noRedraw) this.redraw();
return this;
}
createTile(coords, done) {
const tile = document.createElement("img");
on(tile, "load", this._tileOnLoad.bind(this, done, tile));
on(tile, "error", this._tileOnError.bind(this, done, tile));
if (this.options.crossOrigin || this.options.crossOrigin === "") tile.crossOrigin = this.options.crossOrigin === true ? "" : this.options.crossOrigin;
if (typeof this.options.referrerPolicy === "string") tile.referrerPolicy = this.options.referrerPolicy;
tile.alt = "";
tile.src = this.getTileUrl(coords);
return tile;
}
getTileUrl(coords) {
const data = Object.create(this.options);
Object.assign(data, {
r: Browser_default.retina ? "@2x" : "",
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
z: this._getZoomForUrl()
});
if (this._map && !this._map.options.crs.infinite) {
const invertedY = this._globalTileRange.max.y - coords.y;
if (this.options.tms) data["y"] = invertedY;
data["-y"] = invertedY;
}
return template(this._url, data);
}
_tileOnLoad(done, tile) {
done(null, tile);
}
_tileOnError(done, tile, e) {
const errorUrl = this.options.errorTileUrl;
if (errorUrl && tile.getAttribute("src") !== errorUrl) tile.src = errorUrl;
done(e, tile);
}
_onTileRemove(e) {
e.tile.onload = null;
}
_getZoomForUrl() {
let zoom = this._tileZoom;
const maxZoom = this.options.maxZoom, zoomReverse = this.options.zoomReverse, zoomOffset = this.options.zoomOffset;
if (zoomReverse) zoom = maxZoom - zoom;
return zoom + zoomOffset;
}
_getSubdomain(tilePoint) {
const index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
return this.options.subdomains[index];
}
_abortLoading() {
let i, tile;
for (i of Object.keys(this._tiles)) if (this._tiles[i].coords.z !== this._tileZoom) {
tile = this._tiles[i].el;
tile.onload = falseFn;
tile.onerror = falseFn;
if (!tile.complete) {
tile.setAttribute("src", "");
const coords = this._tiles[i].coords;
tile.remove();
delete this._tiles[i];
this.fire("tileabort", {
tile,
coords
});
}
}
}
_removeTile(key) {
const tile = this._tiles[key];
if (!tile) return;
tile.el.setAttribute("src", "");
return super._removeTile(key);
}
_tileReady(coords, err, tile) {
if (!this._map || tile && tile.getAttribute("src") === "") return;
return super._tileReady(coords, err, tile);
}
_clampZoom(zoom) {
return Math.round(super._clampZoom(zoom));
}
};
//#endregion
//#region src/layer/tile/WMSTileLayer.js
var WMSTileLayer = class WMSTileLayer extends TileLayer {
static {
this.prototype.defaultWmsParams = {
service: "WMS",
request: "GetMap",
layers: "",
styles: "",
format: "image/jpeg",
transparent: false,
version: "1.1.1"
};
this.setDefaultOptions({
crs: null,
uppercase: false
});
}
constructor(url, options) {
super(url, options);
const wmsParams = { ...this.defaultWmsParams };
for (const [key, value] of Object.entries(options)) if (!(key in WMSTileLayer.prototype.options)) wmsParams[key] = value;
const realRetina = this.options.detectRetina && Browser_default.retina ? 2 : 1;
const tileSize = this.getTileSize();
wmsParams.width = tileSize.x * realRetina;
wmsParams.height = tileSize.y * realRetina;
this.wmsParams = wmsParams;
this.callInitHooks(WMSTileLayer);
}
onAdd(map) {
this._crs = this.options.crs ?? map.options.crs;
this._wmsVersion = parseFloat(this.wmsParams.version);
const projectionKey = this._wmsVersion >= 1.3 ? "crs" : "srs";
this.wmsParams[projectionKey] = this._crs.code;
super.onAdd(map);
}
getTileUrl(coords) {
const tileBounds = this._tileCoordsToNwSe(coords), crs = this._crs, bounds = new Bounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), min = bounds.min, max = bounds.max, bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? [
min.y,
min.x,
max.y,
max.x
] : [
min.x,
min.y,
max.x,
max.y
]).join(",");
const url = new URL(super.getTileUrl(coords));
for (const [k, v] of Object.entries({
...this.wmsParams,
bbox
})) url.searchParams.append(this.options.uppercase ? k.toUpperCase() : k, v);
return url.toString();
}
setParams(params, noRedraw) {
Object.assign(this.wmsParams, params);
if (!noRedraw) this.redraw();
return this;
}
};
//#endregion
//#region src/layer/vector/Renderer.js
var Renderer = class Renderer extends BlanketOverlay {
constructor(options) {
super({
...options,
continuous: false
});
stamp(this);
this._layers ??= {};
this.callInitHooks(Renderer);
}
onAdd(map) {
super.onAdd(map);
this.on("update", this._updatePaths, this);
}
onRemove() {
super.onRemove();
this.off("update", this._updatePaths, this);
}
_onZoomEnd() {
for (const layer of Object.values(this._layers)) layer._project();
}
_updatePaths() {
for (const layer of Object.values(this._layers)) layer._update();
}
_onViewReset() {
for (const layer of Object.values(this._layers)) layer._reset();
}
_onSettled() {
this._update();
}
_update() {}
};
//#endregion
//#region src/layer/vector/Canvas.js
const DASH_SEPARATOR_RE = /[, ]+/;
var Canvas = class Canvas extends Renderer {
static {
this.setDefaultOptions({ tolerance: 0 });
}
constructor(...args) {
super(...args);
this.callInitHooks(Canvas);
}
getEvents() {
const events = super.getEvents();
events.viewprereset = this._onViewPreReset;
return events;
}
_onViewPreReset() {
this._postponeUpdatePaths = true;
}
onAdd(map) {
super.onAdd(map);
this._draw();
}
onRemove() {
super.onRemove();
clearTimeout(this._pointerHoverThrottleTimeout);
}
_initContainer() {
const container = this._container = document.createElement("canvas");
on(container, "pointermove", this._onPointerMove, this);
on(container, "click dblclick pointerdown pointerup contextmenu", this._onClick, this);
on(container, "pointerout", this._handlePointerOut, this);
container["_maplair_disable_events"] = true;
this._ctx = container.getContext("2d");
}
_destroyContainer() {
cancelAnimationFrame(this._redrawRequest);
this._redrawRequest = null;
delete this._ctx;
super._destroyContainer();
}
_resizeContainer() {
const size = super._resizeContainer();
const m = this._ctxScale = window.devicePixelRatio;
this._container.width = m * size.x;
this._container.height = m * size.y;
}
_updatePaths() {
if (this._postponeUpdatePaths) return;
this._redrawBounds = null;
for (const layer of Object.values(this._layers)) layer._update();
this._redraw();
}
_update() {
if (this._map._animatingZoom && this._bounds) return;
const b = this._bounds, s = this._ctxScale;
this._ctx.setTransform(s, 0, 0, s, -b.min.x * s, -b.min.y * s);
this.fire("update");
}
_reset() {
super._reset();
if (this._postponeUpdatePaths) {
this._postponeUpdatePaths = false;
this._updatePaths();
}
}
_initPath(layer) {
this._updateDashArray(layer);
this._layers[stamp(layer)] = layer;
const order = layer._order = {
layer,
prev: this._drawLast,
next: null
};
if (this._drawLast) this._drawLast.next = order;
this._drawLast = order;
this._drawFirst ??= this._drawLast;
}
_addPath(layer) {
this._requestRedraw(layer);
}
_removePath(layer) {
const order = layer._order;
const next = order.next;
const prev = order.prev;
if (next) next.prev = prev;
else this._drawLast = prev;
if (prev) prev.next = next;
else this._drawFirst = next;
delete layer._order;
delete this._layers[stamp(layer)];
this._requestRedraw(layer);
}
_updatePath(layer) {
this._extendRedrawBounds(layer);
layer._project();
layer._update();
this._requestRedraw(layer);
}
_updateStyle(layer) {
this._updateDashArray(layer);
this._requestRedraw(layer);
}
_updateDashArray(layer) {
if (typeof layer.options.dashArray === "string") {
const parts = layer.options.dashArray.split(DASH_SEPARATOR_RE);
layer.options._dashArray = parts.map((n) => Number(n)).filter((n) => !isNaN(n));
} else layer.options._dashArray = layer.options.dashArray;
}
_requestRedraw(layer) {
if (!this._map) return;
this._extendRedrawBounds(layer);
this._redrawRequest ??= requestAnimationFrame(this._redraw.bind(this));
}
_extendRedrawBounds(layer) {
if (layer._pxBounds) {
const padding = (layer.options.weight ?? 0) + 1;
this._redrawBounds ??= new Bounds();
this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
}
}
_redraw() {
this._redrawRequest = null;
if (this._redrawBounds) {
this._redrawBounds.min._floor();
this._redrawBounds.max._ceil();
}
this._clear();
this._draw();
this._redrawBounds = null;
}
_clear() {
const bounds = this._redrawBounds;
if (bounds) {
const size = bounds.getSize();
this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
} else {
this._ctx.save();
this._ctx.setTransform(1, 0, 0, 1, 0, 0);
this._ctx.clearRect(0, 0, this._container.width, this._container.height);
this._ctx.restore();
}
}
_draw() {
let layer;
const bounds = this._redrawBounds;
this._ctx.save();
if (bounds) {
const size = bounds.getSize();
this._ctx.beginPath();
this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
this._ctx.clip();
}
this._drawing = true;
for (let order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (!bounds || layer._pxBounds && layer._pxBounds.intersects(bounds)) layer._updatePath();
}
this._drawing = false;
this._ctx.restore();
}
_updatePoly(layer, closed) {
if (!this._drawing) return;
const parts = layer._parts, ctx = this._ctx;
if (!parts.length) return;
ctx.beginPath();
parts.forEach((p0) => {
p0.forEach((p, j) => {
ctx[j ? "lineTo" : "moveTo"](p.x, p.y);
});
if (closed) ctx.closePath();
});
this._fillStroke(ctx, layer);
}
_updateCircle(layer) {
if (!this._drawing || layer._empty()) return;
const p = layer._point, ctx = this._ctx, r = Math.max(Math.round(layer._pxRadius), 1), s = (Math.max(Math.round(layer._pxRadiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
if (s !== 1) ctx.restore();
this._fillStroke(ctx, layer);
}
_fillStroke(ctx, layer) {
const options = layer.options;
if (options.fill) {
ctx.globalAlpha = options.fillOpacity;
ctx.fillStyle = options.fillColor ?? options.color;
ctx.fill(options.fillRule || "evenodd");
}
if (options.stroke && options.weight !== 0) {
ctx.lineDashOffset = Number(options.dashOffset ?? 0);
ctx.setLineDash(options._dashArray ?? []);
ctx.globalAlpha = options.opacity;
ctx.lineWidth = options.weight;
ctx.strokeStyle = options.color;
ctx.lineCap = options.lineCap;
ctx.lineJoin = options.lineJoin;
ctx.stroke();
}
}
_onClick(e) {
const point = this._map.pointerEventToLayerPoint(e);
let layer, clickedLayer;
for (let order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (layer.options.interactive && layer._containsPoint(point)) {
if (!(e.type === "click" || e.type === "preclick") || !this._map._draggableMoved(layer)) clickedLayer = layer;
}
}
this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
}
_onPointerMove(e) {
if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) return;
const point = this._map.pointerEventToLayerPoint(e);
this._handlePointerHover(e, point);
}
_handlePointerOut(e) {
const layer = this._hoveredLayer;
if (layer) {
this._container.classList.remove("maplair-interactive");
this._fireEvent([layer], e, "pointerout");
this._hoveredLayer = null;
this._pointerHoverThrottled = false;
}
}
_handlePointerHover(e, point) {
if (this._pointerHoverThrottled) return;
let layer, candidateHoveredLayer;
for (let order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (layer.options.interactive && layer._containsPoint(point)) candidateHoveredLayer = layer;
}
if (candidateHoveredLayer !== this._hoveredLayer) {
this._handlePointerOut(e);
if (candidateHoveredLayer) {
this._container.classList.add("maplair-interactive");
this._fireEvent([candidateHoveredLayer], e, "pointerover");
this._hoveredLayer = candidateHoveredLayer;
}
}
this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
this._pointerHoverThrottled = true;
this._pointerHoverThrottleTimeout = setTimeout((() => {
this._pointerHoverThrottled = false;
}), 32);
}
_fireEvent(layers, e, type) {
this._map._fireDOMEvent(e, type || e.type, layers);
}
_bringToFront(layer) {
const order = layer._order;
if (!order) return;
const next = order.next;
const prev = order.prev;
if (next) next.prev = prev;
else return;
if (prev) prev.next = next;
else if (next) this._drawFirst = next;
order.prev = this._drawLast;
this._drawLast.next = order;
order.next = null;
this._drawLast = order;
this._requestRedraw(layer);
}
_bringToBack(layer) {
const order = layer._order;
if (!order) return;
const next = order.next;
const prev = order.prev;
if (prev) prev.next = next;
else return;
if (next) next.prev = prev;
else if (prev) this._drawLast = prev;
order.prev = null;
order.next = this._drawFirst;
this._drawFirst.prev = order;
this._drawFirst = order;
this._requestRedraw(layer);
}
};
//#endregion
//#region src/layer/vector/SVG.js
var SVG = class SVG extends Renderer {
constructor(...args) {
super(...args);
this.callInitHooks(SVG);
}
_initContainer() {
this._container = SVG.create("svg");
this._container.setAttribute("pointer-events", "none");
this._rootGroup = SVG.create("g");
this._container.appendChild(this._rootGroup);
}
_destroyContainer() {
super._destroyContainer();
delete this._rootGroup;
delete this._svgSize;
}
_resizeContainer() {
const size = super._resizeContainer();
if (!this._svgSize || !this._svgSize.equals(size)) {
this._svgSize = size;
this._container.setAttribute("width", size.x);
this._container.setAttribute("height", size.y);
}
const b = this._bounds;
if (b) this._container.setAttribute("viewBox", [
b.min.x,
b.min.y,
size.x,
size.y
].join(" "));
}
_update() {
if (this._map._animatingZoom && this._bounds) return;
const b = this._bounds, size = b.getSize();
this._container.setAttribute("viewBox", [
b.min.x,
b.min.y,
size.x,
size.y
].join(" "));
this.fire("update");
}
_initPath(layer) {
const path = layer._path = SVG.create("path");
if (layer.options.className) path.classList.add(...splitWords(layer.options.className));
if (layer.options.interactive) path.classList.add("maplair-interactive");
this._updateStyle(layer);
this._layers[stamp(layer)] = layer;
}
_addPath(layer) {
if (!this._rootGroup) this._initContainer();
this._rootGroup.appendChild(layer._path);
layer.addInteractiveTarget(layer._path);
}
_removePath(layer) {
layer._path.remove();
layer.removeInteractiveTarget(layer._path);
delete this._layers[stamp(layer)];
}
_updatePath(layer) {
layer._project();
layer._update();
}
_updateStyle(layer) {
const path = layer._path, options = layer.options;
if (!path) return;
if (options.stroke) {
path.setAttribute("stroke", options.color);
path.setAttribute("stroke-opacity", options.opacity);
path.setAttribute("stroke-width", options.weight);
path.setAttribute("stroke-linecap", options.lineCap);
path.setAttribute("stroke-linejoin", options.lineJoin);
if (options.dashArray) path.setAttribute("stroke-dasharray", options.dashArray);
else path.removeAttribute("stroke-dasharray");
if (options.dashOffset) path.setAttribute("stroke-dashoffset", options.dashOffset);
else path.removeAttribute("stroke-dashoffset");
} else path.setAttribute("stroke", "none");
if (options.fill) {
path.setAttribute("fill", options.fillColor || options.color);
path.setAttribute("fill-opacity", options.fillOpacity);
path.setAttribute("fill-rule", options.fillRule || "evenodd");
} else path.setAttribute("fill", "none");
}
_updatePoly(layer, closed) {
this._setPath(layer, SVG.pointsToPath(layer._parts, closed));
}
_updateCircle(layer) {
const p = layer._point, r = Math.max(Math.round(layer._pxRadius), 1), arc = `a${r},${Math.max(Math.round(layer._pxRadiusY), 1) || r} 0 1,0 `;
const d = layer._empty() ? "M0 0" : `M${p.x - r},${p.y}${arc}${r * 2},0 ${arc}${-r * 2},0 `;
this._setPath(layer, d);
}
_setPath(layer, path) {
layer._path.setAttribute("d", path);
}
_bringToFront(layer) {
toFront(layer._path);
}
_bringToBack(layer) {
toBack(layer._path);
}
static create(name) {
return document.createElementNS("http://www.w3.org/2000/svg", name);
}
static pointsToPath(rings, closed) {
return rings.flatMap((points) => [...points.map((p, j) => `${(j ? "L" : "M") + p.x} ${p.y}`), closed ? "z" : ""]).join("") || "M0 0";
}
};
//#endregion
//#region src/layer/vector/Rectangle.js
var Rectangle = class Rectangle extends Polygon {
constructor(latLngBounds, options) {
super(Rectangle._boundsToLatLngs(latLngBounds), options);
this.callInitHooks(Rectangle);
}
setBounds(latLngBounds) {
return this.setLatLngs(Rectangle._boundsToLatLngs(latLngBounds));
}
static _boundsToLatLngs(latLngBounds) {
latLngBounds = new LatLngBounds(latLngBounds);
return [
latLngBounds.getSouthWest(),
latLngBounds.getNorthWest(),
latLngBounds.getNorthEast(),
latLngBounds.getSouthEast()
];
}
};
//#endregion
//#region src/layer/vector/Renderer.getRenderer.js
var Renderer_getRenderer_exports = /* @__PURE__ */ __exportAll({ register: () => register });
function register(MaplairMap) {
MaplairMap.include({
getRenderer(layer) {
let renderer = layer.options.renderer ?? this._getPaneRenderer(layer.options.pane) ?? this.options.renderer ?? this._renderer;
if (!renderer) renderer = this._renderer = this._createRenderer();
if (!this.hasLayer(renderer)) this.addLayer(renderer);
return renderer;
},
_getPaneRenderer(name) {
if (name === "overlayPane" || name === void 0) return;
let renderer = this._paneRenderers[name];
if (renderer === void 0) {
renderer = this._createRenderer({ pane: name });
this._paneRenderers[name] = renderer;
}
return renderer;
},
_createRenderer(options) {
return this.options.preferCanvas && new Canvas(options) || new SVG(options);
}
});
}
//#endregion
//#region src/map/handler/BoxZoomHandler.js
var BoxZoomHandler = class BoxZoomHandler extends Handler {
constructor(map) {
super(map);
this._container = map._container;
this._pane = map._panes.overlayPane;
this._resetStateTimeout = 0;
map.on("unload", this._destroy, this);
this.callInitHooks(BoxZoomHandler);
}
addHooks() {
on(this._container, "pointerdown", this._onPointerDown, this);
}
removeHooks() {
off(this._container, "pointerdown", this._onPointerDown, this);
}
moved() {
return this._moved;
}
_destroy() {
this._pane.remove();
delete this._pane;
}
_resetState() {
this._resetStateTimeout = 0;
this._moved = false;
}
_clearDeferredResetState() {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
}
_onPointerDown(e) {
if (!e.shiftKey || e.button !== 0) return false;
this._clearDeferredResetState();
this._resetState();
disableTextSelection();
disableImageDrag();
this._startPoint = this._map.pointerEventToContainerPoint(e);
on(this._container, {
contextmenu: stop,
pointermove: this._onPointerMove,
pointerup: this._onPointerUp,
keydown: this._onKeyDown
}, this);
on(document, "keydown", this._onKeyDown, this);
}
_onPointerMove(e) {
if (!this._moved) {
this._moved = true;
this._box = create("div", "maplair-zoom-box", this._container);
this._container.classList.add("maplair-crosshair");
this._map.fire("boxzoomstart");
}
this._point = this._map.pointerEventToContainerPoint(e);
const bounds = new Bounds(this._point, this._startPoint), size = bounds.getSize();
setPosition(this._box, bounds.min);
this._box.style.width = `${size.x}px`;
this._box.style.height = `${size.y}px`;
}
_finish() {
if (this._moved) {
this._box.remove();
this._container.classList.remove("maplair-crosshair");
}
enableTextSelection();
enableImageDrag();
off(this._container, {
contextmenu: stop,
pointermove: this._onPointerMove,
pointerup: this._onPointerUp
}, this);
off(document, "keydown", this._onKeyDown, this);
}
_onPointerUp(e) {
if (e.button !== 0) return;
this._finish();
if (!this._moved) return;
this._clearDeferredResetState();
this._resetStateTimeout = setTimeout(this._resetState.bind(this), 0);
const bounds = new LatLngBounds(this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point));
this._map.fitBounds(bounds).fire("boxzoomend", { boxZoomBounds: bounds });
}
_onKeyDown(e) {
if (e.code === "Escape") {
this._finish();
this._clearDeferredResetState();
this._resetState();
}
}
static register(MaplairMap) {
MaplairMap.mergeOptions({ boxZoom: true });
MaplairMap.addInitHook("addHandler", "boxZoom", BoxZoomHandler);
}
};
//#endregion
//#region src/map/handler/DoubleClickZoomHandler.js
var DoubleClickZoomHandler = class DoubleClickZoomHandler extends Handler {
constructor(...args) {
super(...args);
this.callInitHooks(DoubleClickZoomHandler);
}
addHooks() {
this._map.on("dblclick", this._onDoubleClick, this);
}
removeHooks() {
this._map.off("dblclick", this._onDoubleClick, this);
}
_onDoubleClick(e) {
const map = this._map, oldZoom = map.getZoom(), delta = map.options.zoomDelta, zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
if (map.options.doubleClickZoom === "center") map.setZoom(zoom);
else map.setZoomAround(e.containerPoint, zoom);
}
static register(MaplairMap) {
MaplairMap.mergeOptions({ doubleClickZoom: true });
MaplairMap.addInitHook("addHandler", "doubleClickZoom", DoubleClickZoomHandler);
}
};
//#endregion
//#region src/map/handler/DragHandler.js
var DragHandler = class DragHandler extends Handler {
constructor(...args) {
super(...args);
this.callInitHooks(DragHandler);
}
addHooks() {
if (!this._draggable) {
const map = this._map;
this._draggable = new Draggable(map._mapPane, map._container);
this._draggable.on({
dragstart: this._onDragStart,
drag: this._onDrag,
dragend: this._onDragEnd
}, this);
this._draggable.on("predrag", this._onPreDragLimit, this);
if (map.options.worldCopyJump) {
this._draggable.on("predrag", this._onPreDragWrap, this);
map.on("zoomend", this._onZoomEnd, this);
map.whenReady(this._onZoomEnd, this);
}
}
this._map._container.classList.add("maplair-grab", "maplair-touch-drag");
this._draggable.enable();
this._positions = [];
this._times = [];
}
removeHooks() {
this._map._container.classList.remove("maplair-grab", "maplair-touch-drag");
this._draggable.disable();
}
moved() {
return this._draggable?._moved;
}
moving() {
return this._draggable?._moving;
}
_onDragStart() {
const map = this._map;
map._stop();
if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
const bounds = new LatLngBounds(this._map.options.maxBounds);
this._offsetLimit = new Bounds(this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1).add(this._map.getSize()));
this._viscosity = Math.min(1, Math.max(0, this._map.options.maxBoundsViscosity));
} else this._offsetLimit = null;
map.fire("movestart").fire("dragstart");
if (map.options.inertia) {
this._positions = [];
this._times = [];
}
}
_onDrag(e) {
if (this._map.options.inertia) {
const time = this._lastTime = Date.now(), pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
this._positions.push(pos);
this._times.push(time);
this._prunePositions(time);
}
this._map.fire("move", e).fire("drag", e);
}
_prunePositions(time) {
while (this._positions.length > 1 && time - this._times[0] > 50) {
this._positions.shift();
this._times.shift();
}
}
_onZoomEnd() {
const pxCenter = this._map.getSize().divideBy(2), pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
}
_viscousLimit(value, threshold) {
return value - (value - threshold) * this._viscosity;
}
_onPreDragLimit() {
if (!this._viscosity || !this._offsetLimit) return;
const offset = this._draggable._newPos.subtract(this._draggable._startPos);
const limit = this._offsetLimit;
if (offset.x < limit.min.x) offset.x = this._viscousLimit(offset.x, limit.min.x);
if (offset.y < limit.min.y) offset.y = this._viscousLimit(offset.y, limit.min.y);
if (offset.x > limit.max.x) offset.x = this._viscousLimit(offset.x, limit.max.x);
if (offset.y > limit.max.y) offset.y = this._viscousLimit(offset.y, limit.max.y);
this._draggable._newPos = this._draggable._startPos.add(offset);
}
_onPreDragWrap() {
const worldWidth = this._worldWidth, halfWidth = Math.round(worldWidth / 2), dx = this._initialWorldOffset, x = this._draggable._newPos.x, newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
this._draggable._absPos = new Point(this._draggable._newPos);
this._draggable._newPos.x = newX;
}
_onDragEnd(e) {
const map = this._map, options = map.options, noInertia = !options.inertia || e.noInertia || this._times.length < 2;
map.fire("dragend", e);
if (noInertia) map.fire("moveend");
else {
this._prunePositions(Date.now());
const direction = this._lastPos.subtract(this._positions[0]), duration = (this._lastTime - this._times[0]) / 1e3, ease = options.easeLinearity, speedVector = direction.multiplyBy(ease / duration), speed = speedVector.distanceTo([0, 0]), limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease);
let offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
if (!offset.x && !offset.y) map.fire("moveend");
else {
offset = map._limitOffset(offset, map.options.maxBounds);
requestAnimationFrame(() => {
map.panBy(offset, {
duration: decelerationDuration,
easeLinearity: ease,
noMoveStart: true,
animate: true
});
});
}
}
}
static register(MaplairMap) {
MaplairMap.mergeOptions({
dragging: true,
inertia: true,
inertiaDeceleration: 3400,
inertiaMaxSpeed: Infinity,
easeLinearity: .2,
worldCopyJump: false,
maxBoundsViscosity: 0
});
MaplairMap.addInitHook("addHandler", "dragging", DragHandler);
}
};
//#endregion
//#region src/map/handler/KeyboardHandler.js
var KeyboardHandler = class KeyboardHandler extends Handler {
static keyCodes = {
left: ["ArrowLeft"],
right: ["ArrowRight"],
down: ["ArrowDown"],
up: ["ArrowUp"],
zoomIn: [
"Equal",
"NumpadAdd",
"BracketRight"
],
zoomOut: [
"Minus",
"NumpadSubtract",
"Digit6",
"Slash"
]
};
constructor(map) {
super(map);
this._setPanDelta(map.options.keyboardPanDelta);
this._setZoomDelta(map.options.zoomDelta);
this.callInitHooks(KeyboardHandler);
}
addHooks() {
const container = this._map._container;
if (container.tabIndex <= 0) container.tabIndex = "0";
container.ariaKeyShortcuts = Object.values(KeyboardHandler.keyCodes).flat().join(" ");
on(container, {
focus: this._onFocus,
blur: this._onBlur,
pointerdown: this._onPointerDown
}, this);
this._map.on({
focus: this._addHooks,
blur: this._removeHooks
}, this);
}
removeHooks() {
this._removeHooks();
off(this._map._container, {
focus: this._onFocus,
blur: this._onBlur,
pointerdown: this._onPointerDown
}, this);
this._map.off({
focus: this._addHooks,
blur: this._removeHooks
}, this);
}
_onPointerDown() {
if (this._focused) return;
const body = document.body, docEl = document.documentElement, top = body.scrollTop || docEl.scrollTop, left = body.scrollLeft || docEl.scrollLeft;
this._map._container.focus();
window.scrollTo(left, top);
}
_onFocus() {
this._focused = true;
this._map.fire("focus");
}
_onBlur() {
this._focused = false;
this._map.fire("blur");
}
_setPanDelta(panDelta) {
const keys = this._panKeys = {}, codes = KeyboardHandler.keyCodes;
for (const code of codes.left) keys[code] = [-1 * panDelta, 0];
for (const code of codes.right) keys[code] = [panDelta, 0];
for (const code of codes.down) keys[code] = [0, panDelta];
for (const code of codes.up) keys[code] = [0, -1 * panDelta];
}
_setZoomDelta(zoomDelta) {
const keys = this._zoomKeys = {}, codes = KeyboardHandler.keyCodes;
for (const code of codes.zoomIn) keys[code] = zoomDelta;
for (const code of codes.zoomOut) keys[code] = -zoomDelta;
}
_addHooks() {
on(document, "keydown", this._onKeyDown, this);
}
_removeHooks() {
off(document, "keydown", this._onKeyDown, this);
}
_onKeyDown(e) {
if (e.altKey || e.ctrlKey || e.metaKey) return;
const key = e.code, map = this._map;
let offset;
if (key in this._panKeys) {
if (!map._panAnim || !map._panAnim._inProgress) {
offset = this._panKeys[key];
if (e.shiftKey) offset = new Point(offset).multiplyBy(3);
if (map.options.maxBounds) offset = map._limitOffset(new Point(offset), map.options.maxBounds);
if (map.options.worldCopyJump) {
const newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
map.panTo(newLatLng);
} else map.panBy(offset);
}
} else if (key in this._zoomKeys) map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
else if (key === "Escape" && map._popup && map._popup.options.closeOnEscapeKey) map.closePopup();
else return;
stop(e);
}
static register(MaplairMap) {
MaplairMap.mergeOptions({
keyboard: true,
keyboardPanDelta: 80
});
MaplairMap.addInitHook("addHandler", "keyboard", KeyboardHandler);
}
};
//#endregion
//#region src/map/handler/PinchZoomHandler.js
var PinchZoomHandler = class PinchZoomHandler extends Handler {
constructor(...args) {
super(...args);
this.callInitHooks(PinchZoomHandler);
}
addHooks() {
this._map._container.classList.add("maplair-touch-zoom");
on(this._map._container, "pointerdown", this._onPointerStart, this);
}
removeHooks() {
this._map._container.classList.remove("maplair-touch-zoom");
off(this._map._container, "pointerdown", this._onPointerStart, this);
off(this._map._container, "pointermove", this._onPointerMove, this);
off(this._map._container, "pointerup pointercancel", this._onPointerEnd, this);
}
_onPointerStart(e) {
const map = this._map;
const pointers = getPointers();
if (pointers.length !== 2 || map._animatingZoom || this._zooming) return;
const p1 = map.pointerEventToContainerPoint(pointers[0]), p2 = map.pointerEventToContainerPoint(pointers[1]);
this._centerPoint = map.getSize()._divideBy(2);
this._startLatLng = map.containerPointToLatLng(this._centerPoint);
if (map.options.pinchZoom !== "center") this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
this._startDist = p1.distanceTo(p2);
this._startZoom = map.getZoom();
this._moved = false;
this._zooming = true;
map._stop();
on(this._map._container, "pointermove", this._onPointerMove, this);
on(this._map._container, "pointerup pointercancel", this._onPointerEnd, this);
e.preventDefault();
}
_onPointerMove(e) {
const pointers = getPointers();
if (pointers.length !== 2 || !this._zooming) return;
const map = this._map, p1 = map.pointerEventToContainerPoint(pointers[0]), p2 = map.pointerEventToContainerPoint(pointers[1]), scale = p1.distanceTo(p2) / this._startDist;
this._zoom = map.getScaleZoom(scale, this._startZoom);
if (!map.options.bounceAtZoomLimits && (this._zoom < map.getMinZoom() && scale < 1 || this._zoom > map.getMaxZoom() && scale > 1)) this._zoom = map._limitZoom(this._zoom);
if (map.options.pinchZoom === "center") {
this._center = this._startLatLng;
if (scale === 1) return;
} else {
const delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
if (scale === 1 && delta.x === 0 && delta.y === 0) return;
this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
}
if (!this._moved) {
map._moveStart(true, false);
this._moved = true;
}
cancelAnimationFrame(this._animRequest);
const moveFn = map._move.bind(map, this._center, this._zoom, {
pinch: true,
round: false
}, void 0);
this._animRequest = requestAnimationFrame(moveFn.bind(this));
e.preventDefault();
}
_onPointerEnd() {
off(this._map._container, "pointermove", this._onPointerMove, this);
off(this._map._container, "pointerup pointercancel", this._onPointerEnd, this);
if (!this._moved || !this._zooming) {
this._zooming = false;
return;
}
this._zooming = false;
cancelAnimationFrame(this._animRequest);
if (this._map.options.zoomAnimation) this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
else this._map._resetView(this._center, this._map._limitZoom(this._zoom));
}
static register(MaplairMap) {
MaplairMap.mergeOptions({
pinchZoom: true,
bounceAtZoomLimits: true
});
MaplairMap.addInitHook("addHandler", "pinchZoom", PinchZoomHandler);
MaplairMap.addInitHook(function() {
this.touchZoom = this.pinchZoom;
if (this.options.touchZoom !== void 0) {
console.warn("Map: touchZoom option is deprecated and will be removed in future versions. Use pinchZoom instead.");
this.options.pinchZoom = this.options.touchZoom;
delete this.options.touchZoom;
}
if (this.options.pinchZoom) this.pinchZoom.enable();
else this.pinchZoom.disable();
});
}
};
//#endregion
//#region src/map/handler/ScrollWheelZoomHandler.js
var ScrollWheelZoomHandler = class ScrollWheelZoomHandler extends Handler {
constructor(...args) {
super(...args);
this.callInitHooks(ScrollWheelZoomHandler);
}
addHooks() {
on(this._map._container, "wheel", this._onWheelScroll, this);
this._delta = 0;
}
removeHooks() {
off(this._map._container, "wheel", this._onWheelScroll, this);
clearTimeout(this._timer);
}
_onWheelScroll(e) {
const delta = getWheelDelta(e);
const debounce = this._map.options.wheelDebounceTime;
this._delta += delta;
this._lastMousePos = this._map.pointerEventToContainerPoint(e);
if (!this._startTime) this._startTime = Date.now();
const left = Math.max(debounce - (Date.now() - this._startTime), 0);
clearTimeout(this._timer);
this._timer = setTimeout(this._performZoom.bind(this), left);
stop(e);
}
_performZoom() {
const map = this._map, zoom = map.getZoom(), snap = this._map.options.zoomSnap ?? 0;
map._stop();
const d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, d4 = snap ? Math.ceil(d3 / snap) * snap : d3, delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
this._delta = 0;
this._startTime = null;
if (!delta) return;
if (map.options.scrollWheelZoom === "center") map.setZoom(zoom + delta);
else map.setZoomAround(this._lastMousePos, zoom + delta);
}
static register(MaplairMap) {
MaplairMap.mergeOptions({
scrollWheelZoom: true,
wheelDebounceTime: 40,
wheelPxPerZoomLevel: 60
});
MaplairMap.addInitHook("addHandler", "scrollWheelZoom", ScrollWheelZoomHandler);
}
};
//#endregion
//#region src/map/handler/TapHoldHandler.js
const tapHoldDelay = 600;
var TapHoldHandler = class TapHoldHandler extends Handler {
constructor(...args) {
super(...args);
this.callInitHooks(TapHoldHandler);
}
addHooks() {
on(this._map._container, "pointerdown", this._onDown, this);
}
removeHooks() {
off(this._map._container, "pointerdown", this._onDown, this);
this._cancel();
this._cancelClickPrevent();
}
_onDown(e) {
clearTimeout(this._holdTimeout);
if (getPointers().length !== 1 || e.pointerType === "mouse") return;
const el = this._map._container;
this._startPos = this._newPos = new Point(e.clientX, e.clientY);
this._holdTimeout = setTimeout((() => {
this._cancel();
if (!this._isTapValid()) return;
on(el, "pointerup", preventDefault);
on(el, "pointerup pointercancel", this._cancelClickPrevent, this);
this._simulateEvent("contextmenu", e);
}), tapHoldDelay);
on(el, "pointerup pointercancel contextmenu", this._cancel, this);
on(el, "pointermove", this._onMove, this);
}
_cancelClickPrevent() {
const el = this._map._container;
off(el, "pointerup", preventDefault);
off(el, "pointerup pointercancel", this._cancelClickPrevent, this);
}
_cancel() {
clearTimeout(this._holdTimeout);
const el = this._map._container;
off(el, "pointerup pointercancel contextmenu", this._cancel, this);
off(el, "pointermove", this._onMove, this);
}
_onMove(e) {
this._newPos = new Point(e.clientX, e.clientY);
}
_isTapValid() {
return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
}
_simulateEvent(type, e) {
const simulatedEvent = new MouseEvent(type, {
bubbles: true,
cancelable: true,
view: window,
screenX: e.screenX,
screenY: e.screenY,
clientX: e.clientX,
clientY: e.clientY
});
simulatedEvent._simulated = true;
e.target.dispatchEvent(simulatedEvent);
}
static register(MaplairMap) {
MaplairMap.mergeOptions({
tapHold: Browser_default.safari && Browser_default.mobile,
tapTolerance: 15
});
MaplairMap.addInitHook("addHandler", "tapHold", TapHoldHandler);
}
};
//#endregion
//#region src/map/Map.js
var MaplairMap = class MaplairMap extends Evented {
static {
this.setDefaultOptions({
crs: EPSG3857,
center: void 0,
zoom: void 0,
minZoom: void 0,
maxZoom: void 0,
layers: [],
maxBounds: void 0,
renderer: void 0,
zoomAnimation: !Browser_default.reducedMotion,
zoomAnimationThreshold: 4,
fadeAnimation: !Browser_default.reducedMotion,
markerZoomAnimation: true,
markerCulling: false,
transform3DLimit: 8388608,
zoomSnap: 1,
zoomDelta: 1,
trackResize: true
});
}
constructor(id, options) {
super();
options = setOptions(this, options);
this._handlers = [];
this._layers = {};
this._zoomBoundLayers = {};
this._sizeChanged = true;
this._initContainer(id);
this._initLayout();
this._initEvents();
if (options.maxBounds) this.setMaxBounds(options.maxBounds);
if (options.zoom !== void 0) this._zoom = this._limitZoom(options.zoom);
if (options.center && options.zoom !== void 0) this.setView(new LatLng(options.center), options.zoom, { reset: true });
this.callInitHooks(MaplairMap);
this._zoomAnimated = this.options.zoomAnimation;
if (this._zoomAnimated) this._createAnimProxy();
this._addLayers(this.options.layers);
}
setView(center, zoom, options) {
zoom = zoom === void 0 ? this._zoom : this._limitZoom(zoom);
center = this._limitCenter(new LatLng(center), zoom, this.options.maxBounds);
options ??= {};
this._stop();
if (this._loaded && !options.reset && options !== true) {
if (options.animate !== void 0) {
options.zoom = {
animate: options.animate,
...options.zoom
};
options.pan = {
animate: options.animate,
duration: options.duration,
...options.pan
};
}
if (this._zoom !== zoom ? this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : this._tryAnimatedPan(center, options.pan)) {
clearTimeout(this._sizeTimer);
return this;
}
}
this._resetView(center, zoom, options.pan?.noMoveStart);
return this;
}
setZoom(zoom, options) {
if (!this._loaded) {
this._zoom = zoom;
return this;
}
return this.setView(this.getCenter(), zoom, { zoom: options });
}
zoomIn(delta, options) {
delta ??= this.options.zoomDelta;
return this.setZoom(this._zoom + delta, options);
}
zoomOut(delta, options) {
delta ??= this.options.zoomDelta;
return this.setZoom(this._zoom - delta, options);
}
setZoomAround(latlng, zoom, options) {
const scale = this.getZoomScale(zoom), viewHalf = this.getSize().divideBy(2), centerOffset = (latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng)).subtract(viewHalf).multiplyBy(1 - 1 / scale), newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
return this.setView(newCenter, zoom, { zoom: options });
}
_getBoundsCenterZoom(bounds, options) {
options ??= {};
bounds = bounds.getBounds ? bounds.getBounds() : new LatLngBounds(bounds);
const paddingTL = new Point(options.paddingTopLeft || options.padding || [0, 0]), paddingBR = new Point(options.paddingBottomRight || options.padding || [0, 0]);
let zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
zoom = typeof options.maxZoom === "number" ? Math.min(options.maxZoom, zoom) : zoom;
if (zoom === Infinity) return {
center: bounds.getCenter(),
zoom
};
const paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), swPoint = this.project(bounds.getSouthWest(), zoom), nePoint = this.project(bounds.getNorthEast(), zoom);
return {
center: this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom),
zoom
};
}
fitBounds(bounds, options) {
bounds = new LatLngBounds(bounds);
if (!bounds.isValid()) throw new Error("Bounds are not valid.");
const target = this._getBoundsCenterZoom(bounds, options);
return this.setView(target.center, target.zoom, options);
}
fitWorld(options) {
return this.fitBounds([[-90, -180], [90, 180]], options);
}
panTo(center, options) {
return this.setView(center, this._zoom, { pan: options });
}
panBy(offset, options) {
offset = new Point(offset).round();
options ??= {};
if (!offset.x && !offset.y) return this.fire("moveend");
if (options.animate !== true && !this.getSize().contains(offset)) {
this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
return this;
}
if (!this._panAnim) {
this._panAnim = new PosAnimation();
this._panAnim.on({
"step": this._onPanTransitionStep,
"end": this._onPanTransitionEnd
}, this);
}
if (!options.noMoveStart) this.fire("movestart");
if (!Browser_default.reducedMotion && options.animate !== false) {
this._mapPane.classList.add("maplair-pan-anim");
const newPos = this._getMapPanePos().subtract(offset).round();
this._panAnim.run(this._mapPane, newPos, options.duration || .25, options.easeLinearity);
} else {
this._rawPanBy(offset);
this.fire("move").fire("moveend");
}
return this;
}
flyTo(targetCenter, targetZoom, options) {
options ??= {};
if (Browser_default.reducedMotion || options.animate === false) return this.setView(targetCenter, targetZoom, options);
this._stop();
const from = this.project(this.getCenter()), to = this.project(targetCenter), size = this.getSize(), startZoom = this._zoom;
targetCenter = new LatLng(targetCenter);
targetZoom = targetZoom === void 0 ? startZoom : this._limitZoom(targetZoom);
const w0 = Math.max(size.x, size.y), w1 = w0 * this.getZoomScale(startZoom, targetZoom), u1 = to.distanceTo(from) || 1, rho = 1.42, rho2 = rho * rho;
function r(i) {
const s1 = i ? -1 : 1, s2 = i ? w1 : w0, b = (w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1) / (2 * s2 * rho2 * u1), sq = Math.sqrt(b * b + 1) - b;
return sq < 15e-9 ? -18 : Math.log(sq);
}
function sinh(n) {
return (Math.exp(n) - Math.exp(-n)) / 2;
}
function cosh(n) {
return (Math.exp(n) + Math.exp(-n)) / 2;
}
function tanh(n) {
return sinh(n) / cosh(n);
}
const r0 = r(0);
function w(s) {
return w0 * (cosh(r0) / cosh(r0 + rho * s));
}
function u(s) {
return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2;
}
function easeOut(t) {
return 1 - (1 - t) ** 1.5;
}
const start = Date.now(), S = (r(1) - r0) / rho, duration = options.duration ? 1e3 * options.duration : 1e3 * S * .8;
const frame = () => {
const t = (Date.now() - start) / duration, s = easeOut(t) * S;
if (t <= 1) {
this._flyToFrame = requestAnimationFrame(frame);
this._move(this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), this.getScaleZoom(w0 / w(s), startZoom), { flyTo: true });
} else this._move(targetCenter, targetZoom)._moveEnd(true);
};
this._moveStart(true, options.noMoveStart);
frame(this);
return this;
}
flyToBounds(bounds, options) {
const target = this._getBoundsCenterZoom(bounds, options);
return this.flyTo(target.center, target.zoom, options);
}
setMaxBounds(bounds) {
bounds = new LatLngBounds(bounds);
if (this.listens("moveend", this._panInsideMaxBounds)) this.off("moveend", this._panInsideMaxBounds);
if (!bounds.isValid()) {
this.options.maxBounds = null;
return this;
}
this.options.maxBounds = bounds;
if (this._loaded) this._panInsideMaxBounds();
return this.on("moveend", this._panInsideMaxBounds);
}
setMinZoom(zoom) {
const oldZoom = this.options.minZoom;
this.options.minZoom = zoom;
if (this._loaded && oldZoom !== zoom) {
this.fire("zoomlevelschange");
if (this.getZoom() < this.options.minZoom) return this.setZoom(zoom);
}
return this;
}
setMaxZoom(zoom) {
const oldZoom = this.options.maxZoom;
this.options.maxZoom = zoom;
if (this._loaded && oldZoom !== zoom) {
this.fire("zoomlevelschange");
if (this.getZoom() > this.options.maxZoom) return this.setZoom(zoom);
}
return this;
}
panInsideBounds(bounds, options) {
this._enforcingBounds = true;
const center = this.getCenter(), newCenter = this._limitCenter(center, this._zoom, new LatLngBounds(bounds));
if (!center.equals(newCenter)) this.panTo(newCenter, options);
this._enforcingBounds = false;
return this;
}
panInside(latlng, options) {
options ??= {};
const paddingTL = new Point(options.paddingTopLeft || options.padding || [0, 0]), paddingBR = new Point(options.paddingBottomRight || options.padding || [0, 0]), pixelCenter = this.project(this.getCenter()), pixelPoint = this.project(latlng), pixelBounds = this.getPixelBounds(), paddedBounds = new Bounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]), paddedSize = paddedBounds.getSize();
if (!paddedBounds.contains(pixelPoint)) {
this._enforcingBounds = true;
const centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
const offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
this.panTo(this.unproject(pixelCenter), options);
this._enforcingBounds = false;
}
return this;
}
invalidateSize(options) {
if (!this._loaded) return this;
options = {
animate: false,
pan: true,
...options === true ? { animate: true } : options
};
const oldSize = this.getSize();
this._sizeChanged = true;
this._lastCenter = null;
const newSize = this.getSize(), oldCenter = oldSize.divideBy(2).round(), newCenter = newSize.divideBy(2).round(), offset = oldCenter.subtract(newCenter);
if (!offset.x && !offset.y) return this;
if (options.animate && options.pan) this.panBy(offset);
else {
if (options.pan) this._rawPanBy(offset);
this.fire("move");
if (options.debounceMoveend) {
clearTimeout(this._sizeTimer);
this._sizeTimer = setTimeout(this.fire.bind(this, "moveend"), 200);
} else this.fire("moveend");
}
return this.fire("resize", {
oldSize,
newSize
});
}
stop() {
this.setZoom(this._limitZoom(this._zoom));
if (!this.options.zoomSnap) this.fire("viewreset");
return this._stop();
}
locate(options) {
options = this._locateOptions = {
timeout: 1e4,
watch: false,
...options
};
if (!("geolocation" in navigator)) {
this._handleGeolocationError({
code: 0,
message: "Geolocation not supported."
});
return this;
}
const onResponse = this._handleGeolocationResponse.bind(this), onError = this._handleGeolocationError.bind(this);
if (options.watch) {
if (this._locationWatchId !== void 0) navigator.geolocation.clearWatch(this._locationWatchId);
this._locationWatchId = navigator.geolocation.watchPosition(onResponse, onError, options);
} else navigator.geolocation.getCurrentPosition(onResponse, onError, options);
return this;
}
stopLocate() {
navigator.geolocation?.clearWatch?.(this._locationWatchId);
if (this._locateOptions) this._locateOptions.setView = false;
return this;
}
_handleGeolocationError(error) {
if (!this._container._maplair_id) return;
const c = error.code, message = error.message || (c === 1 ? "permission denied" : c === 2 ? "position unavailable" : "timeout");
if (this._locateOptions.setView && !this._loaded) this.fitWorld();
this.fire("locationerror", {
code: c,
message: `Geolocation error: ${message}.`
});
}
_handleGeolocationResponse(pos) {
if (!this._container._maplair_id) return;
const lat = pos.coords.latitude, lng = pos.coords.longitude, latlng = new LatLng(lat, lng), bounds = latlng.toBounds(pos.coords.accuracy * 2), options = this._locateOptions;
if (options.setView) {
const zoom = this.getBoundsZoom(bounds);
this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
}
const data = {
latlng,
bounds,
timestamp: pos.timestamp
};
for (const i in pos.coords) if (typeof pos.coords[i] === "number") data[i] = pos.coords[i];
this.fire("locationfound", data);
}
addHandler(name, HandlerClass) {
if (!HandlerClass) return this;
const handler = this[name] = new HandlerClass(this);
this._handlers.push(handler);
if (this.options[name]) handler.enable();
return this;
}
remove() {
this._initEvents(true);
if (this.options.maxBounds) this.off("moveend", this._panInsideMaxBounds);
if (this._containerId !== this._container._maplair_id) throw new Error("Map container is being reused by another instance");
delete this._container._maplair_id;
delete this._containerId;
if (this._locationWatchId !== void 0) this.stopLocate();
this._stop();
disablePointerDetection(this._container);
this._mapPane.remove();
if (this._clearControlPos) this._clearControlPos();
if (this._resizeRequest) {
cancelAnimationFrame(this._resizeRequest);
this._resizeRequest = null;
}
this._clearHandlers();
clearTimeout(this._transitionEndTimer);
clearTimeout(this._sizeTimer);
if (this._loaded) this.fire("unload");
this._destroyAnimProxy();
for (const layer of Object.values(this._layers)) layer.remove();
for (const pane of Object.values(this._panes)) pane.remove();
this._layers = {};
this._panes = {};
delete this._mapPane;
delete this._renderer;
return this;
}
createPane(name, container) {
const pane = create("div", `maplair-pane${name ? ` maplair-${name.replace("Pane", "")}-pane` : ""}`, container || this._mapPane);
if (name) this._panes[name] = pane;
return pane;
}
getCenter() {
this._checkIfLoaded();
if (this._lastCenter && !this._moved()) return new LatLng(this._lastCenter);
return this.layerPointToLatLng(this._getCenterLayerPoint());
}
getZoom() {
return this._zoom;
}
getBounds() {
const bounds = this.getPixelBounds();
return new LatLngBounds(this.unproject(bounds.getBottomLeft()), this.unproject(bounds.getTopRight()));
}
getMinZoom() {
return this.options.minZoom ?? this._layersMinZoom ?? 0;
}
getMaxZoom() {
return this.options.maxZoom ?? this._layersMaxZoom ?? Infinity;
}
getBoundsZoom(bounds, inside, padding) {
bounds = new LatLngBounds(bounds);
padding = new Point(padding ?? [0, 0]);
let zoom = this.getZoom() ?? 0;
const min = this.getMinZoom(), max = this.getMaxZoom(), nw = bounds.getNorthWest(), se = bounds.getSouthEast(), size = this.getSize().subtract(padding), boundsSize = new Bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), snap = this.options.zoomSnap, scalex = size.x / boundsSize.x, scaley = size.y / boundsSize.y, scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
zoom = this.getScaleZoom(scale, zoom);
if (snap) {
zoom = Math.round(zoom / (snap / 100)) * (snap / 100);
zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
}
return Math.max(min, Math.min(max, zoom));
}
getSize() {
if (!this._size || this._sizeChanged) {
this._size = new Point(this._container.clientWidth || 0, this._container.clientHeight || 0);
this._sizeChanged = false;
}
return new Point(this._size);
}
getPixelBounds(center, zoom) {
const topLeftPoint = this._getTopLeftPoint(center, zoom);
return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
}
getPixelOrigin() {
this._checkIfLoaded();
return this._pixelOrigin;
}
getPixelWorldBounds(zoom) {
return this.options.crs.getProjectedBounds(zoom ?? this.getZoom());
}
getPane(pane) {
return typeof pane === "string" ? this._panes[pane] : pane;
}
getPanes() {
return this._panes;
}
getContainer() {
return this._container;
}
getZoomScale(toZoom, fromZoom) {
const crs = this.options.crs;
fromZoom ??= this._zoom;
return crs.scale(toZoom) / crs.scale(fromZoom);
}
getScaleZoom(scale, fromZoom) {
const crs = this.options.crs;
fromZoom ??= this._zoom;
const zoom = crs.zoom(scale * crs.scale(fromZoom));
return isNaN(zoom) ? Infinity : zoom;
}
project(latlng, zoom) {
zoom ??= this._zoom;
return this.options.crs.latLngToPoint(new LatLng(latlng), zoom);
}
unproject(point, zoom) {
zoom ??= this._zoom;
return this.options.crs.pointToLatLng(new Point(point), zoom);
}
layerPointToLatLng(point) {
const projectedPoint = new Point(point).add(this.getPixelOrigin());
return this.unproject(projectedPoint);
}
latLngToLayerPoint(latlng) {
return this.project(new LatLng(latlng))._round()._subtract(this.getPixelOrigin());
}
wrapLatLng(latlng) {
return this.options.crs.wrapLatLng(new LatLng(latlng));
}
wrapLatLngBounds(bounds) {
return this.options.crs.wrapLatLngBounds(new LatLngBounds(bounds));
}
distance(latlng1, latlng2) {
return this.options.crs.distance(new LatLng(latlng1), new LatLng(latlng2));
}
containerPointToLayerPoint(point) {
return new Point(point).subtract(this._getMapPanePos());
}
layerPointToContainerPoint(point) {
return new Point(point).add(this._getMapPanePos());
}
containerPointToLatLng(point) {
const layerPoint = this.containerPointToLayerPoint(new Point(point));
return this.layerPointToLatLng(layerPoint);
}
latLngToContainerPoint(latlng) {
return this.layerPointToContainerPoint(this.latLngToLayerPoint(new LatLng(latlng)));
}
pointerEventToContainerPoint(e) {
return getPointerPosition(e, this._container);
}
pointerEventToLayerPoint(e) {
return this.containerPointToLayerPoint(this.pointerEventToContainerPoint(e));
}
pointerEventToLatLng(e) {
return this.layerPointToLatLng(this.pointerEventToLayerPoint(e));
}
_initContainer(id) {
const container = this._container = get(id);
if (!container) throw new Error("Map container not found.");
else if (container._maplair_id) throw new Error("Map container is already initialized.");
enablePointerDetection(container);
on(container, "scroll", this._onScroll, this);
this._containerId = stamp(container);
}
_initLayout() {
const container = this._container;
this._fadeAnimated = this.options.fadeAnimation;
const classes = ["maplair-container", "maplair-touch"];
if (Browser_default.retina) classes.push("maplair-retina");
if (Browser_default.safari) classes.push("maplair-safari");
if (this._fadeAnimated) classes.push("maplair-fade-anim");
container.classList.add(...classes);
const { position } = getComputedStyle(container);
if (position !== "absolute" && position !== "relative" && position !== "fixed" && position !== "sticky") container.style.position = "relative";
this._initPanes();
if (this._initControlPos) this._initControlPos();
}
_initPanes() {
const panes = this._panes = {};
this._paneRenderers = {};
this._mapPane = this.createPane("mapPane", this._container);
setPosition(this._mapPane, new Point(0, 0));
this.createPane("tilePane");
this.createPane("overlayPane");
this.createPane("shadowPane");
this.createPane("markerPane");
this.createPane("tooltipPane");
this.createPane("popupPane");
if (!this.options.markerZoomAnimation) {
panes.markerPane.classList.add("maplair-zoom-hide");
panes.shadowPane.classList.add("maplair-zoom-hide");
}
}
_resetView(center, zoom, noMoveStart) {
setPosition(this._mapPane, new Point(0, 0));
const loading = !this._loaded;
this._loaded = true;
zoom = this._limitZoom(zoom);
this.fire("viewprereset");
const zoomChanged = this._zoom !== zoom;
this._moveStart(zoomChanged, noMoveStart)._move(center, zoom)._moveEnd(zoomChanged);
this.fire("viewreset");
if (loading) this.fire("load");
}
_moveStart(zoomChanged, noMoveStart) {
if (zoomChanged) this.fire("zoomstart");
if (!noMoveStart) this.fire("movestart");
return this;
}
_move(center, zoom, data, suppressEvent) {
if (zoom === void 0) zoom = this._zoom;
const zoomChanged = this._zoom !== zoom;
this._zoom = zoom;
this._lastCenter = center;
this._pixelOrigin = this._getNewPixelOrigin(center);
if (!suppressEvent) {
if (zoomChanged || data?.pinch) this.fire("zoom", data);
this.fire("move", data);
} else if (data?.pinch) this.fire("zoom", data);
return this;
}
_moveEnd(zoomChanged) {
if (zoomChanged) this.fire("zoomend");
return this.fire("moveend");
}
_stop() {
cancelAnimationFrame(this._flyToFrame);
cancelAnimationFrame(this._zoomAnimFrame);
this._panAnim?.stop();
return this;
}
_rawPanBy(offset) {
setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
}
_getZoomSpan() {
return this.getMaxZoom() - this.getMinZoom();
}
_panInsideMaxBounds() {
if (!this._enforcingBounds) this.panInsideBounds(this.options.maxBounds);
}
_checkIfLoaded() {
if (!this._loaded) throw new Error("Set map center and zoom first.");
}
_initEvents(remove) {
this._targets = {};
this._targets[stamp(this._container)] = this;
(remove ? off : on)(this._container, "click dblclick pointerdown pointerup pointerover pointerout pointermove contextmenu keypress keydown keyup", this._handleDOMEvent, this);
if (this.options.trackResize) {
if (!remove) {
if (!this._resizeObserver) this._resizeObserver = new ResizeObserver(this._onResize.bind(this));
this._resizeObserver.observe(this._container);
} else this._resizeObserver.disconnect();
}
if (this.options.transform3DLimit) (remove ? this.off : this.on).call(this, "moveend", this._onMoveEnd);
}
_onResize() {
cancelAnimationFrame(this._resizeRequest);
this._resizeRequest = requestAnimationFrame(() => {
this.invalidateSize({ debounceMoveend: true });
});
}
_onScroll() {
this._container.scrollTop = 0;
this._container.scrollLeft = 0;
}
_onMoveEnd() {
const pos = this._getMapPanePos();
if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) this._resetView(this.getCenter(), this.getZoom());
}
_findEventTargets(e, type) {
let targets = [], target, src = e.target || e.srcElement, dragging = false;
const isHover = type === "pointerout" || type === "pointerover";
while (src) {
target = this._targets[stamp(src)];
if (target && (type === "click" || type === "preclick") && this._draggableMoved(target)) {
dragging = true;
break;
}
if (target && target.listens(type, true)) {
if (isHover && !isExternalTarget(src, e)) break;
targets.push(target);
if (isHover) break;
}
if (src === this._container) break;
src = src.parentNode;
}
if (!targets.length && !dragging && !isHover && this.listens(type, true)) targets = [this];
return targets;
}
_isClickDisabled(el) {
while (el && el !== this._container) {
if (el["_maplair_disable_click"] || !el.parentNode) return true;
el = el.parentNode;
}
}
_handleDOMEvent(e) {
const el = e.target ?? e.srcElement;
if (!this._loaded || el["_maplair_disable_events"] || e.type === "click" && this._isClickDisabled(el)) return;
const type = e.type;
if (type === "pointerdown") preventOutline(el);
this._fireDOMEvent(e, type);
}
static _pointerEvents = [
"click",
"dblclick",
"pointerover",
"pointerout",
"contextmenu"
];
_fireDOMEvent(e, type, canvasTargets) {
if (type === "click") this._fireDOMEvent(e, "preclick", canvasTargets);
let targets = this._findEventTargets(e, type);
if (canvasTargets) targets = [...canvasTargets.filter((t) => t.listens(type, true)), ...targets];
if (!targets.length) return;
if (type === "contextmenu") e.preventDefault();
const target = targets[0];
const data = { originalEvent: e };
if (e.type !== "keypress" && e.type !== "keydown" && e.type !== "keyup") {
const isMarker = target.getLatLng && (!target._pxRadius || target._pxRadius <= 10);
data.containerPoint = isMarker ? this.latLngToContainerPoint(target.getLatLng()) : this.pointerEventToContainerPoint(e);
data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
}
for (const t of targets) {
t.fire(type, data, true);
if (data.originalEvent._stopped || t.options.bubblingPointerEvents === false && MaplairMap._pointerEvents.includes(type)) return;
}
}
_draggableMoved(obj) {
obj = obj.dragging?.enabled() ? obj : this;
return obj.dragging?.moved() || this.boxZoom?.moved();
}
_clearHandlers() {
for (const handler of this._handlers) handler.disable();
}
whenReady(callback, context) {
if (this._loaded) callback.call(context || this, { target: this });
else this.on("load", callback, context);
return this;
}
_getMapPanePos() {
return getPosition(this._mapPane);
}
_moved() {
const pos = this._getMapPanePos();
return pos && !pos.equals([0, 0]);
}
_getTopLeftPoint(center, zoom) {
return (center && zoom !== void 0 ? this._getNewPixelOrigin(center, zoom) : this.getPixelOrigin()).subtract(this._getMapPanePos());
}
_getNewPixelOrigin(center, zoom) {
const viewHalf = this.getSize()._divideBy(2);
return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
}
_latLngToNewLayerPoint(latlng, zoom, center) {
const topLeft = this._getNewPixelOrigin(center, zoom);
return this.project(latlng, zoom)._subtract(topLeft);
}
_latLngBoundsToNewLayerBounds(latLngBounds, zoom, center) {
const topLeft = this._getNewPixelOrigin(center, zoom);
return new Bounds([
this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
]);
}
_getCenterLayerPoint() {
return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
}
_getCenterOffset(latlng) {
return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
}
_limitCenter(center, zoom, bounds) {
if (!bounds) return center;
const centerPoint = this.project(center, zoom), viewHalf = this.getSize().divideBy(2), viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), offset = this._getBoundsOffset(viewBounds, bounds, zoom);
if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) return center;
return this.unproject(centerPoint.add(offset), zoom);
}
_limitOffset(offset, bounds) {
if (!bounds) return offset;
const viewBounds = this.getPixelBounds(), newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
return offset.add(this._getBoundsOffset(newBounds, bounds));
}
_getBoundsOffset(pxBounds, maxBounds, zoom) {
const projectedMaxBounds = new Bounds(this.project(maxBounds.getNorthEast(), zoom), this.project(maxBounds.getSouthWest(), zoom)), minOffset = projectedMaxBounds.min.subtract(pxBounds.min), maxOffset = projectedMaxBounds.max.subtract(pxBounds.max);
return new Point(this._rebound(minOffset.x, -maxOffset.x), this._rebound(minOffset.y, -maxOffset.y));
}
_rebound(left, right) {
return left + right > 0 ? Math.round(left - right) / 2 : Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
}
_limitZoom(zoom) {
const min = this.getMinZoom(), max = this.getMaxZoom(), snap = this.options.zoomSnap;
if (snap) zoom = Math.round(zoom / snap) * snap;
return Math.max(min, Math.min(max, zoom));
}
_onPanTransitionStep() {
this.fire("move");
}
_onPanTransitionEnd() {
this._mapPane.classList.remove("maplair-pan-anim");
this.fire("moveend");
}
_tryAnimatedPan(center, options) {
const offset = this._getCenterOffset(center)._trunc();
if (options?.animate !== true && !this.getSize().contains(offset)) return false;
this.panBy(offset, options);
return true;
}
_createAnimProxy() {
this._proxy = create("div", "maplair-proxy maplair-zoom-animated");
this._panes.mapPane.appendChild(this._proxy);
this.on("zoomanim", this._animateProxyZoom, this);
this.on("load moveend", this._animMoveEnd, this);
on(this._proxy, "transitionend", this._catchTransitionEnd, this);
}
_animateProxyZoom(e) {
const transform = this._proxy.style.transform;
setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
if (transform === this._proxy.style.transform && this._animatingZoom) this._onZoomTransitionEnd();
}
_animMoveEnd() {
const c = this.getCenter();
const z = this.getZoom();
setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
}
_destroyAnimProxy() {
if (this._proxy) {
off(this._proxy, "transitionend", this._catchTransitionEnd, this);
this._proxy.remove();
this.off("zoomanim", this._animateProxyZoom, this);
this.off("load moveend", this._animMoveEnd, this);
delete this._proxy;
}
}
_catchTransitionEnd(e) {
if (this._animatingZoom && e.propertyName.includes("transform")) this._onZoomTransitionEnd();
}
_nothingToAnimate() {
return !this._container.getElementsByClassName("maplair-zoom-animated").length;
}
_tryAnimatedZoom(center, zoom, options) {
if (this._animatingZoom) return true;
options ??= {};
if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) return false;
const scale = this.getZoomScale(zoom), offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
if (options.animate !== true && !this.getSize().contains(offset)) return false;
this._zoomAnimFrame = requestAnimationFrame(() => {
this._zoomAnimFrame = null;
this._moveStart(true, options.noMoveStart ?? false)._animateZoom(center, zoom, true);
});
return true;
}
_animateZoom(center, zoom, startAnim, noUpdate) {
if (!this._mapPane) return;
if (startAnim) {
this._animatingZoom = true;
this._animateToCenter = center;
this._animateToZoom = zoom;
this._mapPane.classList.add("maplair-zoom-anim");
}
this.fire("zoomanim", {
center,
zoom,
noUpdate
});
if (!this._tempFireZoomEvent) this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
this._move(this._animateToCenter, this._animateToZoom, void 0, true);
this._transitionEndTimer = setTimeout(this._onZoomTransitionEnd.bind(this), 250);
}
_onZoomTransitionEnd() {
if (!this._animatingZoom) return;
this._mapPane?.classList.remove("maplair-zoom-anim");
this._animatingZoom = false;
this._move(this._animateToCenter, this._animateToZoom, void 0, true);
if (this._tempFireZoomEvent) this.fire("zoom");
delete this._tempFireZoomEvent;
this.fire("move");
this._moveEnd(true);
}
};
for (const feature of [
AttributionControl,
Control,
DivOverlay,
Layer,
BoxZoomHandler,
DoubleClickZoomHandler,
DragHandler,
KeyboardHandler,
PinchZoomHandler,
ScrollWheelZoomHandler,
TapHoldHandler,
Popup,
Tooltip,
Renderer_getRenderer_exports,
ZoomControl
]) feature.register(MaplairMap);
const Map$1 = MaplairMap;
//#endregion
//#region src/Maplair.js
const version = version$1;
//#endregion
exports.AttributionControl = AttributionControl;
exports.BlanketOverlay = BlanketOverlay;
exports.Bounds = Bounds;
exports.BoxZoomHandler = BoxZoomHandler;
exports.Browser = Browser_default;
exports.CRS = CRS;
exports.Canvas = Canvas;
exports.Circle = Circle;
exports.CircleMarker = CircleMarker;
exports.Class = Class;
exports.Control = Control;
exports.DefaultIcon = DefaultIcon;
exports.DivIcon = DivIcon;
exports.DivOverlay = DivOverlay;
Object.defineProperty(exports, "DomEvent", {
enumerable: true,
get: function() {
return DomEvent_exports;
}
});
Object.defineProperty(exports, "DomUtil", {
enumerable: true,
get: function() {
return DomUtil_exports;
}
});
exports.DoubleClickZoomHandler = DoubleClickZoomHandler;
exports.DragHandler = DragHandler;
exports.Draggable = Draggable;
exports.EPSG3395 = EPSG3395;
exports.EPSG3857 = EPSG3857;
exports.EPSG4326 = EPSG4326;
exports.EPSG900913 = EPSG900913;
exports.EarthCRS = EarthCRS;
exports.Evented = Evented;
exports.FeatureGroup = FeatureGroup;
exports.GeoJSON = GeoJSON;
exports.GridLayer = GridLayer;
exports.Handler = Handler;
exports.I18n = I18n;
exports.Icon = Icon;
exports.ImageOverlay = ImageOverlay;
exports.KeyboardHandler = KeyboardHandler;
exports.LatLng = LatLng;
exports.LatLngBounds = LatLngBounds;
exports.Layer = Layer;
exports.LayerGroup = LayerGroup;
exports.LayersControl = LayersControl;
Object.defineProperty(exports, "LineUtil", {
enumerable: true,
get: function() {
return LineUtil_exports;
}
});
exports.Map = Map$1;
exports.MaplairMap = MaplairMap;
exports.Marker = Marker;
exports.Path = Path;
exports.PinchZoomHandler = PinchZoomHandler;
exports.Point = Point;
Object.defineProperty(exports, "PolyUtil", {
enumerable: true,
get: function() {
return PolyUtil_exports;
}
});
exports.Polygon = Polygon;
exports.Polyline = Polyline;
exports.Popup = Popup;
exports.PosAnimation = PosAnimation;
Object.defineProperty(exports, "Projection", {
enumerable: true,
get: function() {
return projection_exports;
}
});
exports.Rectangle = Rectangle;
exports.Renderer = Renderer;
exports.SVG = SVG;
exports.SVGOverlay = SVGOverlay;
exports.ScaleControl = ScaleControl;
exports.ScrollWheelZoomHandler = ScrollWheelZoomHandler;
exports.SimpleCRS = SimpleCRS;
exports.TapHoldHandler = TapHoldHandler;
exports.TileLayer = TileLayer;
exports.Tooltip = Tooltip;
exports.Transformation = Transformation;
Object.defineProperty(exports, "Util", {
enumerable: true,
get: function() {
return Util_exports;
}
});
exports.VideoOverlay = VideoOverlay;
exports.WMSTileLayer = WMSTileLayer;
exports.ZoomControl = ZoomControl;
exports.version = version;
});
if (!(typeof exports === 'object' && typeof module !== 'undefined') && !(typeof define === 'function' && define.amd)) {
var L = globalThis.L;
L.noConflict = function () { globalThis.L = previousL; return L; };
}
})(globalThis.L);
//# sourceMappingURL=maplair-global-src.js.map