Source: layer/MercatorTiledImageLayer.js

/*
 * Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
 * National Aeronautics and Space Administration. All rights reserved.
 *
 * The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @exports MercatorTiledImageLayer
 */
define([
        '../util/Color',
        '../geom/Sector',
        '../layer/TiledImageLayer',
        '../geom/Vec2',
        '../util/WWMath'
    ],
    function (Color,
              Sector,
              TiledImageLayer,
              Vec2,
              WWMath) {
        "use strict";

        /**
         * Constructs a layer supporting Mercator imagery.
         * @alias MercatorTiledImageLayer
         * @constructor
         * @augments TiledImageLayer
         * @classdesc Provides an abstract layer to support Mercator layers.
         *
         * @param {Sector} sector The sector this layer covers.
         * @param {Location} levelZeroDelta The size in latitude and longitude of level zero (lowest resolution) tiles.
         * @param {Number} numLevels The number of levels to define for the layer. Each level is successively one power
         * of two higher resolution than the next lower-numbered level. (0 is the lowest resolution level, 1 is twice
         * that resolution, etc.)
         * Each level contains four times as many tiles as the next lower-numbered level, each 1/4 the geographic size.
         * @param {String} imageFormat The mime type of the image format for the layer's tiles, e.g., <em>image/png</em>.
         * @param {String} cachePath A string uniquely identifying this layer relative to other layers.
         * @param {Number} tileWidth The horizontal size of image tiles in pixels.
         * @param {Number} tileHeight The vertical size of image tiles in pixels.
         * @throws {ArgumentError} If any of the specified sector, level-zero delta, cache path or image format arguments are
         * null or undefined, or if the specified number of levels, tile width or tile height is less than 1.
         */
        var MercatorTiledImageLayer = function (sector, levelZeroDelta, numLevels, imageFormat, cachePath,
                                                tileWidth, tileHeight) {
            TiledImageLayer.call(this,
                sector, levelZeroDelta, numLevels, imageFormat, cachePath, tileWidth, tileHeight);

            this.detectBlankImages = false;

            // These pixels are tested in retrieved images to determine whether the image is blank.
            this.testPixels = [
                new Vec2(20, 20),
                new Vec2(235, 20),
                new Vec2(20, 235),
                new Vec2(235, 235)
            ];

            // Create a canvas we can use when unprojecting retrieved images.
            this.destCanvas = document.createElement("canvas");
            this.destContext = this.destCanvas.getContext("2d");
        };

        MercatorTiledImageLayer.prototype = Object.create(TiledImageLayer.prototype);

        // Overridden from TiledImageLayer. Computes a tile's sector and creates the tile.
        // Unlike typical tiles, Tiles at the same level do not have the same sector size.
        MercatorTiledImageLayer.prototype.createTile = function (sector, level, row, column) {
            var mapSize = this.mapSizeForLevel(level.levelNumber),
                swX = WWMath.clamp(column * this.imageSize, 0, mapSize),
                neY = WWMath.clamp(row * this.imageSize, 0, mapSize),
                neX = WWMath.clamp(swX + (this.imageSize), 0, mapSize),
                swY = WWMath.clamp(neY + (this.imageSize), 0, mapSize),
                x, y, swLat, swLon, neLat, neLon;

            x = (swX / mapSize) - 0.5;
            y = 0.5 - (swY / mapSize);
            swLat = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;
            swLon = 360 * x;

            x = (neX / mapSize) - 0.5;
            y = 0.5 - (neY / mapSize);
            neLat = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;
            neLon = 360 * x;

            sector = new Sector(swLat, neLat, swLon, neLon);

            return TiledImageLayer.prototype.createTile.call(this, sector, level, row, column);
        };

        // Overridden from TiledImageLayer to unproject the retrieved image prior to creating a texture for it.
        MercatorTiledImageLayer.prototype.createTexture = function (dc, tile, image) {
            var srcCanvas = dc.canvas2D,
                srcContext = dc.ctx2D,
                srcImageData,
                destCanvas = this.destCanvas,
                destContext = this.destContext,
                destImageData = destContext.createImageData(image.width, image.height),
                sector = tile.sector,
                tMin = WWMath.gudermannianInverse(sector.minLatitude),
                tMax = WWMath.gudermannianInverse(sector.maxLatitude),
                lat, g, srcRow, kSrc, kDest, sy, dy;

            srcCanvas.width = image.width;
            srcCanvas.height = image.height;
            destCanvas.width = image.width;
            destCanvas.height = image.height;

            // Draw the original image to a canvas so image data can be had for it.
            srcContext.drawImage(image, 0, 0, image.width, image.height);
            srcImageData = srcContext.getImageData(0, 0, image.width, image.height);

            // If it's a blank image, mark it as permanently absent.
            if (this.detectBlankImages && this.isBlankImage(image, srcImageData)) {
                this.absentResourceList.markResourceAbsentPermanently(tile.imagePath);
                return null;
            }

            // Unproject the retrieved image.
            for (var n = 0; n < 1; n++) {
                for (var y = 0; y < image.height; y++) {
                    sy = 1 - y / (image.height - 1);
                    lat = sy * sector.deltaLatitude() + sector.minLatitude;
                    g = WWMath.gudermannianInverse(lat);
                    dy = 1 - (g - tMin) / (tMax - tMin);
                    dy = WWMath.clamp(dy, 0, 1);
                    srcRow = Math.floor(dy * (image.height - 1));
                    for (var x = 0; x < image.width; x++) {
                        kSrc = 4 * (x + srcRow * image.width);
                        kDest = 4 * (x + y * image.width);

                        destImageData.data[kDest] = srcImageData.data[kSrc];
                        destImageData.data[kDest + 1] = srcImageData.data[kSrc + 1];
                        destImageData.data[kDest + 2] = srcImageData.data[kSrc + 2];
                        destImageData.data[kDest + 3] = srcImageData.data[kSrc + 3];
                    }
                }
            }

            destContext.putImageData(destImageData, 0, 0);

            return TiledImageLayer.prototype.createTexture.call(this, dc, tile, destCanvas);
        };

        // Determines whether a retrieved image is blank.
        MercatorTiledImageLayer.prototype.isBlankImage = function (image, srcImageData) {
            var pixel, k, pixelValue = null;

            for (var i = 0, len = this.testPixels.length; i < len; i++) {
                pixel = this.testPixels[i];
                k = 4 * (pixel[0] + pixel[1] * image.width);

                if (!pixelValue) {
                    pixelValue = [
                        srcImageData.data[k],
                        srcImageData.data[k + 1],
                        srcImageData.data[k + 2]
                    ];
                } else {
                    if (srcImageData.data[k] != pixelValue[0]
                        || srcImageData.data[k + 1] != pixelValue[1]
                        || srcImageData.data[k + 2] != pixelValue[2]) {
                        return false;
                    }
                }
            }

            return true;
        };

        return MercatorTiledImageLayer;
    }
);