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

Last change on this file since 12167 was 12162, checked in by michael2402, 7 years ago

See #13415: Fix @since tags.

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