/*
Copyright (C) 2001, 2008 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.render.DrawContext;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.util.Logging;
/**
* Flat globe.
* See Flat World Notes inline comments for difference from EllipsoidalGlobe.
*
* @author Patrick Murris - based on EllipsoidalGlobe
* @version $Id: FlatGlobe.java 5229 2008-04-30 18:16:29Z patrickmurris $
*/
public class FlatGlobe extends WWObjectImpl implements Globe
{
public final static String PROJECTION_LAT_LON = "gov.nasa.worldwind.globes.projectionLatLon";
public final static String PROJECTION_MERCATOR = "gov.nasa.worldwind.globes.projectionMercator";
public final static String PROJECTION_SINUSOIDAL = "gov.nasa.worldwind.globes.projectionSinusoidal";
public final static String PROJECTION_MODIFIED_SINUSOIDAL =
"gov.nasa.worldwind.globes.projectionModifiedSinusoidal";
private final double equatorialRadius;
private final double polarRadius;
private final double es;
private final Vec4 center;
private Tessellator tessellator;
private final ElevationModel elevationModel;
private String projection = PROJECTION_MERCATOR;
public FlatGlobe(double equatorialRadius, double polarRadius, double es, ElevationModel em)
{
if (em == null)
{
String msg = Logging.getMessage("nullValue.ElevationModelIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
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);
}
private static class StateKey
{
private final Tessellator tessellator;
private final String projection;
public StateKey(FlatGlobe globe)
{
this.tessellator = globe.tessellator;
this.projection = globe.projection;
}
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StateKey stateKey = (StateKey) o;
if (projection != null ? !projection.equals(stateKey.projection) : stateKey.projection != null)
return false;
//noinspection RedundantIfStatement
if (tessellator != null ? !tessellator.equals(stateKey.tessellator) : stateKey.tessellator != null)
return false;
return true;
}
public int hashCode()
{
int result;
result = (tessellator != null ? tessellator.hashCode() : 0);
result = 31 * result + (projection != null ? projection.hashCode() : 0);
return result;
}
}
public Object getStateKey()
{
return new StateKey(this);
}
public Tessellator getTessellator()
{
return tessellator;
}
public void setTessellator(Tessellator tessellator)
{
this.tessellator = tessellator;
}
// TODO: return the flat globe plane extent radius here?
public final double getRadius()
{
return this.equatorialRadius;
}
public final double getEquatorialRadius()
{
return this.equatorialRadius;
}
public final double getPolarRadius()
{
return this.polarRadius;
}
public double getMaximumRadius()
{
return this.equatorialRadius;
}
// TODO: Find a more accurate workaround then getMaximumRadius()
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 getMaximumRadius();
//return this.computePointFromPosition(latitude, longitude, 0d).getLength3();
}
// TODO: Find a more accurate workaround then getMaximumRadius()
public double getRadiusAt(LatLon latLon)
{
if (latLon == null)
{
String msg = Logging.getMessage("nullValue.LatLonIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return getMaximumRadius();
//return this.computePointFromPosition(latLon.getLatitude(), latLon.getLongitude(), 0d).getLength3();
}
public double getEccentricitySquared()
{
return this.es;
}
public final double getDiameter()
{
return this.equatorialRadius * 2;
}
public final Vec4 getCenter()
{
return this.center;
}
public double getMaxElevation()
{
return this.elevationModel.getMaxElevation();
}
public double getMinElevation()
{
return this.elevationModel.getMinElevation();
}
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.getMinAndMaxElevations(sector);
}
public final Extent getExtent()
{
return this;
}
public void setProjection(String projection)
{
if (projection == null)
{
String message = Logging.getMessage("nullValue.StringIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (this.projection.equals(projection))
return;
this.projection = projection;
this.tessellator = null;
}
public String getProjection()
{
return this.projection;
}
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(this);
}
public Intersection[] intersect(Line line)
{
return this.internalIntersect(line, this.equatorialRadius);
}
public Intersection[] intersect(Line line, double altitude)
{
return this.internalIntersect(line, this.equatorialRadius + altitude);
}
// Flat World Note: plane/line intersection point (OK)
// Flat World Note: extract altitude from equRadius by subtracting this.equatorialRadius (OK)
private Intersection[] internalIntersect(Line line, double equRadius)
{
if (line == null)
{
String message = Logging.getMessage("nullValue.LineIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// Intersection with world plane
Plane plane = new Plane(0, 0, 1, -(equRadius - this.equatorialRadius)); // Flat globe plane
Vec4 p = plane.intersect(line);
if (p == null)
return null;
// Check if we are in the world boundaries
Position pos = this.computePositionFromPoint(p);
if (pos == null)
return null;
if (pos.getLatitude().degrees < -90 || pos.getLatitude().degrees > 90 ||
pos.getLongitude().degrees < -180 || pos.getLongitude().degrees > 180)
return null;
return new Intersection[] {new Intersection(p, false)};
}
// Flat World Note: plane/line intersection test (OK)
public boolean intersects(Line line)
{
if (line == null)
{
String msg = Logging.getMessage("nullValue.LineIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return this.intersect(line) != null;
}
// Flat World Note: plane/plane intersection test (OK)
public boolean intersects(Plane plane)
{
if (plane == null)
{
String msg = Logging.getMessage("nullValue.PlaneIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return !plane.getNormal().equals(Vec4.UNIT_Z);
}
// Flat World Note: return constant (OK)
public Vec4 computeSurfaceNormalAtPoint(Vec4 p)
{
if (p == null)
{
String msg = Logging.getMessage("nullValue.PointIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return Vec4.UNIT_Z;
}
public final ElevationModel getElevationModel()
{
return this.elevationModel;
}
public Double getBestElevation(Angle latitude, Angle longitude)
{
return this.elevationModel.getBestElevation(latitude, longitude);
}
// Flat World Note: return zero if outside the lat/lon normal boundaries (OK)
public final Double getElevationAtResolution(Angle latitude, Angle longitude, double resolution)
{
if (latitude == null || longitude == null)
{
String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (latitude.degrees < -90 || latitude.degrees > 90 || longitude.degrees < -180 || longitude.degrees > 180)
return 0d;
int target = this.elevationModel.getTargetResolution(this, resolution);
return this.elevationModel.getElevationAtResolution(latitude, longitude, target);
}
// Flat World Note: return zero if outside the lat/lon normal boundaries (OK)
public final 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 (latitude.degrees < -90 || latitude.degrees > 90 || longitude.degrees < -180 || longitude.degrees > 180)
return 0d;
return this.elevationModel != null ? this.elevationModel.getElevation(latitude, longitude) : 0;
}
public final 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 final 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 final 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);
}
public final 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());
}
// The code below maps latitude / longitude position to a flat world Cartesian coordinates.
// The world plane is located at the origin and has UNIT-Z as normal.
// The Y axis points to the north pole. The Z axis points up. The X axis completes a right-handed
// coordinate system, and points east. Latitude and longitude zero are at the origine on y and x respectively.
// Sea level is at z = zero.
// Flat World Note: Implement flat projections (OK)
private 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);
}
Vec4 cart = null;
if (this.projection.equals(PROJECTION_LAT_LON))
{
// Lat/Lon projection - plate carree
cart = new Vec4(this.equatorialRadius * longitude.radians,
this.equatorialRadius * latitude.radians,
metersElevation);
}
else if (this.projection.equals(PROJECTION_MERCATOR))
{
// Mercator projection
if (latitude.degrees > 75) latitude = Angle.fromDegrees(75);
if (latitude.degrees < -75) latitude = Angle.fromDegrees(-75);
cart = new Vec4(this.equatorialRadius * longitude.radians,
this.equatorialRadius * Math.log(Math.tan(Math.PI / 4 + latitude.radians / 2)),
metersElevation);
}
else if (this.projection.equals(PROJECTION_SINUSOIDAL))
{
// Sinusoidal projection
cart = new Vec4(this.equatorialRadius * longitude.radians * latitude.cos(),
this.equatorialRadius * latitude.radians,
metersElevation);
}
else if (this.projection.equals(PROJECTION_MODIFIED_SINUSOIDAL))
{
// Modified Sinusoidal projection
cart = new Vec4(this.equatorialRadius * longitude.radians * Math.pow(latitude.cos(), .3),
this.equatorialRadius * latitude.radians,
metersElevation);
}
return cart;
}
// Flat World Note: Implement flat projections (OK)
private Position cartesianToGeodetic(Vec4 cart)
{
if (cart == null)
{
String message = Logging.getMessage("nullValue.PointIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Position pos = null;
if (this.projection.equals(PROJECTION_LAT_LON))
{
// Lat/Lon projection - plate carree
pos = Position.fromRadians(
cart.y / this.equatorialRadius,
cart.x / this.equatorialRadius,
cart.z);
}
else if (this.projection.equals(PROJECTION_MERCATOR))
{
// Mercator projection
pos = Position.fromRadians(
Math.atan(Math.sinh(cart.y / this.equatorialRadius)),
cart.x / this.equatorialRadius,
cart.z);
}
else if (this.projection.equals(PROJECTION_SINUSOIDAL))
{
// Sinusoidal projection
pos = Position.fromRadians(
cart.y / this.equatorialRadius,
cart.x / this.equatorialRadius / Angle.fromRadians(cart.y / this.equatorialRadius).cos(),
cart.z);
}
else if (this.projection.equals(PROJECTION_MODIFIED_SINUSOIDAL))
{
// Modified Sinusoidal projection
pos = Position.fromRadians(
cart.y / this.equatorialRadius,
cart.x / this.equatorialRadius / Math.pow(Angle.fromRadians(cart.y / this.equatorialRadius).cos(), .3),
cart.z);
}
return pos;
}
/**
* 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 <code>globe</code> or <code>sector</code> is null
*/
// Flat World Note: Adapt to flat world tiles (OK)
public Cylinder computeBoundingCylinder(double verticalExaggeration, Sector sector)
{
if (sector == null)
{
String msg = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
// Compute the center points of the bounding cylinder's top and bottom planes.
LatLon center = sector.getCentroid();
double[] minAndMaxElevations = this.getMinAndMaxElevations(sector);
double minHeight = minAndMaxElevations[0] * verticalExaggeration;
double maxHeight = minAndMaxElevations[1] * verticalExaggeration;
Vec4 centroidTop = this.computePointFromPosition(center.getLatitude(), center.getLongitude(), maxHeight);
Vec4 centroidBot = this.computePointFromPosition(center.getLatitude(), center.getLongitude(), minHeight);
/* // Compute radius of circumscribing circle around general quadrilateral.
Vec4 northwest = this.computePointFromPosition(sector.getMaxLatitude(), sector.getMinLongitude(), maxHeight);
Vec4 southeast = this.computePointFromPosition(sector.getMinLatitude(), sector.getMaxLongitude(), maxHeight);
Vec4 southwest = this.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), maxHeight);
Vec4 northeast = this.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), maxHeight);
double a = southwest.distanceTo3(southeast);
double b = southeast.distanceTo3(northeast);
double c = northeast.distanceTo3(northwest);
double d = northwest.distanceTo3(southwest);
double s = 0.5 * (a + b + c + d);
double area = Math.sqrt((s - a) * (s - b) * (s - c) * (s - d));
double radius = Math.sqrt((a * b + c * d) * (a * d + b * c) * (a * c + b * d)) / (4d * area);
*/
// Compute radius of circumscribing circle using largest distance from center to corners.
Vec4 northwest = this.computePointFromPosition(sector.getMaxLatitude(), sector.getMinLongitude(), maxHeight);
Vec4 southeast = this.computePointFromPosition(sector.getMinLatitude(), sector.getMaxLongitude(), maxHeight);
Vec4 southwest = this.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), maxHeight);
Vec4 northeast = this.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), maxHeight);
double a = southwest.distanceTo3(centroidBot);
double b = southeast.distanceTo3(centroidBot);
double c = northeast.distanceTo3(centroidBot);
double d = northwest.distanceTo3(centroidBot);
double radius = Math.max(Math.max(a, b), Math.max(c, d));
return new Cylinder(centroidBot, centroidTop, radius);
}
public SectorGeometryList tessellate(DrawContext dc)
{
if (this.tessellator == null)
{
this.tessellator = (Tessellator) WorldWind.createConfigurationComponent(AVKey.TESSELLATOR_CLASS_NAME);
}
return this.tessellator.tessellate(dc);
}
}