• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2005 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.common.geometry;
17 
18 /**
19  * This class represents a point on the unit sphere as a pair of
20  * latitude-longitude coordinates. Like the rest of the "geometry" package, the
21  * intent is to represent spherical geometry as a mathematical abstraction, so
22  * functions that are specifically related to the Earth's geometry (e.g.
23  * easting/northing conversions) should be put elsewhere.
24  *
25  */
26 public strictfp class S2LatLng {
27 
28   /**
29    * Approximate "effective" radius of the Earth in meters.
30    */
31   public static final double EARTH_RADIUS_METERS = 6367000.0;
32 
33   /** The center point the lat/lng coordinate system. */
34   public static final S2LatLng CENTER = new S2LatLng(0.0, 0.0);
35 
36   private final double latRadians;
37   private final double lngRadians;
38 
fromRadians(double latRadians, double lngRadians)39   public static S2LatLng fromRadians(double latRadians, double lngRadians) {
40     return new S2LatLng(latRadians, lngRadians);
41   }
42 
fromDegrees(double latDegrees, double lngDegrees)43   public static S2LatLng fromDegrees(double latDegrees, double lngDegrees) {
44     return new S2LatLng(S1Angle.degrees(latDegrees), S1Angle.degrees(lngDegrees));
45   }
46 
fromE5(long latE5, long lngE5)47   public static S2LatLng fromE5(long latE5, long lngE5) {
48     return new S2LatLng(S1Angle.e5(latE5), S1Angle.e5(lngE5));
49   }
50 
fromE6(long latE6, long lngE6)51   public static S2LatLng fromE6(long latE6, long lngE6) {
52     return new S2LatLng(S1Angle.e6(latE6), S1Angle.e6(lngE6));
53   }
54 
fromE7(long latE7, long lngE7)55   public static S2LatLng fromE7(long latE7, long lngE7) {
56     return new S2LatLng(S1Angle.e7(latE7), S1Angle.e7(lngE7));
57   }
58 
latitude(S2Point p)59   public static S1Angle latitude(S2Point p) {
60     // We use atan2 rather than asin because the input vector is not necessarily
61     // unit length, and atan2 is much more accurate than asin near the poles.
62     return S1Angle.radians(
63         Math.atan2(p.get(2), Math.sqrt(p.get(0) * p.get(0) + p.get(1) * p.get(1))));
64   }
65 
longitude(S2Point p)66   public static S1Angle longitude(S2Point p) {
67     // Note that atan2(0, 0) is defined to be zero.
68     return S1Angle.radians(Math.atan2(p.get(1), p.get(0)));
69   }
70 
71   /** This is internal to avoid ambiguity about which units are expected. */
S2LatLng(double latRadians, double lngRadians)72   private S2LatLng(double latRadians, double lngRadians) {
73     this.latRadians = latRadians;
74     this.lngRadians = lngRadians;
75   }
76 
77   /**
78    * Basic constructor. The latitude and longitude must be within the ranges
79    * allowed by is_valid() below.
80    *
81    * TODO(dbeaumont): Make this a static factory method (fromLatLng() ?).
82    */
S2LatLng(S1Angle lat, S1Angle lng)83   public S2LatLng(S1Angle lat, S1Angle lng) {
84     this(lat.radians(), lng.radians());
85   }
86 
87   /**
88    * Default constructor for convenience when declaring arrays, etc.
89    *
90    * TODO(dbeaumont): Remove the default constructor (just use CENTER).
91    */
S2LatLng()92   public S2LatLng() {
93     this(0, 0);
94   }
95 
96   /**
97    * Convert a point (not necessarily normalized) to an S2LatLng.
98    *
99    * TODO(dbeaumont): Make this a static factory method (fromPoint() ?).
100    */
S2LatLng(S2Point p)101   public S2LatLng(S2Point p) {
102     this(Math.atan2(p.z, Math.sqrt(p.x * p.x + p.y * p.y)), Math.atan2(p.y, p.x));
103     // The latitude and longitude are already normalized. We use atan2 to
104     // compute the latitude because the input vector is not necessarily unit
105     // length, and atan2 is much more accurate than asin near the poles.
106     // Note that atan2(0, 0) is defined to be zero.
107   }
108 
109   /** Returns the latitude of this point as a new S1Angle. */
lat()110   public S1Angle lat() {
111     return S1Angle.radians(latRadians);
112   }
113 
114   /** Returns the latitude of this point as radians. */
latRadians()115   public double latRadians() {
116     return latRadians;
117   }
118 
119   /** Returns the latitude of this point as degrees. */
latDegrees()120   public double latDegrees() {
121     return 180.0 / Math.PI * latRadians;
122   }
123 
124   /** Returns the longitude of this point as a new S1Angle. */
lng()125   public S1Angle lng() {
126     return S1Angle.radians(lngRadians);
127   }
128 
129   /** Returns the longitude of this point as radians. */
lngRadians()130   public double lngRadians() {
131     return lngRadians;
132   }
133 
134   /** Returns the longitude of this point as degrees. */
lngDegrees()135   public double lngDegrees() {
136     return 180.0 / Math.PI * lngRadians;
137   }
138 
139   /**
140    * Return true if the latitude is between -90 and 90 degrees inclusive and the
141    * longitude is between -180 and 180 degrees inclusive.
142    */
isValid()143   public boolean isValid() {
144     return Math.abs(lat().radians()) <= S2.M_PI_2 && Math.abs(lng().radians()) <= S2.M_PI;
145   }
146 
147   /**
148    * Returns a new S2LatLng based on this instance for which {@link #isValid()}
149    * will be {@code true}.
150    * <ul>
151    * <li>Latitude is clipped to the range {@code [-90, 90]}
152    * <li>Longitude is normalized to be in the range {@code [-180, 180]}
153    * </ul>
154    * <p>If the current point is valid then the returned point will have the same
155    * coordinates.
156    */
normalized()157   public S2LatLng normalized() {
158     // drem(x, 2 * S2.M_PI) reduces its argument to the range
159     // [-S2.M_PI, S2.M_PI] inclusive, which is what we want here.
160     return new S2LatLng(Math.max(-S2.M_PI_2, Math.min(S2.M_PI_2, lat().radians())),
161         Math.IEEEremainder(lng().radians(), 2 * S2.M_PI));
162   }
163 
164   // Clamps the latitude to the range [-90, 90] degrees, and adds or subtracts
165   // a multiple of 360 degrees to the longitude if necessary to reduce it to
166   // the range [-180, 180].
167 
168   /** Convert an S2LatLng to the equivalent unit-length vector (S2Point). */
toPoint()169   public S2Point toPoint() {
170     double phi = lat().radians();
171     double theta = lng().radians();
172     double cosphi = Math.cos(phi);
173     return new S2Point(Math.cos(theta) * cosphi, Math.sin(theta) * cosphi, Math.sin(phi));
174   }
175 
176   /**
177    * Return the distance (measured along the surface of the sphere) to the given
178    * point.
179    */
getDistance(final S2LatLng o)180   public S1Angle getDistance(final S2LatLng o) {
181     // This implements the Haversine formula, which is numerically stable for
182     // small distances but only gets about 8 digits of precision for very large
183     // distances (e.g. antipodal points). Note that 8 digits is still accurate
184     // to within about 10cm for a sphere the size of the Earth.
185     //
186     // This could be fixed with another sin() and cos() below, but at that point
187     // you might as well just convert both arguments to S2Points and compute the
188     // distance that way (which gives about 15 digits of accuracy for all
189     // distances).
190 
191     double lat1 = lat().radians();
192     double lat2 = o.lat().radians();
193     double lng1 = lng().radians();
194     double lng2 = o.lng().radians();
195     double dlat = Math.sin(0.5 * (lat2 - lat1));
196     double dlng = Math.sin(0.5 * (lng2 - lng1));
197     double x = dlat * dlat + dlng * dlng * Math.cos(lat1) * Math.cos(lat2);
198     return S1Angle.radians(2 * Math.atan2(Math.sqrt(x), Math.sqrt(Math.max(0.0, 1.0 - x))));
199     // Return the distance (measured along the surface of the sphere) to the
200     // given S2LatLng. This is mathematically equivalent to:
201     //
202     // S1Angle::FromRadians(ToPoint().Angle(o.ToPoint())
203     //
204     // but this implementation is slightly more efficient.
205   }
206 
207   /**
208    * Returns the surface distance to the given point assuming a constant radius.
209    */
getDistance(final S2LatLng o, double radius)210   public double getDistance(final S2LatLng o, double radius) {
211     // TODO(dbeaumont): Maybe check that radius >= 0 ?
212     return getDistance(o).radians() * radius;
213   }
214 
215   /**
216    * Returns the surface distance to the given point assuming the default Earth
217    * radius of {@link #EARTH_RADIUS_METERS}.
218    */
getEarthDistance(final S2LatLng o)219   public double getEarthDistance(final S2LatLng o) {
220     return getDistance(o, EARTH_RADIUS_METERS);
221   }
222 
223   /**
224    * Adds the given point to this point.
225    * Note that there is no guarantee that the new point will be <em>valid</em>.
226    */
add(final S2LatLng o)227   public S2LatLng add(final S2LatLng o) {
228     return new S2LatLng(latRadians + o.latRadians, lngRadians + o.lngRadians);
229   }
230 
231   /**
232    * Subtracts the given point from this point.
233    * Note that there is no guarantee that the new point will be <em>valid</em>.
234    */
sub(final S2LatLng o)235   public S2LatLng sub(final S2LatLng o) {
236     return new S2LatLng(latRadians - o.latRadians, lngRadians - o.lngRadians);
237   }
238 
239   /**
240    * Scales this point by the given scaling factor.
241    * Note that there is no guarantee that the new point will be <em>valid</em>.
242    */
mul(final double m)243   public S2LatLng mul(final double m) {
244     // TODO(dbeaumont): Maybe check that m >= 0 ?
245     return new S2LatLng(latRadians * m, lngRadians * m);
246   }
247 
248   @Override
equals(Object that)249   public boolean equals(Object that) {
250     if (that instanceof S2LatLng) {
251       S2LatLng o = (S2LatLng) that;
252       return (latRadians == o.latRadians) && (lngRadians == o.lngRadians);
253     }
254     return false;
255   }
256 
257   @Override
hashCode()258   public int hashCode() {
259     long value = 17;
260     value += 37 * value + Double.doubleToLongBits(latRadians);
261     value += 37 * value + Double.doubleToLongBits(lngRadians);
262     return (int) (value ^ (value >>> 32));
263   }
264 
265   /**
266    * Returns true if both the latitude and longitude of the given point are
267    * within {@code maxError} radians of this point.
268    */
approxEquals(S2LatLng o, double maxError)269   public boolean approxEquals(S2LatLng o, double maxError) {
270     return (Math.abs(latRadians - o.latRadians) < maxError)
271         && (Math.abs(lngRadians - o.lngRadians) < maxError);
272   }
273 
274   /**
275    * Returns true if the given point is within {@code 1e-9} radians of this
276    * point. This corresponds to a distance of less than {@code 1cm} at the
277    * surface of the Earth.
278    */
approxEquals(S2LatLng o)279   public boolean approxEquals(S2LatLng o) {
280     return approxEquals(o, 1e-9);
281   }
282 
283   @Override
toString()284   public String toString() {
285     return "(" + latRadians + ", " + lngRadians + ")";
286   }
287 
toStringDegrees()288   public String toStringDegrees() {
289     return "(" + latDegrees() + ", " + lngDegrees() + ")";
290   }
291 }
292