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

src.gov.nasa.worldwind.globes.EllipsoidalGlobe Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.globes;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.terrain.*;
import gov.nasa.worldwind.util.*;

import java.io.IOException;
import java.util.List;

/**
 * Defines a globe modeled as an ellipsoid.
 * This globe uses a Cartesian coordinate system in which the Y axis points to the north pole. The Z axis points to the
 * intersection of the prime meridian and the equator, in the equatorial plane. The X axis completes a right-handed
 * coordinate system, and is 90 degrees east of the Z axis and also in the equatorial plane. Sea level is at z = zero.
 * By default the origin of the coordinate system lies at the center of the globe, but can be set to a different point
 * when the globe is constructed.
 *
 * @author Tom Gaskins
 * @version $Id: EllipsoidalGlobe.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class EllipsoidalGlobe extends WWObjectImpl implements Globe
{
    protected final double equatorialRadius;
    protected final double polarRadius;
    protected final double es;
    private final Vec4 center;
    private ElevationModel elevationModel;
    private Tessellator tessellator;
    protected EGM96 egm96;

    /**
     * Create a new globe. The globe's center point will be (0, 0, 0). The globe will be tessellated using tessellator
     * defined by the {@link AVKey#TESSELLATOR_CLASS_NAME} configuration parameter.
     *
     * @param equatorialRadius Radius of the globe at the equator.
     * @param polarRadius      Radius of the globe at the poles.
     * @param es               Square of the globe's eccentricity.
     * @param em               Elevation model. May be null.
     */
    public EllipsoidalGlobe(double equatorialRadius, double polarRadius, double es, ElevationModel em)
    {
        this.equatorialRadius = equatorialRadius;
        this.polarRadius = polarRadius;
        this.es = es; // assume it's consistent with the two radii
        this.center = Vec4.ZERO;
        this.elevationModel = em;
        this.tessellator = (Tessellator) WorldWind.createConfigurationComponent(AVKey.TESSELLATOR_CLASS_NAME);
    }

    /**
     * Create a new globe, and set the position of the globe's center. The globe will be tessellated using tessellator
     * defined by the {@link AVKey#TESSELLATOR_CLASS_NAME} configuration parameter.
     *
     * @param equatorialRadius Radius of the globe at the equator.
     * @param polarRadius      Radius of the globe at the poles.
     * @param es               Square of the globe's eccentricity.
     * @param em               Elevation model. May be null.
     * @param center           Cartesian coordinates of the globe's center point.
     */
    public EllipsoidalGlobe(double equatorialRadius, double polarRadius, double es, ElevationModel em, Vec4 center)
    {
        this.equatorialRadius = equatorialRadius;
        this.polarRadius = polarRadius;
        this.es = es; // assume it's consistent with the two radii
        this.center = center;
        this.elevationModel = em;
        this.tessellator = (Tessellator) WorldWind.createConfigurationComponent(AVKey.TESSELLATOR_CLASS_NAME);
    }

    protected class StateKey implements GlobeStateKey
    {
        protected Globe globe;
        protected final Tessellator tessellator;
        protected double verticalExaggeration;
        protected ElevationModel elevationModel;

        public StateKey(DrawContext dc)
        {
            if (dc == null)
            {
                String msg = Logging.getMessage("nullValue.DrawContextIsNull");
                Logging.logger().severe(msg);
                throw new IllegalArgumentException(msg);
            }

            this.globe = dc.getGlobe();
            this.tessellator = EllipsoidalGlobe.this.tessellator;
            this.verticalExaggeration = dc.getVerticalExaggeration();
            this.elevationModel = this.globe.getElevationModel();
        }

        public StateKey(Globe globe)
        {
            this.globe = globe;
            this.tessellator = EllipsoidalGlobe.this.tessellator;
            this.verticalExaggeration = 1;
            this.elevationModel = this.globe.getElevationModel();
        }

        public Globe getGlobe()
        {
            return this.globe;
        }

        @SuppressWarnings({"RedundantIfStatement"})
        @Override
        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            StateKey stateKey = (StateKey) o;

            if (Double.compare(stateKey.verticalExaggeration, verticalExaggeration) != 0)
                return false;
            if (elevationModel != null ? !elevationModel.equals(stateKey.elevationModel) :
                stateKey.elevationModel != null)
                return false;
            if (globe != null ? !globe.equals(stateKey.globe) : stateKey.globe != null)
                return false;
            if (tessellator != null ? !tessellator.equals(stateKey.tessellator) : stateKey.tessellator != null)
                return false;

            return true;
        }

        @Override
        public int hashCode()
        {
            int result;
            long temp;
            result = globe != null ? globe.hashCode() : 0;
            result = 31 * result + (tessellator != null ? tessellator.hashCode() : 0);
            temp = verticalExaggeration != +0.0d ? Double.doubleToLongBits(verticalExaggeration) : 0L;
            result = 31 * result + (int) (temp ^ (temp >>> 32));
            result = 31 * result + (elevationModel != null ? elevationModel.hashCode() : 0);
            return result;
        }
    }

    public Object getStateKey(DrawContext dc)
    {
        return this.getGlobeStateKey(dc);
    }

    public GlobeStateKey getGlobeStateKey(DrawContext dc)
    {
        return new StateKey(dc);
    }

    public GlobeStateKey getGlobeStateKey()
    {
        return new StateKey(this);
    }

    public Tessellator getTessellator()
    {
        return tessellator;
    }

    public void setTessellator(Tessellator tessellator)
    {
        this.tessellator = tessellator;
    }

    public ElevationModel getElevationModel()
    {
        return elevationModel;
    }

    public void setElevationModel(ElevationModel elevationModel)
    {
        this.elevationModel = elevationModel;
    }

    public double getRadius()
    {
        return this.equatorialRadius;
    }

    public double getEquatorialRadius()
    {
        return this.equatorialRadius;
    }

    public double getPolarRadius()
    {
        return this.polarRadius;
    }

    public double getMaximumRadius()
    {
        return this.equatorialRadius;
    }

    public double getRadiusAt(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return this.computePointFromPosition(latitude, longitude, 0d).getLength3();
    }

    public double getRadiusAt(LatLon latLon)
    {
        if (latLon == null)
        {
            String msg = Logging.getMessage("nullValue.LatLonIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return this.computePointFromPosition(latLon.getLatitude(), latLon.getLongitude(), 0d).getLength3();
    }

    public double getEccentricitySquared()
    {
        return this.es;
    }

    public double getDiameter()
    {
        return this.equatorialRadius * 2;
    }

    public Vec4 getCenter()
    {
        return this.center;
    }

    public double getMaxElevation()
    {
        return this.elevationModel != null ? this.elevationModel.getMaxElevation() : 0;
    }

    public double getMinElevation()
    {
        // TODO: The value returned might not reflect the globe's actual minimum elevation if the elevation model does
        // not span the full globe. See WWJINT-435.
        return this.elevationModel != null ? this.elevationModel.getMinElevation() : 0;
    }

    public double[] getMinAndMaxElevations(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String msg = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return this.elevationModel != null ? this.elevationModel.getExtremeElevations(latitude, longitude)
            : new double[] {0, 0};
    }

    public double[] getMinAndMaxElevations(Sector sector)
    {
        if (sector == null)
        {
            String message = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.elevationModel != null ? this.elevationModel.getExtremeElevations(sector) : new double[] {0, 0};
    }

    public Extent getExtent()
    {
        return this;
    }

    public double getEffectiveRadius(Plane plane)
    {
        return this.getRadius();
    }

    public boolean intersects(Frustum frustum)
    {
        if (frustum == null)
        {
            String message = Logging.getMessage("nullValue.FrustumIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return frustum.intersects(new Sphere(Vec4.ZERO, this.getRadius()));
    }

    public Intersection[] intersect(Line line)
    {
        return this.intersect(line, this.equatorialRadius, this.polarRadius);
    }

    public Intersection[] intersect(Line line, double altitude)
    {
        return this.intersect(line, this.equatorialRadius + altitude, this.polarRadius + altitude);
    }

    protected Intersection[] intersect(Line line, double equRadius, double polRadius)
    {
        if (line == null)
            return null;

        // Taken from Lengyel, 2Ed., Section 5.2.3, page 148.

        double m = equRadius / polRadius; // "ratio of the x semi-axis length to the y semi-axis length"
        double n = 1d;                    // "ratio of the x semi-axis length to the z semi-axis length"
        double m2 = m * m;
        double n2 = n * n;
        double r2 = equRadius * equRadius; // nominal radius squared //equRadius * polRadius;

        double vx = line.getDirection().x;
        double vy = line.getDirection().y;
        double vz = line.getDirection().z;
        double sx = line.getOrigin().x;
        double sy = line.getOrigin().y;
        double sz = line.getOrigin().z;

        double a = vx * vx + m2 * vy * vy + n2 * vz * vz;
        double b = 2d * (sx * vx + m2 * sy * vy + n2 * sz * vz);
        double c = sx * sx + m2 * sy * sy + n2 * sz * sz - r2;

        double discriminant = discriminant(a, b, c);
        if (discriminant < 0)
            return null;

        double discriminantRoot = Math.sqrt(discriminant);
        if (discriminant == 0)
        {
            Vec4 p = line.getPointAt((-b - discriminantRoot) / (2 * a));
            return new Intersection[] {new Intersection(p, true)};
        }
        else // (discriminant > 0)
        {
            Vec4 near = line.getPointAt((-b - discriminantRoot) / (2 * a));
            Vec4 far = line.getPointAt((-b + discriminantRoot) / (2 * a));
            if (c >= 0) // Line originates outside the Globe.
                return new Intersection[] {new Intersection(near, false), new Intersection(far, false)};
            else // Line originates inside the Globe.
                return new Intersection[] {new Intersection(far, false)};
        }
    }

    static private double discriminant(double a, double b, double c)
    {
        return b * b - 4 * a * c;
    }

    public Intersection[] intersect(Triangle t, double elevation)
    {
        if (t == null)
            return null;

        boolean bA = isPointAboveElevation(t.getA(), elevation);
        boolean bB = isPointAboveElevation(t.getB(), elevation);
        boolean bC = isPointAboveElevation(t.getC(), elevation);

        if (!(bA ^ bB) && !(bB ^ bC))
            return null; // all triangle points are either above or below the given elevation

        Intersection[] inter = new Intersection[2];
        int idx = 0;

        // Assumes that intersect(Line) returns only one intersection when the line
        // originates inside the ellipsoid at the given elevation.
        if (bA ^ bB)
            if (bA)
                inter[idx++] = intersect(new Line(t.getB(), t.getA().subtract3(t.getB())), elevation)[0];
            else
                inter[idx++] = intersect(new Line(t.getA(), t.getB().subtract3(t.getA())), elevation)[0];

        if (bB ^ bC)
            if (bB)
                inter[idx++] = intersect(new Line(t.getC(), t.getB().subtract3(t.getC())), elevation)[0];
            else
                inter[idx++] = intersect(new Line(t.getB(), t.getC().subtract3(t.getB())), elevation)[0];

        if (bC ^ bA)
            if (bC)
                inter[idx] = intersect(new Line(t.getA(), t.getC().subtract3(t.getA())), elevation)[0];
            else
                inter[idx] = intersect(new Line(t.getC(), t.getA().subtract3(t.getC())), elevation)[0];

        return inter;
    }

    public boolean intersects(Line line)
    {
        //noinspection SimplifiableIfStatement
        if (line == null)
            return false;

        return line.distanceTo(this.center) <= this.equatorialRadius;
    }

    public boolean intersects(Plane plane)
    {
        if (plane == null)
            return false;

        double dq1 = plane.dot(this.center);
        return dq1 <= this.equatorialRadius;
    }

    /** {@inheritDoc} */
    public double getProjectedArea(View view)
    {
        if (view == null)
        {
            String message = Logging.getMessage("nullValue.ViewIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return WWMath.computeSphereProjectedArea(view, this.getCenter(), this.getRadius());
    }

    public void applyEGMA96Offsets(String offsetsFilePath) throws IOException
    {
        if (offsetsFilePath != null)
            this.egm96 = new EGM96(offsetsFilePath);
        else
            this.egm96 = null;
    }

    public double getElevations(Sector sector, List latlons, double targetResolution,
        double[] elevations)
    {
        if (this.elevationModel == null)
            return 0;

        double resolution = this.elevationModel.getElevations(sector, latlons, targetResolution, elevations);

        if (this.egm96 != null)
        {
            for (int i = 0; i < elevations.length; i++)
            {
                LatLon latLon = latlons.get(i);
                elevations[i] = elevations[i] + this.egm96.getOffset(latLon.getLatitude(), latLon.getLongitude());
            }
        }

        return resolution;
    }

    public double getElevation(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (this.elevationModel == null)
            return 0;

        double elevation = this.elevationModel.getElevation(latitude, longitude);

        if (this.egm96 != null)
            elevation += this.egm96.getOffset(latitude, longitude);

        return elevation;
    }

    public Vec4 computePointFromPosition(Position position)
    {
        if (position == null)
        {
            String message = Logging.getMessage("nullValue.PositionIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.geodeticToCartesian(position.getLatitude(), position.getLongitude(), position.getElevation());
    }

    public Vec4 computePointFromLocation(LatLon location)
    {
        if (location == null)
        {
            String message = Logging.getMessage("nullValue.PositionIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.geodeticToCartesian(location.getLatitude(), location.getLongitude(), 0);
    }

    public Vec4 computePointFromPosition(LatLon latLon, double metersElevation)
    {
        if (latLon == null)
        {
            String message = Logging.getMessage("nullValue.LatLonIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.geodeticToCartesian(latLon.getLatitude(), latLon.getLongitude(), metersElevation);
    }

    public Vec4 computePointFromPosition(Angle latitude, Angle longitude, double metersElevation)
    {
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.geodeticToCartesian(latitude, longitude, metersElevation);
    }

    public Position computePositionFromPoint(Vec4 point)
    {
        if (point == null)
        {
            String message = Logging.getMessage("nullValue.PointIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.cartesianToGeodetic(point);
    }

    /**
     * Returns the normal to the Globe at the specified position.
     *
     * @param latitude  the latitude of the position.
     * @param longitude the longitude of the position.
     *
     * @return the Globe normal at the specified position.
     */
    public Vec4 computeSurfaceNormalAtLocation(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        double cosLat = latitude.cos();
        double cosLon = longitude.cos();
        double sinLat = latitude.sin();
        double sinLon = longitude.sin();

        double eqSquared = this.equatorialRadius * this.equatorialRadius;
        double polSquared = this.polarRadius * this.polarRadius;

        double x = cosLat * sinLon / eqSquared;
        double y = (1.0 - this.es) * sinLat / polSquared;
        double z = cosLat * cosLon / eqSquared;

        return new Vec4(x, y, z).normalize3();
    }

    /**
     * Returns the normal to the Globe at the specified cartiesian point.
     *
     * @param point the cartesian point.
     *
     * @return the Globe normal at the specified point.
     */
    public Vec4 computeSurfaceNormalAtPoint(Vec4 point)
    {
        if (point == null)
        {
            String msg = Logging.getMessage("nullValue.PointIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        double eqSquared = this.equatorialRadius * this.equatorialRadius;
        double polSquared = this.polarRadius * this.polarRadius;

        double x = (point.x - this.center.x) / eqSquared;
        double y = (point.y - this.center.y) / polSquared;
        double z = (point.z - this.center.z) / eqSquared;

        return new Vec4(x, y, z).normalize3();
    }

    public Vec4 computeNorthPointingTangentAtLocation(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // Latitude is treated clockwise as rotation about the X-axis. We flip the latitude value so that a positive
        // rotation produces a clockwise rotation (when facing the axis).
        latitude = latitude.multiply(-1.0);

        double cosLat = latitude.cos();
        double sinLat = latitude.sin();
        double cosLon = longitude.cos();
        double sinLon = longitude.sin();

        // The north-pointing tangent is derived by rotating the vector (0, 1, 0) about the Y-axis by longitude degrees,
        // then rotating it about the X-axis by -latitude degrees. This can be represented by a combining two rotation
        // matrices Rlat, and Rlon, then transforming the vector (0, 1, 0) by the combined transform:
        //
        // NorthTangent = (Rlon * Rlat) * (0, 1, 0)
        //
        // Since the input vector only has a Y coordinate, this computation can be simplified. The simplified
        // computation is shown here as NorthTangent = (x, y, z).
        //
        double x = sinLat * sinLon;
        //noinspection UnnecessaryLocalVariable
        double y = cosLat;
        double z = sinLat * cosLon;

        return new Vec4(x, y, z).normalize3();
    }

    public Matrix computeModelCoordinateOriginTransform(Angle latitude, Angle longitude, double metersElevation)
    {
        return this.computeSurfaceOrientationAtPosition(latitude, longitude, metersElevation);
    }

    public Matrix computeModelCoordinateOriginTransform(Position position)
    {
        return this.computeSurfaceOrientationAtPosition(position);
    }

    /** {@inheritDoc} */
    public Matrix computeSurfaceOrientationAtPosition(Angle latitude, Angle longitude, double metersElevation)
    {
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Vec4 point = this.geodeticToCartesian(latitude, longitude, metersElevation);
        // Transform to the cartesian coordinates of (latitude, longitude, metersElevation).
        Matrix transform = Matrix.fromTranslation(point);
        // Rotate the coordinate system to match the longitude.
        // Longitude is treated as counter-clockwise rotation about the Y-axis.
        transform = transform.multiply(Matrix.fromRotationY(longitude));
        // Rotate the coordinate system to match the latitude.
        // Latitude is treated clockwise as rotation about the X-axis. We flip the latitude value so that a positive
        // rotation produces a clockwise rotation (when facing the axis).
        transform = transform.multiply(Matrix.fromRotationX(latitude.multiply(-1.0)));
        return transform;
    }

    /** {@inheritDoc} */
    public Matrix computeSurfaceOrientationAtPosition(Position position)
    {
        if (position == null)
        {
            String message = Logging.getMessage("nullValue.PositionIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.computeSurfaceOrientationAtPosition(position.getLatitude(), position.getLongitude(),
            position.getElevation());
    }

    public Position getIntersectionPosition(Line line)
    {
        if (line == null)
        {
            String msg = Logging.getMessage("nullValue.LineIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        Intersection[] intersections = this.intersect(line);
        if (intersections == null)
            return null;

        return this.computePositionFromPoint(intersections[0].getIntersectionPoint());
    }

    /**
     * Maps a position to world Cartesian coordinates. The Y axis points to the north pole. The Z axis points to the
     * intersection of the prime meridian and the equator, in the equatorial plane. The X axis completes a right-handed
     * coordinate system, and is 90 degrees east of the Z axis and also in the equatorial plane. Sea level is at z =
     * zero.
     *
     * @param latitude        the latitude of the position.
     * @param longitude       the longitude of the position.
     * @param metersElevation the number of meters above or below mean sea level.
     *
     * @return The Cartesian point corresponding to the input position.
     */
    protected Vec4 geodeticToCartesian(Angle latitude, Angle longitude, double metersElevation)
    {
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        double cosLat = Math.cos(latitude.radians);
        double sinLat = Math.sin(latitude.radians);
        double cosLon = Math.cos(longitude.radians);
        double sinLon = Math.sin(longitude.radians);

        double rpm = // getRadius (in meters) of vertical in prime meridian
            this.equatorialRadius / Math.sqrt(1.0 - this.es * sinLat * sinLat);

        double x = (rpm + metersElevation) * cosLat * sinLon;
        double y = (rpm * (1.0 - this.es) + metersElevation) * sinLat;
        double z = (rpm + metersElevation) * cosLat * cosLon;

        return new Vec4(x, y, z);
    }
//
//    protected Position cartesianToGeodeticOriginal(Vec4 cart)
//    {
//        if (cart == null)
//        {
//            String message = Logging.getMessage("nullValue.PointIsNull");
//            Logging.logger().severe(message);
//            throw new IllegalArgumentException(message);
//        }
//
//        // according to
//        // H. Vermeille,
//        // Direct transformation from geocentric to geodetic ccordinates,
//        // Journal of Geodesy (2002) 76:451-454
//        double ra2 = 1 / (this.equatorialRadius * equatorialRadius);
//
//        double X = cart.z;
//        //noinspection SuspiciousNameCombination
//        double Y = cart.x;
//        double Z = cart.y;
//        double e2 = this.es;
//        double e4 = e2 * e2;
//
//        double XXpYY = X * X + Y * Y;
//        double sqrtXXpYY = Math.sqrt(XXpYY);
//        double p = XXpYY * ra2;
//        double q = Z * Z * (1 - e2) * ra2;
//        double r = 1 / 6.0 * (p + q - e4);
//        double s = e4 * p * q / (4 * r * r * r);
//        double t = Math.pow(1 + s + Math.sqrt(s * (2 + s)), 1 / 3.0);
//        double u = r * (1 + t + 1 / t);
//        double v = Math.sqrt(u * u + e4 * q);
//        double w = e2 * (u + v - q) / (2 * v);
//        double k = Math.sqrt(u + v + w * w) - w;
//        double D = k * sqrtXXpYY / (k + e2);
//        double lon = 2 * Math.atan2(Y, X + sqrtXXpYY);
//        double sqrtDDpZZ = Math.sqrt(D * D + Z * Z);
//        double lat = 2 * Math.atan2(Z, D + sqrtDDpZZ);
//        double elevation = (k + e2 - 1) * sqrtDDpZZ / k;
//
//        return Position.fromRadians(lat, lon, elevation);
//    }

    /**
     * Compute the geographic position to corresponds to a Cartesian point.
     *
     * @param cart Cartesian point to convert to geographic.
     *
     * @return The geographic position of {@code cart}.
     *
     * @see #geodeticToCartesian(gov.nasa.worldwind.geom.Angle, gov.nasa.worldwind.geom.Angle, double)
     */
    @SuppressWarnings({"SuspiciousNameCombination"})
    protected Position cartesianToGeodetic(Vec4 cart)
    {
        // Contributed by Nathan Kronenfeld. Integrated 1/24/2011. Brings this calculation in line with Vermeille's
        // most recent update.
        if (null == cart)
        {
            String message = Logging.getMessage("nullValue.PointIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // According to
        // H. Vermeille,
        // "An analytical method to transform geocentric into geodetic coordinates"
        // http://www.springerlink.com/content/3t6837t27t351227/fulltext.pdf
        // Journal of Geodesy, accepted 10/2010, not yet published
        double X = cart.z;
        double Y = cart.x;
        double Z = cart.y;
        double XXpYY = X * X + Y * Y;
        double sqrtXXpYY = Math.sqrt(XXpYY);

        double a = this.equatorialRadius;
        double ra2 = 1 / (a * a);
        double e2 = this.es;
        double e4 = e2 * e2;

        // Step 1
        double p = XXpYY * ra2;
        double q = Z * Z * (1 - e2) * ra2;
        double r = (p + q - e4) / 6;

        double h;
        double phi;

        double evoluteBorderTest = 8 * r * r * r + e4 * p * q;
        if (evoluteBorderTest > 0 || q != 0)
        {
            double u;

            if (evoluteBorderTest > 0)
            {
                // Step 2: general case
                double rad1 = Math.sqrt(evoluteBorderTest);
                double rad2 = Math.sqrt(e4 * p * q);

                // 10*e2 is my arbitrary decision of what Vermeille means by "near... the cusps of the evolute".
                if (evoluteBorderTest > 10 * e2)
                {
                    double rad3 = Math.cbrt((rad1 + rad2) * (rad1 + rad2));
                    u = r + 0.5 * rad3 + 2 * r * r / rad3;
                }
                else
                {
                    u = r + 0.5 * Math.cbrt((rad1 + rad2) * (rad1 + rad2)) + 0.5 * Math.cbrt(
                        (rad1 - rad2) * (rad1 - rad2));
                }
            }
            else
            {
                // Step 3: near evolute
                double rad1 = Math.sqrt(-evoluteBorderTest);
                double rad2 = Math.sqrt(-8 * r * r * r);
                double rad3 = Math.sqrt(e4 * p * q);
                double atan = 2 * Math.atan2(rad3, rad1 + rad2) / 3;

                u = -4 * r * Math.sin(atan) * Math.cos(Math.PI / 6 + atan);
            }

            double v = Math.sqrt(u * u + e4 * q);
            double w = e2 * (u + v - q) / (2 * v);
            double k = (u + v) / (Math.sqrt(w * w + u + v) + w);
            double D = k * sqrtXXpYY / (k + e2);
            double sqrtDDpZZ = Math.sqrt(D * D + Z * Z);

            h = (k + e2 - 1) * sqrtDDpZZ / k;
            phi = 2 * Math.atan2(Z, sqrtDDpZZ + D);
        }
        else
        {
            // Step 4: singular disk
            double rad1 = Math.sqrt(1 - e2);
            double rad2 = Math.sqrt(e2 - p);
            double e = Math.sqrt(e2);

            h = -a * rad1 * rad2 / e;
            phi = rad2 / (e * rad2 + rad1 * Math.sqrt(p));
        }

        // Compute lambda
        double lambda;
        double s2 = Math.sqrt(2);
        if ((s2 - 1) * Y < sqrtXXpYY + X)
        {
            // case 1 - -135deg < lambda < 135deg
            lambda = 2 * Math.atan2(Y, sqrtXXpYY + X);
        }
        else if (sqrtXXpYY + Y < (s2 + 1) * X)
        {
            // case 2 - -225deg < lambda < 45deg
            lambda = -Math.PI * 0.5 + 2 * Math.atan2(X, sqrtXXpYY - Y);
        }
        else
        {
            // if (sqrtXXpYY-Y<(s2=1)*X) {  // is the test, if needed, but it's not
            // case 3: - -45deg < lambda < 225deg
            lambda = Math.PI * 0.5 - 2 * Math.atan2(X, sqrtXXpYY + Y);
        }

        return Position.fromRadians(phi, lambda, h);
    }
//
//    /**
//     * Returns a cylinder that minimally surrounds the sector at a specified vertical exaggeration.
//     *
//     * @param verticalExaggeration the vertical exaggeration to apply to the globe's elevations when computing the
//     *                             cylinder.
//     * @param sector               the sector to return the bounding cylinder for.
//     *
//     * @return The minimal bounding cylinder in Cartesian coordinates.
//     *
//     * @throws IllegalArgumentException if sector is null
//     */
//    public Cylinder computeBoundingCylinder(double verticalExaggeration, Sector sector)
//    {
//        if (sector == null)
//        {
//            String msg = Logging.getMessage("nullValue.SectorIsNull");
//            Logging.logger().severe(msg);
//            throw new IllegalArgumentException(msg);
//        }
//
//        return Sector.computeBoundingCylinder(this, verticalExaggeration, sector);
//    }
//
//    /**
//     * Returns a cylinder that minimally surrounds the specified minimum and maximum elevations in the sector at a
//     * specified vertical exaggeration.
//     *
//     * @param verticalExaggeration the vertical exaggeration to apply to the minimum and maximum elevations when
//     *                             computing the cylinder.
//     * @param sector               the sector to return the bounding cylinder for.
//     * @param minElevation         the minimum elevation of the bounding cylinder.
//     * @param maxElevation         the maximum elevation of the bounding cylinder.
//     *
//     * @return The minimal bounding cylinder in Cartesian coordinates.
//     *
//     * @throws IllegalArgumentException if sector is null
//     */
//    public Cylinder computeBoundingCylinder(double verticalExaggeration, Sector sector,
//        double minElevation, double maxElevation)
//    {
//        if (sector == null)
//        {
//            String msg = Logging.getMessage("nullValue.SectorIsNull");
//            Logging.logger().severe(msg);
//            throw new IllegalArgumentException(msg);
//        }
//
//        // Compute the exaggerated minimum and maximum heights.
//        double minHeight = minElevation * verticalExaggeration;
//        double maxHeight = maxElevation * verticalExaggeration;
//
//        if (minHeight == maxHeight)
//            maxHeight = minHeight + 1; // ensure the top and bottom of the cylinder won't be coincident
//
//        // If the sector spans both poles in latitude, or spans greater than 180 degrees in longitude, we cannot use the
//        // sector's Cartesian quadrilateral to compute a bounding cylinde. This is because the quadrilateral is either
//        // smaller than the geometry defined by the sector (when deltaLon >= 180), or the quadrilateral degenerates to
//        // two points (when deltaLat >= 180). So we compute a bounging cylinder that spans the equator and covers the
//        // sector's latitude range. In some cases this cylinder may be too large, but we're typically not interested
//        // in culling these cylinders since the sector will span most of the globe.
//        if (sector.getDeltaLatDegrees() >= 180d || sector.getDeltaLonDegrees() >= 180d)
//        {
//            return this.computeBoundsFromSectorLatitudeRange(sector, minHeight, maxHeight);
//        }
//        // Otherwise, create a standard bounding cylinder that minimally surrounds the specified sector and elevations.
//        else
//        {
//            return this.computeBoundsFromSectorQuadrilateral(sector, minHeight, maxHeight);
//        }
//    }
//
//    public Cylinder computeBoundingCylinderNew(double verticalExaggeration, Sector sector, double minElevation,
//        double maxElevation)
//    {
//        if (sector == null)
//        {
//            String msg = Logging.getMessage("nullValue.SectorIsNull");
//            Logging.logger().severe(msg);
//            throw new IllegalArgumentException(msg);
//        }
//
//        // Compute the exaggerated minimum and maximum heights.
//        double minHeight = minElevation * verticalExaggeration;
//        double maxHeight = maxElevation * verticalExaggeration;
//
//        if (minHeight == maxHeight)
//            maxHeight = minHeight + 1; // ensure the top and bottom of the cylinder won't be coincident
//
//        List points = new ArrayList();
//        for (LatLon ll : sector)
//        {
//            points.add(this.computePointFromPosition(ll, minHeight));
//            points.add(this.computePointFromPosition(ll, maxHeight));
//        }
//        if (sector.getDeltaLatDegrees() >= 180d || sector.getDeltaLonDegrees() >= 180d)
//            points.add(this.computePointFromPosition(sector.getCentroid(), maxHeight));
//
//        try
//        {
//            return Cylinder.compute(points);
//        }
//        catch (Exception e)
//        {
//            return new Cylinder(points.get(0), points.get(0).add3(Vec4.UNIT_Y), 1);
//        }
//    }
//
//    public static void main(String[] args)
//    {
//        EllipsoidalGlobe globe = new Earth();
//        Sector sector = Sector.fromDegrees(0, 1, 0, 1);
//
//        int n = 1000000;
//
//        long start = System.currentTimeMillis();
//        for (int i = 0; i < n; i++)
//        {
//            Extent cyl1 = globe.computeBoundingVolume(1d, sector, 0, 100);
//        }
//        System.out.println(System.currentTimeMillis() - start);
//
//        start = System.currentTimeMillis();
//        for (int i = 0; i < n; i++)
//        {
//            Cylinder cyl2 = globe.computeBoundingCylinder2(1d, sector, 0, 100);
//        }
//        System.out.println(System.currentTimeMillis() - start);
//    }
//
//    public Cylinder computeBoundingCylinder(double verticalExaggeration, Iterable positions)
//    {
//        List points = new ArrayList();
//
//        for (Position pos : positions)
//        {
//            if (pos != null)
//                points.add(this.computePointFromPosition(pos, pos.elevation * verticalExaggeration));
//        }
//
//        return Cylinder.compute(points);
//    }

    public SectorGeometryList tessellate(DrawContext dc)
    {
        if (this.tessellator == null)
        {
            this.tessellator = (Tessellator) WorldWind.createConfigurationComponent(AVKey.TESSELLATOR_CLASS_NAME);

            if (this.tessellator == null)
            {
                String msg = Logging.getMessage("Tessellator.TessellatorUnavailable");
                Logging.logger().severe(msg);
                throw new IllegalStateException(msg);
            }
        }

        return this.tessellator.tessellate(dc);
    }

    /**
     * Determines whether a point is above a given elevation
     *
     * @param point     the Vec4 point to test.
     * @param elevation the elevation to test for.
     *
     * @return true if the given point is above the given elevation.
     */
    public boolean isPointAboveElevation(Vec4 point, double elevation)
    {
        //noinspection SimplifiableIfStatement
        if (point == null)
            return false;

        return (point.x() * point.x()) / ((this.equatorialRadius + elevation) * (this.equatorialRadius + elevation))
            + (point.y() * point.y()) / ((this.polarRadius + elevation) * (this.polarRadius + elevation))
            + (point.z() * point.z()) / ((this.equatorialRadius + elevation) * (this.equatorialRadius + elevation))
            - 1 > 0;
    }

    /**
     * Construct an elevation model given a key for a configuration source and the source's default value.
     *
     * @param key          the key identifying the configuration property in {@link Configuration}.
     * @param defaultValue the default value of the property to use if it's not found in {@link Configuration}.
     *
     * @return a new elevation model configured according to the configuration source.
     */
    public static ElevationModel makeElevationModel(String key, String defaultValue)
    {
        if (key == null)
        {
            String msg = Logging.getMessage("nullValue.KeyIsNull");
            throw new IllegalArgumentException(msg);
        }

        Object configSource = Configuration.getStringValue(key, defaultValue);
        return (ElevationModel) BasicFactory.create(AVKey.ELEVATION_MODEL_FACTORY, configSource);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy