2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
7 package gov
.nasa
.worldwind
.globes
;
9 import gov
.nasa
.worldwind
.*;
10 import gov
.nasa
.worldwind
.avlist
.AVKey
;
11 import gov
.nasa
.worldwind
.render
.DrawContext
;
12 import gov
.nasa
.worldwind
.geom
.*;
13 import gov
.nasa
.worldwind
.util
.Logging
;
15 /** Experimental flat globe
16 * See TODOs for difference from EllipsoidalGlobe
17 * @author Patrick Murris - base on EllipsoidalGlobe
20 public class FlatGlobe
extends WWObjectImpl
implements Globe
22 public final static String PROJECTION_LAT_LON
= "gov.nasa.worldwind.globes.projectionLatLon";
23 public final static String PROJECTION_SINUSOIDAL
= "gov.nasa.worldwind.globes.projectionSinusoidal";
24 public final static String PROJECTION_MERCATOR
= "gov.nasa.worldwind.globes.projectionMercator";
25 public final static String PROJECTION_TEST
= "gov.nasa.worldwind.globes.projectionTest";
27 private final double equatorialRadius
;
28 private final double polarRadius
;
29 private final double es
;
30 private final Vec4 center
;
31 private Tessellator tessellator
;
33 private final ElevationModel elevationModel
;
34 private String projection
= PROJECTION_LAT_LON
;
36 public FlatGlobe(double equatorialRadius
, double polarRadius
, double es
, ElevationModel em
)
40 String msg
= Logging
.getMessage("nullValue.ElevationModelIsNull");
41 Logging
.logger().severe(msg
);
42 throw new IllegalArgumentException(msg
);
45 this.equatorialRadius
= equatorialRadius
;
46 this.polarRadius
= polarRadius
;
47 this.es
= es
; // assume it's consistent with the two radii
48 this.center
= Vec4
.ZERO
;
49 this.elevationModel
= em
;
52 public Tessellator
getTessellator()
57 public void setTessellator(Tessellator tessellator
)
59 this.tessellator
= tessellator
;
62 public final double getRadius()
64 return this.equatorialRadius
;
67 public final double getEquatorialRadius()
69 return this.equatorialRadius
;
72 public final double getPolarRadius()
74 return this.polarRadius
;
77 public double getMaximumRadius()
79 return this.equatorialRadius
;
82 // TODO: Find a more accurate workaround then getRadius()
83 public double getRadiusAt(Angle latitude
, Angle longitude
)
85 if (latitude
== null || longitude
== null)
87 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
88 Logging
.logger().severe(msg
);
89 throw new IllegalArgumentException(msg
);
92 //return this.computePointFromPosition(latitude, longitude, 0d).getLength3();
95 // TODO: Find a more accurate workaround then getRadius()
96 public double getRadiusAt(LatLon latLon
)
100 String msg
= Logging
.getMessage("nullValue.LatLonIsNull");
101 Logging
.logger().severe(msg
);
102 throw new IllegalArgumentException(msg
);
105 //return this.computePointFromPosition(latLon.getLatitude(), latLon.getLongitude(), 0d).getLength3();
108 public double getEccentricitySquared()
113 public final double getDiameter()
115 return this.equatorialRadius
* 2;
118 public final Vec4
getCenter()
123 public double getMaxElevation()
125 return this.elevationModel
.getMaxElevation();
128 public double getMinElevation()
130 return this.elevationModel
.getMinElevation();
133 public double getMaxElevation(Sector sector
)
135 return this.elevationModel
.getMaxElevation(sector
);
138 public double getMinElevation(Sector sector
)
140 return this.elevationModel
.getMinElevation(sector
);
143 public final Extent
getExtent()
148 public void setProjection(String projection
)
150 this.projection
= projection
;
153 public String
getProjection()
155 return this.projection
;
158 public boolean intersects(Frustum frustum
)
162 String message
= Logging
.getMessage("nullValue.FrustumIsNull");
163 Logging
.logger().severe(message
);
164 throw new IllegalArgumentException(message
);
167 return frustum
.intersects(this);
170 public Intersection
[] intersect(Line line
)
172 return this.intersect(line
, this.equatorialRadius
, this.polarRadius
);
175 public Intersection
[] intersect(Line line
, double altitude
)
177 return this.intersect(line
, this.equatorialRadius
+ altitude
, this.polarRadius
+ altitude
);
180 // TODO: plane/line intersection point (OK)
181 // TODO: extract altitude from equRadius by subtracting this.equatorialRadius (OK)
182 private Intersection
[] intersect(Line line
, double equRadius
, double polRadius
)
186 String message
= Logging
.getMessage("nullValue.LineIsNull");
187 Logging
.logger().severe(message
);
188 throw new IllegalArgumentException(message
);
190 // Intersection with world plane
191 Plane plane
= new Plane(0, 0, 1, equRadius
- this.equatorialRadius
); // Flat globe plane
192 Vec4 p
= plane
.intersect(line
);
195 // Check if we are in the world boundaries
196 Position pos
= this.computePositionFromPoint(p
);
199 if(pos
.getLatitude().degrees
< -90 || pos
.getLatitude().degrees
> 90 ||
200 pos
.getLongitude().degrees
< -180 || pos
.getLongitude().degrees
> 180)
203 return new Intersection
[] {new Intersection(p
, false)};
206 static private double discriminant(double a
, double b
, double c
)
208 return b
* b
- 4 * a
* c
;
211 // TODO: plane/line intersection test (OK)
212 public boolean intersects(Line line
)
216 String msg
= Logging
.getMessage("nullValue.LineIsNull");
217 Logging
.logger().severe(msg
);
218 throw new IllegalArgumentException(msg
);
221 return this.intersect(line
) != null;
224 // TODO: plane/plane intersection test (OK)
225 public boolean intersects(Plane plane
)
229 String msg
= Logging
.getMessage("nullValue.PlaneIsNull");
230 Logging
.logger().severe(msg
);
231 throw new IllegalArgumentException(msg
);
234 return !plane
.getNormal().equals(Vec4
.UNIT_Z
);
238 // TODO: return constant (OK)
239 public Vec4
computeSurfaceNormalAtPoint(Vec4 p
)
243 String msg
= Logging
.getMessage("nullValue.PointIsNull");
244 Logging
.logger().severe(msg
);
245 throw new IllegalArgumentException(msg
);
248 return new Vec4(0, 0, 1);
251 public final ElevationModel
getElevationModel()
253 return this.elevationModel
;
256 public Double
getBestElevation(Angle latitude
, Angle longitude
)
258 return this.elevationModel
.getBestElevation(latitude
, longitude
);
261 // TODO: return zero if outside the lat/lon normal boundaries (OK)
262 public final Double
getElevationAtResolution(Angle latitude
, Angle longitude
, double resolution
)
264 if (latitude
== null || longitude
== null)
266 String message
= Logging
.getMessage("nullValue.LatitudeOrLongitudeIsNull");
267 Logging
.logger().severe(message
);
268 throw new IllegalArgumentException(message
);
270 if(latitude
.degrees
< -90 || latitude
.degrees
> 90 || longitude
.degrees
< -180 || longitude
.degrees
> 180)
273 int target
= this.elevationModel
.getTargetResolution(this, resolution
);
274 return this.elevationModel
.getElevationAtResolution(latitude
, longitude
, target
);
277 // TODO: return zero if outside the lat/lon normal boundaries (OK)
278 public final double getElevation(Angle latitude
, Angle longitude
)
280 if (latitude
== null || longitude
== null)
282 String message
= Logging
.getMessage("nullValue.LatitudeOrLongitudeIsNull");
283 Logging
.logger().severe(message
);
284 throw new IllegalArgumentException(message
);
286 if(latitude
.degrees
< -90 || latitude
.degrees
> 90 || longitude
.degrees
< -180 || longitude
.degrees
> 180)
288 return this.elevationModel
!= null ?
this.elevationModel
.getElevation(latitude
, longitude
) : 0;
291 public final Vec4
computePointFromPosition(Position position
)
293 if (position
== null)
295 String message
= Logging
.getMessage("nullValue.PositionIsNull");
296 Logging
.logger().severe(message
);
297 throw new IllegalArgumentException(message
);
300 return this.geodeticToCartesian(position
.getLatitude(), position
.getLongitude(), position
.getElevation());
303 public final Vec4
computePointFromPosition(Angle latitude
, Angle longitude
, double metersElevation
)
305 if (latitude
== null || longitude
== null)
307 String message
= Logging
.getMessage("nullValue.LatitudeOrLongitudeIsNull");
308 Logging
.logger().severe(message
);
309 throw new IllegalArgumentException(message
);
312 return this.geodeticToCartesian(latitude
, longitude
, metersElevation
);
315 public final Position
computePositionFromPoint(Vec4 point
)
319 String message
= Logging
.getMessage("nullValue.PointIsNull");
320 Logging
.logger().severe(message
);
321 throw new IllegalArgumentException(message
);
324 return this.cartesianToGeodetic(point
);
327 public final Position
getIntersectionPosition(Line line
)
331 String msg
= Logging
.getMessage("nullValue.LineIsNull");
332 Logging
.logger().severe(msg
);
333 throw new IllegalArgumentException(msg
);
336 Intersection
[] intersections
= this.intersect(line
);
337 if (intersections
== null)
340 return this.computePositionFromPoint(intersections
[0].getIntersectionPoint());
344 // The code below maps latitude / longitude position to globe-centered Cartesian coordinates.
345 // The Y axis points to the north pole. The Z axis points to the intersection of the prime
346 // meridian and the equator, in the equatorial plane. The X axis completes a right-handed
347 // coordinate system, and is 90 degrees east of the Z axis and also in the equatorial plane.
349 private Vec4 geodeticToCartesianEl(Angle latitude, Angle longitude, double metersElevation)
351 if (latitude == null || longitude == null)
353 String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
354 Logging.logger().severe(message);
355 throw new IllegalArgumentException(message);
358 double cosLat = latitude.cos();
359 double sinLat = latitude.sin();
361 double rpm = // getRadius (in meters) of vertical in prime meridian
362 this.equatorialRadius / Math.sqrt(1.0 - this.es * sinLat * sinLat);
364 double x = (rpm + metersElevation) * cosLat * longitude.sin();
365 double y = (rpm * (1.0 - this.es) + metersElevation) * sinLat;
366 double z = (rpm + metersElevation) * cosLat * longitude.cos();
368 Vec4 cart = new Vec4(x, y, z);
369 //System.out.println("geodeticToCartesian: " + latitude + ", " + longitude + ", " + metersElevation + " / " + cart);
373 private Position cartesianToGeodeticEl(Vec4 cart)
377 String message = Logging.getMessage("nullValue.PointIsNull");
378 Logging.logger().severe(message);
379 throw new IllegalArgumentException(message);
384 // Direct transformation from geocentric to geodetic ccordinates,
385 // Journal of Geodesy (2002) 76:451-454
386 double ra2 = 1 / (this.equatorialRadius * equatorialRadius);
389 //noinspection SuspiciousNameCombination
395 double XXpYY = X * X + Y * Y;
396 double sqrtXXpYY = Math.sqrt(XXpYY);
397 double p = XXpYY * ra2;
398 double q = Z * Z * (1 - e2) * ra2;
399 double r = 1 / 6.0 * (p + q - e4);
400 double s = e4 * p * q / (4 * r * r * r);
401 double t = Math.pow(1 + s + Math.sqrt(s * (2 + s)), 1 / 3.0);
402 double u = r * (1 + t + 1 / t);
403 double v = Math.sqrt(u * u + e4 * q);
404 double w = e2 * (u + v - q) / (2 * v);
405 double k = Math.sqrt(u + v + w * w) - w;
406 double D = k * sqrtXXpYY / (k + e2);
407 double lon = 2 * Math.atan2(Y, X + sqrtXXpYY);
408 double sqrtDDpZZ = Math.sqrt(D * D + Z * Z);
409 double lat = 2 * Math.atan2(Z, D + sqrtDDpZZ);
410 double elevation = (k + e2 - 1) * sqrtDDpZZ / k;
412 Position pos = Position.fromRadians(lat, lon, elevation);
413 //System.out.println("cartesianToGeodetic: " + cart + " / " + pos);
417 // The code below maps latitude / longitude position to globe-centered Cartesian coordinates.
418 // The Y axis points to the north pole. The Z axis points to the intersection of the prime
419 // meridian and the equator, in the equatorial plane. The X axis completes a right-handed
420 // coordinate system, and is 90 degrees east of the Z axis and also in the equatorial plane.
421 // TODO: Implement flat projections (OK)
422 private Vec4
geodeticToCartesian(Angle latitude
, Angle longitude
, double metersElevation
)
424 if (latitude
== null || longitude
== null)
426 String message
= Logging
.getMessage("nullValue.LatitudeOrLongitudeIsNull");
427 Logging
.logger().severe(message
);
428 throw new IllegalArgumentException(message
);
432 if(this.projection
.compareToIgnoreCase(PROJECTION_LAT_LON
) == 0)
434 // Lat/Lon projection - plate carré
435 cart
= new Vec4(this.equatorialRadius
* longitude
.radians
,
436 this.equatorialRadius
* latitude
.radians
,
439 else if(this.projection
.compareToIgnoreCase(PROJECTION_MERCATOR
) == 0)
441 // Mercator projection
442 if(latitude
.degrees
> 75) latitude
= Angle
.fromDegrees(75);
443 if(latitude
.degrees
< -75) latitude
= Angle
.fromDegrees(-75);
444 cart
= new Vec4(this.equatorialRadius
* longitude
.radians
,
445 this.equatorialRadius
* Math
.log(Math
.tan(Math
.PI
/ 4 + latitude
.radians
/ 2)),
448 else if(this.projection
.compareToIgnoreCase(PROJECTION_SINUSOIDAL
) == 0)
450 // Sinusoidal projection
451 cart
= new Vec4(this.equatorialRadius
* longitude
.radians
* latitude
.cos(),
452 this.equatorialRadius
* latitude
.radians
,
455 else if(this.projection
.compareToIgnoreCase(PROJECTION_TEST
) == 0)
458 double r
= Math
.sqrt(latitude
.radians
* latitude
.radians
+ longitude
.radians
* longitude
.radians
) * this.equatorialRadius
;
459 cart
= new Vec4(this.equatorialRadius
* longitude
.radians
* Math
.pow(latitude
.cos(), .3),
460 this.equatorialRadius
* latitude
.radians
,
463 //System.out.println("geodeticToCartesian: " + latitude + ", " + longitude + ", " + metersElevation + " / " + cart);
464 //System.out.println(this.projection);
468 // TODO: Implement flat projections (OK)
469 private Position
cartesianToGeodetic(Vec4 cart
)
473 String message
= Logging
.getMessage("nullValue.PointIsNull");
474 Logging
.logger().severe(message
);
475 throw new IllegalArgumentException(message
);
479 if(this.projection
.compareToIgnoreCase(PROJECTION_LAT_LON
) == 0)
481 // Lat/Lon projection - plate carré
482 pos
= Position
.fromRadians(
483 cart
.y
/ this.equatorialRadius
,
484 cart
.x
/ this.equatorialRadius
,
487 else if(this.projection
.compareToIgnoreCase(PROJECTION_MERCATOR
) == 0)
489 // Mercator projection
490 pos
= Position
.fromRadians(
491 Math
.atan(Math
.sinh(cart
.y
/ this.equatorialRadius
)) ,
492 cart
.x
/ this.equatorialRadius
,
495 else if(this.projection
.compareToIgnoreCase(PROJECTION_SINUSOIDAL
) == 0)
497 // Sinusoidal projection
498 pos
= Position
.fromRadians(
499 cart
.y
/ this.equatorialRadius
,
500 cart
.x
/ this.equatorialRadius
/ Angle
.fromRadians(cart
.y
/ this.equatorialRadius
).cos(),
503 else if(this.projection
.compareToIgnoreCase(PROJECTION_TEST
) == 0)
506 pos
= Position
.fromRadians(
507 cart
.y
/ this.equatorialRadius
,
508 cart
.x
/ this.equatorialRadius
/ Math
.pow(Angle
.fromRadians(cart
.y
/ this.equatorialRadius
).cos(), .3),
511 //System.out.println("cartesianToGeodetic: " + cart + " / " + pos);
517 * Returns a cylinder that minimally surrounds the sector at a specified vertical exaggeration.
519 * @param verticalExaggeration the vertical exaggeration to apply to the globe's elevations when computing the
521 * @param sector the sector to return the bounding cylinder for.
522 * @return The minimal bounding cylinder in Cartesian coordinates.
523 * @throws IllegalArgumentException if <code>globe</code> or <code>sector</code> is null
525 // TODO: Adapt to flat world tiles (OK)
526 public Cylinder
computeBoundingCylinder(double verticalExaggeration
, Sector sector
)
530 String msg
= Logging
.getMessage("nullValue.SectorIsNull");
531 Logging
.logger().severe(msg
);
532 throw new IllegalArgumentException(msg
);
535 // Compute the center points of the bounding cylinder's top and bottom planes.
536 LatLon center
= sector
.getCentroid();
537 double maxHeight
= this.getMaxElevation(sector
) * verticalExaggeration
;
538 double minHeight
= 0; //globe.getMinElevation(sector) * verticalExaggeration;
539 Vec4 centroidTop
= this.computePointFromPosition(center
.getLatitude(), center
.getLongitude(), maxHeight
);
540 Vec4 centroidBot
= this.computePointFromPosition(center
.getLatitude(), center
.getLongitude(), minHeight
);
542 // Compute radius of circumscribing circle around general quadrilateral.
543 Vec4 northwest
= this.computePointFromPosition(sector
.getMaxLatitude(), sector
.getMinLongitude(), maxHeight
);
544 Vec4 southeast
= this.computePointFromPosition(sector
.getMinLatitude(), sector
.getMaxLongitude(), maxHeight
);
545 Vec4 southwest
= this.computePointFromPosition(sector
.getMinLatitude(), sector
.getMinLongitude(), maxHeight
);
546 Vec4 northeast
= this.computePointFromPosition(sector
.getMaxLatitude(), sector
.getMaxLongitude(), maxHeight
);
547 double a
= southwest
.distanceTo3(southeast
);
548 double b
= southeast
.distanceTo3(northeast
);
549 double c
= northeast
.distanceTo3(northwest
);
550 double d
= northwest
.distanceTo3(southwest
);
551 double s
= 0.5 * (a
+ b
+ c
+ d
);
552 double area
= Math
.sqrt((s
- a
) * (s
- b
) * (s
- c
) * (s
- d
));
553 double radius
= Math
.sqrt((a
* b
+ c
* d
) * (a
* d
+ b
* c
) * (a
* c
+ b
* d
)) / (4d
* area
);
555 return new Cylinder(centroidBot
, centroidTop
, radius
);
558 public boolean equals(Object o
)
560 if (this == o
) return true;
561 if (o
== null || getClass() != o
.getClass()) return false;
563 FlatGlobe flatGlobe
= (FlatGlobe
) o
;
565 if (Double
.compare(flatGlobe
.equatorialRadius
, equatorialRadius
) != 0) return false;
566 if (Double
.compare(flatGlobe
.polarRadius
, polarRadius
) != 0) return false;
567 if (center
!= null ?
!center
.equals(flatGlobe
.center
) : flatGlobe
.center
!= null) return false;
568 if (elevationModel
!= null ?
!elevationModel
.equals(flatGlobe
.elevationModel
)
569 : flatGlobe
.elevationModel
!= null)
571 if (projection
!= null ?
!projection
.equals(flatGlobe
.projection
) : flatGlobe
.projection
!= null) return false;
572 if (tessellator
!= null ?
!tessellator
.equals(flatGlobe
.tessellator
) : flatGlobe
.tessellator
!= null)
578 public int hashCode()
582 temp
= equatorialRadius
!= +0.0d ? Double
.doubleToLongBits(equatorialRadius
) : 0L;
583 result
= (int) (temp ^
(temp
>>> 32));
584 temp
= polarRadius
!= +0.0d ? Double
.doubleToLongBits(polarRadius
) : 0L;
585 result
= 31 * result
+ (int) (temp ^
(temp
>>> 32));
586 result
= 31 * result
+ (center
!= null ? center
.hashCode() : 0);
587 result
= 31 * result
+ (tessellator
!= null ? tessellator
.hashCode() : 0);
588 result
= 31 * result
+ (elevationModel
!= null ? elevationModel
.hashCode() : 0);
589 result
= 31 * result
+ (projection
!= null ? projection
.hashCode() : 0);
593 public SectorGeometryList
tessellate(DrawContext dc
)
595 if (this.tessellator
== null)
597 this.tessellator
= (Tessellator
) WorldWind
.createConfigurationComponent(AVKey
.TESSELLATOR_CLASS_NAME
);
600 return this.tessellator
.tessellate(dc
);