source: josm/trunk/src/org/openstreetmap/josm/data/coor/LatLon.java@ 12792

Last change on this file since 12792 was 12792, checked in by bastiK, 7 years ago

closes #15273, see #15229, see #15182 - add command line interface module for projections

  • run josm project --help to see the options
  • extracts parser from LatLon and CustomProjection into LatLonParser
  • Property svn:eol-style set to native
File size: 17.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.coor;
3
4import static java.lang.Math.PI;
5import static java.lang.Math.asin;
6import static java.lang.Math.atan2;
7import static java.lang.Math.cos;
8import static java.lang.Math.sin;
9import static java.lang.Math.sqrt;
10import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
11import static org.openstreetmap.josm.tools.I18n.trc;
12import static org.openstreetmap.josm.tools.Utils.toRadians;
13
14import java.awt.geom.Area;
15import java.text.DecimalFormat;
16import java.text.NumberFormat;
17import java.util.Arrays;
18import java.util.Locale;
19import java.util.Objects;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.data.Bounds;
23import org.openstreetmap.josm.data.coor.conversion.DMSCoordinateFormat;
24import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
25import org.openstreetmap.josm.data.coor.conversion.LatLonParser;
26import org.openstreetmap.josm.data.coor.conversion.NauticalCoordinateFormat;
27import org.openstreetmap.josm.tools.Logging;
28import org.openstreetmap.josm.tools.Utils;
29
30/**
31 * LatLon are unprojected latitude / longitude coordinates.
32 * <br>
33 * <b>Latitude</b> specifies the north-south position in degrees
34 * where valid values are in the [-90,90] and positive values specify positions north of the equator.
35 * <br>
36 * <b>Longitude</b> specifies the east-west position in degrees
37 * where valid values are in the [-180,180] and positive values specify positions east of the prime meridian.
38 * <br>
39 * <img alt="lat/lon" src="https://upload.wikimedia.org/wikipedia/commons/6/62/Latitude_and_Longitude_of_the_Earth.svg">
40 * <br>
41 * This class is immutable.
42 *
43 * @author Imi
44 */
45public class LatLon extends Coordinate implements ILatLon {
46
47 private static final long serialVersionUID = 1L;
48
49 /**
50 * Minimum difference in location to not be represented as the same position.
51 * The API returns 7 decimals.
52 */
53 public static final double MAX_SERVER_PRECISION = 1e-7;
54 /**
55 * The inverse of the server precision
56 * @see #MAX_SERVER_PRECISION
57 */
58 public static final double MAX_SERVER_INV_PRECISION = 1e7;
59
60 /**
61 * The (0,0) coordinates.
62 * @since 6178
63 */
64 public static final LatLon ZERO = new LatLon(0, 0);
65
66 /** North pole. */
67 public static final LatLon NORTH_POLE = new LatLon(90, 0);
68 /** South pole. */
69 public static final LatLon SOUTH_POLE = new LatLon(-90, 0);
70
71 /**
72 * The normal number format for server precision coordinates
73 */
74 public static final DecimalFormat cDdFormatter;
75 /**
76 * The number format used for high precision coordinates
77 */
78 public static final DecimalFormat cDdHighPecisionFormatter;
79 static {
80 // Don't use the localized decimal separator. This way we can present
81 // a comma separated list of coordinates.
82 cDdFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
83 cDdFormatter.applyPattern("###0.0######");
84 cDdHighPecisionFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
85 cDdHighPecisionFormatter.applyPattern("###0.0##########");
86 }
87
88 /**
89 * Character denoting South, as string.
90 * @deprecated use {@link LatLonParser#SOUTH}
91 */
92 @Deprecated
93 public static final String SOUTH = trc("compass", "S");
94 /**
95 * Character denoting North, as string.
96 * @deprecated use {@link LatLonParser#NORTH}
97 */
98 @Deprecated
99 public static final String NORTH = trc("compass", "N");
100 /**
101 * Character denoting West, as string.
102 * @deprecated use {@link LatLonParser#WEST}
103 */
104 @Deprecated
105 public static final String WEST = trc("compass", "W");
106 /**
107 * Character denoting East, as string.
108 * @deprecated use {@link LatLonParser#EAST}
109 */
110 @Deprecated
111 public static final String EAST = trc("compass", "E");
112
113 /**
114 * Replies true if lat is in the range [-90,90]
115 *
116 * @param lat the latitude
117 * @return true if lat is in the range [-90,90]
118 */
119 public static boolean isValidLat(double lat) {
120 return lat >= -90d && lat <= 90d;
121 }
122
123 /**
124 * Replies true if lon is in the range [-180,180]
125 *
126 * @param lon the longitude
127 * @return true if lon is in the range [-180,180]
128 */
129 public static boolean isValidLon(double lon) {
130 return lon >= -180d && lon <= 180d;
131 }
132
133 /**
134 * Make sure longitude value is within <code>[-180, 180]</code> range.
135 * @param lon the longitude in degrees
136 * @return lon plus/minus multiples of <code>360</code>, as needed to get
137 * in <code>[-180, 180]</code> range
138 */
139 public static double normalizeLon(double lon) {
140 if (lon >= -180 && lon <= 180)
141 return lon;
142 else {
143 lon = lon % 360.0;
144 if (lon > 180) {
145 return lon - 360;
146 } else if (lon < -180) {
147 return lon + 360;
148 }
149 return lon;
150 }
151 }
152
153 /**
154 * Replies true if lat is in the range [-90,90] and lon is in the range [-180,180]
155 *
156 * @return true if lat is in the range [-90,90] and lon is in the range [-180,180]
157 */
158 public boolean isValid() {
159 return isValidLat(lat()) && isValidLon(lon());
160 }
161
162 /**
163 * Clamp the lat value to be inside the world.
164 * @param value The value
165 * @return The value clamped to the world.
166 */
167 public static double toIntervalLat(double value) {
168 return Utils.clamp(value, -90, 90);
169 }
170
171 /**
172 * Returns a valid OSM longitude [-180,+180] for the given extended longitude value.
173 * For example, a value of -181 will return +179, a value of +181 will return -179.
174 * @param value A longitude value not restricted to the [-180,+180] range.
175 * @return a valid OSM longitude [-180,+180]
176 */
177 public static double toIntervalLon(double value) {
178 if (isValidLon(value))
179 return value;
180 else {
181 int n = (int) (value + Math.signum(value)*180.0) / 360;
182 return value - n*360.0;
183 }
184 }
185
186 /**
187 * Replies the coordinate in degrees/minutes/seconds format
188 * @param pCoordinate The coordinate to convert
189 * @return The coordinate in degrees/minutes/seconds format
190 * @deprecated use {@link #degreesMinutesSeconds} instead
191 */
192 @Deprecated
193 public static String dms(double pCoordinate) {
194 return degreesMinutesSeconds(pCoordinate);
195 }
196
197 /**
198 * Replies the coordinate in degrees/minutes/seconds format
199 * @param pCoordinate The coordinate to convert
200 * @return The coordinate in degrees/minutes/seconds format
201 * @since 12561
202 * @deprecated use {@link DMSCoordinateFormat#degreesMinutesSeconds(double)}
203 */
204 @Deprecated
205 public static String degreesMinutesSeconds(double pCoordinate) {
206 return DMSCoordinateFormat.degreesMinutesSeconds(pCoordinate);
207 }
208
209 /**
210 * Replies the coordinate in degrees/minutes format
211 * @param pCoordinate The coordinate to convert
212 * @return The coordinate in degrees/minutes format
213 * @since 12537
214 * @deprecated use {@link NauticalCoordinateFormat#degreesMinutes(double)}
215 */
216 @Deprecated
217 public static String degreesMinutes(double pCoordinate) {
218 return NauticalCoordinateFormat.degreesMinutes(pCoordinate);
219 }
220
221 /**
222 * Replies the coordinate in degrees/minutes format
223 * @param pCoordinate The coordinate to convert
224 * @return The coordinate in degrees/minutes format
225 * @deprecated use {@link #degreesMinutes(double)} instead
226 */
227 @Deprecated
228 public static String dm(double pCoordinate) {
229 return degreesMinutes(pCoordinate);
230 }
231
232 /**
233 * Constructs a new object representing the given latitude/longitude.
234 * @param lat the latitude, i.e., the north-south position in degrees
235 * @param lon the longitude, i.e., the east-west position in degrees
236 */
237 public LatLon(double lat, double lon) {
238 super(lon, lat);
239 }
240
241 /**
242 * Creates a new LatLon object for the given coordinate
243 * @param coor The coordinates to copy from.
244 */
245 public LatLon(ILatLon coor) {
246 super(coor.lon(), coor.lat());
247 }
248
249 @Override
250 public double lat() {
251 return y;
252 }
253
254 /**
255 * Formats the latitude part according to the given format
256 * @param d the coordinate format to use
257 * @return the formatted latitude
258 * @deprecated use {@link org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat#latToString(ILatLon)}
259 */
260 @Deprecated
261 public String latToString(CoordinateFormat d) {
262 return d.getICoordinateFormat().latToString(this);
263 }
264
265 @Override
266 public double lon() {
267 return x;
268 }
269
270 /**
271 * Formats the longitude part according to the given format
272 * @param d the coordinate format to use
273 * @return the formatted longitude
274 * @deprecated use {@link org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat#lonToString(ILatLon)}
275 */
276 @Deprecated
277 public String lonToString(CoordinateFormat d) {
278 return d.getICoordinateFormat().lonToString(this);
279 }
280
281 /**
282 * @param other other lat/lon
283 * @return <code>true</code> if the other point has almost the same lat/lon
284 * values, only differing by no more than 1 / {@link #MAX_SERVER_PRECISION MAX_SERVER_PRECISION}.
285 */
286 public boolean equalsEpsilon(LatLon other) {
287 double p = MAX_SERVER_PRECISION / 2;
288 return Math.abs(lat()-other.lat()) <= p && Math.abs(lon()-other.lon()) <= p;
289 }
290
291 /**
292 * Determines if this lat/lon is outside of the world
293 * @return <code>true</code>, if the coordinate is outside the world, compared by using lat/lon.
294 */
295 public boolean isOutSideWorld() {
296 Bounds b = Main.getProjection().getWorldBoundsLatLon();
297 return lat() < b.getMinLat() || lat() > b.getMaxLat() ||
298 lon() < b.getMinLon() || lon() > b.getMaxLon();
299 }
300
301 /**
302 * Determines if this lat/lon is within the given bounding box.
303 * @param b bounding box
304 * @return <code>true</code> if this is within the given bounding box.
305 */
306 public boolean isWithin(Bounds b) {
307 return b.contains(this);
308 }
309
310 /**
311 * Check if this is contained in given area or area is null.
312 *
313 * @param a Area
314 * @return <code>true</code> if this is contained in given area or area is null.
315 */
316 public boolean isIn(Area a) {
317 return a == null || a.contains(x, y);
318 }
319
320 /**
321 * Computes the distance between this lat/lon and another point on the earth.
322 * Uses Haversine formular.
323 * @param other the other point.
324 * @return distance in metres.
325 */
326 public double greatCircleDistance(LatLon other) {
327 double sinHalfLat = sin(toRadians(other.lat() - this.lat()) / 2);
328 double sinHalfLon = sin(toRadians(other.lon() - this.lon()) / 2);
329 double d = 2 * WGS84.a * asin(
330 sqrt(sinHalfLat*sinHalfLat +
331 cos(toRadians(this.lat()))*cos(toRadians(other.lat()))*sinHalfLon*sinHalfLon));
332 // For points opposite to each other on the sphere,
333 // rounding errors could make the argument of asin greater than 1
334 // (This should almost never happen.)
335 if (java.lang.Double.isNaN(d)) {
336 Logging.error("NaN in greatCircleDistance");
337 d = PI * WGS84.a;
338 }
339 return d;
340 }
341
342 /**
343 * Returns the heading that you have to use to get from this lat/lon to another.
344 *
345 * Angle starts from north and increases counterclockwise (!), PI/2 means west.
346 * You can get usual clockwise angle from {@link #bearing(LatLon)} method.
347 * This method is kept as deprecated because it is called from many plugins.
348 *
349 * (I don't know the original source of this formula, but see
350 * <a href="https://math.stackexchange.com/questions/720/how-to-calculate-a-heading-on-the-earths-surface">this question</a>
351 * for some hints how it is derived.)
352 *
353 * @deprecated see bearing method
354 * @param other the "destination" position
355 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
356 */
357 @Deprecated
358 public double heading(LatLon other) {
359 double hd = atan2(sin(toRadians(this.lon() - other.lon())) * cos(toRadians(other.lat())),
360 cos(toRadians(this.lat())) * sin(toRadians(other.lat())) -
361 sin(toRadians(this.lat())) * cos(toRadians(other.lat())) * cos(toRadians(this.lon() - other.lon())));
362 hd %= 2 * PI;
363 if (hd < 0) {
364 hd += 2 * PI;
365 }
366 return hd;
367 }
368
369 /**
370 * Returns bearing from this point to another.
371 *
372 * Angle starts from north and increases clockwise, PI/2 means east.
373 * Old deprecated method {@link #heading(LatLon)} used unusual reverse angle.
374 *
375 * Please note that reverse bearing (from other point to this point) should NOT be
376 * calculated from return value of this method, because great circle path
377 * between the two points have different bearings at each position.
378 *
379 * To get bearing from another point to this point call other.bearing(this)
380 *
381 * @param other the "destination" position
382 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
383 */
384 public double bearing(LatLon other) {
385 double lat1 = toRadians(this.lat());
386 double lat2 = toRadians(other.lat());
387 double dlon = toRadians(other.lon() - this.lon());
388 double bearing = atan2(
389 sin(dlon) * cos(lat2),
390 cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
391 );
392 bearing %= 2 * PI;
393 if (bearing < 0) {
394 bearing += 2 * PI;
395 }
396 return bearing;
397 }
398
399 /**
400 * Returns this lat/lon pair in human-readable format.
401 *
402 * @return String in the format "lat=1.23456 deg, lon=2.34567 deg"
403 */
404 public String toDisplayString() {
405 NumberFormat nf = NumberFormat.getInstance();
406 nf.setMaximumFractionDigits(5);
407 return "lat=" + nf.format(lat()) + "\u00B0, lon=" + nf.format(lon()) + '\u00B0';
408 }
409
410 /**
411 * Returns this lat/lon pair in human-readable format separated by {@code separator}.
412 * @param separator values separator
413 * @return String in the format {@code "1.23456[separator]2.34567"}
414 * @deprecated method removed without replacment
415 */
416 @Deprecated
417 public String toStringCSV(String separator) {
418 return Utils.join(separator, Arrays.asList(
419 DecimalDegreesCoordinateFormat.INSTANCE.latToString(this),
420 DecimalDegreesCoordinateFormat.INSTANCE.lonToString(this)
421 ));
422 }
423
424 /**
425 * Interpolate between this and a other latlon
426 * @param ll2 The other lat/lon object
427 * @param proportion The proportion to interpolate
428 * @return a new latlon at this position if proportion is 0, at the other position it proportion is 1 and lineary interpolated otherwise.
429 */
430 public LatLon interpolate(LatLon ll2, double proportion) {
431 // this is an alternate form of this.lat() + proportion * (ll2.lat() - this.lat()) that is slightly faster
432 return new LatLon((1 - proportion) * this.lat() + proportion * ll2.lat(),
433 (1 - proportion) * this.lon() + proportion * ll2.lon());
434 }
435
436 /**
437 * Get the center between two lat/lon points
438 * @param ll2 The other {@link LatLon}
439 * @return The center at the average coordinates of the two points. Does not take the 180° meridian into account.
440 */
441 public LatLon getCenter(LatLon ll2) {
442 // The JIT will inline this for us, it is as fast as the normal /2 approach
443 return interpolate(ll2, .5);
444 }
445
446 /**
447 * Returns the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
448 *
449 * @param ll the specified coordinate to be measured against this {@code LatLon}
450 * @return the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
451 * @since 6166
452 */
453 public double distance(final LatLon ll) {
454 return super.distance(ll);
455 }
456
457 /**
458 * Returns the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
459 *
460 * @param ll the specified coordinate to be measured against this {@code LatLon}
461 * @return the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
462 * @since 6166
463 */
464 public double distanceSq(final LatLon ll) {
465 return super.distanceSq(ll);
466 }
467
468 @Override
469 public String toString() {
470 return "LatLon[lat="+lat()+",lon="+lon()+']';
471 }
472
473 /**
474 * Returns the value rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}.
475 * @param value lat/lon value
476 *
477 * @return rounded value
478 */
479 public static double roundToOsmPrecision(double value) {
480 return Math.round(value * MAX_SERVER_INV_PRECISION) / MAX_SERVER_INV_PRECISION;
481 }
482
483 /**
484 * Replies a clone of this lat LatLon, rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}
485 *
486 * @return a clone of this lat LatLon
487 */
488 public LatLon getRoundedToOsmPrecision() {
489 return new LatLon(
490 roundToOsmPrecision(lat()),
491 roundToOsmPrecision(lon())
492 );
493 }
494
495 @Override
496 public int hashCode() {
497 return Objects.hash(x, y);
498 }
499
500 @Override
501 public boolean equals(Object obj) {
502 if (this == obj) return true;
503 if (obj == null || getClass() != obj.getClass()) return false;
504 LatLon that = (LatLon) obj;
505 return Double.compare(that.x, x) == 0 &&
506 Double.compare(that.y, y) == 0;
507 }
508
509 /**
510 * Parses the given string as lat/lon.
511 * @param coord String to parse
512 * @return parsed lat/lon
513 * @since 11045
514 * @deprecated use {@link LatLonParser#parse(java.lang.String)}
515 */
516 @Deprecated
517 public static LatLon parse(String coord) {
518 return LatLonParser.parse(coord);
519 }
520}
Note: See TracBrowser for help on using the repository browser.