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

Last change on this file since 13750 was 13602, checked in by Don-vip, 6 years ago

see #16129 - projections rework for new ESRI file

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