2828public final class LatLng {
2929
3030 /** Minimum Angular resolution. */
31- public static final double MINIMUM_ANGULAR_RESOLUTION = Math .PI * 1.0e-12 ; // taken from lucene's spatial3d
31+ private static final double MINIMUM_ANGULAR_RESOLUTION = Math .PI * 1.0e-12 ; // taken from lucene's spatial3d
32+
33+ /**
34+ * pi / 2.0
35+ */
36+ private static final double M_PI_2 = 1.5707963267948966 ;
3237
3338 // lat / lon in radians
3439 private final double lon ;
@@ -67,13 +72,59 @@ public double getLonDeg() {
6772 * @return The azimuth in radians.
6873 */
6974 double geoAzimuthRads (double lat , double lon ) {
75+ // algorithm from the original H3 library
7076 final double cosLat = FastMath .cos (lat );
7177 return FastMath .atan2 (
7278 cosLat * FastMath .sin (lon - this .lon ),
7379 FastMath .cos (this .lat ) * FastMath .sin (lat ) - FastMath .sin (this .lat ) * cosLat * FastMath .cos (lon - this .lon )
7480 );
7581 }
7682
83+ /**
84+ * Computes the point on the sphere with a specified azimuth and distance from
85+ * this point.
86+ *
87+ * @param az The desired azimuth.
88+ * @param distance The desired distance.
89+ * @return The LatLng point.
90+ */
91+ LatLng geoAzDistanceRads (double az , double distance ) {
92+ // algorithm from the original H3 library
93+ az = Vec2d .posAngleRads (az );
94+ final double sinDistance = FastMath .sin (distance );
95+ final double cosDistance = FastMath .cos (distance );
96+ final double sinP1Lat = FastMath .sin (getLatRad ());
97+ final double cosP1Lat = FastMath .cos (getLatRad ());
98+ final double sinlat = Math .max (-1.0 , Math .min (1.0 , sinP1Lat * cosDistance + cosP1Lat * sinDistance * FastMath .cos (az )));
99+ final double lat = FastMath .asin (sinlat );
100+ if (Math .abs (lat - M_PI_2 ) < Constants .EPSILON ) { // north pole
101+ return new LatLng (M_PI_2 , 0.0 );
102+ } else if (Math .abs (lat + M_PI_2 ) < Constants .EPSILON ) { // south pole
103+ return new LatLng (-M_PI_2 , 0.0 );
104+ } else {
105+ final double cosLat = FastMath .cos (lat );
106+ final double sinlng = Math .max (-1.0 , Math .min (1.0 , FastMath .sin (az ) * sinDistance / cosLat ));
107+ final double coslng = Math .max (-1.0 , Math .min (1.0 , (cosDistance - sinP1Lat * FastMath .sin (lat )) / cosP1Lat / cosLat ));
108+ return new LatLng (lat , constrainLng (getLonRad () + FastMath .atan2 (sinlng , coslng )));
109+ }
110+ }
111+
112+ /**
113+ * constrainLng makes sure longitudes are in the proper bounds
114+ *
115+ * @param lng The origin lng value
116+ * @return The corrected lng value
117+ */
118+ private static double constrainLng (double lng ) {
119+ while (lng > Math .PI ) {
120+ lng = lng - Constants .M_2PI ;
121+ }
122+ while (lng < -Math .PI ) {
123+ lng = lng + Constants .M_2PI ;
124+ }
125+ return lng ;
126+ }
127+
77128 /**
78129 * Determines the maximum latitude of the great circle defined by this LatLng to the provided LatLng.
79130 *
@@ -92,7 +143,7 @@ private static double greatCircleMaxLatitude(LatLng latLng1, LatLng latLng2) {
92143 assert latLng1 .lat >= latLng2 .lat ;
93144 final double az = latLng1 .geoAzimuthRads (latLng2 .lat , latLng2 .lon );
94145 // the great circle contains the maximum latitude only if the azimuth is between -90 and 90 degrees.
95- if (Math .abs (az ) < Math . PI / 2 ) {
146+ if (Math .abs (az ) < M_PI_2 ) {
96147 return FastMath .acos (Math .abs (FastMath .sin (az ) * FastMath .cos (latLng1 .lat )));
97148 }
98149 return latLng1 .lat ;
@@ -116,14 +167,14 @@ private static double greatCircleMinLatitude(LatLng latLng1, LatLng latLng2) {
116167 // we compute the min latitude using Clairaut's formula (https://streckenflug.at/download/formeln.pdf)
117168 final double az = latLng1 .geoAzimuthRads (latLng2 .lat , latLng2 .lon );
118169 // the great circle contains the minimum latitude only if the azimuth is not between -90 and 90 degrees.
119- if (Math .abs (az ) > Math . PI / 2 ) {
170+ if (Math .abs (az ) > M_PI_2 ) {
120171 // note the sign
121172 return -FastMath .acos (Math .abs (FastMath .sin (az ) * FastMath .cos (latLng1 .lat )));
122173 }
123174 return latLng1 .lat ;
124175 }
125176
126- private boolean isNumericallyIdentical (LatLng latLng ) {
177+ boolean isNumericallyIdentical (LatLng latLng ) {
127178 return Math .abs (this .lat - latLng .lat ) < MINIMUM_ANGULAR_RESOLUTION && Math .abs (this .lon - latLng .lon ) < MINIMUM_ANGULAR_RESOLUTION ;
128179 }
129180
0 commit comments