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

commonMain.earth.worldwind.globe.Globe.kt Maven / Gradle / Ivy

Go to download

The WorldWind Kotlin SDK (WWK) includes the library, examples and tutorials for building multiplatform 3D virtual globe applications for Android, Web and Java.

There is a newer version: 1.5.23
Show newest version
package earth.worldwind.globe

import earth.worldwind.geom.*
import earth.worldwind.globe.elevation.ElevationModel
import earth.worldwind.globe.projection.GeographicProjection
import earth.worldwind.globe.projection.Wgs84Projection
import kotlin.math.PI
import kotlin.math.sin
import kotlin.math.sqrt

/**
 * Planet or celestial object approximated by a reference ellipsoid and elevation models. Globe expresses its
 * ellipsoidal parameters and elevation values in meters.
 */
open class Globe(
    /**
     * The globe's reference ellipsoid defining the globe's equatorial radius and polar radius.
     */
    var ellipsoid: Ellipsoid = Ellipsoid.WGS84,
    /**
     * Indicates the geographic projection used by this globe. The projection specifies this globe's Cartesian
     * coordinate system.
     */
    var projection: GeographicProjection = Wgs84Projection()
) {
    /**
     * Represents the elevations for an area, often but not necessarily the whole globe.
     */
    var elevationModel = ElevationModel()
    /**
     * Indicates the radius in meters of the globe's ellipsoid at the equator.
     */
    val equatorialRadius get() = ellipsoid.semiMajorAxis
    /**
     * Indicates the radius in meters of the globe's ellipsoid at the poles.
     */
    val polarRadius get() = ellipsoid.semiMinorAxis
    /**
     * Indicates the eccentricity squared parameter of the globe's ellipsoid. This is equivalent to `2*f -
     * f*f`, where `f` is the ellipsoid's flattening parameter.
     */
    val eccentricitySquared get() = ellipsoid.eccentricitySquared
    /**
     * Indicates whether this is a 2D globe.
     */
    val is2D get() = projection.is2D
    /**
     * Indicates whether this projection is continuous with itself horizontally.
     */
    val isContinuous get() = projection.isContinuous
    /**
     * Indicates the geographic limits of this projection.
     */
    val projectionLimits get() = projection.projectionLimits
    /**
     * Current globe state.
     */
    val state get() = State(ellipsoid, projection.displayName)
    /**
     * The globe offset in 2D continuous projection. Center is the default for 3D.
     */
    var offset = Offset.Center
        set (value) {
            field = value
            // Calculate horizontal projection offset in meters
            offsetValue = when (value) {
                Offset.Center -> 0.0
                Offset.Right -> 2.0 * PI * equatorialRadius
                Offset.Left -> -2.0 * PI * equatorialRadius
            }
        }
    protected var offsetValue = 0.0

    /**
     * An offset to apply to this globe when translating between Geographic positions and Cartesian points.
     * Used during scrolling to position points appropriately.
     */
    enum class Offset { Center, Right, Left }

    /**
     * Used to compare states during rendering to determine whether globe-state dependent cached values must be updated.
     */
    data class State(private val ellipsoid: Ellipsoid, private val projectionName: String)

    /**
     * Indicates the radius in meters of the globe's ellipsoid at a specified location.
     *
     * @param latitude  the location's latitude
     * @param longitude the location's longitude
     *
     * @return the radius in meters of the globe's ellipsoid at the specified location
     */
    @Suppress("UNUSED_PARAMETER")
    fun getRadiusAt(latitude: Angle, longitude: Angle): Double {
        // The radius for an ellipsoidal globe is a function of its latitude. The following solution was derived by
        // observing that the length of the ellipsoidal point at the specified latitude and longitude indicates the
        // radius at that location. The formula for the length of the ellipsoidal point was then converted into the
        // simplified form below.
        val sinLat = sin(latitude.inRadians)
        val ec2 = ellipsoid.eccentricitySquared
        val rpm = ellipsoid.semiMajorAxis / sqrt(1 - ec2 * sinLat * sinLat)
        return rpm * sqrt(1 + (ec2 * ec2 - 2 * ec2) * sinLat * sinLat)
    }

    /**
     * Converts a geographic position to Cartesian coordinates. This globe's projection specifies the Cartesian
     * coordinate system.
     *
     * @param latitude  the position's latitude
     * @param longitude the position's longitude
     * @param altitude  the position's altitude in meters
     * @param result    a pre-allocated [Vec3] in which to store the computed X, Y and Z Cartesian coordinates
     *
     * @return the result argument, set to the computed Cartesian coordinates
     */
    fun geographicToCartesian(latitude: Angle, longitude: Angle, altitude: Double, result: Vec3) =
        projection.geographicToCartesian(this, latitude, longitude, altitude, offsetValue, result)

    fun geographicToCartesianNormal(latitude: Angle, longitude: Angle, result: Vec3) =
        projection.geographicToCartesianNormal(this, latitude, longitude, result)

    fun geographicToCartesianTransform(latitude: Angle, longitude: Angle, altitude: Double, result: Matrix4) =
        projection.geographicToCartesianTransform(this, latitude, longitude, altitude, result)

    fun geographicToCartesianGrid(
        sector: Sector, numLat: Int, numLon: Int, height: FloatArray?, verticalExaggeration: Float,
        origin: Vec3?, result: FloatArray, rowOffset: Int = 0, rowStride: Int = 0
    ) = projection.geographicToCartesianGrid(
        this, sector, numLat, numLon, height, verticalExaggeration,
        origin, offsetValue, result, rowOffset, rowStride
    )

    fun geographicToCartesianBorder(
        sector: Sector, numLat: Int, numLon: Int, height: Float, origin: Vec3, result: FloatArray
    ) = projection.geographicToCartesianBorder(this, sector, numLat, numLon, height, origin, offsetValue, result)

    /**
     * Converts a Cartesian point to a geographic position. This globe's projection specifies the Cartesian coordinate
     * system.
     *
     * @param x      the Cartesian point's X component
     * @param y      the Cartesian point's Y component
     * @param z      the Cartesian point's Z component
     * @param result a pre-allocated [Position] in which to store the computed geographic position
     *
     * @return the result argument, set to the computed geographic position
     */
    fun cartesianToGeographic(x: Double, y: Double, z: Double, result: Position) =
        projection.cartesianToGeographic(this, x, y, z, offsetValue, result).also {
            if (is2D) result.longitude = result.longitude.normalize180()
        }

    fun cartesianToLocalTransform(x: Double, y: Double, z: Double, result: Matrix4) =
        projection.cartesianToLocalTransform(this, x, y, z, result)

    /**
     * Indicates the distance to the globe's horizon from a specified height above the globe's ellipsoid. The result of
     * this method is undefined if the height is negative.
     *
     * @param height the viewer's height above the globe's ellipsoid in meters
     *
     * @return the horizon distance in meters
     */
    fun horizonDistance(height: Double) = if (height > 0.0) sqrt(height * (2 * ellipsoid.semiMajorAxis + height)) else 0.0

    /**
     * Computes the first intersection of this globe with a specified line. The line is interpreted as a ray;
     * intersection points behind the line's origin are ignored.
     *
     * @param line   the line to intersect with this globe
     * @param result a pre-allocated [Vec3] in which to return the computed point
     *
     * @return true if the ray intersects the globe, otherwise false
     */
    fun intersect(line: Line, result: Vec3) = projection.intersect(this, line, result)

    /**
     * Determine terrain altitude in specified geographic point from elevation model
     *
     * @param latitude  location latitude
     * @param longitude location longitude
     * @param retrieve  retrieve the most detailed elevation data instead of using first available cached value
     *
     * @return Elevation in meters in specified location
     */
    fun getElevation(latitude: Angle, longitude: Angle, retrieve: Boolean = false) =
        elevationModel.getHeight(latitude, longitude, retrieve).toDouble()

    /**
     * Get absolute position with terrain elevation at specified coordinates
     *
     * @param latitude Specified latitude
     * @param longitude Specified longitude
     *
     * @return Absolute position with terrain elevation
     */
    fun getAbsolutePosition(latitude: Angle, longitude: Angle) =
        Position(latitude, longitude, getElevation(latitude, longitude, retrieve = true))

    /**
     * Get absolute position for specified position and specified altitude mode
     *
     * @param position Specified position
     * @param altitudeMode Specified altitude mode
     *
     * @return Absolute position for specified altitude mode
     */
    fun getAbsolutePosition(position: Position, altitudeMode: AltitudeMode) = when (altitudeMode) {
        AltitudeMode.CLAMP_TO_GROUND -> getAbsolutePosition(position.latitude, position.longitude)
        AltitudeMode.RELATIVE_TO_GROUND -> getAbsolutePosition(position.latitude, position.longitude).apply {
            altitude += position.altitude
        }
        else -> Position(position)
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy