/* * L.Map is the central class of the API - it is used to create a map. */ L.Map = L.Class.extend({ includes: L.Mixin.Events, options: { // projection crs: L.CRS.EPSG3857 || L.CRS.EPSG4326, scale: function (zoom) { return 256 * Math.pow(2, zoom); }, // state center: null, zoom: null, layers: [], // interaction dragging: true, touchZoom: L.Browser.touch && !L.Browser.android, scrollWheelZoom: !L.Browser.touch, doubleClickZoom: true, boxZoom: true, // controls zoomControl: true, attributionControl: true, // animation fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android, zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android && !L.Browser.mobileOpera, // misc trackResize: true, closePopupOnClick: true, worldCopyJump: true }, // constructor initialize: function (id, options) { // (HTMLElement or String, Object) L.Util.setOptions(this, options); this._container = L.DomUtil.get(id); if (this._container._leaflet) { throw new Error("Map container is already initialized."); } this._container._leaflet = true; this._initLayout(); if (L.DomEvent) { this._initEvents(); if (L.Handler) { this._initInteraction(); } if (L.Control) { this._initControls(); } } if (this.options.maxBounds) { this.setMaxBounds(this.options.maxBounds); } var center = this.options.center, zoom = this.options.zoom; if (center !== null && zoom !== null) { this.setView(center, zoom, true); } var layers = this.options.layers; layers = (layers instanceof Array ? layers : [layers]); this._tileLayersNum = 0; this._initLayers(layers); }, // public methods that modify map state // replaced by animation-powered implementation in Map.PanAnimation.js setView: function (center, zoom) { // reset the map view this._resetView(center, this._limitZoom(zoom)); return this; }, setZoom: function (zoom) { // (Number) return this.setView(this.getCenter(), zoom); }, zoomIn: function () { return this.setZoom(this._zoom + 1); }, zoomOut: function () { return this.setZoom(this._zoom - 1); }, fitBounds: function (bounds) { // (LatLngBounds) var zoom = this.getBoundsZoom(bounds); return this.setView(bounds.getCenter(), zoom); }, fitWorld: function () { var sw = new L.LatLng(-60, -170), ne = new L.LatLng(85, 179); return this.fitBounds(new L.LatLngBounds(sw, ne)); }, panTo: function (center) { // (LatLng) return this.setView(center, this._zoom); }, panBy: function (offset) { // (Point) // replaced with animated panBy in Map.Animation.js this.fire('movestart'); this._rawPanBy(offset); this.fire('move'); this.fire('moveend'); return this; }, setMaxBounds: function (bounds) { this.options.maxBounds = bounds; if (!bounds) { this._boundsMinZoom = null; return this; } var minZoom = this.getBoundsZoom(bounds, true); this._boundsMinZoom = minZoom; if (this._loaded) { if (this._zoom < minZoom) { this.setView(bounds.getCenter(), minZoom); } else { this.panInsideBounds(bounds); } } return this; }, panInsideBounds: function (bounds) { var viewBounds = this.getBounds(), viewSw = this.project(viewBounds.getSouthWest()), viewNe = this.project(viewBounds.getNorthEast()), sw = this.project(bounds.getSouthWest()), ne = this.project(bounds.getNorthEast()), dx = 0, dy = 0; if (viewNe.y < ne.y) { // north dy = ne.y - viewNe.y; } if (viewNe.x > ne.x) { // east dx = ne.x - viewNe.x; } if (viewSw.y > sw.y) { // south dy = sw.y - viewSw.y; } if (viewSw.x < sw.x) { // west dx = sw.x - viewSw.x; } return this.panBy(new L.Point(dx, dy, true)); }, addLayer: function (layer, insertAtTheTop) { var id = L.Util.stamp(layer); if (this._layers[id]) { return this; } this._layers[id] = layer; if (layer.options && !isNaN(layer.options.maxZoom)) { this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom); } if (layer.options && !isNaN(layer.options.minZoom)) { this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom); } //TODO getMaxZoom, getMinZoom in ILayer (instead of options) if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { this._tileLayersNum++; layer.on('load', this._onTileLayerLoad, this); } if (this.attributionControl && layer.getAttribution) { this.attributionControl.addAttribution(layer.getAttribution()); } var onMapLoad = function () { layer.onAdd(this, insertAtTheTop); this.fire('layeradd', {layer: layer}); }; if (this._loaded) { onMapLoad.call(this); } else { this.on('load', onMapLoad, this); } return this; }, removeLayer: function (layer) { var id = L.Util.stamp(layer); if (this._layers[id]) { layer.onRemove(this); delete this._layers[id]; if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { this._tileLayersNum--; layer.off('load', this._onTileLayerLoad, this); } if (this.attributionControl && layer.getAttribution) { this.attributionControl.removeAttribution(layer.getAttribution()); } this.fire('layerremove', {layer: layer}); } return this; }, hasLayer: function (layer) { var id = L.Util.stamp(layer); return this._layers.hasOwnProperty(id); }, invalidateSize: function () { var oldSize = this.getSize(); this._sizeChanged = true; if (this.options.maxBounds) { this.setMaxBounds(this.options.maxBounds); } if (!this._loaded) { return this; } this._rawPanBy(oldSize.subtract(this.getSize()).divideBy(2)); this.fire('move'); clearTimeout(this._sizeTimer); this._sizeTimer = setTimeout(L.Util.bind(function () { this.fire('moveend'); }, this), 200); return this; }, // public methods for getting map state getCenter: function (unbounded) { // (Boolean) var viewHalf = this.getSize().divideBy(2), centerPoint = this._getTopLeftPoint().add(viewHalf); return this.unproject(centerPoint, this._zoom, unbounded); }, getZoom: function () { return this._zoom; }, getBounds: function () { var bounds = this.getPixelBounds(), sw = this.unproject(new L.Point(bounds.min.x, bounds.max.y), this._zoom, true), ne = this.unproject(new L.Point(bounds.max.x, bounds.min.y), this._zoom, true); return new L.LatLngBounds(sw, ne); }, getMinZoom: function () { var z1 = this.options.minZoom || 0, z2 = this._layersMinZoom || 0, z3 = this._boundsMinZoom || 0; return Math.max(z1, z2, z3); }, getMaxZoom: function () { var z1 = isNaN(this.options.maxZoom) ? Infinity : this.options.maxZoom, z2 = this._layersMaxZoom || Infinity; return Math.min(z1, z2); }, getBoundsZoom: function (bounds, inside) { // (LatLngBounds) var size = this.getSize(), zoom = this.options.minZoom || 0, maxZoom = this.getMaxZoom(), ne = bounds.getNorthEast(), sw = bounds.getSouthWest(), boundsSize, nePoint, swPoint, zoomNotFound = true; if (inside) { zoom--; } do { zoom++; nePoint = this.project(ne, zoom); swPoint = this.project(sw, zoom); boundsSize = new L.Point(nePoint.x - swPoint.x, swPoint.y - nePoint.y); if (!inside) { zoomNotFound = (boundsSize.x <= size.x) && (boundsSize.y <= size.y); } else { zoomNotFound = (boundsSize.x < size.x) || (boundsSize.y < size.y); } } while (zoomNotFound && (zoom <= maxZoom)); if (zoomNotFound && inside) { return null; } return inside ? zoom : zoom - 1; }, getSize: function () { if (!this._size || this._sizeChanged) { this._size = new L.Point(this._container.clientWidth, this._container.clientHeight); this._sizeChanged = false; } return this._size; }, getPixelBounds: function () { var topLeftPoint = this._getTopLeftPoint(), size = this.getSize(); return new L.Bounds(topLeftPoint, topLeftPoint.add(size)); }, getPixelOrigin: function () { return this._initialTopLeftPoint; }, getPanes: function () { return this._panes; }, // conversion methods mouseEventToContainerPoint: function (e) { // (MouseEvent) return L.DomEvent.getMousePosition(e, this._container); }, mouseEventToLayerPoint: function (e) { // (MouseEvent) return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); }, mouseEventToLatLng: function (e) { // (MouseEvent) return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); }, containerPointToLayerPoint: function (point) { // (Point) return point.subtract(L.DomUtil.getPosition(this._mapPane)); }, layerPointToContainerPoint: function (point) { // (Point) return point.add(L.DomUtil.getPosition(this._mapPane)); }, layerPointToLatLng: function (point) { // (Point) return this.unproject(point.add(this._initialTopLeftPoint)); }, latLngToLayerPoint: function (latlng) { // (LatLng) return this.project(latlng)._round()._subtract(this._initialTopLeftPoint); }, project: function (latlng, zoom) { // (LatLng[, Number]) -> Point zoom = (typeof zoom === 'undefined' ? this._zoom : zoom); return this.options.crs.latLngToPoint(latlng, this.options.scale(zoom)); }, unproject: function (point, zoom, unbounded) { // (Point[, Number, Boolean]) -> LatLng zoom = (typeof zoom === 'undefined' ? this._zoom : zoom); return this.options.crs.pointToLatLng(point, this.options.scale(zoom), unbounded); }, // private methods that modify map state _initLayout: function () { var container = this._container; container.innerHTML = ''; container.className += ' leaflet-container'; if (this.options.fadeAnimation) { container.className += ' leaflet-fade-anim'; } var position = L.DomUtil.getStyle(container, 'position'); if (position !== 'absolute' && position !== 'relative') { container.style.position = 'relative'; } this._initPanes(); if (this._initControlPos) { this._initControlPos(); } }, _initPanes: function () { var panes = this._panes = {}; this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container); this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane); this._objectsPane = panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane); panes.shadowPane = this._createPane('leaflet-shadow-pane'); panes.overlayPane = this._createPane('leaflet-overlay-pane'); panes.markerPane = this._createPane('leaflet-marker-pane'); panes.popupPane = this._createPane('leaflet-popup-pane'); }, _createPane: function (className, container) { return L.DomUtil.create('div', className, container || this._objectsPane); }, _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) { var zoomChanged = (this._zoom !== zoom); if (!afterZoomAnim) { this.fire('movestart'); if (zoomChanged) { this.fire('zoomstart'); } } this._zoom = zoom; this._initialTopLeftPoint = this._getNewTopLeftPoint(center); if (!preserveMapOffset) { L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); } else { var offset = L.DomUtil.getPosition(this._mapPane); this._initialTopLeftPoint._add(offset); } this._tileLayersToLoad = this._tileLayersNum; this.fire('viewreset', {hard: !preserveMapOffset}); this.fire('move'); if (zoomChanged || afterZoomAnim) { this.fire('zoomend'); } this.fire('moveend'); if (!this._loaded) { this._loaded = true; this.fire('load'); } }, _initLayers: function (layers) { this._layers = {}; var i, len; for (i = 0, len = layers.length; i < len; i++) { this.addLayer(layers[i]); } }, _initControls: function () { if (this.options.zoomControl) { this.addControl(new L.Control.Zoom()); } if (this.options.attributionControl) { this.attributionControl = new L.Control.Attribution(); this.addControl(this.attributionControl); } }, _rawPanBy: function (offset) { var mapPaneOffset = L.DomUtil.getPosition(this._mapPane); L.DomUtil.setPosition(this._mapPane, mapPaneOffset.subtract(offset)); }, // map events _initEvents: function () { L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this); var events = ['dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu']; var i, len; for (i = 0, len = events.length; i < len; i++) { L.DomEvent.addListener(this._container, events[i], this._fireMouseEvent, this); } if (this.options.trackResize) { L.DomEvent.addListener(window, 'resize', this._onResize, this); } }, _onResize: function () { L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container); }, _onMouseClick: function (e) { if (!this._loaded || (this.dragging && this.dragging.moved())) { return; } this.fire('pre' + e.type); this._fireMouseEvent(e); }, _fireMouseEvent: function (e) { if (!this._loaded) { return; } var type = e.type; type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type)); if (!this.hasEventListeners(type)) { return; } if (type === 'contextmenu') { L.DomEvent.preventDefault(e); } this.fire(type, { latlng: this.mouseEventToLatLng(e), layerPoint: this.mouseEventToLayerPoint(e) }); }, _initInteraction: function () { var handlers = { dragging: L.Map.Drag, touchZoom: L.Map.TouchZoom, doubleClickZoom: L.Map.DoubleClickZoom, scrollWheelZoom: L.Map.ScrollWheelZoom, boxZoom: L.Map.BoxZoom }; var i; for (i in handlers) { if (handlers.hasOwnProperty(i) && handlers[i]) { this[i] = new handlers[i](this); if (this.options[i]) { this[i].enable(); } // TODO move enabling to handler contructor } } }, _onTileLayerLoad: function () { // clear scaled tiles after all new tiles are loaded (for performance) this._tileLayersToLoad--; if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) { clearTimeout(this._clearTileBgTimer); this._clearTileBgTimer = setTimeout(L.Util.bind(this._clearTileBg, this), 500); } }, // private methods for getting map state _getTopLeftPoint: function () { if (!this._loaded) { throw new Error('Set map center and zoom first.'); } var offset = L.DomUtil.getPosition(this._mapPane); return this._initialTopLeftPoint.subtract(offset); }, _getNewTopLeftPoint: function (center) { var viewHalf = this.getSize().divideBy(2); return this.project(center).subtract(viewHalf).round(); }, _limitZoom: function (zoom) { var min = this.getMinZoom(); var max = this.getMaxZoom(); return Math.max(min, Math.min(max, zoom)); } });