source: josm/trunk/src/org/openstreetmap/josm/data/Bounds.java@ 9243

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

javadoc update

  • Property svn:eol-style set to native
File size: 16.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Rectangle2D;
7import java.text.DecimalFormat;
8import java.text.MessageFormat;
9
10import org.openstreetmap.josm.data.coor.LatLon;
11import org.openstreetmap.josm.data.osm.BBox;
12import org.openstreetmap.josm.tools.CheckParameterUtil;
13
14/**
15 * This is a simple data class for "rectangular" areas of the world, given in
16 * lat/lon min/max values. The values are rounded to LatLon.OSM_SERVER_PRECISION
17 *
18 * @author imi
19 */
20public class Bounds {
21 /**
22 * The minimum and maximum coordinates.
23 */
24 private double minLat, minLon, maxLat, maxLon;
25
26 public LatLon getMin() {
27 return new LatLon(minLat, minLon);
28 }
29
30 /**
31 * Returns min latitude of bounds. Efficient shortcut for {@code getMin().lat()}.
32 *
33 * @return min latitude of bounds.
34 * @since 6203
35 */
36 public double getMinLat() {
37 return minLat;
38 }
39
40 /**
41 * Returns min longitude of bounds. Efficient shortcut for {@code getMin().lon()}.
42 *
43 * @return min longitude of bounds.
44 * @since 6203
45 */
46 public double getMinLon() {
47 return minLon;
48 }
49
50 public LatLon getMax() {
51 return new LatLon(maxLat, maxLon);
52 }
53
54 /**
55 * Returns max latitude of bounds. Efficient shortcut for {@code getMax().lat()}.
56 *
57 * @return max latitude of bounds.
58 * @since 6203
59 */
60 public double getMaxLat() {
61 return maxLat;
62 }
63
64 /**
65 * Returns max longitude of bounds. Efficient shortcut for {@code getMax().lon()}.
66 *
67 * @return max longitude of bounds.
68 * @since 6203
69 */
70 public double getMaxLon() {
71 return maxLon;
72 }
73
74 public enum ParseMethod {
75 MINLAT_MINLON_MAXLAT_MAXLON,
76 LEFT_BOTTOM_RIGHT_TOP
77 }
78
79 /**
80 * Construct bounds out of two points. Coords will be rounded.
81 * @param min min lat/lon
82 * @param max max lat/lon
83 */
84 public Bounds(LatLon min, LatLon max) {
85 this(min.lat(), min.lon(), max.lat(), max.lon());
86 }
87
88 public Bounds(LatLon min, LatLon max, boolean roundToOsmPrecision) {
89 this(min.lat(), min.lon(), max.lat(), max.lon(), roundToOsmPrecision);
90 }
91
92 /**
93 * Constructs bounds out a single point.
94 * @param b lat/lon
95 */
96 public Bounds(LatLon b) {
97 this(b, true);
98 }
99
100 /**
101 * Single point Bounds defined by lat/lon {@code b}.
102 * Coordinates will be rounded to osm precision if {@code roundToOsmPrecision} is true.
103 *
104 * @param b lat/lon of given point.
105 * @param roundToOsmPrecision defines if lat/lon will be rounded.
106 */
107 public Bounds(LatLon b, boolean roundToOsmPrecision) {
108 this(b.lat(), b.lon(), roundToOsmPrecision);
109 }
110
111 /**
112 * Single point Bounds defined by point [lat,lon].
113 * Coordinates will be rounded to osm precision if {@code roundToOsmPrecision} is true.
114 *
115 * @param lat latitude of given point.
116 * @param lon longitude of given point.
117 * @param roundToOsmPrecision defines if lat/lon will be rounded.
118 * @since 6203
119 */
120 public Bounds(double lat, double lon, boolean roundToOsmPrecision) {
121 // Do not call this(b, b) to avoid GPX performance issue (see #7028) until roundToOsmPrecision() is improved
122 if (roundToOsmPrecision) {
123 this.minLat = LatLon.roundToOsmPrecision(lat);
124 this.minLon = LatLon.roundToOsmPrecision(lon);
125 } else {
126 this.minLat = lat;
127 this.minLon = lon;
128 }
129 this.maxLat = this.minLat;
130 this.maxLon = this.minLon;
131 }
132
133 public Bounds(double minlat, double minlon, double maxlat, double maxlon) {
134 this(minlat, minlon, maxlat, maxlon, true);
135 }
136
137 public Bounds(double minlat, double minlon, double maxlat, double maxlon, boolean roundToOsmPrecision) {
138 if (roundToOsmPrecision) {
139 this.minLat = LatLon.roundToOsmPrecision(minlat);
140 this.minLon = LatLon.roundToOsmPrecision(minlon);
141 this.maxLat = LatLon.roundToOsmPrecision(maxlat);
142 this.maxLon = LatLon.roundToOsmPrecision(maxlon);
143 } else {
144 this.minLat = minlat;
145 this.minLon = minlon;
146 this.maxLat = maxlat;
147 this.maxLon = maxlon;
148 }
149 }
150
151 public Bounds(double[] coords) {
152 this(coords, true);
153 }
154
155 public Bounds(double[] coords, boolean roundToOsmPrecision) {
156 CheckParameterUtil.ensureParameterNotNull(coords, "coords");
157 if (coords.length != 4)
158 throw new IllegalArgumentException(MessageFormat.format("Expected array of length 4, got {0}", coords.length));
159 if (roundToOsmPrecision) {
160 this.minLat = LatLon.roundToOsmPrecision(coords[0]);
161 this.minLon = LatLon.roundToOsmPrecision(coords[1]);
162 this.maxLat = LatLon.roundToOsmPrecision(coords[2]);
163 this.maxLon = LatLon.roundToOsmPrecision(coords[3]);
164 } else {
165 this.minLat = coords[0];
166 this.minLon = coords[1];
167 this.maxLat = coords[2];
168 this.maxLon = coords[3];
169 }
170 }
171
172 public Bounds(String asString, String separator) {
173 this(asString, separator, ParseMethod.MINLAT_MINLON_MAXLAT_MAXLON);
174 }
175
176 public Bounds(String asString, String separator, ParseMethod parseMethod) {
177 this(asString, separator, parseMethod, true);
178 }
179
180 public Bounds(String asString, String separator, ParseMethod parseMethod, boolean roundToOsmPrecision) {
181 CheckParameterUtil.ensureParameterNotNull(asString, "asString");
182 String[] components = asString.split(separator);
183 if (components.length != 4)
184 throw new IllegalArgumentException(
185 MessageFormat.format("Exactly four doubles expected in string, got {0}: {1}", components.length, asString));
186 double[] values = new double[4];
187 for (int i = 0; i < 4; i++) {
188 try {
189 values[i] = Double.parseDouble(components[i]);
190 } catch (NumberFormatException e) {
191 throw new IllegalArgumentException(MessageFormat.format("Illegal double value ''{0}''", components[i]), e);
192 }
193 }
194
195 switch (parseMethod) {
196 case LEFT_BOTTOM_RIGHT_TOP:
197 this.minLat = initLat(values[1], roundToOsmPrecision);
198 this.minLon = initLon(values[0], roundToOsmPrecision);
199 this.maxLat = initLat(values[3], roundToOsmPrecision);
200 this.maxLon = initLon(values[2], roundToOsmPrecision);
201 break;
202 case MINLAT_MINLON_MAXLAT_MAXLON:
203 default:
204 this.minLat = initLat(values[0], roundToOsmPrecision);
205 this.minLon = initLon(values[1], roundToOsmPrecision);
206 this.maxLat = initLat(values[2], roundToOsmPrecision);
207 this.maxLon = initLon(values[3], roundToOsmPrecision);
208 }
209 }
210
211 protected static double initLat(double value, boolean roundToOsmPrecision) {
212 if (!LatLon.isValidLat(value))
213 throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", value));
214 return roundToOsmPrecision ? LatLon.roundToOsmPrecision(value) : value;
215 }
216
217 protected static double initLon(double value, boolean roundToOsmPrecision) {
218 if (!LatLon.isValidLon(value))
219 throw new IllegalArgumentException(tr("Illegal longitude value ''{0}''", value));
220 return roundToOsmPrecision ? LatLon.roundToOsmPrecision(value) : value;
221 }
222
223 /**
224 * Creates new {@code Bounds} from an existing one.
225 * @param other The bounds to copy
226 */
227 public Bounds(final Bounds other) {
228 this(other.minLat, other.minLon, other.maxLat, other.maxLon);
229 }
230
231 public Bounds(Rectangle2D rect) {
232 this(rect.getMinY(), rect.getMinX(), rect.getMaxY(), rect.getMaxX());
233 }
234
235 /**
236 * Creates new bounds around a coordinate pair <code>center</code>. The
237 * new bounds shall have an extension in latitude direction of <code>latExtent</code>,
238 * and in longitude direction of <code>lonExtent</code>.
239 *
240 * @param center the center coordinate pair. Must not be null.
241 * @param latExtent the latitude extent. &gt; 0 required.
242 * @param lonExtent the longitude extent. &gt; 0 required.
243 * @throws IllegalArgumentException if center is null
244 * @throws IllegalArgumentException if latExtent &lt;= 0
245 * @throws IllegalArgumentException if lonExtent &lt;= 0
246 */
247 public Bounds(LatLon center, double latExtent, double lonExtent) {
248 CheckParameterUtil.ensureParameterNotNull(center, "center");
249 if (latExtent <= 0.0)
250 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 expected, got {1}", "latExtent", latExtent));
251 if (lonExtent <= 0.0)
252 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 expected, got {1}", "lonExtent", lonExtent));
253
254 this.minLat = LatLon.roundToOsmPrecision(LatLon.toIntervalLat(center.lat() - latExtent / 2));
255 this.minLon = LatLon.roundToOsmPrecision(LatLon.toIntervalLon(center.lon() - lonExtent / 2));
256 this.maxLat = LatLon.roundToOsmPrecision(LatLon.toIntervalLat(center.lat() + latExtent / 2));
257 this.maxLon = LatLon.roundToOsmPrecision(LatLon.toIntervalLon(center.lon() + lonExtent / 2));
258 }
259
260 /**
261 * Creates BBox with same coordinates.
262 *
263 * @return BBox with same coordinates.
264 * @since 6203
265 */
266 public BBox toBBox() {
267 return new BBox(minLon, minLat, maxLon, maxLat);
268 }
269
270 @Override
271 public String toString() {
272 return "Bounds["+minLat+','+minLon+','+maxLat+','+maxLon+']';
273 }
274
275 public String toShortString(DecimalFormat format) {
276 return format.format(minLat) + ' '
277 + format.format(minLon) + " / "
278 + format.format(maxLat) + ' '
279 + format.format(maxLon);
280 }
281
282 /**
283 * @return Center of the bounding box.
284 */
285 public LatLon getCenter() {
286 if (crosses180thMeridian()) {
287 double lat = (minLat + maxLat) / 2;
288 double lon = (minLon + maxLon - 360.0) / 2;
289 if (lon < -180.0) {
290 lon += 360.0;
291 }
292 return new LatLon(lat, lon);
293 } else {
294 return new LatLon((minLat + maxLat) / 2, (minLon + maxLon) / 2);
295 }
296 }
297
298 /**
299 * Extend the bounds if necessary to include the given point.
300 * @param ll The point to include into these bounds
301 */
302 public void extend(LatLon ll) {
303 extend(ll.lat(), ll.lon());
304 }
305
306 /**
307 * Extend the bounds if necessary to include the given point [lat,lon].
308 * Good to use if you know coordinates to avoid creation of LatLon object.
309 * @param lat Latitude of point to include into these bounds
310 * @param lon Longitude of point to include into these bounds
311 * @since 6203
312 */
313 public void extend(final double lat, final double lon) {
314 if (lat < minLat) {
315 minLat = LatLon.roundToOsmPrecision(lat);
316 }
317 if (lat > maxLat) {
318 maxLat = LatLon.roundToOsmPrecision(lat);
319 }
320 if (crosses180thMeridian()) {
321 if (lon > maxLon && lon < minLon) {
322 if (Math.abs(lon - minLon) <= Math.abs(lon - maxLon)) {
323 minLon = LatLon.roundToOsmPrecision(lon);
324 } else {
325 maxLon = LatLon.roundToOsmPrecision(lon);
326 }
327 }
328 } else {
329 if (lon < minLon) {
330 minLon = LatLon.roundToOsmPrecision(lon);
331 }
332 if (lon > maxLon) {
333 maxLon = LatLon.roundToOsmPrecision(lon);
334 }
335 }
336 }
337
338 public void extend(Bounds b) {
339 extend(b.minLat, b.minLon);
340 extend(b.maxLat, b.maxLon);
341 }
342
343 /**
344 * Determines if the given point {@code ll} is within these bounds.
345 * @param ll The lat/lon to check
346 * @return {@code true} if {@code ll} is within these bounds, {@code false} otherwise
347 */
348 public boolean contains(LatLon ll) {
349 if (ll.lat() < minLat || ll.lat() > maxLat)
350 return false;
351 if (crosses180thMeridian()) {
352 if (ll.lon() > maxLon && ll.lon() < minLon)
353 return false;
354 } else {
355 if (ll.lon() < minLon || ll.lon() > maxLon)
356 return false;
357 }
358 return true;
359 }
360
361 private static boolean intersectsLonCrossing(Bounds crossing, Bounds notCrossing) {
362 return notCrossing.minLon <= crossing.maxLon || notCrossing.maxLon >= crossing.minLon;
363 }
364
365 /**
366 * The two bounds intersect? Compared to java Shape.intersects, if does not use
367 * the interior but the closure. ("&gt;=" instead of "&gt;")
368 * @param b other bounds
369 * @return {@code true} if the two bounds intersect
370 */
371 public boolean intersects(Bounds b) {
372 if (b.maxLat < minLat || b.minLat > maxLat)
373 return false;
374
375 if (crosses180thMeridian() && !b.crosses180thMeridian()) {
376 return intersectsLonCrossing(this, b);
377 } else if (!crosses180thMeridian() && b.crosses180thMeridian()) {
378 return intersectsLonCrossing(b, this);
379 } else if (crosses180thMeridian() && b.crosses180thMeridian()) {
380 return true;
381 } else {
382 return b.maxLon >= minLon && b.minLon <= maxLon;
383 }
384 }
385
386 /**
387 * Determines if this Bounds object crosses the 180th Meridian.
388 * See http://wiki.openstreetmap.org/wiki/180th_meridian
389 * @return true if this Bounds object crosses the 180th Meridian.
390 */
391 public boolean crosses180thMeridian() {
392 return this.minLon > this.maxLon;
393 }
394
395 /**
396 * Converts the lat/lon bounding box to an object of type Rectangle2D.Double
397 * @return the bounding box to Rectangle2D.Double
398 */
399 public Rectangle2D.Double asRect() {
400 double w = maxLon-minLon + (crosses180thMeridian() ? 360.0 : 0.0);
401 return new Rectangle2D.Double(minLon, minLat, w, maxLat-minLat);
402 }
403
404 public double getArea() {
405 double w = maxLon-minLon + (crosses180thMeridian() ? 360.0 : 0.0);
406 return w * (maxLat - minLat);
407 }
408
409 public String encodeAsString(String separator) {
410 StringBuilder sb = new StringBuilder();
411 sb.append(minLat).append(separator).append(minLon)
412 .append(separator).append(maxLat).append(separator)
413 .append(maxLon);
414 return sb.toString();
415 }
416
417 /**
418 * <p>Replies true, if this bounds are <em>collapsed</em>, i.e. if the min
419 * and the max corner are equal.</p>
420 *
421 * @return true, if this bounds are <em>collapsed</em>
422 */
423 public boolean isCollapsed() {
424 return Double.doubleToLongBits(minLat) == Double.doubleToLongBits(maxLat)
425 && Double.doubleToLongBits(minLon) == Double.doubleToLongBits(maxLon);
426 }
427
428 public boolean isOutOfTheWorld() {
429 return
430 minLat < -90 || minLat > 90 ||
431 maxLat < -90 || maxLat > 90 ||
432 minLon < -180 || minLon > 180 ||
433 maxLon < -180 || maxLon > 180;
434 }
435
436 public void normalize() {
437 minLat = LatLon.toIntervalLat(minLat);
438 maxLat = LatLon.toIntervalLat(maxLat);
439 minLon = LatLon.toIntervalLon(minLon);
440 maxLon = LatLon.toIntervalLon(maxLon);
441 }
442
443 @Override
444 public int hashCode() {
445 final int prime = 31;
446 int result = 1;
447 long temp;
448 temp = Double.doubleToLongBits(maxLat);
449 result = prime * result + (int) (temp ^ (temp >>> 32));
450 temp = Double.doubleToLongBits(maxLon);
451 result = prime * result + (int) (temp ^ (temp >>> 32));
452 temp = Double.doubleToLongBits(minLat);
453 result = prime * result + (int) (temp ^ (temp >>> 32));
454 temp = Double.doubleToLongBits(minLon);
455 result = prime * result + (int) (temp ^ (temp >>> 32));
456 return result;
457 }
458
459 @Override
460 public boolean equals(Object obj) {
461 if (this == obj)
462 return true;
463 if (obj == null)
464 return false;
465 if (getClass() != obj.getClass())
466 return false;
467 Bounds other = (Bounds) obj;
468 if (Double.doubleToLongBits(maxLat) != Double.doubleToLongBits(other.maxLat))
469 return false;
470 if (Double.doubleToLongBits(maxLon) != Double.doubleToLongBits(other.maxLon))
471 return false;
472 if (Double.doubleToLongBits(minLat) != Double.doubleToLongBits(other.minLat))
473 return false;
474 if (Double.doubleToLongBits(minLon) != Double.doubleToLongBits(other.minLon))
475 return false;
476 return true;
477 }
478}
Note: See TracBrowser for help on using the repository browser.