insideout10/wordlift-plugin-js

View on GitHub
bower_components/leaflet/src/layer/tile/TileLayer.js

Summary

Maintainability
D
1 day
Test Coverage
/*
 * L.TileLayer is used for standard xyz-numbered tile layers.
 */

L.TileLayer = L.Class.extend({
    includes: L.Mixin.Events,

    options: {
        minZoom: 0,
        maxZoom: 18,
        tileSize: 256,
        subdomains: 'abc',
        errorTileUrl: '',
        attribution: '',
        zoomOffset: 0,
        opacity: 1,
        /*
        maxNativeZoom: null,
        zIndex: null,
        tms: false,
        continuousWorld: false,
        noWrap: false,
        zoomReverse: false,
        detectRetina: false,
        reuseTiles: false,
        bounds: false,
        */
        unloadInvisibleTiles: L.Browser.mobile,
        updateWhenIdle: L.Browser.mobile
    },

    initialize: function (url, options) {
        options = L.setOptions(this, options);

        // detecting retina displays, adjusting tileSize and zoom levels
        if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {

            options.tileSize = Math.floor(options.tileSize / 2);
            options.zoomOffset++;

            if (options.minZoom > 0) {
                options.minZoom--;
            }
            this.options.maxZoom--;
        }

        if (options.bounds) {
            options.bounds = L.latLngBounds(options.bounds);
        }

        this._url = url;

        var subdomains = this.options.subdomains;

        if (typeof subdomains === 'string') {
            this.options.subdomains = subdomains.split('');
        }
    },

    onAdd: function (map) {
        this._map = map;
        this._animated = map._zoomAnimated;

        // create a container div for tiles
        this._initContainer();

        // set up events
        map.on({
            'viewreset': this._reset,
            'moveend': this._update
        }, this);

        if (this._animated) {
            map.on({
                'zoomanim': this._animateZoom,
                'zoomend': this._endZoomAnim
            }, this);
        }

        if (!this.options.updateWhenIdle) {
            this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
            map.on('move', this._limitedUpdate, this);
        }

        this._reset();
        this._update();
    },

    addTo: function (map) {
        map.addLayer(this);
        return this;
    },

    onRemove: function (map) {
        this._container.parentNode.removeChild(this._container);

        map.off({
            'viewreset': this._reset,
            'moveend': this._update
        }, this);

        if (this._animated) {
            map.off({
                'zoomanim': this._animateZoom,
                'zoomend': this._endZoomAnim
            }, this);
        }

        if (!this.options.updateWhenIdle) {
            map.off('move', this._limitedUpdate, this);
        }

        this._container = null;
        this._map = null;
    },

    bringToFront: function () {
        var pane = this._map._panes.tilePane;

        if (this._container) {
            pane.appendChild(this._container);
            this._setAutoZIndex(pane, Math.max);
        }

        return this;
    },

    bringToBack: function () {
        var pane = this._map._panes.tilePane;

        if (this._container) {
            pane.insertBefore(this._container, pane.firstChild);
            this._setAutoZIndex(pane, Math.min);
        }

        return this;
    },

    getAttribution: function () {
        return this.options.attribution;
    },

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

    setOpacity: function (opacity) {
        this.options.opacity = opacity;

        if (this._map) {
            this._updateOpacity();
        }

        return this;
    },

    setZIndex: function (zIndex) {
        this.options.zIndex = zIndex;
        this._updateZIndex();

        return this;
    },

    setUrl: function (url, noRedraw) {
        this._url = url;

        if (!noRedraw) {
            this.redraw();
        }

        return this;
    },

    redraw: function () {
        if (this._map) {
            this._reset({hard: true});
            this._update();
        }
        return this;
    },

    _updateZIndex: function () {
        if (this._container && this.options.zIndex !== undefined) {
            this._container.style.zIndex = this.options.zIndex;
        }
    },

    _setAutoZIndex: function (pane, compare) {

        var layers = pane.children,
            edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
            zIndex, i, len;

        for (i = 0, len = layers.length; i < len; i++) {

            if (layers[i] !== this._container) {
                zIndex = parseInt(layers[i].style.zIndex, 10);

                if (!isNaN(zIndex)) {
                    edgeZIndex = compare(edgeZIndex, zIndex);
                }
            }
        }

        this.options.zIndex = this._container.style.zIndex =
                (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
    },

    _updateOpacity: function () {
        var i,
            tiles = this._tiles;

        if (L.Browser.ielt9) {
            for (i in tiles) {
                L.DomUtil.setOpacity(tiles[i], this.options.opacity);
            }
        } else {
            L.DomUtil.setOpacity(this._container, this.options.opacity);
        }
    },

    _initContainer: function () {
        var tilePane = this._map._panes.tilePane;

        if (!this._container) {
            this._container = L.DomUtil.create('div', 'leaflet-layer');

            this._updateZIndex();

            if (this._animated) {
                var className = 'leaflet-tile-container';

                this._bgBuffer = L.DomUtil.create('div', className, this._container);
                this._tileContainer = L.DomUtil.create('div', className, this._container);

            } else {
                this._tileContainer = this._container;
            }

            tilePane.appendChild(this._container);

            if (this.options.opacity < 1) {
                this._updateOpacity();
            }
        }
    },

    _reset: function (e) {
        for (var key in this._tiles) {
            this.fire('tileunload', {tile: this._tiles[key]});
        }

        this._tiles = {};
        this._tilesToLoad = 0;

        if (this.options.reuseTiles) {
            this._unusedTiles = [];
        }

        this._tileContainer.innerHTML = '';

        if (this._animated && e && e.hard) {
            this._clearBgBuffer();
        }

        this._initContainer();
    },

    _getTileSize: function () {
        var map = this._map,
            zoom = map.getZoom() + this.options.zoomOffset,
            zoomN = this.options.maxNativeZoom,
            tileSize = this.options.tileSize;

        if (zoomN && zoom > zoomN) {
            tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize);
        }

        return tileSize;
    },

    _update: function () {

        if (!this._map) { return; }

        var map = this._map,
            bounds = map.getPixelBounds(),
            zoom = map.getZoom(),
            tileSize = this._getTileSize();

        if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
            return;
        }

        var tileBounds = L.bounds(
                bounds.min.divideBy(tileSize)._floor(),
                bounds.max.divideBy(tileSize)._floor());

        this._addTilesFromCenterOut(tileBounds);

        if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
            this._removeOtherTiles(tileBounds);
        }
    },

    _addTilesFromCenterOut: function (bounds) {
        var queue = [],
            center = bounds.getCenter();

        var j, i, point;

        for (j = bounds.min.y; j <= bounds.max.y; j++) {
            for (i = bounds.min.x; i <= bounds.max.x; i++) {
                point = new L.Point(i, j);

                if (this._tileShouldBeLoaded(point)) {
                    queue.push(point);
                }
            }
        }

        var tilesToLoad = queue.length;

        if (tilesToLoad === 0) { return; }

        // load tiles in order of their distance to center
        queue.sort(function (a, b) {
            return a.distanceTo(center) - b.distanceTo(center);
        });

        var fragment = document.createDocumentFragment();

        // if its the first batch of tiles to load
        if (!this._tilesToLoad) {
            this.fire('loading');
        }

        this._tilesToLoad += tilesToLoad;

        for (i = 0; i < tilesToLoad; i++) {
            this._addTile(queue[i], fragment);
        }

        this._tileContainer.appendChild(fragment);
    },

    _tileShouldBeLoaded: function (tilePoint) {
        if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
            return false; // already loaded
        }

        var options = this.options;

        if (!options.continuousWorld) {
            var limit = this._getWrapTileNum();

            // don't load if exceeds world bounds
            if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) ||
                tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; }
        }

        if (options.bounds) {
            var tileSize = options.tileSize,
                nwPoint = tilePoint.multiplyBy(tileSize),
                sePoint = nwPoint.add([tileSize, tileSize]),
                nw = this._map.unproject(nwPoint),
                se = this._map.unproject(sePoint);

            // TODO temporary hack, will be removed after refactoring projections
            // https://github.com/Leaflet/Leaflet/issues/1618
            if (!options.continuousWorld && !options.noWrap) {
                nw = nw.wrap();
                se = se.wrap();
            }

            if (!options.bounds.intersects([nw, se])) { return false; }
        }

        return true;
    },

    _removeOtherTiles: function (bounds) {
        var kArr, x, y, key;

        for (key in this._tiles) {
            kArr = key.split(':');
            x = parseInt(kArr[0], 10);
            y = parseInt(kArr[1], 10);

            // remove tile if it's out of bounds
            if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
                this._removeTile(key);
            }
        }
    },

    _removeTile: function (key) {
        var tile = this._tiles[key];

        this.fire('tileunload', {tile: tile, url: tile.src});

        if (this.options.reuseTiles) {
            L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
            this._unusedTiles.push(tile);

        } else if (tile.parentNode === this._tileContainer) {
            this._tileContainer.removeChild(tile);
        }

        // for https://github.com/CloudMade/Leaflet/issues/137
        if (!L.Browser.android) {
            tile.onload = null;
            tile.src = L.Util.emptyImageUrl;
        }

        delete this._tiles[key];
    },

    _addTile: function (tilePoint, container) {
        var tilePos = this._getTilePos(tilePoint);

        // get unused tile - or create a new tile
        var tile = this._getTile();

        /*
        Chrome 20 layouts much faster with top/left (verify with timeline, frames)
        Android 4 browser has display issues with top/left and requires transform instead
        (other browsers don't currently care) - see debug/hacks/jitter.html for an example
        */
        L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);

        this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;

        this._loadTile(tile, tilePoint);

        if (tile.parentNode !== this._tileContainer) {
            container.appendChild(tile);
        }
    },

    _getZoomForUrl: function () {

        var options = this.options,
            zoom = this._map.getZoom();

        if (options.zoomReverse) {
            zoom = options.maxZoom - zoom;
        }

        zoom += options.zoomOffset;

        return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom;
    },

    _getTilePos: function (tilePoint) {
        var origin = this._map.getPixelOrigin(),
            tileSize = this._getTileSize();

        return tilePoint.multiplyBy(tileSize).subtract(origin);
    },

    // image-specific code (override to implement e.g. Canvas or SVG tile layer)

    getTileUrl: function (tilePoint) {
        return L.Util.template(this._url, L.extend({
            s: this._getSubdomain(tilePoint),
            z: tilePoint.z,
            x: tilePoint.x,
            y: tilePoint.y
        }, this.options));
    },

    _getWrapTileNum: function () {
        var crs = this._map.options.crs,
            size = crs.getSize(this._map.getZoom());
        return size.divideBy(this._getTileSize())._floor();
    },

    _adjustTilePoint: function (tilePoint) {

        var limit = this._getWrapTileNum();

        // wrap tile coordinates
        if (!this.options.continuousWorld && !this.options.noWrap) {
            tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x;
        }

        if (this.options.tms) {
            tilePoint.y = limit.y - tilePoint.y - 1;
        }

        tilePoint.z = this._getZoomForUrl();
    },

    _getSubdomain: function (tilePoint) {
        var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
        return this.options.subdomains[index];
    },

    _getTile: function () {
        if (this.options.reuseTiles && this._unusedTiles.length > 0) {
            var tile = this._unusedTiles.pop();
            this._resetTile(tile);
            return tile;
        }
        return this._createTile();
    },

    // Override if data stored on a tile needs to be cleaned up before reuse
    _resetTile: function (/*tile*/) {},

    _createTile: function () {
        var tile = L.DomUtil.create('img', 'leaflet-tile');
        tile.style.width = tile.style.height = this._getTileSize() + 'px';
        tile.galleryimg = 'no';

        tile.onselectstart = tile.onmousemove = L.Util.falseFn;

        if (L.Browser.ielt9 && this.options.opacity !== undefined) {
            L.DomUtil.setOpacity(tile, this.options.opacity);
        }
        // without this hack, tiles disappear after zoom on Chrome for Android
        // https://github.com/Leaflet/Leaflet/issues/2078
        if (L.Browser.mobileWebkit3d) {
            tile.style.WebkitBackfaceVisibility = 'hidden';
        }
        return tile;
    },

    _loadTile: function (tile, tilePoint) {
        tile._layer  = this;
        tile.onload  = this._tileOnLoad;
        tile.onerror = this._tileOnError;

        this._adjustTilePoint(tilePoint);
        tile.src     = this.getTileUrl(tilePoint);

        this.fire('tileloadstart', {
            tile: tile,
            url: tile.src
        });
    },

    _tileLoaded: function () {
        this._tilesToLoad--;

        if (this._animated) {
            L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated');
        }

        if (!this._tilesToLoad) {
            this.fire('load');

            if (this._animated) {
                // clear scaled tiles after all new tiles are loaded (for performance)
                clearTimeout(this._clearBgBufferTimer);
                this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
            }
        }
    },

    _tileOnLoad: function () {
        var layer = this._layer;

        //Only if we are loading an actual image
        if (this.src !== L.Util.emptyImageUrl) {
            L.DomUtil.addClass(this, 'leaflet-tile-loaded');

            layer.fire('tileload', {
                tile: this,
                url: this.src
            });
        }

        layer._tileLoaded();
    },

    _tileOnError: function () {
        var layer = this._layer;

        layer.fire('tileerror', {
            tile: this,
            url: this.src
        });

        var newUrl = layer.options.errorTileUrl;
        if (newUrl) {
            this.src = newUrl;
        }

        layer._tileLoaded();
    }
});

L.tileLayer = function (url, options) {
    return new L.TileLayer(url, options);
};