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

Last change on this file since 7509 was 6830, checked in by Don-vip, 10 years ago

javadoc fixes for jdk8 compatibility

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