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.Main;
020import org.openstreetmap.josm.data.Bounds;
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 = Main.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 (java.lang.Double.isNaN(d)) {
237            Logging.error("NaN in greatCircleDistance");
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 &lt;= hd &lt; 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}