Source: util/LevelSet.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 LevelSet
 */
define([
        '../error/ArgumentError',
        '../util/Level',
        '../geom/Location',
        '../util/Logger'
    ],
    function (ArgumentError,
              Level,
              Location,
              Logger) {
        "use strict";

        /**
         * Constructs a level set.
         * @alias Level
         * @constructor
         * @classdesc Represents a multi-resolution, hierarchical collection of tiles. Applications typically do not
         * interact with this class.
         * @param {Sector} sector The sector spanned by this level set.
         * @param {Location} levelZeroDelta The geographic size of tiles in the lowest resolution level of this level set.
         * @param {Number} numLevels The number of levels in the level set.
         * @param {Number} tileWidth The height in pixels of images associated with tiles in this level set, or the number of sample
         * points in the longitudinal direction of elevation tiles associate with this level set.
         * @param {Number} tileHeight The height in pixels of images associated with tiles in this level set, or the number of sample
         * points in the latitudinal direction of elevation tiles associate with this level set.
         * @throws {ArgumentError} If the specified sector or level-zero-delta is null or undefined, the level zero
         * delta values are less than or equal to zero, or any of the number-of-levels, tile-width or tile-height
         * arguments are less than 1.
         */
        var LevelSet = function (sector, levelZeroDelta, numLevels, tileWidth, tileHeight) {
            if (!sector) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor", "missingSector"));
            }

            if (!levelZeroDelta) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor",
                        "The specified level zero delta is null or undefined"));
            }

            if (levelZeroDelta.latitude <= 0 || levelZeroDelta.longitude <= 0) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor",
                        "The specified level zero delta is less than or equal to zero."));
            }

            if (numLevels < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor",
                        "The specified number of levels is less than one."));
            }

            if (tileWidth < 1 || tileHeight < 1) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "constructor",
                        "The specified tile width or tile height is less than one."));
            }

            /**
             * The sector spanned by this level set.
             * @type {Sector}
             * @readonly
             */
            this.sector = sector;

            /**
             * The geographic size of the lowest resolution (level 0) tiles in this level set.
             * @type {Location}
             * @readonly
             */
            this.levelZeroDelta = levelZeroDelta;

            /**
             * The number of levels in this level set.
             * @type {Number}
             * @readonly
             */
            this.numLevels = numLevels;

            /**
             *  The width in pixels of images associated with tiles in this level set, or the number of sample points
             *  in the longitudinal direction of elevation tiles associated with this level set.
             * @type {Number}
             * @readonly
             */
            this.tileWidth = tileWidth;

            /**
             *  The height in pixels of images associated with tiles in this level set, or the number of sample points
             *  in the latitudinal direction of elevation tiles associated with this level set.
             * @type {Number}
             * @readonly
             */
            this.tileHeight = tileHeight;

            this.levels = [];

            for (var i = 0; i < numLevels; i += 1) {
                var n = Math.pow(2, i),
                    latDelta = levelZeroDelta.latitude / n,
                    lonDelta = levelZeroDelta.longitude / n,
                    tileDelta = new Location(latDelta, lonDelta),
                    level = new Level(i, tileDelta, this);

                this.levels[i] = level;
            }
        };

        /**
         * Returns the number of levels that match the specified resolution. firstLevelResolution indicates the
         * resolution of the first level's tiles, in degrees per pixel. This depends only on the LevelSet configuration,
         * and is derived by evaluating {@code levelZeroDelta.latitude / tileHeight}. lastLevelResolution indicates the
         * resolution of the data represented by the LevelSet.
         *
         * The returned level count is a fractional value. The dataset resolution is rarely an even multiple of the
         * first level resolution, so the ideal last level is typically somewhere in between two levels. An integer
         * level count can be computed depending on the desired behavior. If the last level should
         * <i>match or exceed</i> the data resolution, take the ceiling of the returned value. Otherwise, if the last
         * level should be <i>no more than<i/> the data resolution, take the floor of the returned value.
         *
         * @param {Number} firstLevelResolution the known resolution of the first level in degrees per pixel
         * @param {Number} lastLevelResolution the desired resolution of the last level in degrees per pixel
         *
         * @return {Number} the number of levels as a fractional level count
         *
         * @throws {ArgumentError} If either resolution is null, undefined, or zero
         */
        LevelSet.numLevelsForResolution = function (firstLevelResolution, lastLevelResolution) {
            if (!firstLevelResolution || !lastLevelResolution) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "LevelSet", "numLevelsForResolution", "missingResolution"));
            }

            var lastLevel = Math.log(firstLevelResolution / lastLevelResolution) / Math.log(2); // fractional level address

            if (lastLevel < 0) {
                lastLevel = 0; // ensure at least one level is used, resolution can be less than the first level resolution
            }

            return lastLevel + 1; // convert level number to level count
        };

        /**
         * Returns the {@link Level} for a specified level number.
         * @param {Number} levelNumber The number of the desired level.
         * @returns {Level} The requested level, or null if the level does not exist.
         */
        LevelSet.prototype.level = function (levelNumber) {
            if (levelNumber < 0 || levelNumber >= this.levels.length) {
                return null;
            } else {
                return this.levels[levelNumber];
            }
        };

        /**
         * Returns the level with a specified texel size.
         * This function returns the first level if the specified texel size is greater than the first level's texel
         * size, and returns the last level if the delta is less than the last level's texel size.
         * @param {Number} texelSize The size of pixels or elevation cells in the level, in radians per pixel or cell.
         */
        LevelSet.prototype.levelForTexelSize = function (texelSize) {
            // TODO: Replace this loop with a computation.
            var lastLevel = this.lastLevel();

            if (lastLevel.texelSize >= texelSize) {
                return lastLevel; // Can't do any better than the last level.
            }

            for (var index = 0, length = this.levels.length; index < length; index += 1) {
                var level = this.levels[index];
                if (level.texelSize <= texelSize) {
                    return level;
                }
            }

            return lastLevel;
        };

        /**
         * Returns the first (lowest resolution) level of this level set.
         * @returns {Level} The first level of this level set.
         */
        LevelSet.prototype.firstLevel = function () {
            return this.levels[0];
        };

        /**
         * Returns the last (highest resolution) level of this level set.
         * @returns {Level} The last level of this level set.
         */
        LevelSet.prototype.lastLevel = function () {
            return this.levels[this.levels.length - 1];
        };

        return LevelSet;
    });