1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.data.coor;
|
---|
3 |
|
---|
4 | import static java.lang.Math.PI;
|
---|
5 | import static java.lang.Math.asin;
|
---|
6 | import static java.lang.Math.atan2;
|
---|
7 | import static java.lang.Math.cos;
|
---|
8 | import static java.lang.Math.sin;
|
---|
9 | import static java.lang.Math.sqrt;
|
---|
10 | import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
|
---|
11 | import static org.openstreetmap.josm.tools.Utils.toRadians;
|
---|
12 |
|
---|
13 | import org.openstreetmap.josm.data.projection.Projecting;
|
---|
14 | import org.openstreetmap.josm.tools.Logging;
|
---|
15 |
|
---|
16 | /**
|
---|
17 | * This interface represents a coordinate in LatLon space.
|
---|
18 | * <p>
|
---|
19 | * It provides methods to get the coordinates. The coordinates may be unknown.
|
---|
20 | * In this case, both {@link #lat()} and {@link #lon()} need to return a NaN value and {@link #isLatLonKnown()} needs to return false.
|
---|
21 | * <p>
|
---|
22 | * Whether the coordinates are immutable or not is implementation specific.
|
---|
23 | *
|
---|
24 | * @author Michael Zangl
|
---|
25 | * @since 12161
|
---|
26 | */
|
---|
27 | public interface ILatLon {
|
---|
28 | /**
|
---|
29 | * Minimum difference in location to not be represented as the same position.
|
---|
30 | * The API returns 7 decimals.
|
---|
31 | */
|
---|
32 | double MAX_SERVER_PRECISION = 1e-7;
|
---|
33 |
|
---|
34 | /**
|
---|
35 | * Returns the longitude, i.e., the east-west position in degrees.
|
---|
36 | * @return the longitude or NaN if {@link #isLatLonKnown()} returns false
|
---|
37 | */
|
---|
38 | double lon();
|
---|
39 |
|
---|
40 | /**
|
---|
41 | * Returns the latitude, i.e., the north-south position in degrees.
|
---|
42 | * @return the latitude or NaN if {@link #isLatLonKnown()} returns false
|
---|
43 | */
|
---|
44 | double lat();
|
---|
45 |
|
---|
46 | /**
|
---|
47 | * Determines if this object has valid coordinates.
|
---|
48 | * @return {@code true} if this object has valid coordinates
|
---|
49 | */
|
---|
50 | default boolean isLatLonKnown() {
|
---|
51 | return !Double.isNaN(lat()) && !Double.isNaN(lon());
|
---|
52 | }
|
---|
53 |
|
---|
54 | /**
|
---|
55 | * Replies the projected east/north coordinates.
|
---|
56 | * <p>
|
---|
57 | * The result of the last conversion may be cached. Null is returned in case this object is invalid.
|
---|
58 | * @param projecting The projection to use.
|
---|
59 | * @return The projected east/north coordinates
|
---|
60 | * @since 10827
|
---|
61 | */
|
---|
62 | default EastNorth getEastNorth(Projecting projecting) {
|
---|
63 | if (!isLatLonKnown()) {
|
---|
64 | return null;
|
---|
65 | } else {
|
---|
66 | return projecting.latlon2eastNorth(this);
|
---|
67 | }
|
---|
68 | }
|
---|
69 |
|
---|
70 | /**
|
---|
71 | * Determines if the other point has almost the same lat/lon values.
|
---|
72 | * @param other other lat/lon
|
---|
73 | * @return <code>true</code> if the other point has almost the same lat/lon
|
---|
74 | * values, only differing by no more than 1 / {@link #MAX_SERVER_PRECISION MAX_SERVER_PRECISION}.
|
---|
75 | * @since 18464 (extracted from {@link LatLon})
|
---|
76 | */
|
---|
77 | default boolean equalsEpsilon(ILatLon other) {
|
---|
78 | return equalsEpsilon(other, MAX_SERVER_PRECISION);
|
---|
79 | }
|
---|
80 |
|
---|
81 | /**
|
---|
82 | * Determines if the other point has almost the same lat/lon values.
|
---|
83 | * @param other other lat/lon
|
---|
84 | * @param precision The precision to use
|
---|
85 | * @return <code>true</code> if the other point has almost the same lat/lon
|
---|
86 | * values, only differing by no more than 1 / precision.
|
---|
87 | * @since 18464 (extracted from {@link LatLon})
|
---|
88 | */
|
---|
89 | default boolean equalsEpsilon(ILatLon other, double precision) {
|
---|
90 | double p = precision / 2;
|
---|
91 | return Math.abs(lat() - other.lat()) <= p && Math.abs(lon() - other.lon()) <= p;
|
---|
92 | }
|
---|
93 |
|
---|
94 | /**
|
---|
95 | * Computes the distance between this lat/lon and another point on the earth.
|
---|
96 | * Uses <a href="https://en.wikipedia.org/wiki/Haversine_formula">Haversine formula</a>.
|
---|
97 | * @param other the other point.
|
---|
98 | * @return distance in metres.
|
---|
99 | * @since 18494 (extracted from {@link LatLon})
|
---|
100 | */
|
---|
101 | default double greatCircleDistance(ILatLon other) {
|
---|
102 | double sinHalfLat = sin(toRadians(other.lat() - this.lat()) / 2);
|
---|
103 | double sinHalfLon = sin(toRadians(other.lon() - this.lon()) / 2);
|
---|
104 | double d = 2 * WGS84.a * asin(
|
---|
105 | sqrt(sinHalfLat*sinHalfLat +
|
---|
106 | cos(toRadians(this.lat()))*cos(toRadians(other.lat()))*sinHalfLon*sinHalfLon));
|
---|
107 | // For points opposite to each other on the sphere,
|
---|
108 | // rounding errors could make the argument of asin greater than 1
|
---|
109 | // (This should almost never happen.)
|
---|
110 | if (Double.isNaN(d)) {
|
---|
111 | Logging.error("NaN in greatCircleDistance: {0} {1}", this, other);
|
---|
112 | d = PI * WGS84.a;
|
---|
113 | }
|
---|
114 | return d;
|
---|
115 | }
|
---|
116 |
|
---|
117 | /**
|
---|
118 | * Returns bearing from this point to another.
|
---|
119 | *
|
---|
120 | * Angle starts from north and increases clockwise, PI/2 means east.
|
---|
121 | *
|
---|
122 | * Please note that reverse bearing (from other point to this point) should NOT be
|
---|
123 | * calculated from return value of this method, because great circle path
|
---|
124 | * between the two points have different bearings at each position.
|
---|
125 | *
|
---|
126 | * To get bearing from another point to this point call other.bearing(this)
|
---|
127 | *
|
---|
128 | * @param other the "destination" position
|
---|
129 | * @return heading in radians in the range 0 <= hd < 2*PI
|
---|
130 | * @since 18494 (extracted from {@link LatLon}, added in 9796)
|
---|
131 | */
|
---|
132 | default double bearing(ILatLon other) {
|
---|
133 | double lat1 = toRadians(this.lat());
|
---|
134 | double lat2 = toRadians(other.lat());
|
---|
135 | double dlon = toRadians(other.lon() - this.lon());
|
---|
136 | double bearing = atan2(
|
---|
137 | sin(dlon) * cos(lat2),
|
---|
138 | cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
|
---|
139 | );
|
---|
140 | bearing %= 2 * PI;
|
---|
141 | if (bearing < 0) {
|
---|
142 | bearing += 2 * PI;
|
---|
143 | }
|
---|
144 | return bearing;
|
---|
145 | }
|
---|
146 |
|
---|
147 | /**
|
---|
148 | * Does a linear interpolation between two ILatLon instances.
|
---|
149 | * @param ll2 The other ILatLon instance.
|
---|
150 | * @param proportion The proportion the other instance influences the result.
|
---|
151 | * @return The new {@link ILatLon} position.
|
---|
152 | * @since 18589
|
---|
153 | */
|
---|
154 | default ILatLon interpolate(ILatLon ll2, double proportion) {
|
---|
155 | // this is an alternate form of this.lat() + proportion * (ll2.lat() - this.lat()) that is slightly faster
|
---|
156 | return new LatLon((1 - proportion) * this.lat() + proportion * ll2.lat(),
|
---|
157 | (1 - proportion) * this.lon() + proportion * ll2.lon());
|
---|
158 | }
|
---|
159 |
|
---|
160 | /**
|
---|
161 | * Returns the square of euclidean distance from this {@code Coordinate} to a specified coordinate.
|
---|
162 | *
|
---|
163 | * @param lon the X coordinate of the specified point to be measured against this {@code Coordinate}
|
---|
164 | * @param lat the Y coordinate of the specified point to be measured against this {@code Coordinate}
|
---|
165 | * @return the square of the euclidean distance from this {@code Coordinate} to a specified coordinate
|
---|
166 | * @since 18589
|
---|
167 | */
|
---|
168 | default double distanceSq(final double lon, final double lat) {
|
---|
169 | final double dx = this.lon() - lon;
|
---|
170 | final double dy = this.lat() - lat;
|
---|
171 | return dx * dx + dy * dy;
|
---|
172 | }
|
---|
173 |
|
---|
174 | /**
|
---|
175 | * Returns the euclidean distance from this {@code ILatLon} to a specified {@code ILatLon}.
|
---|
176 | *
|
---|
177 | * @param other the specified coordinate to be measured against this {@code ILatLon}
|
---|
178 | * @return the euclidean distance from this {@code ILatLon} to a specified {@code ILatLon}
|
---|
179 | * @since 18589
|
---|
180 | */
|
---|
181 | default double distanceSq(final ILatLon other) {
|
---|
182 | return this.distanceSq(other.lon(), other.lat());
|
---|
183 | }
|
---|
184 | }
|
---|