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

Last change on this file since 9005 was 8846, checked in by Don-vip, 9 years ago

sonar - fb-contrib - minor performance improvements:

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