// Leaflet.PixiOverlay // version: 1.9.4 // author: Manuel Baclet // license: MIT (function (factory) { if (typeof define === 'function' && define.amd) { // AMD define(['leaflet', 'pixi.js'], factory); } else if (typeof module !== 'undefined') { // Node/CommonJS module.exports = factory(require('leaflet'), require('pixi.js')); } else { // Browser globals if (typeof window.L === 'undefined') { throw new Error('Leaflet must be loaded first'); } if (typeof window.PIXI === 'undefined') { throw new Error('Pixi.js must be loaded first'); } factory(window.L, window.PIXI); } }(function (L, PIXI) { var round = L.Point.prototype._round; var no_round = function () { return this; }; function setEventSystem(renderer, destroyInteractionManager, autoPreventDefault) { var eventSystem = (PIXI.VERSION < "7") ? renderer.plugins.interaction : renderer.events; if (destroyInteractionManager) { eventSystem.destroy(); } else if (!autoPreventDefault) { eventSystem.autoPreventDefault = false; } } function projectionZoom(map) { var maxZoom = map.getMaxZoom(); var minZoom = map.getMinZoom(); if (maxZoom === Infinity) return minZoom + 8; return (maxZoom + minZoom) / 2; } var pixiOverlayClass = { options: { // @option padding: Number = 0.1 // 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.1, // @option forceCanvas: Boolean = false // Force use of a 2d-canvas forceCanvas: false, // @option doubleBuffering: Boolean = false // Help to prevent flicker when refreshing display on some devices (e.g. iOS devices) // It is ignored if rendering is done with 2d-canvas doubleBuffering: false, // @option resolution: Number = 1 // Resolution of the renderer canvas resolution: L.Browser.retina ? 2 : 1, // @option projectionZoom(map: map): Number // return the layer projection zoom level projectionZoom: projectionZoom, // @option destroyInteractionManager: Boolean = false // Destroy PIXI EventSystem destroyInteractionManager: false, // @option // Customize PIXI EventSystem autoPreventDefault property // This option is ignored if destroyInteractionManager is set autoPreventDefault: true, // @option resolution: Boolean = false // Enables drawing buffer preservation preserveDrawingBuffer: false, // @option resolution: Boolean = true // Clear the canvas before the new render pass clearBeforeRender: true, // @option shouldRedrawOnMove(e: moveEvent): Boolean // filter move events that should trigger a layer redraw shouldRedrawOnMove: function () { return false; }, }, initialize: function (drawCallback, pixiContainer, options) { L.setOptions(this, options); L.stamp(this); this._drawCallback = drawCallback; this._pixiContainer = pixiContainer; this._rendererOptions = { resolution: this.options.resolution, antialias: true, forceCanvas: this.options.forceCanvas, preserveDrawingBuffer: this.options.preserveDrawingBuffer, clearBeforeRender: this.options.clearBeforeRender }; if (PIXI.VERSION < "6") { this._rendererOptions.transparent = true; } else { this._rendererOptions.backgroundAlpha = 0; } this._doubleBuffering = PIXI.utils.isWebGLSupported() && !this.options.forceCanvas && this.options.doubleBuffering; }, _setMap: function () { }, _setContainerStyle: function () { }, _addContainer: function () { this.getPane().appendChild(this._container); }, _setEvents: function () { }, onAdd: function (targetMap) { this._setMap(targetMap); if (!this._container) { var container = this._container = L.DomUtil.create('div', 'leaflet-pixi-overlay'); container.style.position = 'absolute'; this._renderer = PIXI.autoDetectRenderer(this._rendererOptions); setEventSystem( this._renderer, this.options.destroyInteractionManager, this.options.autoPreventDefault ); container.appendChild(this._renderer.view); if (this._zoomAnimated) { L.DomUtil.addClass(container, 'leaflet-zoom-animated'); this._setContainerStyle(); } if (this._doubleBuffering) { this._auxRenderer = PIXI.autoDetectRenderer(this._rendererOptions); setEventSystem( this._auxRenderer, this.options.destroyInteractionManager, this.options.autoPreventDefault ); container.appendChild(this._auxRenderer.view); this._renderer.view.style.position = 'absolute'; this._auxRenderer.view.style.position = 'absolute'; } } this._addContainer(); this._setEvents(); var map = this._map; this._initialZoom = this.options.projectionZoom(map); this._wgsOrigin = L.latLng([0, 0]); this._wgsInitialShift = map.project(this._wgsOrigin, this._initialZoom); this._mapInitialZoom = map.getZoom(); var _layer = this; this.utils = { latLngToLayerPoint: function (latLng, zoom) { zoom = (zoom === undefined) ? _layer._initialZoom : zoom; var projectedPoint = map.project(L.latLng(latLng), zoom); return projectedPoint; }, layerPointToLatLng: function (point, zoom) { zoom = (zoom === undefined) ? _layer._initialZoom : zoom; var projectedPoint = L.point(point); return map.unproject(projectedPoint, zoom); }, getScale: function (zoom) { if (zoom === undefined) return map.getZoomScale(map.getZoom(), _layer._initialZoom); else return map.getZoomScale(zoom, _layer._initialZoom); }, getRenderer: function () { return _layer._renderer; }, getContainer: function () { return _layer._pixiContainer; }, getMap: function () { return _layer._map; } }; this._update({ type: 'add' }); }, onRemove: function () { L.DomUtil.remove(this._container); }, getEvents: function () { var events = { zoom: this._onZoom, move: this._onMove, moveend: this._update }; if (this._zoomAnimated) { events.zoomanim = this._onAnimZoom; } return events; }, _onZoom: function () { this._updateTransform(this._map.getCenter(), this._map.getZoom()); }, _onAnimZoom: function (e) { this._updateTransform(e.center, e.zoom); }, _onMove: function (e) { if (this.options.shouldRedrawOnMove(e)) { this._update(e); } }, _updateTransform: function (center, zoom) { var scale = this._map.getZoomScale(zoom, this._zoom), viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), currentCenterPoint = this._map.project(this._center, zoom), topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint) .subtract(this._map._getNewPixelOrigin(center, zoom)); if (L.Browser.any3d) { L.DomUtil.setTransform(this._container, topLeftOffset, scale); } else { L.DomUtil.setPosition(this._container, topLeftOffset); } }, _redraw: function (offset, e) { this._disableLeafletRounding(); var scale = this._map.getZoomScale(this._zoom, this._initialZoom), shift = this._map.latLngToLayerPoint(this._wgsOrigin) ._subtract(this._wgsInitialShift.multiplyBy(scale))._subtract(offset); this._pixiContainer.scale.set(scale); this._pixiContainer.position.set(shift.x, shift.y); this._drawCallback(this.utils, e); this._enableLeafletRounding(); }, _update: function (e) { // is this really useful? if (this._map._animatingZoom && this._bounds) { return; } // Update pixel bounds of renderer container var p = this.options.padding, mapSize = this._map.getSize(), min = this._map.containerPointToLayerPoint(mapSize.multiplyBy(-p)).round(); this._bounds = new L.Bounds(min, min.add(mapSize.multiplyBy(1 + p * 2)).round()); this._center = this._map.getCenter(); this._zoom = this._map.getZoom(); if (this._doubleBuffering) { var currentRenderer = this._renderer; this._renderer = this._auxRenderer; this._auxRenderer = currentRenderer; } var view = this._renderer.view; var b = this._bounds, container = this._container, size = b.getSize(); if (!this._renderer.size || this._renderer.size.x !== size.x || this._renderer.size.y !== size.y) { if (this._renderer.gl) { this._renderer.resolution = this.options.resolution; if (this._renderer.rootRenderTarget) { this._renderer.rootRenderTarget.resolution = this.options.resolution; } } this._renderer.resize(size.x, size.y); view.style.width = size.x + 'px'; view.style.height = size.y + 'px'; if (this._renderer.gl) { var gl = this._renderer.gl; if (gl.drawingBufferWidth !== this._renderer.width) { var resolution = this.options.resolution * gl.drawingBufferWidth / this._renderer.width; this._renderer.resolution = resolution; if (this._renderer.rootRenderTarget) { this._renderer.rootRenderTarget.resolution = resolution; } this._renderer.resize(size.x, size.y); } } this._renderer.size = size; } if (this._doubleBuffering) { var self = this; requestAnimationFrame(function () { self._redraw(b.min, e); self._renderer.gl.finish(); view.style.visibility = 'visible'; self._auxRenderer.view.style.visibility = 'hidden'; L.DomUtil.setPosition(container, b.min); }); } else { this._redraw(b.min, e); L.DomUtil.setPosition(container, b.min); } }, _disableLeafletRounding: function () { L.Point.prototype._round = no_round; }, _enableLeafletRounding: function () { L.Point.prototype._round = round; }, redraw: function (data) { if (this._map) { this._disableLeafletRounding(); this._drawCallback(this.utils, data); this._enableLeafletRounding(); } return this; }, _destroy: function () { this._renderer.destroy(true); if (this._doubleBuffering) { this._auxRenderer.destroy(true); } }, destroy: function () { this.remove(); this._destroy(); } }; if (L.version >= "1") { L.PixiOverlay = L.Layer.extend(pixiOverlayClass); } else { // backport some leaflet@1.0.0 methods L.Map.prototype.getZoomScale = function (toZoom, fromZoom) { var crs = this.options.crs; fromZoom = fromZoom === undefined ? this._zoom : fromZoom; return crs.scale(toZoom) / crs.scale(fromZoom); }; L.DomUtil.setTransform = function (el, offset, scale) { var pos = offset || new L.Point(0, 0); el.style[L.DomUtil.TRANSFORM] = (L.Browser.ie3d ? 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + (scale ? ' scale(' + scale + ')' : ''); }; // patch pixiOverlayClass for leaflet@0.7.7 pixiOverlayClass.includes = L.Mixin.Events; pixiOverlayClass.addTo = function (map) { map.addLayer(this); return this; }; pixiOverlayClass._setMap = function (map) { this._map = map; this._zoomAnimated = map._zoomAnimated; }; pixiOverlayClass._setContainerStyle = function () { var self = this; [ '-webkit-transform-origin', '-ms-transform-origin', 'transform-origin' ].forEach(function (property) { self._container.style[property] = '0 0'; }); }; pixiOverlayClass._addContainer = function () { this._map.getPanes()[this.options.pane || 'overlayPane'] .appendChild(this._container); }; pixiOverlayClass._setEvents = function () { var events = this.getEvents(); for (var evt in events) { this._map.on(evt, events[evt], this); } }; pixiOverlayClass.onRemove = function () { this._map = null; var parent = this._container.parentNode; if (parent) { parent.removeChild(this._container); } var events = this.getEvents(); for (var evt in events) { this._map.off(evt, events[evt], this); } }; pixiOverlayClass.destroy = function () { var map = this._map || this._mapToAdd; if (map) { map.removeLayer(this); } this._destroy(); }; L.PixiOverlay = L.Class.extend(pixiOverlayClass); } // @factory L.pixiOverlay(drawCallback: function, pixiContainer: PIXI.Container, options?: L.PixiOverlay options) // Creates a PixiOverlay with the given arguments. L.pixiOverlay = function (drawCallback, pixiContainer, options) { return L.Browser.canvas ? new L.PixiOverlay(drawCallback, pixiContainer, options) : null; }; }));