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

Last change on this file since 13633 was 13173, checked in by Don-vip, 6 years ago

see #15310 - remove most of deprecated APIs

  • Property svn:eol-style set to native
File size: 13.5 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.Utils.toRadians;
12
13import java.awt.geom.Area;
14import java.text.DecimalFormat;
15import java.text.NumberFormat;
16import java.util.Locale;
17import java.util.Objects;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.Bounds;
21import org.openstreetmap.josm.tools.Logging;
22import org.openstreetmap.josm.tools.Utils;
23
24/**
25 * LatLon are unprojected latitude / longitude coordinates.
26 * <br>
27 * <b>Latitude</b> specifies the north-south position in degrees
28 * where valid values are in the [-90,90] and positive values specify positions north of the equator.
29 * <br>
30 * <b>Longitude</b> specifies the east-west position in degrees
31 * where valid values are in the [-180,180] and positive values specify positions east of the prime meridian.
32 * <br>
33 * <img alt="lat/lon" src="https://upload.wikimedia.org/wikipedia/commons/6/62/Latitude_and_Longitude_of_the_Earth.svg">
34 * <br>
35 * This class is immutable.
36 *
37 * @author Imi
38 */
39public class LatLon extends Coordinate implements ILatLon {
40
41 private static final long serialVersionUID = 1L;
42
43 /**
44 * Minimum difference in location to not be represented as the same position.
45 * The API returns 7 decimals.
46 */
47 public static final double MAX_SERVER_PRECISION = 1e-7;
48 /**
49 * The inverse of the server precision
50 * @see #MAX_SERVER_PRECISION
51 */
52 public static final double MAX_SERVER_INV_PRECISION = 1e7;
53
54 /**
55 * The (0,0) coordinates.
56 * @since 6178
57 */
58 public static final LatLon ZERO = new LatLon(0, 0);
59
60 /** North pole. */
61 public static final LatLon NORTH_POLE = new LatLon(90, 0);
62 /** South pole. */
63 public static final LatLon SOUTH_POLE = new LatLon(-90, 0);
64
65 /**
66 * The normal number format for server precision coordinates
67 */
68 public static final DecimalFormat cDdFormatter;
69 /**
70 * The number format used for high precision coordinates
71 */
72 public static final DecimalFormat cDdHighPecisionFormatter;
73 static {
74 // Don't use the localized decimal separator. This way we can present
75 // a comma separated list of coordinates.
76 cDdFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
77 cDdFormatter.applyPattern("###0.0######");
78 cDdHighPecisionFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
79 cDdHighPecisionFormatter.applyPattern("###0.0##########");
80 }
81
82 /**
83 * Replies true if lat is in the range [-90,90]
84 *
85 * @param lat the latitude
86 * @return true if lat is in the range [-90,90]
87 */
88 public static boolean isValidLat(double lat) {
89 return lat >= -90d && lat <= 90d;
90 }
91
92 /**
93 * Replies true if lon is in the range [-180,180]
94 *
95 * @param lon the longitude
96 * @return true if lon is in the range [-180,180]
97 */
98 public static boolean isValidLon(double lon) {
99 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 the heading that you have to use to get from this lat/lon to another.
245 *
246 * Angle starts from north and increases counterclockwise (!), PI/2 means west.
247 * You can get usual clockwise angle from {@link #bearing(LatLon)} method.
248 * This method is kept as deprecated because it is called from many plugins.
249 *
250 * (I don't know the original source of this formula, but see
251 * <a href="https://math.stackexchange.com/questions/720/how-to-calculate-a-heading-on-the-earths-surface">this question</a>
252 * for some hints how it is derived.)
253 *
254 * @deprecated see bearing method
255 * @param other the "destination" position
256 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
257 */
258 @Deprecated
259 public double heading(LatLon other) {
260 double hd = atan2(sin(toRadians(this.lon() - other.lon())) * cos(toRadians(other.lat())),
261 cos(toRadians(this.lat())) * sin(toRadians(other.lat())) -
262 sin(toRadians(this.lat())) * cos(toRadians(other.lat())) * cos(toRadians(this.lon() - other.lon())));
263 hd %= 2 * PI;
264 if (hd < 0) {
265 hd += 2 * PI;
266 }
267 return hd;
268 }
269
270 /**
271 * Returns bearing from this point to another.
272 *
273 * Angle starts from north and increases clockwise, PI/2 means east.
274 * Old deprecated method {@link #heading(LatLon)} used unusual reverse angle.
275 *
276 * Please note that reverse bearing (from other point to this point) should NOT be
277 * calculated from return value of this method, because great circle path
278 * between the two points have different bearings at each position.
279 *
280 * To get bearing from another point to this point call other.bearing(this)
281 *
282 * @param other the "destination" position
283 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
284 */
285 public double bearing(LatLon other) {
286 double lat1 = toRadians(this.lat());
287 double lat2 = toRadians(other.lat());
288 double dlon = toRadians(other.lon() - this.lon());
289 double bearing = atan2(
290 sin(dlon) * cos(lat2),
291 cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
292 );
293 bearing %= 2 * PI;
294 if (bearing < 0) {
295 bearing += 2 * PI;
296 }
297 return bearing;
298 }
299
300 /**
301 * Returns this lat/lon pair in human-readable format.
302 *
303 * @return String in the format "lat=1.23456 deg, lon=2.34567 deg"
304 */
305 public String toDisplayString() {
306 NumberFormat nf = NumberFormat.getInstance();
307 nf.setMaximumFractionDigits(5);
308 return "lat=" + nf.format(lat()) + "\u00B0, lon=" + nf.format(lon()) + '\u00B0';
309 }
310
311 /**
312 * Interpolate between this and a other latlon
313 * @param ll2 The other lat/lon object
314 * @param proportion The proportion to interpolate
315 * @return a new latlon at this position if proportion is 0, at the other position it proportion is 1 and lineary interpolated otherwise.
316 */
317 public LatLon interpolate(LatLon ll2, double proportion) {
318 // this is an alternate form of this.lat() + proportion * (ll2.lat() - this.lat()) that is slightly faster
319 return new LatLon((1 - proportion) * this.lat() + proportion * ll2.lat(),
320 (1 - proportion) * this.lon() + proportion * ll2.lon());
321 }
322
323 /**
324 * Get the center between two lat/lon points
325 * @param ll2 The other {@link LatLon}
326 * @return The center at the average coordinates of the two points. Does not take the 180° meridian into account.
327 */
328 public LatLon getCenter(LatLon ll2) {
329 // The JIT will inline this for us, it is as fast as the normal /2 approach
330 return interpolate(ll2, .5);
331 }
332
333 /**
334 * Returns the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
335 *
336 * @param ll the specified coordinate to be measured against this {@code LatLon}
337 * @return the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
338 * @since 6166
339 */
340 public double distance(final LatLon ll) {
341 return super.distance(ll);
342 }
343
344 /**
345 * Returns the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
346 *
347 * @param ll the specified coordinate to be measured against this {@code LatLon}
348 * @return the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
349 * @since 6166
350 */
351 public double distanceSq(final LatLon ll) {
352 return super.distanceSq(ll);
353 }
354
355 @Override
356 public String toString() {
357 return "LatLon[lat="+lat()+",lon="+lon()+']';
358 }
359
360 /**
361 * Returns the value rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}.
362 * @param value lat/lon value
363 *
364 * @return rounded value
365 */
366 public static double roundToOsmPrecision(double value) {
367 return Math.round(value * MAX_SERVER_INV_PRECISION) / MAX_SERVER_INV_PRECISION;
368 }
369
370 /**
371 * Replies a clone of this lat LatLon, rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}
372 *
373 * @return a clone of this lat LatLon
374 */
375 public LatLon getRoundedToOsmPrecision() {
376 return new LatLon(
377 roundToOsmPrecision(lat()),
378 roundToOsmPrecision(lon())
379 );
380 }
381
382 @Override
383 public int hashCode() {
384 return Objects.hash(x, y);
385 }
386
387 @Override
388 public boolean equals(Object obj) {
389 if (this == obj) return true;
390 if (obj == null || getClass() != obj.getClass()) return false;
391 LatLon that = (LatLon) obj;
392 return Double.compare(that.x, x) == 0 &&
393 Double.compare(that.y, y) == 0;
394 }
395}
Note: See TracBrowser for help on using the repository browser.