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.

The 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