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

Last change on this file since 14794 was 14794, checked in by Don-vip, 5 years ago

see #17353 - better error message

  • Property svn:eol-style set to native
File size: 12.3 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.projection.ProjectionRegistry;
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 = ProjectionRegistry.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 (Double.isNaN(d)) {
237 Logging.error("NaN in greatCircleDistance: {0} {1}", this, other);
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}
Note: See TracBrowser for help on using the repository browser.