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

Last change on this file since 11348 was 11272, checked in by michael2402, 7 years ago

See #13361: Cleanup code and formatting

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