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, 2 weeks 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.