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
.geom
;
9 import gov
.nasa
.worldwind
.util
.Logging
;
12 * Represents a point on the two-dimensional surface of a globe. Latitude is the degrees North and ranges between [-90,
13 * 90], while longitude refers to degrees East, and ranges between (-180, 180].
15 * Instances of <code>LatLon</code> are immutable.
18 * @version $Id: LatLon.java 2975 2007-09-21 17:05:19Z tgaskins $
22 private final Angle latitude
;
23 private final Angle longitude
;
26 * Factor method for obtaining a new <code>LatLon</code> from two angles expressed in radians.
28 * @param latitude in radians
29 * @param longitude in radians
30 * @return a new <code>LatLon</code> from the given angles, which are expressed as radians
32 public static LatLon
fromRadians(double latitude
, double longitude
)
34 return new LatLon(Math
.toDegrees(latitude
), Math
.toDegrees(longitude
));
38 * Factory method for obtaining a new <code>LatLon</code> from two angles expressed in degrees.
40 * @param latitude in degrees
41 * @param longitude in degrees
42 * @return a new <code>LatLon</code> from the given angles, which are expressed as degrees
44 public static LatLon
fromDegrees(double latitude
, double longitude
)
46 return new LatLon(latitude
, longitude
);
49 private LatLon(double latitude
, double longitude
)
51 this.latitude
= Angle
.fromDegrees(latitude
);
52 this.longitude
= Angle
.fromDegrees(longitude
);
56 * Contructs a new <code>LatLon</code> from two angles. Neither angle may be null.
58 * @param latitude latitude
59 * @param longitude longitude
60 * @throws IllegalArgumentException if <code>latitude</code> or <code>longitude</code> is null
62 public LatLon(Angle latitude
, Angle longitude
)
64 if (latitude
== null || longitude
== null)
66 String message
= Logging
.getMessage("nullValue.LatitudeOrLongitudeIsNull");
67 Logging
.logger().severe(message
);
68 throw new IllegalArgumentException(message
);
71 this.latitude
= latitude
;
72 this.longitude
= longitude
;
76 * Obtains the latitude of this <code>LatLon</code>.
78 * @return this <code>LatLon</code>'s latitude
80 public final Angle
getLatitude()
86 * Obtains the longitude of this <code>LatLon</code>.
88 * @return this <code>LatLon</code>'s longitude
90 public final Angle
getLongitude()
92 return this.longitude
;
95 public static LatLon
interpolate(double amount
, LatLon value1
, LatLon value2
)
97 if ((value1
== null) || (value2
== null))
99 String message
= Logging
.getMessage("nullValue.LatLonIsNull");
100 Logging
.logger().severe(message
);
101 throw new IllegalArgumentException(message
);
109 Quaternion beginQuat
= Quaternion
.fromRotationYPR(value1
.getLongitude(), value1
.getLatitude(), Angle
.ZERO
);
110 Quaternion endQuat
= Quaternion
.fromRotationYPR(value2
.getLongitude(), value2
.getLatitude(), Angle
.ZERO
);
111 Quaternion quaternion
= Quaternion
.slerp(amount
, beginQuat
, endQuat
);
113 Angle lat
= quaternion
.getRotationY();
114 Angle lon
= quaternion
.getRotationX();
115 if ((lat
== null) || (lon
== null))
118 return new LatLon(lat
, lon
);
122 * Computes the great circle angular distance between two locations. The return value gives the distance as the
123 * angle between the two positions on the pi radius circle. In radians, this angle is also the arc length of the
124 * segment between the two positions on that circle. To compute a distance in meters from this value, multiply it by
125 * the radius of the globe.
127 * @param p1 LatLon of the first location
128 * @param p2 LatLon of the second location
129 * @return the angular distance between the two locations. In radians, this value is the arc length of the radius pi
132 public static Angle
sphericalDistance(LatLon p1
, LatLon p2
)
134 if ((p1
== null) || (p2
== null))
136 String message
= Logging
.getMessage("nullValue.LatLonIsNull");
137 Logging
.logger().severe(message
);
138 throw new IllegalArgumentException(message
);
141 // TODO: Perhaps use more accurate formula described here: http://en.wikipedia.org/wiki/Great-circle_distance
142 double radLatA
= p1
.getLatitude().radians
;
143 double radLatB
= p2
.getLatitude().radians
;
144 double radLonA
= p1
.getLongitude().radians
;
145 double radLonB
= p2
.getLongitude().radians
;
147 Math
.cos(radLatA
) * Math
.cos(radLatB
) * Math
.cos(radLonA
- radLonB
) + Math
.sin(radLatA
) * Math
.sin(radLatB
);
148 return a
> 1 ? Angle
.ZERO
: Angle
.fromRadians(Math
.acos(a
));
151 private static final double HALF_PI
= Math
.PI
/ 2d
;
153 public static Angle
azimuth(LatLon p1
, LatLon p2
)
155 if ((p1
== null) || (p2
== null))
157 String message
= Logging
.getMessage("nullValue.LatLonIsNull");
158 Logging
.logger().severe(message
);
159 throw new IllegalArgumentException(message
);
162 double lat1
= p1
.getLatitude().radians
;
163 double lon1
= p1
.getLongitude().radians
;
164 double lat2
= p2
.getLatitude().radians
;
165 double lon2
= p2
.getLongitude().radians
;
167 if (lat1
== lat2
&& lon1
== lon2
)
171 return lat1
> lat2 ? Angle
.POS180
: Angle
.ZERO
;
173 double c
= Math
.acos(Math
.cos(lat1
) * Math
.cos(lat2
) * Math
.cos(lon2
- lon1
) + Math
.sin(lat1
) * Math
.sin(lat2
));
174 double A
= Math
.asin(Math
.cos(lat2
) * Math
.sin(lon2
- lon1
) / c
);
178 else if (lon2
< lon1
)
181 return Angle
.fromRadians(A
);
184 public static LatLon
endPosition(LatLon p
, double azimuth
, double pathLength
)
188 String message
= Logging
.getMessage("nullValue.LatLonIsNull");
189 Logging
.logger().severe(message
);
190 throw new IllegalArgumentException(message
);
193 double a
= Math
.acos(Math
.cos(pathLength
) * Math
.cos(HALF_PI
- p
.getLatitude().radians
)
194 + Math
.sin(HALF_PI
- p
.getLatitude().radians
) * Math
.sin(pathLength
) * Math
.cos(azimuth
));
196 double B
= Math
.asin(Math
.sin(pathLength
) * Math
.sin(azimuth
) / Math
.sin(a
));
198 return new LatLon(Angle
.fromRadians(HALF_PI
- a
).normalizedLatitude(),
199 Angle
.fromRadians(B
+ p
.getLongitude().radians
).normalizedLongitude());
202 public LatLon
add(LatLon that
)
206 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
207 Logging
.logger().severe(msg
);
208 throw new IllegalArgumentException(msg
);
211 Angle lat
= Angle
.normalizedLatitude(this.latitude
.add(that
.latitude
));
212 Angle lon
= Angle
.normalizedLongitude(this.longitude
.add(that
.longitude
));
214 return new LatLon(lat
, lon
);
217 public LatLon
subtract(LatLon that
)
221 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
222 Logging
.logger().severe(msg
);
223 throw new IllegalArgumentException(msg
);
226 Angle lat
= Angle
.normalizedLatitude(this.latitude
.subtract(that
.latitude
));
227 Angle lon
= Angle
.normalizedLongitude(this.longitude
.subtract(that
.longitude
));
229 return new LatLon(lat
, lon
);
232 public LatLon
add(Position that
)
236 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
237 Logging
.logger().severe(msg
);
238 throw new IllegalArgumentException(msg
);
241 Angle lat
= Angle
.normalizedLatitude(this.latitude
.add(that
.getLatitude()));
242 Angle lon
= Angle
.normalizedLongitude(this.longitude
.add(that
.getLongitude()));
244 return new LatLon(lat
, lon
);
247 public LatLon
subtract(Position that
)
251 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
252 Logging
.logger().severe(msg
);
253 throw new IllegalArgumentException(msg
);
256 Angle lat
= Angle
.normalizedLatitude(this.latitude
.subtract(that
.getLatitude()));
257 Angle lon
= Angle
.normalizedLongitude(this.longitude
.subtract(that
.getLongitude()));
259 return new LatLon(lat
, lon
);
262 public static boolean positionsCrossDateLine(Iterable
<LatLon
> positions
)
264 if (positions
== null)
266 String msg
= Logging
.getMessage("nullValue.PositionsListIsNull");
267 Logging
.logger().severe(msg
);
268 throw new IllegalArgumentException(msg
);
272 for (LatLon posNext
: positions
)
276 // A segment cross the line if end pos have different longitude signs
277 // and are more than 180 degress longitude apart
278 if (Math
.signum(pos
.getLongitude().degrees
) != Math
.signum(posNext
.getLongitude().degrees
))
280 double delta
= Math
.abs(pos
.getLongitude().degrees
- posNext
.getLongitude().degrees
);
281 if (delta
> 180 && delta
< 360)
291 public static boolean positionsCrossLongitudeBoundary(LatLon p1
, LatLon p2
)
293 if (p1
== null || p2
== null)
295 String msg
= Logging
.getMessage("nullValue.PositionIsNull");
296 Logging
.logger().severe(msg
);
297 throw new IllegalArgumentException(msg
);
300 // A segment cross the line if end pos have different longitude signs
301 // and are more than 180 degress longitude apart
302 if (Math
.signum(p1
.getLongitude().degrees
) != Math
.signum(p2
.getLongitude().degrees
))
304 double delta
= Math
.abs(p1
.getLongitude().degrees
- p2
.getLongitude().degrees
);
305 if (delta
> 180 && delta
< 360)
313 public String
toString()
315 return "(" + this.latitude
.toString() + ", " + this.longitude
.toString() + ")";
319 public boolean equals(Object o
)
323 if (o
== null || getClass() != o
.getClass())
326 final gov
.nasa
.worldwind
.geom
.LatLon latLon
= (gov
.nasa
.worldwind
.geom
.LatLon
) o
;
328 if (!latitude
.equals(latLon
.latitude
))
330 //noinspection RedundantIfStatement
331 if (!longitude
.equals(latLon
.longitude
))
338 public int hashCode()
341 result
= latitude
.hashCode();
342 result
= 29 * result
+ longitude
.hashCode();
347 * Compute the forward azimuth between two positions
349 * @param p1 first position
350 * @param p2 second position
351 * @param equatorialRadius the equatorial radius of the globe in meters
352 * @param polarRadius the polar radius of the globe in meters
353 * @return the azimuth
355 public Angle
ellipsoidalForwardAzimuth(LatLon p1
, LatLon p2
, double equatorialRadius
, double polarRadius
)
357 // TODO: What if polar radius is larger than equatorial radius?
358 final double F
= (equatorialRadius
- polarRadius
) / equatorialRadius
; // flattening = 1.0 / 298.257223563;
359 final double R
= 1.0 - F
;
361 if (p1
== null || p2
== null)
363 String message
= Logging
.getMessage("nullValue.PositionIsNull");
364 Logging
.logger().severe(message
);
365 throw new IllegalArgumentException(message
);
368 // See ellipsoidalDistance() below for algorithm info.
370 double GLAT1
= p1
.getLatitude().radians
;
371 double GLAT2
= p2
.getLatitude().radians
;
372 double TU1
= R
* Math
.sin(GLAT1
) / Math
.cos(GLAT1
);
373 double TU2
= R
* Math
.sin(GLAT2
) / Math
.cos(GLAT2
);
374 double CU1
= 1. / Math
.sqrt(TU1
* TU1
+ 1.);
375 double CU2
= 1. / Math
.sqrt(TU2
* TU2
+ 1.);
376 double S
= CU1
* CU2
;
377 double BAZ
= S
* TU2
;
378 double FAZ
= BAZ
* TU1
;
380 return Angle
.fromRadians(FAZ
);
383 // TODO: Need method to compute end position from initial position, azimuth and distance. The companion to the
384 // spherical version, endPosition(), above.
387 * Computes the distance between two points on an ellipsoid iteratively.
389 * NOTE: This method was copied from the UniData NetCDF Java library. http://www.unidata.ucar.edu/software/netcdf-java/
391 * Algorithm from U.S. National Geodetic Survey, FORTRAN program "inverse," subroutine "INVER1," by L. PFEIFER and
392 * JOHN G. GERGEN. See http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html
394 * Original documentation: SOLUTION OF THE GEODETIC INVERSE PROBLEM AFTER T.VINCENTY MODIFIED RAINSFORD'S METHOD
395 * WITH HELMERT'S ELLIPTICAL TERMS EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL
396 * STANDPOINT/FOREPOINT MUST NOT BE THE GEOGRAPHIC POLE
398 * Requires close to 1.4 E-5 seconds wall clock time per call on a 550 MHz Pentium with Linux 7.2.
400 * @param p1 first position
401 * @param p2 second position
402 * @param equatorialRadius the equatorial radius of the globe in meters
403 * @param polarRadius the polar radius of the globe in meters
404 * @return distance in meters between the two points
406 public static double ellipsoidalDistance(LatLon p1
, LatLon p2
, double equatorialRadius
, double polarRadius
)
408 // TODO: I think there is a non-iterative way to calculate the distance. Find it and compare with this one.
409 // TODO: What if polar radius is larger than equatorial radius?
410 final double F
= (equatorialRadius
- polarRadius
) / equatorialRadius
; // flattening = 1.0 / 298.257223563;
411 final double R
= 1.0 - F
;
412 final double EPS
= 0.5E-13;
414 if (p1
== null || p2
== null)
416 String message
= Logging
.getMessage("nullValue.PositionIsNull");
417 Logging
.logger().severe(message
);
418 throw new IllegalArgumentException(message
);
421 // Algorithm from National Geodetic Survey, FORTRAN program "inverse,"
422 // subroutine "INVER1," by L. PFEIFER and JOHN G. GERGEN.
423 // http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html
424 // Conversion to JAVA from FORTRAN was made with as few changes as possible
425 // to avoid errors made while recasting form, and to facilitate any future
426 // comparisons between the original code and the altered version in Java.
427 // Original documentation:
428 // SOLUTION OF THE GEODETIC INVERSE PROBLEM AFTER T.VINCENTY
429 // MODIFIED RAINSFORD'S METHOD WITH HELMERT'S ELLIPTICAL TERMS
430 // EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL
431 // STANDPOINT/FOREPOINT MUST NOT BE THE GEOGRAPHIC POLE
432 // A IS THE SEMI-MAJOR AXIS OF THE REFERENCE ELLIPSOID
433 // F IS THE FLATTENING (NOT RECIPROCAL) OF THE REFERNECE ELLIPSOID
434 // LATITUDES GLAT1 AND GLAT2
435 // AND LONGITUDES GLON1 AND GLON2 ARE IN RADIANS POSITIVE NORTH AND EAST
436 // FORWARD AZIMUTHS AT BOTH POINTS RETURNED IN RADIANS FROM NORTH
438 // Reference ellipsoid is the WGS-84 ellipsoid.
439 // See http://www.colorado.edu/geography/gcraft/notes/datum/elist.html
440 // FAZ is forward azimuth in radians from pt1 to pt2;
441 // BAZ is backward azimuth from point 2 to 1;
442 // S is distance in meters.
444 // Conversion to JAVA from FORTRAN was made with as few changes as possible
445 // to avoid errors made while recasting form, and to facilitate any future
446 // comparisons between the original code and the altered version in Java.
448 //IMPLICIT REAL*8 (A-H,O-Z)
449 // COMMON/CONST/PI,RAD
451 double GLAT1
= p1
.getLatitude().radians
;
452 double GLAT2
= p2
.getLatitude().radians
;
453 double TU1
= R
* Math
.sin(GLAT1
) / Math
.cos(GLAT1
);
454 double TU2
= R
* Math
.sin(GLAT2
) / Math
.cos(GLAT2
);
455 double CU1
= 1. / Math
.sqrt(TU1
* TU1
+ 1.);
456 double SU1
= CU1
* TU1
;
457 double CU2
= 1. / Math
.sqrt(TU2
* TU2
+ 1.);
458 double S
= CU1
* CU2
;
459 double BAZ
= S
* TU2
;
460 double FAZ
= BAZ
* TU1
;
461 double GLON1
= p1
.getLongitude().radians
;
462 double GLON2
= p2
.getLongitude().radians
;
463 double X
= GLON2
- GLON1
;
464 double D
, SX
, CX
, SY
, CY
, Y
, SA
, C2A
, CZ
, E
, C
;
470 TU2
= BAZ
- SU1
* CU2
* CX
;
471 SY
= Math
.sqrt(TU1
* TU1
+ TU2
* TU2
);
473 Y
= Math
.atan2(SY
, CY
);
481 E
= CZ
* CZ
* 2. - 1.;
482 C
= ((-3. * C2A
+ 4.) * F
+ 4.) * C2A
* F
/ 16.;
484 X
= ((E
* CY
* C
+ CZ
) * SY
* C
+ Y
) * SA
;
485 X
= (1. - C
) * X
* F
+ GLON2
- GLON1
;
486 //IF(DABS(D-X).GT.EPS) GO TO 100
487 } while (Math
.abs(D
- X
) > EPS
);
489 //FAZ = Math.atan2(TU1, TU2);
490 //BAZ = Math.atan2(CU1 * SX, BAZ * CX - SU1 * CU2) + Math.PI;
491 X
= Math
.sqrt((1. / R
/ R
- 1.) * C2A
+ 1.) + 1.;
494 C
= (X
* X
/ 4. + 1.) / C
;
495 D
= (0.375 * X
* X
- 1.) * X
;
498 S
= ((((SY
* SY
* 4. - 3.) * S
* CZ
* D
/ 6. - X
) * D
/ 4. + CZ
) * SY
499 * D
+ Y
) * C
* equatorialRadius
* R
;