All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.earth.worldwind.util.LevelSet.kt Maven / Gradle / Ivy

package earth.worldwind.util

import earth.worldwind.geom.Angle
import earth.worldwind.geom.Location
import earth.worldwind.geom.Sector
import earth.worldwind.util.Logger.ERROR
import earth.worldwind.util.Logger.logMessage
import kotlin.math.ln
import kotlin.math.roundToInt

/**
 * Multi-resolution, hierarchical collection of tiles organized into levels of increasing resolution. Applications
 * typically do not interact with this class.
 */
open class LevelSet {
    /**
     * The sector spanned by this level set.
     */
    val sector: Sector
    /**
     * Tile origin for this level set
     */
    val tileOrigin: Sector
    /**
     * The geographic width and height of tiles in the first level (the lowest resolution) of this level set.
     */
    val firstLevelDelta: Location
    /**
     * 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.
     */
    val tileWidth: Int
    /**
     * 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.
     */
    val tileHeight: Int
    /**
     * Determines how many levels to skip from retrieving tile data during tile pyramid subdivision.
     */
    val levelOffset: Int
    /**
     * The hierarchical levels, sorted from lowest to highest resolution.
     */
    protected val levels: Array
    /**
     * Returns the number of levels in this level set.
     */
    val numLevels get() = levels.size
    /**
     * Returns the first level (the lowest resolution) of this level set.
     */
    val firstLevel get() = levels.first()
    /**
     * Returns the last level (the highest resolution) of this level set.
     */
    val lastLevel get() = levels.last()

    /**
     * Constructs an empty level set with no levels. The methods `level`, `levelForResolution`,
     * `firstLevel` and `lastLevel` always return null.
     */
    constructor() {
        sector = Sector()
        tileOrigin = Sector()
        firstLevelDelta = Location()
        tileWidth = 0
        tileHeight = 0
        levelOffset = 0
        levels = emptyArray()
    }

    /**
     * Constructs a level set with specified parameters.
     *
     * @param sector          the sector spanned by this level set
     * @param tileOrigin      the origin for this level set
     * @param firstLevelDelta the geographic width and height of tiles in the first level (the lowest resolution)
     * of the level set
     * @param numLevels       the number of levels in the level set
     * @param 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 leve set
     * @param 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
     * @param levelOffset     determines how many levels to skip from retrieving texture during tile pyramid subdivision
     *
     * @throws IllegalArgumentException If any dimension is zero
     */
    constructor(
        sector: Sector, tileOrigin: Sector, firstLevelDelta: Location, numLevels: Int, tileWidth: Int, tileHeight: Int, levelOffset: Int = 0
    ) {
        require(firstLevelDelta.latitude.inDegrees > 0.0 && firstLevelDelta.longitude.inDegrees > 0.0) {
            logMessage(ERROR, "LevelSet", "constructor", "invalidTileDelta")
        }
        require(numLevels >= 0) {
            logMessage(ERROR, "LevelSet", "constructor", "invalidNumLevels")
        }
        require(tileWidth >= 1 && tileHeight >= 1) {
            logMessage(ERROR, "LevelSet", "constructor", "invalidWidthOrHeight")
        }
        this.sector = sector
        this.tileOrigin = tileOrigin
        this.firstLevelDelta = firstLevelDelta
        this.tileWidth = tileWidth
        this.tileHeight = tileHeight
        this.levelOffset = levelOffset
        this.levels = Array(numLevels) {
            val divisor = 1 shl it
            Level(this, it, Location(firstLevelDelta.latitude / divisor, firstLevelDelta.longitude / divisor))
        }
    }

    /**
     * Constructs a level set with parameters from a specified level set.
     *
     * @param levelSet source level set
     */
    constructor(levelSet: LevelSet): this(
        Sector(levelSet.sector),
        Sector(levelSet.tileOrigin),
        Location(levelSet.firstLevelDelta),
        levelSet.numLevels,
        levelSet.tileWidth,
        levelSet.tileHeight,
        levelSet.levelOffset
    )

    /**
     * Constructs a level set with parameters from a specified configuration. The configuration's sector must be
     * non-null, its first level delta must be positive, its number of levels must be 1 or more, and its tile width and
     * tile height must be 1 or greater.
     *
     * @param config the configuration for this level set
     */
    constructor(config: LevelSetConfig): this(
        config.sector,
        config.tileOrigin,
        config.firstLevelDelta,
        config.numLevels,
        config.tileWidth,
        config.tileHeight,
        config.levelOffset
    )

    /**
     * Returns the [Level] for a specified level number.
     *
     * @param levelNumber the number of the desired level
     *
     * @return the requested level, or null if the level does not exist
     */
    fun level(levelNumber: Int) = if (levelNumber in levels.indices) levels[levelNumber] else null

    /**
     * Returns the level that most closely approximates the specified resolution.
     *
     * @param resolution the desired resolution in angular value of latitude per pixel.
     *
     * @return the level for the specified resolution, or null if this level set is empty
     *
     * @throws IllegalArgumentException If the resolution is not positive
     * @throws IllegalStateException If this level set is empty
     */
    fun levelForResolution(resolution: Angle): Level {
        require(resolution.inDegrees > 0.0) {
            logMessage(ERROR, "LevelSetConfig", "levelForResolution", "invalidResolution")
        }
        if (levels.isEmpty()) error("This level set is empty")
        val firstLevelDegreesPerPixel = firstLevelDelta.latitude.inDegrees / tileHeight
        val level = ln(firstLevelDegreesPerPixel / resolution.inDegrees) / ln(2.0) // fractional level address
        val levelNumber = level.roundToInt() // nearest neighbor level
        return when {
            levelNumber < 0 -> levels[0] // unable to match the resolution; return the first level
            levelNumber < levels.size -> levels[levelNumber] // nearest neighbor level is in this level set
            else -> levels[levels.size - 1] // unable to match the resolution; return the last level
        }
    }

    /**
     * Determine the min relevant level for the sector of this level set
     *
     * @param maxLevel Maximum available level
     * @return Minimum relevant level number
     */
    fun minRelevantLevel(maxLevel: Level = lastLevel) = levels[sector.minLevelNumber(maxLevel.levelNumber)]

    /**
     * Calculates approximate count of tiles in specified sector within specified resolution range
     *
     * @param sector co calculate tiles amount
     * @param minLevel minimal required level
     * @param maxLevel maximal required level
     * @return tiles count
     */
    fun tileCount(sector: Sector, minLevel: Level, maxLevel: Level): Long {
        var tileCount = 0L
        var level = minLevel
        do {
            tileCount += level.tilesInSector(sector)
            level = level.nextLevel ?: break
        } while (level.levelNumber <= maxLevel.levelNumber)
        return tileCount
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy