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

Last change on this file since 10680 was 10680, checked in by Don-vip, 8 years ago

sonar - pmd:UseVarargs - Use Varargs

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