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

Last change on this file was 18871, checked in by taylor.smock, 6 months ago

See #23218: Use newer error_prone versions when compiling on Java 11+

error_prone 2.11 dropped support for compiling with Java 8, although it still
supports compiling for Java 8. The "major" new check for us is NotJavadoc since
we used /** in quite a few places which were not javadoc.

Other "new" checks that are of interest:

  • AlreadyChecked: if (foo) { doFoo(); } else if (!foo) { doBar(); }
  • UnnecessaryStringBuilder: Avoid StringBuilder (Java converts + to StringBuilder behind-the-scenes, but may also do something else if it performs better)
  • NonApiType: Avoid specific interface types in function definitions
  • NamedLikeContextualKeyword: Avoid using restricted names for classes and methods
  • UnusedMethod: Unused private methods should be removed

This fixes most of the new error_prone issues and some SonarLint issues.

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