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