leaflet-customlayer
Version:
Leaflet overlay plugin: L.CustomLayer - fully custom Layer.
441 lines (345 loc) • 10.2 kB
JavaScript
import L from "leaflet";
/**
* @class CustomLayer
* @inherits Layer
*
* Leaflet overlay plugin: L.CustomLayer - fully custom Layer.
*/
const CustomLayer = L.Layer.extend({
// CustomLayer options
options: {
// How much to extend the clip area around the map view (relative to its size)
// e.g. 0.1 would be 10% of map view in each direction
padding: 0,
// How much to extend click tolerance round a path/object on the map
tolerance: 0,
// The DomElement object to display
container: null,
// The opacity of the layer, [0,1]
opacity: 1,
// Is the layer visible
visible: true,
// The zIndex of the layer
zIndex: undefined,
// Set the visibility min zoom level
minZoom: 0,
// Set the visibility max zoom level
maxZoom: 18
// Whether to redraw during zoom panning, complex drawing suggestions are set to false (next version)
// alwaysRender: true
},
initialize(options) {
L.setOptions(this, options);
/* Built-in Date */
L.stamp(this);
this._map = undefined;
this._container = undefined;
this._bounds = undefined;
this._center = undefined;
this._zoom = undefined;
this._padding = undefined;
},
/* Built-in Lifecycle */
beforeAdd() {
this._zoomVisible = true;
},
getEvents() {
// Layer trigger priority
// move: movestart -> move -> moveend
// zoom: zoomstart -> movestart -> zoomanim -> zoom -> move -> zoomend -> moveend
// resize: move -> resize -> moveend
let events = {
viewreset: this._onLayerViewReset,
zoom: this._onLayerZoom,
moveend: this._onLayerMoveEnd,
zoomend: this._onLayerZoomEnd
};
if (this._zoomAnimated) {
events.zoomanim = this._onLayerAnimZoom;
}
return events;
},
onAdd() {
this.fire("layer-beforemount"); // Lifecycle beforeMount
if (!this._container) {
this._initContainer(); // defined by renderer implementations
}
this.setOpacity(this.options.opacity);
if (window.isNaN(this.options.zIndex)) {
switch (this._container.tagName) {
// http://www.w3.org/1999/xhtml
case "CANVAS": {
this.setZIndex(100);
break;
}
// http://www.w3.org/2000/svg
case "svg": {
this.setZIndex(200);
break;
}
default: {
this.setZIndex(100);
}
}
} else {
this.setZIndex(this.options.zIndex);
}
this.getPane().appendChild(this._container);
this._onZoomVisible();
this.fire("layer-mounted"); // Lifecycle mounted
this._update();
},
onRemove() {
this.fire("layer-beforedestroy"); // Lifecycle beforeDestroy
this._destroyContainer();
this.fire("layer-destroyed"); // Lifecycle destroyed
},
/* Built-in Events */
_onLayerViewReset() {
this._reset();
},
_onLayerAnimZoom(ev) {
this._updateTransform(ev.center, ev.zoom);
},
_onLayerZoom() {
this._updateTransform(this._map.getCenter(), this._map.getZoom());
},
_onLayerZoomEnd() {},
_onLayerMoveEnd() {
if (!this._isZoomVisible()) {
this._zoomHide();
return;
}
this._update();
},
_onZoomVisible() {
if (this._isZoomVisible()) {
this._zoomShow();
} else {
this._zoomHide();
}
},
/* Built-in Methods */
_initContainer() {
const container = (this._container = this.options.container);
L.DomUtil.addClass(container, "leaflet-layer");
if (this._zoomAnimated) {
L.DomUtil.addClass(this._container, "leaflet-zoom-animated");
}
},
_destroyContainer() {
L.DomUtil.remove(this._container);
delete this._container;
},
_isZoomVisible() {
const minZoom = this.options.minZoom;
const maxZoom = this.options.maxZoom;
const zoom = this._map.getZoom();
const isVisible = zoom >= minZoom && zoom <= maxZoom;
return isVisible;
},
_zoomShow() {
if (this._zoomVisible) return;
this._zoomVisible = true;
this._map.off({ zoomend: this._onZoomVisible }, this);
if (!this.options.visible) return;
this._map.on(this.getEvents(), this);
this.getContainer().style.display = "";
// Subsequent moveend events take the place of update
// this._update();
},
_zoomHide() {
if (!this._zoomVisible) return;
this._zoomVisible = false;
this._map.off(this.getEvents(), this);
this._map.on({ zoomend: this._onZoomVisible }, this);
this.getContainer().style.display = "none";
},
_updateTransform(center, zoom) {
const scale = this._map.getZoomScale(zoom, this._zoom);
const position = L.DomUtil.getPosition(this._container);
const viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding);
const currentCenterPoint = this._map.project(this._center, zoom);
const destCenterPoint = this._map.project(center, zoom);
const centerOffset = destCenterPoint.subtract(currentCenterPoint);
const topLeftOffset = viewHalf
.multiplyBy(-scale)
.add(position)
.add(viewHalf)
.subtract(centerOffset);
if (L.Browser.any3d) {
L.DomUtil.setTransform(this._container, topLeftOffset, scale);
} else {
L.DomUtil.setPosition(this._container, topLeftOffset);
}
},
_update() {
if (this._map._animatingZoom && this._bounds) {
return;
}
this.__update();
let b = this._bounds;
let container = this._container;
L.DomUtil.setPosition(container, b.min);
this.fire("layer-render"); // Lifecycle render
},
__update() {
// Update pixel bounds of renderer container (for positioning/sizing/clipping later)
// Subclasses are responsible of firing the 'update' event.
const p = this.options.padding;
const size = this._map.getSize();
const min = this._map.containerPointToLayerPoint(size.multiplyBy(-p));
this._padding = size.multiplyBy(p);
// this._bounds = new L.Bounds(
// min.round(),
// min.add(size.multiplyBy(1 + p * 2)).round()
// );
this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)));
this._center = this._map.getCenter();
this._zoom = this._map.getZoom();
},
_reset() {
this._update();
this._updateTransform(this._center, this._zoom);
},
/**
* API
*/
/* Methods */
getContainer() {
return this._container;
},
setContainer(newContainer) {
const old = this.getContainer();
const parent = old.parentNode;
delete this._container;
this.options.container = newContainer;
if (!this._container) {
this._initContainer();
}
this.setOpacity(this.options.opacity);
if (window.isNaN(this.options.zIndex)) {
switch (this._container.tagName) {
case "CANVAS": {
this.setZIndex(100);
break;
}
case "svg": {
this.setZIndex(200);
break;
}
default: {
this.setZIndex(100);
}
}
} else {
this.setZIndex(this.options.zIndex);
}
if (parent) {
parent.replaceChild(newContainer, old);
} else {
this.getPane().appendChild(newContainer);
}
this._update();
return this;
},
getOpacity() {
return this.options.opacity;
},
setOpacity(opacity) {
this.getContainer().style.opacity = this.options.opacity = opacity * 1;
return this;
},
getZIndex() {
return this.options.zIndex;
},
setZIndex(zIndex) {
this.getContainer().style.zIndex = this.options.zIndex = zIndex * 1;
return this;
},
show() {
if (this.options.visible) return;
this.options.visible = true;
if (!this._isZoomVisible()) {
this._zoomHide();
return;
}
this._map.on(this.getEvents(), this);
this.getContainer().style.display = "";
this._update();
return this;
},
hide() {
if (!this.options.visible) return;
this.options.visible = false;
this._zoomHide();
this._map.off(this.getEvents(), this);
this.getContainer().style.display = "none";
return this;
},
setFullLayerBounds() {
const container = this.getContainer();
const size = this._bounds.getSize();
const padding = this._padding;
switch (container.tagName) {
case "CANVAS": {
const dpr = L.Browser.retina ? 2 : 1;
container.width = dpr * size.x;
container.height = dpr * size.y;
container.style.width = size.x + "px";
container.style.height = size.y + "px";
const ctx = container.getContext("2d");
if (L.Browser.retina) ctx.scale(dpr, dpr);
ctx.translate(padding.x, padding.y);
return { container, ctx, dpr };
}
case "svg": {
container.setAttribute("width", size.x);
container.setAttribute("height", size.y);
container.style.width = size.x + "px";
container.style.height = size.y + "px";
container.setAttribute(
"viewBox",
`${-padding.x} ${-padding.y} ${size.x} ${size.y}`
);
return { container };
}
case "DIV": {
container.style.boxSizing = "content-box";
container.style.width = size.x - padding.x + "px";
container.style.height = size.y - padding.y + "px";
container.style.padding = `${padding.y}px ${padding.x}px`;
return { container };
}
default: {
container.setAttribute("width", size.x);
container.setAttribute("height", size.y);
container.style.width = size.x + "px";
container.style.height = size.y + "px";
return { container };
}
}
}
/* Events */
// on("layer-beforemount", fn);
// on("layer-mounted", fn);
// on("layer-render", fn);
// on("layer-beforedestroy", fn);
// on("layer-destroyed", fn);
});
// @factory L.customLayer(options?: Renderer options)
// Creates a CustomLayer renderer with the given options.
function customLayer(options) {
return L.customLayer ? new CustomLayer(options) : null;
}
/**
* Plugin Props
*/
L.CustomLayer = CustomLayer;
L.customLayer = customLayer;
/**
* Exports
*/
export { CustomLayer };
export { customLayer };
export default customLayer;