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