insideout10/wordlift-plugin-js

View on GitHub
bower_components/leaflet/src/map/Map.js

Summary

Maintainability
F
4 days
Test Coverage
/*
 * 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: {
        crs: L.CRS.EPSG3857,

        /*
        center: LatLng,
        zoom: Number,
        layers: Array,
        */

        fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
        trackResize: true,
        markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
    },

    initialize: function (id, options) { // (HTMLElement or String, Object)
        options = L.setOptions(this, options);


        this._initContainer(id);
        this._initLayout();

        // hack for https://github.com/Leaflet/Leaflet/issues/1980
        this._onResize = L.bind(this._onResize, this);

        this._initEvents();

        if (options.maxBounds) {
            this.setMaxBounds(options.maxBounds);
        }

        if (options.center && options.zoom !== undefined) {
            this.setView(L.latLng(options.center), options.zoom, {reset: true});
        }

        this._handlers = [];

        this._layers = {};
        this._zoomBoundLayers = {};
        this._tileLayersNum = 0;

        this.callInitHooks();

        this._addLayers(options.layers);
    },


    // public methods that modify map state

    // replaced by animation-powered implementation in Map.PanAnimation.js
    setView: function (center, zoom) {
        zoom = zoom === undefined ? this.getZoom() : zoom;
        this._resetView(L.latLng(center), this._limitZoom(zoom));
        return this;
    },

    setZoom: function (zoom, options) {
        if (!this._loaded) {
            this._zoom = this._limitZoom(zoom);
            return this;
        }
        return this.setView(this.getCenter(), zoom, {zoom: options});
    },

    zoomIn: function (delta, options) {
        return this.setZoom(this._zoom + (delta || 1), options);
    },

    zoomOut: function (delta, options) {
        return this.setZoom(this._zoom - (delta || 1), options);
    },

    setZoomAround: function (latlng, zoom, options) {
        var scale = this.getZoomScale(zoom),
            viewHalf = this.getSize().divideBy(2),
            containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),

            centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
            newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));

        return this.setView(newCenter, zoom, {zoom: options});
    },

    fitBounds: function (bounds, options) {

        options = options || {};
        bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);

        var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
            paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),

            zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)),
            paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),

            swPoint = this.project(bounds.getSouthWest(), zoom),
            nePoint = this.project(bounds.getNorthEast(), zoom),
            center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);

        zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom;

        return this.setView(center, zoom, options);
    },

    fitWorld: function (options) {
        return this.fitBounds([[-90, -180], [90, 180]], options);
    },

    panTo: function (center, options) { // (LatLng)
        return this.setView(center, this._zoom, {pan: options});
    },

    panBy: function (offset) { // (Point)
        // replaced with animated panBy in Map.PanAnimation.js
        this.fire('movestart');

        this._rawPanBy(L.point(offset));

        this.fire('move');
        return this.fire('moveend');
    },

    setMaxBounds: function (bounds) {
        bounds = L.latLngBounds(bounds);

        this.options.maxBounds = bounds;

        if (!bounds) {
            return this.off('moveend', this._panInsideMaxBounds, this);
        }

        if (this._loaded) {
            this._panInsideMaxBounds();
        }

        return this.on('moveend', this._panInsideMaxBounds, this);
    },

    panInsideBounds: function (bounds, options) {
        var center = this.getCenter(),
            newCenter = this._limitCenter(center, this._zoom, bounds);

        if (center.equals(newCenter)) { return this; }

        return this.panTo(newCenter, options);
    },

    addLayer: function (layer) {
        // TODO method is too big, refactor

        var id = L.stamp(layer);

        if (this._layers[id]) { return this; }

        this._layers[id] = layer;

        // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
        if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
            this._zoomBoundLayers[id] = layer;
            this._updateZoomLevels();
        }

        // TODO looks ugly, refactor!!!
        if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
            this._tileLayersNum++;
            this._tileLayersToLoad++;
            layer.on('load', this._onTileLayerLoad, this);
        }

        if (this._loaded) {
            this._layerAdd(layer);
        }

        return this;
    },

    removeLayer: function (layer) {
        var id = L.stamp(layer);

        if (!this._layers[id]) { return this; }

        if (this._loaded) {
            layer.onRemove(this);
        }

        delete this._layers[id];

        if (this._loaded) {
            this.fire('layerremove', {layer: layer});
        }

        if (this._zoomBoundLayers[id]) {
            delete this._zoomBoundLayers[id];
            this._updateZoomLevels();
        }

        // TODO looks ugly, refactor
        if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
            this._tileLayersNum--;
            this._tileLayersToLoad--;
            layer.off('load', this._onTileLayerLoad, this);
        }

        return this;
    },

    hasLayer: function (layer) {
        if (!layer) { return false; }

        return (L.stamp(layer) in this._layers);
    },

    eachLayer: function (method, context) {
        for (var i in this._layers) {
            method.call(context, this._layers[i]);
        }
        return this;
    },

    invalidateSize: function (options) {
        if (!this._loaded) { return this; }

        options = L.extend({
            animate: false,
            pan: true
        }, options === true ? {animate: true} : options);

        var oldSize = this.getSize();
        this._sizeChanged = true;
        this._initialCenter = null;

        var 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(L.bind(this.fire, this, 'moveend'), 200);
            } else {
                this.fire('moveend');
            }
        }

        return this.fire('resize', {
            oldSize: oldSize,
            newSize: newSize
        });
    },

    // TODO handler.addTo
    addHandler: function (name, HandlerClass) {
        if (!HandlerClass) { return this; }

        var handler = this[name] = new HandlerClass(this);

        this._handlers.push(handler);

        if (this.options[name]) {
            handler.enable();
        }

        return this;
    },

    remove: function () {
        if (this._loaded) {
            this.fire('unload');
        }

        this._initEvents('off');

        try {
            // throws error in IE6-8
            delete this._container._leaflet;
        } catch (e) {
            this._container._leaflet = undefined;
        }

        this._clearPanes();
        if (this._clearControlPos) {
            this._clearControlPos();
        }

        this._clearHandlers();

        return this;
    },


    // public methods for getting map state

    getCenter: function () { // (Boolean) -> LatLng
        this._checkIfLoaded();

        if (this._initialCenter && !this._moved()) {
            return this._initialCenter;
        }
        return this.layerPointToLatLng(this._getCenterLayerPoint());
    },

    getZoom: function () {
        return this._zoom;
    },

    getBounds: function () {
        var bounds = this.getPixelBounds(),
            sw = this.unproject(bounds.getBottomLeft()),
            ne = this.unproject(bounds.getTopRight());

        return new L.LatLngBounds(sw, ne);
    },

    getMinZoom: function () {
        return this.options.minZoom === undefined ?
            (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :
            this.options.minZoom;
    },

    getMaxZoom: function () {
        return this.options.maxZoom === undefined ?
            (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
            this.options.maxZoom;
    },

    getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
        bounds = L.latLngBounds(bounds);

        var zoom = this.getMinZoom() - (inside ? 1 : 0),
            maxZoom = this.getMaxZoom(),
            size = this.getSize(),

            nw = bounds.getNorthWest(),
            se = bounds.getSouthEast(),

            zoomNotFound = true,
            boundsSize;

        padding = L.point(padding || [0, 0]);

        do {
            zoom++;
            boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
            zoomNotFound = !inside ? size.contains(boundsSize) : 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.clone();
    },

    getPixelBounds: function () {
        var topLeftPoint = this._getTopLeftPoint();
        return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
    },

    getPixelOrigin: function () {
        this._checkIfLoaded();
        return this._initialTopLeftPoint;
    },

    getPanes: function () {
        return this._panes;
    },

    getContainer: function () {
        return this._container;
    },


    // TODO replace with universal implementation after refactoring projections

    getZoomScale: function (toZoom) {
        var crs = this.options.crs;
        return crs.scale(toZoom) / crs.scale(this._zoom);
    },

    getScaleZoom: function (scale) {
        return this._zoom + (Math.log(scale) / Math.LN2);
    },


    // conversion methods

    project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
        zoom = zoom === undefined ? this._zoom : zoom;
        return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
    },

    unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
        zoom = zoom === undefined ? this._zoom : zoom;
        return this.options.crs.pointToLatLng(L.point(point), zoom);
    },

    layerPointToLatLng: function (point) { // (Point)
        var projectedPoint = L.point(point).add(this.getPixelOrigin());
        return this.unproject(projectedPoint);
    },

    latLngToLayerPoint: function (latlng) { // (LatLng)
        var projectedPoint = this.project(L.latLng(latlng))._round();
        return projectedPoint._subtract(this.getPixelOrigin());
    },

    containerPointToLayerPoint: function (point) { // (Point)
        return L.point(point).subtract(this._getMapPanePos());
    },

    layerPointToContainerPoint: function (point) { // (Point)
        return L.point(point).add(this._getMapPanePos());
    },

    containerPointToLatLng: function (point) {
        var layerPoint = this.containerPointToLayerPoint(L.point(point));
        return this.layerPointToLatLng(layerPoint);
    },

    latLngToContainerPoint: function (latlng) {
        return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
    },

    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));
    },


    // map initialization methods

    _initContainer: function (id) {
        var container = this._container = L.DomUtil.get(id);

        if (!container) {
            throw new Error('Map container not found.');
        } else if (container._leaflet) {
            throw new Error('Map container is already initialized.');
        }

        container._leaflet = true;
    },

    _initLayout: function () {
        var container = this._container;

        L.DomUtil.addClass(container, 'leaflet-container' +
            (L.Browser.touch ? ' leaflet-touch' : '') +
            (L.Browser.retina ? ' leaflet-retina' : '') +
            (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
            (this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));

        var position = L.DomUtil.getStyle(container, 'position');

        if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
            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);
        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');

        var zoomHide = ' leaflet-zoom-hide';

        if (!this.options.markerZoomAnimation) {
            L.DomUtil.addClass(panes.markerPane, zoomHide);
            L.DomUtil.addClass(panes.shadowPane, zoomHide);
            L.DomUtil.addClass(panes.popupPane, zoomHide);
        }
    },

    _createPane: function (className, container) {
        return L.DomUtil.create('div', className, container || this._panes.objectsPane);
    },

    _clearPanes: function () {
        this._container.removeChild(this._mapPane);
    },

    _addLayers: function (layers) {
        layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];

        for (var i = 0, len = layers.length; i < len; i++) {
            this.addLayer(layers[i]);
        }
    },


    // private methods that modify map state

    _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._initialCenter = center;

        this._initialTopLeftPoint = this._getNewTopLeftPoint(center);

        if (!preserveMapOffset) {
            L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
        } else {
            this._initialTopLeftPoint._add(this._getMapPanePos());
        }

        this._tileLayersToLoad = this._tileLayersNum;

        var loading = !this._loaded;
        this._loaded = true;

        if (loading) {
            this.fire('load');
            this.eachLayer(this._layerAdd, this);
        }

        this.fire('viewreset', {hard: !preserveMapOffset});

        this.fire('move');

        if (zoomChanged || afterZoomAnim) {
            this.fire('zoomend');
        }

        this.fire('moveend', {hard: !preserveMapOffset});
    },

    _rawPanBy: function (offset) {
        L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
    },

    _getZoomSpan: function () {
        return this.getMaxZoom() - this.getMinZoom();
    },

    _updateZoomLevels: function () {
        var i,
            minZoom = Infinity,
            maxZoom = -Infinity,
            oldZoomSpan = this._getZoomSpan();

        for (i in this._zoomBoundLayers) {
            var layer = this._zoomBoundLayers[i];
            if (!isNaN(layer.options.minZoom)) {
                minZoom = Math.min(minZoom, layer.options.minZoom);
            }
            if (!isNaN(layer.options.maxZoom)) {
                maxZoom = Math.max(maxZoom, layer.options.maxZoom);
            }
        }

        if (i === undefined) { // we have no tilelayers
            this._layersMaxZoom = this._layersMinZoom = undefined;
        } else {
            this._layersMaxZoom = maxZoom;
            this._layersMinZoom = minZoom;
        }

        if (oldZoomSpan !== this._getZoomSpan()) {
            this.fire('zoomlevelschange');
        }
    },

    _panInsideMaxBounds: function () {
        this.panInsideBounds(this.options.maxBounds);
    },

    _checkIfLoaded: function () {
        if (!this._loaded) {
            throw new Error('Set map center and zoom first.');
        }
    },

    // map events

    _initEvents: function (onOff) {
        if (!L.DomEvent) { return; }

        onOff = onOff || 'on';

        L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);

        var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
                      'mouseleave', 'mousemove', 'contextmenu'],
            i, len;

        for (i = 0, len = events.length; i < len; i++) {
            L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);
        }

        if (this.options.trackResize) {
            L.DomEvent[onOff](window, 'resize', this._onResize, this);
        }
    },

    _onResize: function () {
        L.Util.cancelAnimFrame(this._resizeRequest);
        this._resizeRequest = L.Util.requestAnimFrame(
                function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container);
    },

    _onMouseClick: function (e) {
        if (!this._loaded || (!e._simulated &&
                ((this.dragging && this.dragging.moved()) ||
                 (this.boxZoom  && this.boxZoom.moved()))) ||
                    L.DomEvent._skipped(e)) { return; }

        this.fire('preclick');
        this._fireMouseEvent(e);
    },

    _fireMouseEvent: function (e) {
        if (!this._loaded || L.DomEvent._skipped(e)) { 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);
        }

        var containerPoint = this.mouseEventToContainerPoint(e),
            layerPoint = this.containerPointToLayerPoint(containerPoint),
            latlng = this.layerPointToLatLng(layerPoint);

        this.fire(type, {
            latlng: latlng,
            layerPoint: layerPoint,
            containerPoint: containerPoint,
            originalEvent: e
        });
    },

    _onTileLayerLoad: function () {
        this._tileLayersToLoad--;
        if (this._tileLayersNum && !this._tileLayersToLoad) {
            this.fire('tilelayersload');
        }
    },

    _clearHandlers: function () {
        for (var i = 0, len = this._handlers.length; i < len; i++) {
            this._handlers[i].disable();
        }
    },

    whenReady: function (callback, context) {
        if (this._loaded) {
            callback.call(context || this, this);
        } else {
            this.on('load', callback, context);
        }
        return this;
    },

    _layerAdd: function (layer) {
        layer.onAdd(this);
        this.fire('layeradd', {layer: layer});
    },


    // private methods for getting map state

    _getMapPanePos: function () {
        return L.DomUtil.getPosition(this._mapPane);
    },

    _moved: function () {
        var pos = this._getMapPanePos();
        return pos && !pos.equals([0, 0]);
    },

    _getTopLeftPoint: function () {
        return this.getPixelOrigin().subtract(this._getMapPanePos());
    },

    _getNewTopLeftPoint: function (center, zoom) {
        var viewHalf = this.getSize()._divideBy(2);
        // TODO round on display, not calculation to increase precision?
        return this.project(center, zoom)._subtract(viewHalf)._round();
    },

    _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
        var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
        return this.project(latlng, newZoom)._subtract(topLeft);
    },

    // layer point of the current center
    _getCenterLayerPoint: function () {
        return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
    },

    // offset of the specified place to the current center in pixels
    _getCenterOffset: function (latlng) {
        return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
    },

    // adjust center for view to get inside bounds
    _limitCenter: function (center, zoom, bounds) {

        if (!bounds) { return center; }

        var centerPoint = this.project(center, zoom),
            viewHalf = this.getSize().divideBy(2),
            viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
            offset = this._getBoundsOffset(viewBounds, bounds, zoom);

        return this.unproject(centerPoint.add(offset), zoom);
    },

    // adjust offset for view to get inside bounds
    _limitOffset: function (offset, bounds) {
        if (!bounds) { return offset; }

        var viewBounds = this.getPixelBounds(),
            newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));

        return offset.add(this._getBoundsOffset(newBounds, bounds));
    },

    // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
    _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
        var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min),
            seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max),

            dx = this._rebound(nwOffset.x, -seOffset.x),
            dy = this._rebound(nwOffset.y, -seOffset.y);

        return new L.Point(dx, dy);
    },

    _rebound: function (left, right) {
        return left + right > 0 ?
            Math.round(left - right) / 2 :
            Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
    },

    _limitZoom: function (zoom) {
        var min = this.getMinZoom(),
            max = this.getMaxZoom();

        return Math.max(min, Math.min(max, zoom));
    }
});

L.map = function (id, options) {
    return new L.Map(id, options);
};