maplair
Version:
JavaScript library for mobile-friendly interactive maps
844 lines (843 loc) • 32.3 kB
JavaScript
import { setOptions, stamp } from "../core/Util.js";
import { Point } from "../geometry/Point.js";
import Browser_default from "../core/Browser.js";
import { disablePointerDetection, enablePointerDetection } from "../dom/DomEvent.PointerEvents.js";
import { getPointerPosition, isExternalTarget, off, on } from "../dom/DomEvent.js";
import { create, get, getPosition, preventOutline, setPosition, setTransform } from "../dom/DomUtil.js";
import { Control } from "../control/Control.js";
import { AttributionControl } from "../control/AttributionControl.js";
import { ZoomControl } from "../control/ZoomControl.js";
import { Evented } from "../core/Events.js";
import { PosAnimation } from "../dom/PosAnimation.js";
import { Bounds } from "../geometry/Bounds.js";
import { LatLngBounds } from "../geo/LatLngBounds.js";
import { LatLng } from "../geo/LatLng.js";
import { EPSG3857 } from "../geo/crs/EPSG3857.js";
import { Layer } from "../layer/Layer.js";
import { DivOverlay } from "../layer/DivOverlay.js";
import { Popup } from "../layer/Popup.js";
import { Tooltip } from "../layer/Tooltip.js";
import { Renderer_getRenderer_exports } from "../layer/vector/Renderer.getRenderer.js";
import { BoxZoomHandler } from "./handler/BoxZoomHandler.js";
import { DoubleClickZoomHandler } from "./handler/DoubleClickZoomHandler.js";
import { DragHandler } from "./handler/DragHandler.js";
import { KeyboardHandler } from "./handler/KeyboardHandler.js";
import { PinchZoomHandler } from "./handler/PinchZoomHandler.js";
import { ScrollWheelZoomHandler } from "./handler/ScrollWheelZoomHandler.js";
import { TapHoldHandler } from "./handler/TapHoldHandler.js";
//#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 className = `maplair-pane${name ? ` maplair-${name.replace("Pane", "")}-pane` : ""}`, pane = create("div", className, 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(), sw = this.unproject(bounds.getBottomLeft()), ne = this.unproject(bounds.getTopRight());
return new LatLngBounds(sw, ne);
}
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), dx = this._rebound(minOffset.x, -maxOffset.x), dy = this._rebound(minOffset.y, -maxOffset.y);
return new Point(dx, dy);
}
_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 = MaplairMap;
//#endregion
export { Map, MaplairMap };
//# sourceMappingURL=Map.js.map