UNPKG

leaflet-ui

Version:

Leaflet presets for common user interface customizations

1,499 lines (1,206 loc) 247 kB
(function (factory) { typeof define === 'function' && define.amd ? define(factory) : factory(); }((function () { 'use strict'; var version = "0.6.0+master.a8975e8e"; // Following https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md (function (factory, window) { // define an AMD module that relies on 'leaflet' if (typeof define === 'function' && define.amd) { define(['leaflet'], factory); // define a Common JS module that relies on 'leaflet' } else if (typeof exports === 'object') { module.exports = factory(require('leaflet')); } // attach your plugin to the global 'L' variable if (typeof window !== 'undefined' && window.L) { factory(window.L); } }(function (L) { L.locales = {}; L.locale = null; L.registerLocale = function registerLocale(code, locale) { L.locales[code] = L.Util.extend({}, L.locales[code], locale); }; L.setLocale = function setLocale(code) { L.locale = code; }; return L.i18n = L._ = function translate(string, data) { if (L.locale && L.locales[L.locale] && L.locales[L.locale][string]) { string = L.locales[L.locale][string]; } try { // Do not fail if some data is missing // a bad translation should not break the app string = L.Util.template(string, data); } catch (err) {/*pass*/ } return string; }; }, window)); /** * L.DomUtil */ const domUtilProto = L.extend({}, L.DomUtil); L.extend(L.DomUtil, { setTransform: function(el, offset, scale, bearing, pivot) { var pos = offset || new L.Point(0, 0); if (!bearing) { offset = pos._round(); return domUtilProto.setTransform.call(this, el, offset, scale); } pos = pos.rotateFrom(bearing, pivot); el.style[L.DomUtil.TRANSFORM] = 'translate3d(' + pos.x + 'px,' + pos.y + 'px' + ',0)' + (scale ? ' scale(' + scale + ')' : '') + ' rotate(' + bearing + 'rad)'; }, setPosition: function(el, point, bearing, pivot) { // (HTMLElement, Point[, Boolean]) if (!bearing) { return domUtilProto.setPosition.call(this, el, point); } /*eslint-disable */ el._leaflet_pos = point; /*eslint-enable */ if (L.Browser.any3d) { L.DomUtil.setTransform(el, point, undefined, bearing, pivot); } else { el.style.left = point.x + 'px'; el.style.top = point.y + 'px'; } }, // Constants for rotation DEG_TO_RAD: Math.PI / 180, RAD_TO_DEG: 180 / Math.PI, }); /** * L.Draggable */ L.Draggable.include({ updateMapBearing: function(mapBearing) { this._mapBearing = mapBearing; }, }); /** * L.Point */ L.extend(L.Point.prototype, { // Rotate around (0,0) by applying the 2D rotation matrix: // ⎡ x' ⎤ = ⎡ cos θ -sin θ ⎤ ⎡ x ⎤ // ⎣ y' ⎦ ⎣ sin θ cos θ ⎦ ⎣ y ⎦ // Theta must be given in radians. rotate: function(theta) { if (!theta) { return this; } var sinTheta = Math.sin(theta); var cosTheta = Math.cos(theta); return new L.Point( this.x * cosTheta - this.y * sinTheta, this.x * sinTheta + this.y * cosTheta ); }, // Rotate around (pivot.x, pivot.y) by: // 1. subtract (pivot.x, pivot.y) // 2. rotate around (0, 0) // 3. add (pivot.x, pivot.y) back // same as `this.subtract(pivot).rotate(theta).add(pivot)` rotateFrom: function(theta, pivot) { if (!theta) { return this; } var sinTheta = Math.sin(theta); var cosTheta = Math.cos(theta); var cx = pivot.x, cy = pivot.y; var x = this.x - cx, y = this.y - cy; return new L.Point( x * cosTheta - y * sinTheta + cx, x * sinTheta + y * cosTheta + cy ); }, }); /** * L.DivOverlay */ const divOverlayProto = L.extend({}, L.DivOverlay.prototype); L.DivOverlay.include({ getEvents: function() { return L.extend(divOverlayProto.getEvents.call(this), { rotate: this._updatePosition }); }, _updatePosition: function() { if (!this._map) { return; } var pos = this._map.latLngToLayerPoint(this._latlng), offset = L.point(this.options.offset), anchor = this._getAnchor(); if (this._zoomAnimated) { // TODO: use divOverlayProto._updatePosition if (this._map._rotate) { pos = this._map.rotatedPointToMapPanePoint(pos); } L.DomUtil.setPosition(this._container, pos.add(anchor)); } else { offset = offset.add(pos).add(anchor); } var bottom = this._containerBottom = -offset.y, left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; // bottom position the popup in case the height of the popup changes (images loading etc) this._container.style.bottom = bottom + 'px'; this._container.style.left = left + 'px'; }, }); /** * L.Popup */ const popupProto = L.extend({}, L.Popup.prototype); L.Popup.include({ _animateZoom: function(e) { if (!this._map._rotate) { popupProto._animateZoom.call(this, e); } var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), anchor = this._getAnchor(); pos = this._map.rotatedPointToMapPanePoint(pos); L.DomUtil.setPosition(this._container, pos.add(anchor)); }, _adjustPan: function() { if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; } var map = this._map, marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0, containerHeight = this._container.offsetHeight + marginBottom, containerWidth = this._containerWidth, layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); layerPos._add(L.DomUtil.getPosition(this._container)); // var containerPos = map.layerPointToContainerPoint(layerPos); // TODO: use popupProto._adjustPan var containerPos = layerPos._add(this._map._getMapPanePos()), padding = L.point(this.options.autoPanPadding), paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), size = map.getSize(), dx = 0, dy = 0; if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right dx = containerPos.x + containerWidth - size.x + paddingBR.x; } if (containerPos.x - dx - paddingTL.x < 0) { // left dx = containerPos.x - paddingTL.x; } if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom dy = containerPos.y + containerHeight - size.y + paddingBR.y; } if (containerPos.y - dy - paddingTL.y < 0) { // top dy = containerPos.y - paddingTL.y; } // @namespace Map // @section Popup events // @event autopanstart: Event // Fired when the map starts autopanning when opening a popup. if (dx || dy) { map .fire('autopanstart') .panBy([dx, dy]); } }, }); /** * L.Tooltip */ const tooltipProto = L.extend({}, L.Tooltip.prototype); L.Tooltip.include({ _updatePosition: function() { if (!this._map._rotate) { return tooltipProto._updatePosition.call(this); } var pos = this._map.latLngToLayerPoint(this._latlng); pos = this._map.rotatedPointToMapPanePoint(pos); this._setPosition(pos); }, _animateZoom: function(e) { if (!this._map._rotate) { return tooltipProto._animateZoom.call(this, e); } var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); pos = this._map.rotatedPointToMapPanePoint(pos); this._setPosition(pos); }, }); /** * L.Icon */ const iconProto = L.extend({}, L.Icon.prototype); L.Icon.include({ _setIconStyles: function(img, name) { var options = this.options; var sizeOption = options[name + 'Size']; if (typeof sizeOption === 'number') { sizeOption = [sizeOption, sizeOption]; } var size = L.point(sizeOption), anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor || size && size.divideBy(2, true)); img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); if (anchor) { img.style.marginLeft = (-anchor.x) + 'px'; img.style.marginTop = (-anchor.y) + 'px'; // TODO: use iconProto._setIconStyles img.style[L.DomUtil.TRANSFORM + "Origin"] = anchor.x + "px " + anchor.y + "px 0px"; } if (size) { img.style.width = size.x + 'px'; img.style.height = size.y + 'px'; } }, }); /** * L.Handler.MarkerDrag */ var markerDragProto; var MarkerDrag = { _onDragStart: function() { if (!this._marker._map._rotate) { return markerDragProto._onDragStart.call(this) } this._draggable.updateMapBearing(this._marker._map._bearing); }, _onDrag: function(e) { var marker = this._marker, // TODO: use markerDragProto._onDrag rotated_marker = marker.options.rotation || marker.options.rotateWithView, shadow = marker._shadow, iconPos = L.DomUtil.getPosition(marker._icon); // TODO: use markerDragProto._onDrag // update shadow position if (!rotated_marker && shadow) { L.DomUtil.setPosition(shadow, iconPos); } // TODO: use markerDragProto._onDrag if (marker._map._rotate) { // Reverse calculation from mapPane coordinates to rotatePane coordinates iconPos = marker._map.mapPanePointToRotatedPoint(iconPos); } var latlng = marker._map.layerPointToLatLng(iconPos); marker._latlng = latlng; e.latlng = latlng; e.oldLatLng = this._oldLatLng; // TODO: use markerDragProto._onDrag if (rotated_marker) marker.setLatLng(latlng); // use `setLatLng` to presisit rotation. low efficiency else marker.fire('move', e); // `setLatLng` will trig 'move' event. we imitate here. // @event drag: Event // Fired repeatedly while the user drags the marker. marker .fire('drag', e); }, _onDragEnd: function(e) { if (this._marker._map._rotate) { this._marker.update(); } markerDragProto._onDragEnd.call(this, e); }, }; /** * L.Marker */ const markerProto = L.extend({}, L.Marker.prototype); L.Marker.mergeOptions({ // @option rotation: Number = 0 // Rotation of this marker in rad rotation: 0, // @option rotateWithView: Boolean = false // Rotate this marker when map rotates rotateWithView: false, }); L.Marker.include({ getEvents: function() { return L.extend(markerProto.getEvents.call(this), { rotate: this.update }); }, onAdd: function(map) { markerProto.onAdd.call(this, map); map.on('rotate', this.update, this); }, _initInteraction: function() { var ret = markerProto._initInteraction.call(this); if (this.dragging && this.dragging.enabled() && this._map && this._map._rotate) { // L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable markerDragProto = markerDragProto || Object.getPrototypeOf(this.dragging); this.dragging._onDragStart = MarkerDrag._onDragStart.bind(this.dragging); this.dragging._onDrag = MarkerDrag._onDrag.bind(this.dragging); this.dragging._onDragEnd = MarkerDrag._onDragEnd.bind(this.dragging); this.dragging.disable(); this.dragging.enable(); } return ret; }, _setPos: function(pos) { // TODO: use markerProto._setPos if (this._map._rotate) { pos = this._map.rotatedPointToMapPanePoint(pos); } // TODO: use markerProto._setPos var bearing = this.options.rotation || 0; if (this.options.rotateWithView) { bearing += this._map._bearing; } // TODO: use markerProto._setPos L.DomUtil.setPosition(this._icon, pos, bearing, pos); // TODO: use markerProto._setPos if (this._shadow) { L.DomUtil.setPosition(this._shadow, pos, bearing, pos); } this._zIndex = pos.y + this.options.zIndexOffset; this._resetZIndex(); }, _updateZIndex: function(offset) { if (!this._map._rotate) { return markerProto._updateZIndex.call(this, offset) } this._icon.style.zIndex = Math.round(this._zIndex + offset); }, setRotation: function(rotation) { this.options.rotation = rotation; this.update(); }, }); /** * L.GridLayer */ const gridLayerProto = L.extend({}, L.GridLayer.prototype); L.GridLayer.include({ getEvents: function() { var events = gridLayerProto.getEvents.call(this); if (this._map._rotate && !this.options.updateWhenIdle) { if (!this._onRotate) { this._onRotate = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this); } events.rotate = this._onRotate; } return events; }, _getTiledPixelBounds: function(center) { if (!this._map._rotate) { return gridLayerProto._getTiledPixelBounds.call(this, center); } var 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(), size = map.getSize(), halfSize = new L.Bounds([ map.containerPointToLayerPoint([0, 0]).floor(), map.containerPointToLayerPoint([size.x, 0]).floor(), map.containerPointToLayerPoint([0, size.y]).floor(), map.containerPointToLayerPoint([size.x, size.y]).floor() ]).getSize().divideBy(scale * 2); return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); }, }); /** * L.Canvas */ const canvasProto = L.extend({}, L.Canvas.prototype); L.Canvas.include({ onAdd: function() { canvasProto.onAdd.call(this); // When rotating the canvas itself, it is cleared by some weird reason, so redraw. this._map.on('rotate', this._redraw, this); }, onRemove: function() { canvasProto.onRemove.call(this); this._map.off('rotate', this._redraw, this); }, _update: function() { canvasProto._update.call(this); // Tell paths to redraw themselves this.fire('update'); }, }); /** * L.Renderer */ const rendererProto = L.extend({}, L.Renderer.prototype); L.Renderer.include({ onAdd: function() { rendererProto.onAdd.call(this); // this._map.on('rotate', this._update, this); }, onRemove: function() { rendererProto.onRemove.call(this); // this._map.off('rotate', this._update, this); }, _updateTransform: function(center, zoom) { if (!this._map._rotate) { return rendererProto._updateTransform.call(this, center, zoom); } var scale = this._map.getZoomScale(zoom, this._zoom), offset = this._map._latLngToNewLayerPoint(this._topLeft, zoom, center); if (L.Browser.any3d) { L.DomUtil.setTransform(this._container, offset, scale); } else { L.DomUtil.setPosition(this._container, offset); } }, _update: function() { if (!this._map._rotate) { return rendererProto._update.call(this); } // Update pixel bounds of renderer container (for positioning/sizing/clipping later) // Subclasses are responsible of firing the 'update' event. var p = this.options.padding, map = this._map, size = this._map.getSize(), padMin = size.multiplyBy(-p), padMax = size.multiplyBy(1 + p), //// TODO: Somehow refactor this out into map.something() - the code is //// pretty much the same as in GridLayer. clip = new L.Bounds([ map.containerPointToLayerPoint([padMin.x, padMin.y]).floor(), map.containerPointToLayerPoint([padMin.x, padMax.y]).floor(), map.containerPointToLayerPoint([padMax.x, padMin.y]).floor(), map.containerPointToLayerPoint([padMax.x, padMax.y]).floor() ]); //min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); this._bounds = clip; // this._topLeft = clip.min; this._topLeft = this._map.layerPointToLatLng(clip.min); this._center = this._map.getCenter(); this._zoom = this._map.getZoom(); }, }); /** * L.SVG */ const svgProto = L.extend({}, L.SVG.prototype); L.SVG.include({ _update: function() { svgProto._update.call(this); if (this._map._rotate) { this.fire('update'); } }, }); /** * L.Map */ const mapProto = L.extend({}, L.Map.prototype); L.Map.mergeOptions({ rotate: false, bearing: 0, }); L.Map.include({ initialize: function(id, options) { // (HTMLElement or String, Object) if (options.rotate) { this._rotate = true; this._bearing = 0; } mapProto.initialize.call(this, id, options); if(this.options.rotate){ this.setBearing(this.options.bearing); } }, // createPane: function(name, container) { // if (!this._rotate || name == 'mapPane') { // return mapProto.createPane.call(this, name, container); // } // // init "rotatePane" // if (!this._rotatePane) { // // this._pivot = this.getSize().divideBy(2); // this._rotatePane = mapProto.createPane.call(this, 'rotatePane', this._mapPane); // L.DomUtil.setPosition(this._rotatePane, new L.Point(0, 0), this._bearing, this._pivot); // } // return mapProto.createPane.call(this, name, container || this._rotatePane); // }, containerPointToLayerPoint: function(point) { // (Point) if (!this._rotate) { return mapProto.containerPointToLayerPoint.call(this, point); } return L.point(point) .subtract(this._getMapPanePos()) .rotateFrom(-this._bearing, this._getRotatePanePos()) .subtract(this._getRotatePanePos()); }, getBounds: function() { if (!this._rotate) { return mapProto.getBounds.call(this); } var size = this.getSize(); var topleft = this.layerPointToLatLng(this.containerPointToLayerPoint([0, 0])), topright = this.layerPointToLatLng(this.containerPointToLayerPoint([size.x, 0])), bottomright = this.layerPointToLatLng(this.containerPointToLayerPoint([size.x, size.y])), bottomleft = this.layerPointToLatLng(this.containerPointToLayerPoint([0, size.y])); // Use LatLngBounds' build-in constructor that automatically extends the bounds to fit the passed points return new L.LatLngBounds([topleft, topright, bottomright, bottomleft]); }, layerPointToContainerPoint: function(point) { // (Point) if (!this._rotate) { return mapProto.layerPointToContainerPoint.call(this, point); } return L.point(point) .add(this._getRotatePanePos()) .rotateFrom(this._bearing, this._getRotatePanePos()) .add(this._getMapPanePos()); }, // Rotation methods // setBearing will work with just the 'theta' parameter. setBearing: function(theta) { if (!L.Browser.any3d || !this._rotate) { return; } var rotatePanePos = this._getRotatePanePos(); var halfSize = this.getSize().divideBy(2); this._pivot = this._getMapPanePos().clone().multiplyBy(-1).add(halfSize); rotatePanePos = rotatePanePos.rotateFrom(-this._bearing, this._pivot); this._bearing = theta * L.DomUtil.DEG_TO_RAD; // TODO: mod 360 this._rotatePanePos = rotatePanePos.rotateFrom(this._bearing, this._pivot); L.DomUtil.setPosition(this._rotatePane, rotatePanePos, this._bearing, this._pivot); this.fire('rotate'); }, getBearing: function() { return this._bearing * L.DomUtil.RAD_TO_DEG; }, _initPanes: function() { var panes = this._panes = {}; this._paneRenderers = {}; // @section // // Panes are DOM elements used to control the ordering of layers on the map. You // can access panes with [`map.getPane`](#map-getpane) or // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the // [`map.createPane`](#map-createpane) method. // // Every map has the following default panes that differ only in zIndex. // // @pane mapPane: HTMLElement = 'auto' // Pane that contains all other map panes this._mapPane = this.createPane('mapPane', this._container); L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); if (this._rotate) { this._rotatePane = this.createPane('rotatePane', this._mapPane); this._norotatePane = this.createPane('norotatePane', this._mapPane); // @pane tilePane: HTMLElement = 2 // Pane for tile layers this.createPane('tilePane', this._rotatePane); // @pane overlayPane: HTMLElement = 4 // Pane for overlays like polylines and polygons this.createPane('overlayPane', this._rotatePane); // @pane shadowPane: HTMLElement = 5 // Pane for overlay shadows (e.g. marker shadows) this.createPane('shadowPane', this._norotatePane); // @pane markerPane: HTMLElement = 6 // Pane for marker icons this.createPane('markerPane', this._norotatePane); // @pane tooltipPane: HTMLElement = 650 // Pane for tooltips. this.createPane('tooltipPane', this._norotatePane); // @pane popupPane: HTMLElement = 700 // Pane for popups. this.createPane('popupPane', this._norotatePane); } else { // @pane tilePane: HTMLElement = 2 // Pane for tile layers this.createPane('tilePane'); // @pane overlayPane: HTMLElement = 4 // Pane for overlays like polylines and polygons this.createPane('overlayPane'); // @pane shadowPane: HTMLElement = 5 // Pane for overlay shadows (e.g. marker shadows) this.createPane('shadowPane'); // @pane markerPane: HTMLElement = 6 // Pane for marker icons this.createPane('markerPane'); // @pane tooltipPane: HTMLElement = 650 // Pane for tooltips. this.createPane('tooltipPane'); // @pane popupPane: HTMLElement = 700 // Pane for popups. this.createPane('popupPane'); } if (!this.options.markerZoomAnimation) { L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide'); L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide'); } }, // @method rotatedPointToMapPanePoint(point: Point): Point // Converts a coordinate from the rotated pane reference system // to the reference system of the not rotated map pane. rotatedPointToMapPanePoint: function(point) { return L.point(point).rotate(this._bearing)._add(this._getRotatePanePos()); }, // @method mapPanePointToRotatedPoint(point: Point): Point // Converts a coordinate from the not rotated map pane reference system // to the reference system of the rotated pane. mapPanePointToRotatedPoint: function(point) { return L.point(point)._subtract(this._getRotatePanePos()).rotate(-this._bearing); }, // offset of the specified place to the current center in pixels _getCenterOffset: function(latlng) { var centerOffset = mapProto._getCenterOffset.call(this, latlng); if (this._rotate) { centerOffset = centerOffset.rotate(this._bearing); } return centerOffset; }, _getRotatePanePos: function() { return this._rotatePanePos || new L.Point(0, 0); }, _getNewPixelOrigin: function(center, zoom) { var viewHalf = this.getSize()._divideBy(2); if (!this._rotate) { mapProto._getNewPixelOrigin.call(this, center, zoom); } return this.project(center, zoom) .rotate(this._bearing) ._subtract(viewHalf) ._add(this._getMapPanePos()) ._add(this._getRotatePanePos()) .rotate(-this._bearing) ._round(); }, _handleGeolocationResponse: function(pos) { var lat = pos.coords.latitude, lng = pos.coords.longitude, // TODO: use mapProto._handleGeolocationResponse hdg = pos.coords.heading, latlng = new L.LatLng(lat, lng), bounds = latlng.toBounds(pos.coords.accuracy), options = this._locateOptions; if (options.setView) { var zoom = this.getBoundsZoom(bounds); this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); } var data = { latlng: latlng, bounds: bounds, timestamp: pos.timestamp, // TODO: use mapProto._handleGeolocationResponse heading: hdg }; for (var i in pos.coords) { if (typeof pos.coords[i] === 'number') { data[i] = pos.coords[i]; } } // @event locationfound: LocationEvent // Fired when geolocation (using the [`locate`](#map-locate) method) // went successfully. this.fire('locationfound', data); }, }); /* * L.Map.CompassBearing will rotate the map according to a smartphone's compass. */ L.Map.CompassBearing = L.Handler.extend({ initialize: function(map) { if (!window.DeviceOrientationEvent) { this._capable = false; return; } this._capable = true; this._map = map; this._throttled = L.Util.throttle(this._onDeviceOrientation, 1000, this); }, addHooks: function() { if (this._capable && this._map._rotate) { L.DomEvent.on(window, 'deviceorientation', this._throttled, this); } }, removeHooks: function() { if (this._capable && this._map._rotate) { L.DomEvent.off(window, 'deviceorientation', this._throttled, this); } }, _onDeviceOrientation: function(event) { if (event.alpha !== null) { this._map.setBearing(event.alpha - window.orientation); } }, }); // @section Handlers // @property compassBearing: Handler // Compass bearing handler. L.Map.addInitHook('addHandler', 'compassBearing', L.Map.CompassBearing); /* * L.Handler.ContainerMutation triggers `invalidateResize` when the map's DOM container mutates. */ // @namespace Map // @section Interaction Options L.Map.mergeOptions({ // @option trackContainerMutation: Boolean = false // Whether the map uses [mutation observers](https://developer.mozilla.org/docs/Web/API/MutationObserver) // to detect changes in its container and trigger `invalidateSize`. Disabled // by default due to support not being available in all web browsers. trackContainerMutation: false }); L.Map.ContainerMutation = L.Handler.extend({ addHooks: function() { if (!L.Browser.mutation) { return; } if (!this._observer) { this._observer = new MutationObserver(L.Util.bind(this._onMutation, this)); } this._observer.observe(this._map.getContainer(), { childList: false, attributes: true, characterData: false, subtree: false, attributeFilter: ['style'] }); }, removeHooks: function() { if (!L.Browser.mutation) { return; } this._observer.disconnect(); }, _onMutation: function() { this._map.invalidateSize(); }, }); // @section Handlers // @property containerMutation: Handler // Container mutation handler (disabled unless [`trackContainerMutation`](#map-trackcontainermutation) is set). L.Map.addInitHook('addHandler', 'trackContainerMutation', L.Map.ContainerMutation); /* * L.Handler.TouchGestures is both TouchZoom plus TouchRotate. */ // @namespace Map // @section Interaction Options L.Map.mergeOptions({ // @option bounceAtZoomLimits: Boolean = true // Set it to false if you don't want the map to zoom beyond min/max zoom // and then bounce back when pinch-zooming. bounceAtZoomLimits: true, }); L.Map.TouchGestures = L.Handler.extend({ initialize: function(map) { this._map = map; this.rotate = !!this._map.options.touchRotate; this.zoom = !!this._map.options.touchZoom; }, addHooks: function() { L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); }, removeHooks: function() { L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); }, _onTouchStart: function(e) { var map = this._map; if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating) { return; } var p1 = map.mouseEventToContainerPoint(e.touches[0]), p2 = map.mouseEventToContainerPoint(e.touches[1]), vector = p1.subtract(p2); this._centerPoint = map.getSize()._divideBy(2); this._startLatLng = map.containerPointToLatLng(this._centerPoint); if (this.zoom) { if (map.options.touchZoom !== 'center') { this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); } this._startDist = p1.distanceTo(p2); this._startZoom = map.getZoom(); this._zooming = true; } else { this._zooming = false; } if (this.rotate) { this._startTheta = Math.atan(vector.x / vector.y); this._startBearing = map.getBearing(); if (vector.y < 0) { this._startBearing += 180; } this._rotating = true; } else { this._rotating = false; } this._moved = false; map.stop(); L.DomEvent .on(document, 'touchmove', this._onTouchMove, this) .on(document, 'touchend', this._onTouchEnd, this); L.DomEvent.preventDefault(e); }, _onTouchMove: function(e) { if (!e.touches || e.touches.length !== 2 || !(this._zooming || this._rotating)) { return; } var map = this._map, p1 = map.mouseEventToContainerPoint(e.touches[0]), p2 = map.mouseEventToContainerPoint(e.touches[1]), vector = p1.subtract(p2), scale = p1.distanceTo(p2) / this._startDist, delta; if (this._rotating) { var theta = Math.atan(vector.x / vector.y); var bearingDelta = (theta - this._startTheta) * L.DomUtil.RAD_TO_DEG; if (vector.y < 0) { bearingDelta += 180; } if (bearingDelta) { /// TODO: The pivot should be the last touch point, but zoomAnimation manages to /// overwrite the rotate pane position. Maybe related to #3529. map.setBearing(this._startBearing - bearingDelta); } } if (this._zooming) { 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.touchZoom === 'center') { this._center = this._startLatLng; if (scale === 1) { return; } } else { // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } var alpha = -map.getBearing() * L.DomUtil.DEG_TO_RAD; this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(delta.rotate(alpha))); } } if (!this._moved) { map._moveStart(true); this._moved = true; } L.Util.cancelAnimFrame(this._animRequest); var moveFn = L.bind(map._move, map, this._center, this._zoom, { pinch: true, round: false }); this._animRequest = L.Util.requestAnimFrame(moveFn, this, true); L.DomEvent.preventDefault(e); }, _onTouchEnd: function() { if (!this._moved || !this._zooming) { this._zooming = false; return; } this._zooming = false; this._rotating = false; L.Util.cancelAnimFrame(this._animRequest); L.DomEvent .off(document, 'touchmove', this._onTouchMove) .off(document, 'touchend', this._onTouchEnd); if (this.zoom) { // Pinch updates GridLayers' levels only when snapZoom is off, so snapZoom becomes noUpdate. if (this._map.options.zoomAnimation) { this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.snapZoom); } else { this._map._resetView(this._center, this._map._limitZoom(this._zoom)); } } }, }); // @section Handlers // @property touchGestures: Handler // Touch gestures handler. L.Map.addInitHook('addHandler', 'touchGestures', L.Map.TouchGestures); /* * L.Handler.TouchRotate is used by L.Map to add two-finger rotation gestures. */ // @namespace Map // @section Interaction Options L.Map.mergeOptions({ // @section Touch interaction options // @option touchRotate: Boolean|String = * // Whether the map can be rotated with a two-finger rotation gesture touchRotate: false, }); L.Map.TouchRotate = L.Handler.extend({ addHooks: function() { this._map.touchGestures.enable(); this._map.touchGestures.rotate = true; }, removeHooks: function() { this._map.touchGestures.rotate = false; }, }); // @section Handlers // @property touchZoom: Handler // Touch rotate handler. L.Map.addInitHook('addHandler', 'touchRotate', L.Map.TouchRotate); /* * L.Handler.ShiftKeyRotate is used by L.Map to add shift-wheel rotation. */ // @namespace Map // @section Interaction Options L.Map.mergeOptions({ // @section ShiftKey interaction options // @option shiftKeyRotate: Boolean|String = * // Whether the map can be rotated with a shit-wheel rotation shiftKeyRotate: true, }); L.Map.ShiftKeyRotate = L.Handler.extend({ addHooks: function() { L.DomEvent.on(this._map._container, "wheel", this._handleShiftScroll, this); // this._map.shiftKeyRotate.enable(); this._map.shiftKeyRotate.rotate = true; }, removeHooks: function() { L.DomEvent.off(this._map._container, "wheel", this._handleShiftScroll, this); this._map.shiftKeyRotate.rotate = false; }, _handleShiftScroll: function(e) { if (e.shiftKey) { e.preventDefault(); this._map.scrollWheelZoom.disable(); this._map.setBearing((this._map._bearing * L.DomUtil.RAD_TO_DEG) + Math.sign(e.deltaY) * 5); } else { this._map.scrollWheelZoom.enable(); } }, }); // @section Handlers // @property touchZoom: Handler // Touch rotate handler. L.Map.addInitHook('addHandler', 'shiftKeyRotate', L.Map.ShiftKeyRotate); // decrease "scrollWheelZoom" handler priority over "shiftKeyRotate" handler L.Map.addInitHook(function() { if (this.scrollWheelZoom.enabled() && this.shiftKeyRotate.enabled()) { this.scrollWheelZoom.disable(); this.scrollWheelZoom.enable(); } }); /* * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. */ // @namespace Map // @section Interaction Options L.Map.mergeOptions({ // @section Touch interaction options // @option touchZoom: Boolean|String = * // Whether the map can be zoomed by touch-dragging with two fingers. If // passed `'center'`, it will zoom to the center of the view regardless of // where the touch events (fingers) were. Enabled for touch-capable web // browsers except for old Androids. touchZoom: L.Browser.touch && !L.Browser.android23, bounceAtZoomLimits: false, }); L.Map.TouchZoom = L.Handler.extend({ addHooks: function() { L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom'); this._map.touchGestures.enable(); this._map.touchGestures.zoom = true; }, removeHooks: function() { L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom'); this._map.touchGestures.zoom = false; }, }); // @section Handlers // @property touchZoom: Handler // Touch zoom handler. L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); /** * L.Control.Rotate */ // A tri-state control for map rotation. States are: // Locked (default) // Unlocked (user can pinch-rotate) // Follow (rotation follows device orientation, if available) L.Control.Rotate = L.Control.extend({ options: { position: 'topleft', closeOnZeroBearing: true }, onAdd: function(map) { this._onDeviceOrientation = L.Util.throttle(this._unthrottledOnDeviceOrientation, 100, this); var container = this._container = L.DomUtil.create('div', 'leaflet-control-rotate leaflet-bar'); // this.button = L.Control.Zoom.prototype._createButton.call(this, 'R', 'leaflet-control-rotate', 'leaflet-control-rotate', container, this._toggleLock); var arrow = this._arrow = L.DomUtil.create('span', 'leaflet-control-rotate-arrow'); arrow.style.backgroundImage = `url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")`; arrow.style.cursor = 'grab'; arrow.style.display = 'block'; arrow.style.width = '100%'; arrow.style.height = '100%'; arrow.style.backgroundRepeat = 'no-repeat'; arrow.style.backgroundPosition = '50%'; // Copy-pasted from L.Control.Zoom var link = this._link = L.DomUtil.create('a', 'leaflet-control-rotate-toggle', container); link.appendChild(arrow); link.href = '#'; link.title = 'Rotate map'; L.DomEvent .on(link, 'dblclick', L.DomEvent.stopPropagation) .on(link, 'mousedown', this._handleMouseDown, this) .on(link, 'click', L.DomEvent.stop) .on(link, 'click', this._cycleState, this) .on(link, 'click', this._refocusOnMap, this); if (!L.Browser.any3d) { L.DomUtil.addClass(link, 'leaflet-disabled'); } this._restyle(); map.on('rotate', this._restyle.bind(this)); // State flag this._follow = false; this._canFollow = false; if (this.options.closeOnZeroBearing && map.getBearing() === 0) { container.style.display = 'none'; } return container; }, _handleMouseDown: function(e) { L.DomEvent.stopPropagation(e); this.dragging = true; this.dragstartX = e.pageX; this.dragstartY = e.pageY; L.DomEvent .on(document, 'mousemove', this._handleMouseDrag, this) .on(document, 'mouseup', this._handleMouseUp, this); }, _handleMouseUp: function(e) { L.DomEvent.stopPropagation(e); this.dragging = false; L.DomEvent .off(document, 'mousemove', this._handleMouseDrag, this) .off(document, 'mouseup', this._handleMouseUp, this); }, _handleMouseDrag: function(e) { if (!this.dragging) { return; } var deltaX = e.clientX - this.dragstartX; this._map.setBearing(deltaX); }, _cycleState: function(ev) { var map = this._map; if (!map) { return; } if (!map.touchRotate.enabled() && !map.compassBearing.enabled()) { // Go from disabled to touch map.touchRotate.enable(); // console.log('state is now: touch rotate'); } else { if (!map.compassBearing.enabled()) { // Go from touch to compass map.touchRotate.disable(); map.compassBearing.enable(); // console.log('state is now: compass'); // It is possible that compass is not supported. If so, // the hangler will automatically go from compass to disabled. } else { // Go from compass to disabled map.compassBearing.disable(); // console.log('state is now: locked'); map.setBearing(0); if (this.options.closeOnZeroBearing) { map.touchRotate.enable(); } } } this._restyle(); }, _restyle: function() { if (this._map.options.rotate) { var map = this._map; var bearing = map.getBearing(); if (this.options.closeOnZeroBearing && bearing) { this._container.style.display = 'block'; } var cssTransform = 'rotate(' + bearing + 'deg)'; this._arrow.style.transform = cssTransform; if (map.compassBearing.enabled()) { this._link.style.backgroundColor = 'orange'; } else if (map.touchRotate.enabled()) { this._link.style.backgroundColor = null; } else { this._link.style.backgroundColor = 'grey'; if (this.options.closeOnZeroBearing && map.getBearing() === 0) { this._container.style.display = 'none'; } } } else { L.DomUtil.addClass(this._link, 'leaflet-disabled'); } }, }); L.control.rotate = function(options) { return new L.Control.Rotate(options); }; L.Map.mergeOptions({ rotateControl: true, }); L.Map.addInitHook(function() { if (this.options.rotateControl) { var options = typeof this.options.rotateControl === 'object' ? this.options.rotateControl : {}; this.rotateControl = L.control.rotate(options); this.addControl(this.rotateControl); } }); /*! Copyright (c) 2016 Dominik Moritz This file is part of the leaflet locate control. It is licensed under the MIT license. You can find the project at: https://github.com/domoritz/leaflet-locatecontrol */ (function (factory, window) { // see https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md#module-loaders // for details on how to structure a leaflet plugin. // define an AMD module that relies on 'leaflet' if (typeof define === 'function' && define.amd) { define(['leaflet'], factory); // define a Common JS module that relies on 'leaflet' } else if (typeof exports === 'object') { if (typeof window !== 'undefined' && window.L) { module.exports = factory(L); } else { module.exports = factory(require('leaflet')); } } // attach your plugin to the global 'L' variable if (typeof window !== 'undefined' && window.L){ window.L.Control.Locate = factory(L); } } (function (L) { const LDomUtilApplyClassesMethod = (method, element, classNames) => { classNames = classNames.split(' '); classNames.forEach(function(className) { L.DomUtil[method].call(this, element, className); }); }; const addClasses = (el, names) => LDomUtilApplyClassesMethod('addClass', el, names); const removeClasses = (el, names) => LDomUtilApplyClassesMethod('removeClass', el, names); /** * Compatible with L.Circle but a true marker instead of a path */ const LocationMarker = L.Marker.extend({ initialize(latlng, options) { L.Util.setOptions(this, options); this._latlng = latlng; this.createIcon(); }, /** * Create a styled circle location marker */ createIcon() { const opt = this.options; let style = ''; if (opt.color !== undefined) { style += `stroke:${opt.color};`; } if (opt.weight !== undefined) { style += `stroke-width:${opt.weight};`; } if (opt.fillColor !== undefined) { style += `fill:${opt.fillColor};`; } if (opt.fillOpacity !== undefined) { style += `fill-opacity:${opt.fillOpacity};`; } if (opt.opacity !== undefined) { style += `opacity:${opt.opacity};`; } const icon = this._getIconSVG(opt, style); this._locationIcon = L.divIcon({ className: icon.className, html: icon.svg, iconSize: [icon.w,icon.h], }); this.setIcon(this._locationIcon); }, /** * Return the raw svg for the shape * * Split so can be easily overridden */ _getIconSVG(options, style) { const r = options.radius; const w = options.weight; const s = r + w; const s2 = s * 2; const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${s2}" height="${s2}" version="1.1" viewBox="-${s} -${s} ${s2} ${s2}">` + '<circle r="'+r+'" style="'+style+'" />' + '</svg>'; return { className: 'leaflet-control-locate-location', svg, w: s2, h: s2 }; }, setStyle(style) { L.Util.setOptions(this, style); this.createIcon(); } }); const CompassMarker =