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

Last change on this file since 16011 was 15449, checked in by GerdP, 5 years ago

see #13538: deprecate LatLon.isOutSideWorld()

  • Property svn:eol-style set to native
File size: 12.4 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.data.Bounds;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.projection.ProjectionRegistry;
22import org.openstreetmap.josm.tools.Logging;
23import org.openstreetmap.josm.tools.Utils;
24
25/**
26 * LatLon are unprojected latitude / longitude coordinates.
27 * <br>
28 * <b>Latitude</b> specifies the north-south position in degrees
29 * where valid values are in the [-90,90] and positive values specify positions north of the equator.
30 * <br>
31 * <b>Longitude</b> specifies the east-west position in degrees
32 * where valid values are in the [-180,180] and positive values specify positions east of the prime meridian.
33 * <br>
34 * <img alt="lat/lon" src="https://upload.wikimedia.org/wikipedia/commons/6/62/Latitude_and_Longitude_of_the_Earth.svg">
35 * <br>
36 * This class is immutable.
37 *
38 * @author Imi
39 */
40public class LatLon extends Coordinate implements ILatLon {
41
42 private static final long serialVersionUID = 1L;
43
44 /**
45 * Minimum difference in location to not be represented as the same position.
46 * The API returns 7 decimals.
47 */
48 public static final double MAX_SERVER_PRECISION = 1e-7;
49 /**
50 * The inverse of the server precision
51 * @see #MAX_SERVER_PRECISION
52 */
53 public static final double MAX_SERVER_INV_PRECISION = 1e7;
54
55 /**
56 * The (0,0) coordinates.
57 * @since 6178
58 */
59 public static final LatLon ZERO = new LatLon(0, 0);
60
61 /** North pole. */
62 public static final LatLon NORTH_POLE = new LatLon(90, 0);
63 /** South pole. */
64 public static final LatLon SOUTH_POLE = new LatLon(-90, 0);
65
66 /**
67 * The normal number format for server precision coordinates
68 */
69 public static final DecimalFormat cDdFormatter;
70 /**
71 * The number format used for high precision coordinates
72 */
73 public static final DecimalFormat cDdHighPecisionFormatter;
74 static {
75 // Don't use the localized decimal separator. This way we can present
76 // a comma separated list of coordinates.
77 cDdFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
78 cDdFormatter.applyPattern("###0.0######");
79 cDdHighPecisionFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
80 cDdHighPecisionFormatter.applyPattern("###0.0##########");
81 }
82
83 /**
84 * Replies true if lat is in the range [-90,90]
85 *
86 * @param lat the latitude
87 * @return true if lat is in the range [-90,90]
88 */
89 public static boolean isValidLat(double lat) {
90 return lat >= -90d && lat <= 90d;
91 }
92
93 /**
94 * Replies true if lon is in the range [-180,180]
95 *
96 * @param lon the longitude
97 * @return true if lon is in the range [-180,180]
98 */
99 public static boolean isValidLon(double lon) {
100 return lon >= -180d && lon <= 180d;
101 }
102
103 /**
104 * Make sure longitude value is within <code>[-180, 180]</code> range.
105 * @param lon the longitude in degrees
106 * @return lon plus/minus multiples of <code>360</code>, as needed to get
107 * in <code>[-180, 180]</code> range
108 */
109 public static double normalizeLon(double lon) {
110 if (lon >= -180 && lon <= 180)
111 return lon;
112 else {
113 lon = lon % 360.0;
114 if (lon > 180) {
115 return lon - 360;
116 } else if (lon < -180) {
117 return lon + 360;
118 }
119 return lon;
120 }
121 }
122
123 /**
124 * Replies true if lat is in the range [-90,90] and lon is in the range [-180,180]
125 *
126 * @return true if lat is in the range [-90,90] and lon is in the range [-180,180]
127 */
128 public boolean isValid() {
129 return isValidLat(lat()) && isValidLon(lon());
130 }
131
132 /**
133 * Clamp the lat value to be inside the world.
134 * @param value The value
135 * @return The value clamped to the world.
136 */
137 public static double toIntervalLat(double value) {
138 return Utils.clamp(value, -90, 90);
139 }
140
141 /**
142 * Returns a valid OSM longitude [-180,+180] for the given extended longitude value.
143 * For example, a value of -181 will return +179, a value of +181 will return -179.
144 * @param value A longitude value not restricted to the [-180,+180] range.
145 * @return a valid OSM longitude [-180,+180]
146 */
147 public static double toIntervalLon(double value) {
148 if (isValidLon(value))
149 return value;
150 else {
151 int n = (int) (value + Math.signum(value)*180.0) / 360;
152 return value - n*360.0;
153 }
154 }
155
156 /**
157 * Constructs a new object representing the given latitude/longitude.
158 * @param lat the latitude, i.e., the north-south position in degrees
159 * @param lon the longitude, i.e., the east-west position in degrees
160 */
161 public LatLon(double lat, double lon) {
162 super(lon, lat);
163 }
164
165 /**
166 * Creates a new LatLon object for the given coordinate
167 * @param coor The coordinates to copy from.
168 */
169 public LatLon(ILatLon coor) {
170 super(coor.lon(), coor.lat());
171 }
172
173 @Override
174 public double lat() {
175 return y;
176 }
177
178 @Override
179 public double lon() {
180 return x;
181 }
182
183 /**
184 * @param other other lat/lon
185 * @return <code>true</code> if the other point has almost the same lat/lon
186 * values, only differing by no more than 1 / {@link #MAX_SERVER_PRECISION MAX_SERVER_PRECISION}.
187 */
188 public boolean equalsEpsilon(LatLon other) {
189 double p = MAX_SERVER_PRECISION / 2;
190 return Math.abs(lat()-other.lat()) <= p && Math.abs(lon()-other.lon()) <= p;
191 }
192
193 /**
194 * Determines if this lat/lon is outside of the world
195 * @return <code>true</code>, if the coordinate is outside the world, compared by using lat/lon.
196 * @deprecated use {@link Node#isOutSideWorld} instead, see also #13538.
197 */
198 @Deprecated
199 public boolean isOutSideWorld() {
200 Bounds b = ProjectionRegistry.getProjection().getWorldBoundsLatLon();
201 return lat() < b.getMinLat() || lat() > b.getMaxLat() ||
202 lon() < b.getMinLon() || lon() > b.getMaxLon();
203 }
204
205 /**
206 * Determines if this lat/lon is within the given bounding box.
207 * @param b bounding box
208 * @return <code>true</code> if this is within the given bounding box.
209 */
210 public boolean isWithin(Bounds b) {
211 return b.contains(this);
212 }
213
214 /**
215 * Check if this is contained in given area or area is null.
216 *
217 * @param a Area
218 * @return <code>true</code> if this is contained in given area or area is null.
219 */
220 public boolean isIn(Area a) {
221 return a == null || a.contains(x, y);
222 }
223
224 /**
225 * Computes the distance between this lat/lon and another point on the earth.
226 * Uses Haversine formular.
227 * @param other the other point.
228 * @return distance in metres.
229 */
230 public double greatCircleDistance(LatLon other) {
231 double sinHalfLat = sin(toRadians(other.lat() - this.lat()) / 2);
232 double sinHalfLon = sin(toRadians(other.lon() - this.lon()) / 2);
233 double d = 2 * WGS84.a * asin(
234 sqrt(sinHalfLat*sinHalfLat +
235 cos(toRadians(this.lat()))*cos(toRadians(other.lat()))*sinHalfLon*sinHalfLon));
236 // For points opposite to each other on the sphere,
237 // rounding errors could make the argument of asin greater than 1
238 // (This should almost never happen.)
239 if (Double.isNaN(d)) {
240 Logging.error("NaN in greatCircleDistance: {0} {1}", this, other);
241 d = PI * WGS84.a;
242 }
243 return d;
244 }
245
246 /**
247 * Returns bearing from this point to another.
248 *
249 * Angle starts from north and increases clockwise, PI/2 means east.
250 *
251 * Please note that reverse bearing (from other point to this point) should NOT be
252 * calculated from return value of this method, because great circle path
253 * between the two points have different bearings at each position.
254 *
255 * To get bearing from another point to this point call other.bearing(this)
256 *
257 * @param other the "destination" position
258 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
259 * @since 9796
260 */
261 public double bearing(LatLon other) {
262 double lat1 = toRadians(this.lat());
263 double lat2 = toRadians(other.lat());
264 double dlon = toRadians(other.lon() - this.lon());
265 double bearing = atan2(
266 sin(dlon) * cos(lat2),
267 cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
268 );
269 bearing %= 2 * PI;
270 if (bearing < 0) {
271 bearing += 2 * PI;
272 }
273 return bearing;
274 }
275
276 /**
277 * Returns this lat/lon pair in human-readable format.
278 *
279 * @return String in the format "lat=1.23456 deg, lon=2.34567 deg"
280 */
281 public String toDisplayString() {
282 NumberFormat nf = NumberFormat.getInstance();
283 nf.setMaximumFractionDigits(5);
284 return "lat=" + nf.format(lat()) + "\u00B0, lon=" + nf.format(lon()) + '\u00B0';
285 }
286
287 /**
288 * Interpolate between this and a other latlon
289 * @param ll2 The other lat/lon object
290 * @param proportion The proportion to interpolate
291 * @return a new latlon at this position if proportion is 0, at the other position it proportion is 1 and lineary interpolated otherwise.
292 */
293 public LatLon interpolate(LatLon ll2, double proportion) {
294 // this is an alternate form of this.lat() + proportion * (ll2.lat() - this.lat()) that is slightly faster
295 return new LatLon((1 - proportion) * this.lat() + proportion * ll2.lat(),
296 (1 - proportion) * this.lon() + proportion * ll2.lon());
297 }
298
299 /**
300 * Get the center between two lat/lon points
301 * @param ll2 The other {@link LatLon}
302 * @return The center at the average coordinates of the two points. Does not take the 180° meridian into account.
303 */
304 public LatLon getCenter(LatLon ll2) {
305 // The JIT will inline this for us, it is as fast as the normal /2 approach
306 return interpolate(ll2, .5);
307 }
308
309 /**
310 * Returns the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
311 *
312 * @param ll the specified coordinate to be measured against this {@code LatLon}
313 * @return the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
314 * @since 6166
315 */
316 public double distance(final LatLon ll) {
317 return super.distance(ll);
318 }
319
320 /**
321 * Returns the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
322 *
323 * @param ll the specified coordinate to be measured against this {@code LatLon}
324 * @return the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
325 * @since 6166
326 */
327 public double distanceSq(final LatLon ll) {
328 return super.distanceSq(ll);
329 }
330
331 @Override
332 public String toString() {
333 return "LatLon[lat="+lat()+",lon="+lon()+']';
334 }
335
336 /**
337 * Returns the value rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}.
338 * @param value lat/lon value
339 *
340 * @return rounded value
341 */
342 public static double roundToOsmPrecision(double value) {
343 return Math.round(value * MAX_SERVER_INV_PRECISION) / MAX_SERVER_INV_PRECISION;
344 }
345
346 /**
347 * Replies a clone of this lat LatLon, rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}
348 *
349 * @return a clone of this lat LatLon
350 */
351 public LatLon getRoundedToOsmPrecision() {
352 return new LatLon(
353 roundToOsmPrecision(lat()),
354 roundToOsmPrecision(lon())
355 );
356 }
357
358 @Override
359 public int hashCode() {
360 return Objects.hash(x, y);
361 }
362
363 @Override
364 public boolean equals(Object obj) {
365 if (this == obj) return true;
366 if (obj == null || getClass() != obj.getClass()) return false;
367 LatLon that = (LatLon) obj;
368 return Double.compare(that.x, x) == 0 &&
369 Double.compare(that.y, y) == 0;
370 }
371}
Note: See TracBrowser for help on using the repository browser.