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 3665 2007-11-29 22:01:11Z dcollins $
22 public static final LatLon ZERO
= new LatLon(Angle
.ZERO
, Angle
.ZERO
);
24 private final Angle latitude
;
25 private final Angle longitude
;
28 * Factor method for obtaining a new <code>LatLon</code> from two angles expressed in radians.
30 * @param latitude in radians
31 * @param longitude in radians
32 * @return a new <code>LatLon</code> from the given angles, which are expressed as radians
34 public static LatLon
fromRadians(double latitude
, double longitude
)
36 return new LatLon(Math
.toDegrees(latitude
), Math
.toDegrees(longitude
));
40 * Factory method for obtaining a new <code>LatLon</code> from two angles expressed in degrees.
42 * @param latitude in degrees
43 * @param longitude in degrees
44 * @return a new <code>LatLon</code> from the given angles, which are expressed as degrees
46 public static LatLon
fromDegrees(double latitude
, double longitude
)
48 return new LatLon(latitude
, longitude
);
51 private LatLon(double latitude
, double longitude
)
53 this.latitude
= Angle
.fromDegrees(latitude
);
54 this.longitude
= Angle
.fromDegrees(longitude
);
58 * Contructs a new <code>LatLon</code> from two angles. Neither angle may be null.
60 * @param latitude latitude
61 * @param longitude longitude
62 * @throws IllegalArgumentException if <code>latitude</code> or <code>longitude</code> is null
64 public LatLon(Angle latitude
, Angle longitude
)
66 if (latitude
== null || longitude
== null)
68 String message
= Logging
.getMessage("nullValue.LatitudeOrLongitudeIsNull");
69 Logging
.logger().severe(message
);
70 throw new IllegalArgumentException(message
);
73 this.latitude
= latitude
;
74 this.longitude
= longitude
;
78 * Obtains the latitude of this <code>LatLon</code>.
80 * @return this <code>LatLon</code>'s latitude
82 public final Angle
getLatitude()
88 * Obtains the longitude of this <code>LatLon</code>.
90 * @return this <code>LatLon</code>'s longitude
92 public final Angle
getLongitude()
94 return this.longitude
;
97 public static LatLon
interpolate(double amount
, LatLon value1
, LatLon value2
)
99 if ((value1
== null) || (value2
== null))
101 String message
= Logging
.getMessage("nullValue.LatLonIsNull");
102 Logging
.logger().severe(message
);
103 throw new IllegalArgumentException(message
);
111 Quaternion beginQuat
= Quaternion
.fromRotationYPR(value1
.getLongitude(), value1
.getLatitude(), Angle
.ZERO
);
112 Quaternion endQuat
= Quaternion
.fromRotationYPR(value2
.getLongitude(), value2
.getLatitude(), Angle
.ZERO
);
113 Quaternion quaternion
= Quaternion
.slerp(amount
, beginQuat
, endQuat
);
115 Angle lat
= quaternion
.getRotationY();
116 Angle lon
= quaternion
.getRotationX();
117 if ((lat
== null) || (lon
== null))
120 return new LatLon(lat
, lon
);
124 * Computes the great circle angular distance between two locations. The return value gives the distance as the
125 * angle between the two positions on the pi radius circle. In radians, this angle is also the arc length of the
126 * segment between the two positions on that circle. To compute a distance in meters from this value, multiply it by
127 * the radius of the globe.
129 * @param p1 LatLon of the first location
130 * @param p2 LatLon of the second location
131 * @return the angular distance between the two locations. In radians, this value is the arc length of the radius pi
134 public static Angle
sphericalDistance(LatLon p1
, LatLon p2
)
136 if ((p1
== null) || (p2
== null))
138 String message
= Logging
.getMessage("nullValue.LatLonIsNull");
139 Logging
.logger().severe(message
);
140 throw new IllegalArgumentException(message
);
143 double lat1
= p1
.getLatitude().radians
;
144 double lon1
= p1
.getLongitude().radians
;
145 double lat2
= p2
.getLatitude().radians
;
146 double lon2
= p2
.getLongitude().radians
;
148 if (lat1
== lat2
&& lon1
== lon2
)
151 // Taken from "Map Projections - A Working Manual", page 30, equation 5-3a.
152 // The traditional d=2*asin(a) form has been replaced with d=2*atan2(sqrt(a), sqrt(1-a))
153 // to reduce rounding errors with large distances.
154 double a
= Math
.sin((lat2
- lat1
) / 2.0) * Math
.sin((lat2
- lat1
) / 2.0)
155 + Math
.cos(lat1
) * Math
.cos(lat2
) * Math
.sin((lon2
- lon1
) / 2.0) * Math
.sin((lon2
- lon1
) / 2.0);
156 double distanceRadians
= 2.0 * Math
.atan2(Math
.sqrt(a
), Math
.sqrt(1 - a
));
158 if (Double
.isNaN(distanceRadians
))
161 return Angle
.fromRadians(distanceRadians
);
164 public static Angle
azimuth(LatLon p1
, LatLon p2
)
166 if ((p1
== null) || (p2
== null))
168 String message
= Logging
.getMessage("nullValue.LatLonIsNull");
169 Logging
.logger().severe(message
);
170 throw new IllegalArgumentException(message
);
173 double lat1
= p1
.getLatitude().radians
;
174 double lon1
= p1
.getLongitude().radians
;
175 double lat2
= p2
.getLatitude().radians
;
176 double lon2
= p2
.getLongitude().radians
;
178 if (lat1
== lat2
&& lon1
== lon2
)
182 return lat1
> lat2 ? Angle
.POS180
: Angle
.ZERO
;
184 // Taken from "Map Projections - A Working Manual", page 30, equation 5-4b.
185 // The atan2() function is used in place of the traditional atan(y/x) to simplify the case when x==0.
186 double y
= Math
.cos(lat2
) * Math
.sin(lon2
- lon1
);
187 double x
= Math
.cos(lat1
) * Math
.sin(lat2
) - Math
.sin(lat1
) * Math
.cos(lat2
) * Math
.cos(lon2
- lon1
);
188 double azimuthRadians
= Math
.atan2(y
, x
);
190 if (Double
.isNaN(azimuthRadians
))
193 return Angle
.fromRadians(azimuthRadians
);
196 public static LatLon
endPosition(LatLon p
, double azimuthRadians
, double pathLengthRadians
)
200 String message
= Logging
.getMessage("nullValue.LatLonIsNull");
201 Logging
.logger().severe(message
);
202 throw new IllegalArgumentException(message
);
205 double lat
= p
.getLatitude().radians
;
206 double lon
= p
.getLongitude().radians
;
208 // Taken from "Map Projections - A Working Manual", page 31, equation 5-5 and 5-6.
209 double endLatRadians
= Math
.asin(Math
.sin(lat
) * Math
.cos(pathLengthRadians
)
210 + Math
.cos(lat
) * Math
.sin(pathLengthRadians
) * Math
.cos(azimuthRadians
));
211 double endLonRadians
= lon
+ Math
.atan2(
212 Math
.sin(pathLengthRadians
) * Math
.sin(azimuthRadians
),
213 Math
.cos(lat
) * Math
.cos(pathLengthRadians
)
214 - Math
.sin(lat
) * Math
.sin(pathLengthRadians
) * Math
.cos(azimuthRadians
));
216 if (Double
.isNaN(endLatRadians
) || Double
.isNaN(endLonRadians
))
220 Angle
.fromRadians(endLatRadians
).normalizedLatitude(),
221 Angle
.fromRadians(endLonRadians
).normalizedLongitude());
224 public LatLon
add(LatLon that
)
228 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
229 Logging
.logger().severe(msg
);
230 throw new IllegalArgumentException(msg
);
233 Angle lat
= Angle
.normalizedLatitude(this.latitude
.add(that
.latitude
));
234 Angle lon
= Angle
.normalizedLongitude(this.longitude
.add(that
.longitude
));
236 return new LatLon(lat
, lon
);
239 public LatLon
subtract(LatLon that
)
243 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
244 Logging
.logger().severe(msg
);
245 throw new IllegalArgumentException(msg
);
248 Angle lat
= Angle
.normalizedLatitude(this.latitude
.subtract(that
.latitude
));
249 Angle lon
= Angle
.normalizedLongitude(this.longitude
.subtract(that
.longitude
));
251 return new LatLon(lat
, lon
);
254 public LatLon
add(Position that
)
258 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
259 Logging
.logger().severe(msg
);
260 throw new IllegalArgumentException(msg
);
263 Angle lat
= Angle
.normalizedLatitude(this.latitude
.add(that
.getLatitude()));
264 Angle lon
= Angle
.normalizedLongitude(this.longitude
.add(that
.getLongitude()));
266 return new LatLon(lat
, lon
);
269 public LatLon
subtract(Position that
)
273 String msg
= Logging
.getMessage("nullValue.AngleIsNull");
274 Logging
.logger().severe(msg
);
275 throw new IllegalArgumentException(msg
);
278 Angle lat
= Angle
.normalizedLatitude(this.latitude
.subtract(that
.getLatitude()));
279 Angle lon
= Angle
.normalizedLongitude(this.longitude
.subtract(that
.getLongitude()));
281 return new LatLon(lat
, lon
);
284 public static boolean positionsCrossDateLine(Iterable
<LatLon
> positions
)
286 if (positions
== null)
288 String msg
= Logging
.getMessage("nullValue.PositionsListIsNull");
289 Logging
.logger().severe(msg
);
290 throw new IllegalArgumentException(msg
);
294 for (LatLon posNext
: positions
)
298 // A segment cross the line if end pos have different longitude signs
299 // and are more than 180 degress longitude apart
300 if (Math
.signum(pos
.getLongitude().degrees
) != Math
.signum(posNext
.getLongitude().degrees
))
302 double delta
= Math
.abs(pos
.getLongitude().degrees
- posNext
.getLongitude().degrees
);
303 if (delta
> 180 && delta
< 360)
313 public static boolean positionsCrossLongitudeBoundary(LatLon p1
, LatLon p2
)
315 if (p1
== null || p2
== null)
317 String msg
= Logging
.getMessage("nullValue.PositionIsNull");
318 Logging
.logger().severe(msg
);
319 throw new IllegalArgumentException(msg
);
322 // A segment cross the line if end pos have different longitude signs
323 // and are more than 180 degress longitude apart
324 if (Math
.signum(p1
.getLongitude().degrees
) != Math
.signum(p2
.getLongitude().degrees
))
326 double delta
= Math
.abs(p1
.getLongitude().degrees
- p2
.getLongitude().degrees
);
327 if (delta
> 180 && delta
< 360)
335 public String
toString()
337 String las
= String
.format("Lat %7.4f\u00B0", this.getLatitude().getDegrees());
338 String los
= String
.format("Lon %7.4f\u00B0", this.getLongitude().getDegrees());
339 return "(" + las
+ ", " + los
+ ")";
343 public boolean equals(Object o
)
347 if (o
== null || getClass() != o
.getClass())
350 final gov
.nasa
.worldwind
.geom
.LatLon latLon
= (gov
.nasa
.worldwind
.geom
.LatLon
) o
;
352 if (!latitude
.equals(latLon
.latitude
))
354 //noinspection RedundantIfStatement
355 if (!longitude
.equals(latLon
.longitude
))
362 public int hashCode()
365 result
= latitude
.hashCode();
366 result
= 29 * result
+ longitude
.hashCode();
371 * Compute the forward azimuth between two positions
373 * @param p1 first position
374 * @param p2 second position
375 * @param equatorialRadius the equatorial radius of the globe in meters
376 * @param polarRadius the polar radius of the globe in meters
377 * @return the azimuth
379 public Angle
ellipsoidalForwardAzimuth(LatLon p1
, LatLon p2
, double equatorialRadius
, double polarRadius
)
381 // TODO: What if polar radius is larger than equatorial radius?
382 final double F
= (equatorialRadius
- polarRadius
) / equatorialRadius
; // flattening = 1.0 / 298.257223563;
383 final double R
= 1.0 - F
;
385 if (p1
== null || p2
== null)
387 String message
= Logging
.getMessage("nullValue.PositionIsNull");
388 Logging
.logger().severe(message
);
389 throw new IllegalArgumentException(message
);
392 // See ellipsoidalDistance() below for algorithm info.
394 double GLAT1
= p1
.getLatitude().radians
;
395 double GLAT2
= p2
.getLatitude().radians
;
396 double TU1
= R
* Math
.sin(GLAT1
) / Math
.cos(GLAT1
);
397 double TU2
= R
* Math
.sin(GLAT2
) / Math
.cos(GLAT2
);
398 double CU1
= 1. / Math
.sqrt(TU1
* TU1
+ 1.);
399 double CU2
= 1. / Math
.sqrt(TU2
* TU2
+ 1.);
400 double S
= CU1
* CU2
;
401 double BAZ
= S
* TU2
;
402 double FAZ
= BAZ
* TU1
;
404 return Angle
.fromRadians(FAZ
);
407 // TODO: Need method to compute end position from initial position, azimuth and distance. The companion to the
408 // spherical version, endPosition(), above.
411 * Computes the distance between two points on an ellipsoid iteratively.
413 * NOTE: This method was copied from the UniData NetCDF Java library. http://www.unidata.ucar.edu/software/netcdf-java/
415 * Algorithm from U.S. National Geodetic Survey, FORTRAN program "inverse," subroutine "INVER1," by L. PFEIFER and
416 * JOHN G. GERGEN. See http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html
418 * Original documentation: SOLUTION OF THE GEODETIC INVERSE PROBLEM AFTER T.VINCENTY MODIFIED RAINSFORD'S METHOD
419 * WITH HELMERT'S ELLIPTICAL TERMS EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL
420 * STANDPOINT/FOREPOINT MUST NOT BE THE GEOGRAPHIC POLE
422 * Requires close to 1.4 E-5 seconds wall clock time per call on a 550 MHz Pentium with Linux 7.2.
424 * @param p1 first position
425 * @param p2 second position
426 * @param equatorialRadius the equatorial radius of the globe in meters
427 * @param polarRadius the polar radius of the globe in meters
428 * @return distance in meters between the two points
430 public static double ellipsoidalDistance(LatLon p1
, LatLon p2
, double equatorialRadius
, double polarRadius
)
432 // TODO: I think there is a non-iterative way to calculate the distance. Find it and compare with this one.
433 // TODO: What if polar radius is larger than equatorial radius?
434 final double F
= (equatorialRadius
- polarRadius
) / equatorialRadius
; // flattening = 1.0 / 298.257223563;
435 final double R
= 1.0 - F
;
436 final double EPS
= 0.5E-13;
438 if (p1
== null || p2
== null)
440 String message
= Logging
.getMessage("nullValue.PositionIsNull");
441 Logging
.logger().severe(message
);
442 throw new IllegalArgumentException(message
);
445 // Algorithm from National Geodetic Survey, FORTRAN program "inverse,"
446 // subroutine "INVER1," by L. PFEIFER and JOHN G. GERGEN.
447 // http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html
448 // Conversion to JAVA from FORTRAN was made with as few changes as possible
449 // to avoid errors made while recasting form, and to facilitate any future
450 // comparisons between the original code and the altered version in Java.
451 // Original documentation:
452 // SOLUTION OF THE GEODETIC INVERSE PROBLEM AFTER T.VINCENTY
453 // MODIFIED RAINSFORD'S METHOD WITH HELMERT'S ELLIPTICAL TERMS
454 // EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL
455 // STANDPOINT/FOREPOINT MUST NOT BE THE GEOGRAPHIC POLE
456 // A IS THE SEMI-MAJOR AXIS OF THE REFERENCE ELLIPSOID
457 // F IS THE FLATTENING (NOT RECIPROCAL) OF THE REFERNECE ELLIPSOID
458 // LATITUDES GLAT1 AND GLAT2
459 // AND LONGITUDES GLON1 AND GLON2 ARE IN RADIANS POSITIVE NORTH AND EAST
460 // FORWARD AZIMUTHS AT BOTH POINTS RETURNED IN RADIANS FROM NORTH
462 // Reference ellipsoid is the WGS-84 ellipsoid.
463 // See http://www.colorado.edu/geography/gcraft/notes/datum/elist.html
464 // FAZ is forward azimuth in radians from pt1 to pt2;
465 // BAZ is backward azimuth from point 2 to 1;
466 // S is distance in meters.
468 // Conversion to JAVA from FORTRAN was made with as few changes as possible
469 // to avoid errors made while recasting form, and to facilitate any future
470 // comparisons between the original code and the altered version in Java.
472 //IMPLICIT REAL*8 (A-H,O-Z)
473 // COMMON/CONST/PI,RAD
475 double GLAT1
= p1
.getLatitude().radians
;
476 double GLAT2
= p2
.getLatitude().radians
;
477 double TU1
= R
* Math
.sin(GLAT1
) / Math
.cos(GLAT1
);
478 double TU2
= R
* Math
.sin(GLAT2
) / Math
.cos(GLAT2
);
479 double CU1
= 1. / Math
.sqrt(TU1
* TU1
+ 1.);
480 double SU1
= CU1
* TU1
;
481 double CU2
= 1. / Math
.sqrt(TU2
* TU2
+ 1.);
482 double S
= CU1
* CU2
;
483 double BAZ
= S
* TU2
;
484 double FAZ
= BAZ
* TU1
;
485 double GLON1
= p1
.getLongitude().radians
;
486 double GLON2
= p2
.getLongitude().radians
;
487 double X
= GLON2
- GLON1
;
488 double D
, SX
, CX
, SY
, CY
, Y
, SA
, C2A
, CZ
, E
, C
;
494 TU2
= BAZ
- SU1
* CU2
* CX
;
495 SY
= Math
.sqrt(TU1
* TU1
+ TU2
* TU2
);
497 Y
= Math
.atan2(SY
, CY
);
505 E
= CZ
* CZ
* 2. - 1.;
506 C
= ((-3. * C2A
+ 4.) * F
+ 4.) * C2A
* F
/ 16.;
508 X
= ((E
* CY
* C
+ CZ
) * SY
* C
+ Y
) * SA
;
509 X
= (1. - C
) * X
* F
+ GLON2
- GLON1
;
510 //IF(DABS(D-X).GT.EPS) GO TO 100
511 } while (Math
.abs(D
- X
) > EPS
);
513 //FAZ = Math.atan2(TU1, TU2);
514 //BAZ = Math.atan2(CU1 * SX, BAZ * CX - SU1 * CU2) + Math.PI;
515 X
= Math
.sqrt((1. / R
/ R
- 1.) * C2A
+ 1.) + 1.;
518 C
= (X
* X
/ 4. + 1.) / C
;
519 D
= (0.375 * X
* X
- 1.) * X
;
522 S
= ((((SY
* SY
* 4. - 3.) * S
* CZ
* D
/ 6. - X
) * D
/ 4. + CZ
) * SY
523 * D
+ Y
) * C
* equatorialRadius
* R
;