001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.coor; 003 004import static java.lang.Math.PI; 005import static java.lang.Math.asin; 006import static java.lang.Math.atan2; 007import static java.lang.Math.cos; 008import static java.lang.Math.sin; 009import static java.lang.Math.sqrt; 010import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84; 011import static org.openstreetmap.josm.tools.Utils.toRadians; 012 013import java.awt.geom.Area; 014import java.text.DecimalFormat; 015import java.text.NumberFormat; 016import java.util.Locale; 017import java.util.Objects; 018 019import org.openstreetmap.josm.data.Bounds; 020import org.openstreetmap.josm.data.osm.Node; 021import org.openstreetmap.josm.data.projection.ProjectionRegistry; 022import org.openstreetmap.josm.tools.Logging; 023import org.openstreetmap.josm.tools.Utils; 024 025/** 026 * LatLon are unprojected latitude / longitude coordinates. 027 * <br> 028 * <b>Latitude</b> specifies the north-south position in degrees 029 * where valid values are in the [-90,90] and positive values specify positions north of the equator. 030 * <br> 031 * <b>Longitude</b> specifies the east-west position in degrees 032 * where valid values are in the [-180,180] and positive values specify positions east of the prime meridian. 033 * <br> 034 * <img alt="lat/lon" src="https://upload.wikimedia.org/wikipedia/commons/6/62/Latitude_and_Longitude_of_the_Earth.svg"> 035 * <br> 036 * This class is immutable. 037 * 038 * @author Imi 039 */ 040public class LatLon extends Coordinate implements ILatLon { 041 042 private static final long serialVersionUID = 1L; 043 044 /** 045 * Minimum difference in location to not be represented as the same position. 046 * The API returns 7 decimals. 047 */ 048 public static final double MAX_SERVER_PRECISION = 1e-7; 049 /** 050 * The inverse of the server precision 051 * @see #MAX_SERVER_PRECISION 052 */ 053 public static final double MAX_SERVER_INV_PRECISION = 1e7; 054 055 /** 056 * The (0,0) coordinates. 057 * @since 6178 058 */ 059 public static final LatLon ZERO = new LatLon(0, 0); 060 061 /** North pole. */ 062 public static final LatLon NORTH_POLE = new LatLon(90, 0); 063 /** South pole. */ 064 public static final LatLon SOUTH_POLE = new LatLon(-90, 0); 065 066 /** 067 * The normal number format for server precision coordinates 068 */ 069 public static final DecimalFormat cDdFormatter; 070 /** 071 * The number format used for high precision coordinates 072 */ 073 public static final DecimalFormat cDdHighPecisionFormatter; 074 static { 075 // Don't use the localized decimal separator. This way we can present 076 // a comma separated list of coordinates. 077 cDdFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK); 078 cDdFormatter.applyPattern("###0.0######"); 079 cDdHighPecisionFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK); 080 cDdHighPecisionFormatter.applyPattern("###0.0##########"); 081 } 082 083 /** 084 * Replies true if lat is in the range [-90,90] 085 * 086 * @param lat the latitude 087 * @return true if lat is in the range [-90,90] 088 */ 089 public static boolean isValidLat(double lat) { 090 return lat >= -90d && lat <= 90d; 091 } 092 093 /** 094 * Replies true if lon is in the range [-180,180] 095 * 096 * @param lon the longitude 097 * @return true if lon is in the range [-180,180] 098 */ 099 public static boolean isValidLon(double lon) { 100 return lon >= -180d && lon <= 180d; 101 } 102 103 /** 104 * Make sure longitude value is within <code>[-180, 180]</code> range. 105 * @param lon the longitude in degrees 106 * @return lon plus/minus multiples of <code>360</code>, as needed to get 107 * in <code>[-180, 180]</code> range 108 */ 109 public static double normalizeLon(double lon) { 110 if (lon >= -180 && lon <= 180) 111 return lon; 112 else { 113 lon = lon % 360.0; 114 if (lon > 180) { 115 return lon - 360; 116 } else if (lon < -180) { 117 return lon + 360; 118 } 119 return lon; 120 } 121 } 122 123 /** 124 * Replies true if lat is in the range [-90,90] and lon is in the range [-180,180] 125 * 126 * @return true if lat is in the range [-90,90] and lon is in the range [-180,180] 127 */ 128 public boolean isValid() { 129 return isValidLat(lat()) && isValidLon(lon()); 130 } 131 132 /** 133 * Clamp the lat value to be inside the world. 134 * @param value The value 135 * @return The value clamped to the world. 136 */ 137 public static double toIntervalLat(double value) { 138 return Utils.clamp(value, -90, 90); 139 } 140 141 /** 142 * Returns a valid OSM longitude [-180,+180] for the given extended longitude value. 143 * For example, a value of -181 will return +179, a value of +181 will return -179. 144 * @param value A longitude value not restricted to the [-180,+180] range. 145 * @return a valid OSM longitude [-180,+180] 146 */ 147 public static double toIntervalLon(double value) { 148 if (isValidLon(value)) 149 return value; 150 else { 151 int n = (int) (value + Math.signum(value)*180.0) / 360; 152 return value - n*360.0; 153 } 154 } 155 156 /** 157 * Constructs a new object representing the given latitude/longitude. 158 * @param lat the latitude, i.e., the north-south position in degrees 159 * @param lon the longitude, i.e., the east-west position in degrees 160 */ 161 public LatLon(double lat, double lon) { 162 super(lon, lat); 163 } 164 165 /** 166 * Creates a new LatLon object for the given coordinate 167 * @param coor The coordinates to copy from. 168 */ 169 public LatLon(ILatLon coor) { 170 super(coor.lon(), coor.lat()); 171 } 172 173 @Override 174 public double lat() { 175 return y; 176 } 177 178 @Override 179 public double lon() { 180 return x; 181 } 182 183 /** 184 * @param other other lat/lon 185 * @return <code>true</code> if the other point has almost the same lat/lon 186 * values, only differing by no more than 1 / {@link #MAX_SERVER_PRECISION MAX_SERVER_PRECISION}. 187 */ 188 public boolean equalsEpsilon(LatLon other) { 189 double p = MAX_SERVER_PRECISION / 2; 190 return Math.abs(lat()-other.lat()) <= p && Math.abs(lon()-other.lon()) <= p; 191 } 192 193 /** 194 * Determines if this lat/lon is outside of the world 195 * @return <code>true</code>, if the coordinate is outside the world, compared by using lat/lon. 196 * @deprecated use {@link Node#isOutSideWorld} instead, see also #13538. 197 */ 198 @Deprecated 199 public boolean isOutSideWorld() { 200 Bounds b = ProjectionRegistry.getProjection().getWorldBoundsLatLon(); 201 return lat() < b.getMinLat() || lat() > b.getMaxLat() || 202 lon() < b.getMinLon() || lon() > b.getMaxLon(); 203 } 204 205 /** 206 * Determines if this lat/lon is within the given bounding box. 207 * @param b bounding box 208 * @return <code>true</code> if this is within the given bounding box. 209 */ 210 public boolean isWithin(Bounds b) { 211 return b.contains(this); 212 } 213 214 /** 215 * Check if this is contained in given area or area is null. 216 * 217 * @param a Area 218 * @return <code>true</code> if this is contained in given area or area is null. 219 */ 220 public boolean isIn(Area a) { 221 return a == null || a.contains(x, y); 222 } 223 224 /** 225 * Computes the distance between this lat/lon and another point on the earth. 226 * Uses Haversine formular. 227 * @param other the other point. 228 * @return distance in metres. 229 */ 230 public double greatCircleDistance(LatLon other) { 231 double sinHalfLat = sin(toRadians(other.lat() - this.lat()) / 2); 232 double sinHalfLon = sin(toRadians(other.lon() - this.lon()) / 2); 233 double d = 2 * WGS84.a * asin( 234 sqrt(sinHalfLat*sinHalfLat + 235 cos(toRadians(this.lat()))*cos(toRadians(other.lat()))*sinHalfLon*sinHalfLon)); 236 // For points opposite to each other on the sphere, 237 // rounding errors could make the argument of asin greater than 1 238 // (This should almost never happen.) 239 if (Double.isNaN(d)) { 240 Logging.error("NaN in greatCircleDistance: {0} {1}", this, other); 241 d = PI * WGS84.a; 242 } 243 return d; 244 } 245 246 /** 247 * Returns bearing from this point to another. 248 * 249 * Angle starts from north and increases clockwise, PI/2 means east. 250 * 251 * Please note that reverse bearing (from other point to this point) should NOT be 252 * calculated from return value of this method, because great circle path 253 * between the two points have different bearings at each position. 254 * 255 * To get bearing from another point to this point call other.bearing(this) 256 * 257 * @param other the "destination" position 258 * @return heading in radians in the range 0 <= hd < 2*PI 259 * @since 9796 260 */ 261 public double bearing(LatLon other) { 262 double lat1 = toRadians(this.lat()); 263 double lat2 = toRadians(other.lat()); 264 double dlon = toRadians(other.lon() - this.lon()); 265 double bearing = atan2( 266 sin(dlon) * cos(lat2), 267 cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon) 268 ); 269 bearing %= 2 * PI; 270 if (bearing < 0) { 271 bearing += 2 * PI; 272 } 273 return bearing; 274 } 275 276 /** 277 * Returns this lat/lon pair in human-readable format. 278 * 279 * @return String in the format "lat=1.23456 deg, lon=2.34567 deg" 280 */ 281 public String toDisplayString() { 282 NumberFormat nf = NumberFormat.getInstance(); 283 nf.setMaximumFractionDigits(5); 284 return "lat=" + nf.format(lat()) + "\u00B0, lon=" + nf.format(lon()) + '\u00B0'; 285 } 286 287 /** 288 * Interpolate between this and a other latlon 289 * @param ll2 The other lat/lon object 290 * @param proportion The proportion to interpolate 291 * @return a new latlon at this position if proportion is 0, at the other position it proportion is 1 and lineary interpolated otherwise. 292 */ 293 public LatLon interpolate(LatLon ll2, double proportion) { 294 // this is an alternate form of this.lat() + proportion * (ll2.lat() - this.lat()) that is slightly faster 295 return new LatLon((1 - proportion) * this.lat() + proportion * ll2.lat(), 296 (1 - proportion) * this.lon() + proportion * ll2.lon()); 297 } 298 299 /** 300 * Get the center between two lat/lon points 301 * @param ll2 The other {@link LatLon} 302 * @return The center at the average coordinates of the two points. Does not take the 180° meridian into account. 303 */ 304 public LatLon getCenter(LatLon ll2) { 305 // The JIT will inline this for us, it is as fast as the normal /2 approach 306 return interpolate(ll2, .5); 307 } 308 309 /** 310 * Returns the euclidean distance from this {@code LatLon} to a specified {@code LatLon}. 311 * 312 * @param ll the specified coordinate to be measured against this {@code LatLon} 313 * @return the euclidean distance from this {@code LatLon} to a specified {@code LatLon} 314 * @since 6166 315 */ 316 public double distance(final LatLon ll) { 317 return super.distance(ll); 318 } 319 320 /** 321 * Returns the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}. 322 * 323 * @param ll the specified coordinate to be measured against this {@code LatLon} 324 * @return the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon} 325 * @since 6166 326 */ 327 public double distanceSq(final LatLon ll) { 328 return super.distanceSq(ll); 329 } 330 331 @Override 332 public String toString() { 333 return "LatLon[lat="+lat()+",lon="+lon()+']'; 334 } 335 336 /** 337 * Returns the value rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}. 338 * @param value lat/lon value 339 * 340 * @return rounded value 341 */ 342 public static double roundToOsmPrecision(double value) { 343 return Math.round(value * MAX_SERVER_INV_PRECISION) / MAX_SERVER_INV_PRECISION; 344 } 345 346 /** 347 * Replies a clone of this lat LatLon, rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION} 348 * 349 * @return a clone of this lat LatLon 350 */ 351 public LatLon getRoundedToOsmPrecision() { 352 return new LatLon( 353 roundToOsmPrecision(lat()), 354 roundToOsmPrecision(lon()) 355 ); 356 } 357 358 @Override 359 public int hashCode() { 360 return Objects.hash(x, y); 361 } 362 363 @Override 364 public boolean equals(Object obj) { 365 if (this == obj) return true; 366 if (obj == null || getClass() != obj.getClass()) return false; 367 LatLon that = (LatLon) obj; 368 return Double.compare(that.x, x) == 0 && 369 Double.compare(that.y, y) == 0; 370 } 371}