001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import java.awt.geom.Rectangle2D; 005import java.util.Arrays; 006import java.util.Objects; 007 008import org.openstreetmap.josm.data.Bounds; 009import org.openstreetmap.josm.data.coor.ILatLon; 010import org.openstreetmap.josm.data.coor.LatLon; 011import org.openstreetmap.josm.data.coor.QuadTiling; 012import org.openstreetmap.josm.tools.Utils; 013 014/** 015 * A BBox represents an area in lat/lon space. It is used for the quad tree. 016 * 017 * In contrast to a {@link Bounds} object, a BBox can represent an invalid (empty) area. 018 */ 019public class BBox { 020 021 protected double xmin = Double.POSITIVE_INFINITY; 022 protected double xmax = Double.NEGATIVE_INFINITY; 023 protected double ymin = Double.POSITIVE_INFINITY; 024 protected double ymax = Double.NEGATIVE_INFINITY; 025 026 /** 027 * Constructs a new (invalid) BBox 028 */ 029 public BBox() { 030 // Nothing to do 031 } 032 033 /** 034 * Constructs a new {@code BBox} defined by a single point. 035 * 036 * @param x X coordinate 037 * @param y Y coordinate 038 * @since 6203 039 */ 040 public BBox(final double x, final double y) { 041 add(x, y); 042 } 043 044 /** 045 * Constructs a new {@code BBox} defined by points <code>a</code> and <code>b</code>. 046 * Result is minimal BBox containing both points if they are both valid, else undefined 047 * 048 * @param a first point 049 * @param b second point 050 */ 051 public BBox(LatLon a, LatLon b) { 052 this(a.lon(), a.lat(), b.lon(), b.lat()); 053 } 054 055 /** 056 * Constructs a new {@code BBox} from another one. 057 * 058 * @param copy the BBox to copy 059 */ 060 public BBox(BBox copy) { 061 this.xmin = copy.xmin; 062 this.xmax = copy.xmax; 063 this.ymin = copy.ymin; 064 this.ymax = copy.ymax; 065 } 066 067 /** 068 * Creates bbox around the coordinate (x, y). 069 * Coordinate defines center of bbox, its edge will be 2*r. 070 * 071 * @param x X coordinate 072 * @param y Y coordinate 073 * @param r size 074 * @since 13140 075 */ 076 public BBox(double x, double y, double r) { 077 this(x - r, y - r, x + r, y + r); 078 } 079 080 /** 081 * Create minimal BBox so that {@code this.bounds(ax,ay)} and {@code this.bounds(bx,by)} will both return true 082 * @param ax left or right X value (-180 .. 180) 083 * @param ay top or bottom Y value (-90 .. 90) 084 * @param bx left or right X value (-180 .. 180) 085 * @param by top or bottom Y value (-90 .. 90) 086 */ 087 public BBox(double ax, double ay, double bx, double by) { 088 if (!(Double.isNaN(ax) || Double.isNaN(ay) || Double.isNaN(bx) || Double.isNaN(by))) { 089 add(ax, ay); 090 add(bx, by); 091 } 092 // otherwise use default which is an invalid BBox 093 } 094 095 /** 096 * Create BBox for all nodes of the way with known coordinates. 097 * If no node has a known coordinate, an invalid BBox is returned. 098 * @param w the way 099 */ 100 public BBox(Way w) { 101 w.getNodes().forEach(this::add); 102 } 103 104 /** 105 * Create BBox for a node. An invalid BBox is returned if the coordinates are not known. 106 * @param n the node 107 */ 108 public BBox(Node n) { 109 this((ILatLon) n); 110 } 111 112 /** 113 * Create BBox for a given latlon. An invalid BBox is returned if the coordinates are not known. 114 * @param ll The lat lon position 115 */ 116 public BBox(ILatLon ll) { 117 add(ll); 118 } 119 120 /** 121 * Add a point to an existing BBox. Extends this bbox if necessary so that this.bounds(c) will return true 122 * if c is a valid LatLon instance. 123 * Kept for binary compatibility 124 * @param c a LatLon point 125 */ 126 public final void add(LatLon c) { 127 add((ILatLon) c); 128 } 129 130 /** 131 * Add a point to an existing BBox. Extends this bbox if necessary so that this.bounds(c) will return true 132 * if c is a valid LatLon instance. 133 * If it is invalid or <code>null</code>, this call is ignored. 134 * @param c a LatLon point. 135 */ 136 public final void add(ILatLon c) { 137 if (c != null) { 138 add(c.lon(), c.lat()); 139 } 140 } 141 142 /** 143 * Extends this bbox to include the point (x, y) 144 * @param x X coordinate 145 * @param y Y coordinate 146 */ 147 public final void add(double x, double y) { 148 if (!Double.isNaN(x) && !Double.isNaN(y)) { 149 xmin = Math.min(xmin, x); 150 xmax = Math.max(xmax, x); 151 ymin = Math.min(ymin, y); 152 ymax = Math.max(ymax, y); 153 } 154 } 155 156 /** 157 * Extends this bbox to include the bbox other. Does nothing if other is not valid. 158 * @param other a bbox 159 */ 160 public final void add(BBox other) { 161 if (other.isValid()) { 162 xmin = Math.min(xmin, other.xmin); 163 xmax = Math.max(xmax, other.xmax); 164 ymin = Math.min(ymin, other.ymin); 165 ymax = Math.max(ymax, other.ymax); 166 } 167 } 168 169 /** 170 * Extends this bbox to include the bbox of the primitive extended by extraSpace. 171 * @param primitive an OSM primitive 172 * @param extraSpace the value to extend the primitives bbox. Unit is in LatLon degrees. 173 */ 174 public void addPrimitive(OsmPrimitive primitive, double extraSpace) { 175 BBox primBbox = primitive.getBBox(); 176 add(primBbox.xmin - extraSpace, primBbox.ymin - extraSpace); 177 add(primBbox.xmax + extraSpace, primBbox.ymax + extraSpace); 178 } 179 180 /** 181 * Gets the height of the bbox. 182 * @return The difference between ymax and ymin. 0 for invalid bboxes. 183 */ 184 public double height() { 185 if (isValid()) { 186 return ymax - ymin; 187 } else { 188 return 0; 189 } 190 } 191 192 /** 193 * Gets the width of the bbox. 194 * @return The difference between xmax and xmin. 0 for invalid bboxes. 195 */ 196 public double width() { 197 if (isValid()) { 198 return xmax - xmin; 199 } else { 200 return 0; 201 } 202 } 203 204 /** 205 * Tests, whether the bbox {@code b} lies completely inside this bbox. 206 * @param b bounding box 207 * @return {@code true} if {@code b} lies completely inside this bbox 208 */ 209 public boolean bounds(BBox b) { 210 return xmin <= b.xmin && xmax >= b.xmax 211 && ymin <= b.ymin && ymax >= b.ymax; 212 } 213 214 /** 215 * Tests, whether the Point {@code c} lies within the bbox. 216 * @param c point 217 * @return {@code true} if {@code c} lies within the bbox 218 */ 219 public boolean bounds(LatLon c) { 220 return xmin <= c.lon() && xmax >= c.lon() 221 && ymin <= c.lat() && ymax >= c.lat(); 222 } 223 224 /** 225 * Tests, whether two BBoxes intersect as an area. 226 * I.e. whether there exists a point that lies in both of them. 227 * @param b other bounding box 228 * @return {@code true} if this bbox intersects with the other 229 */ 230 public boolean intersects(BBox b) { 231 return xmin <= b.xmax && xmax >= b.xmin 232 && ymin <= b.ymax && ymax >= b.ymin; 233 } 234 235 /** 236 * Returns the top-left point. 237 * @return The top-left point 238 */ 239 public LatLon getTopLeft() { 240 return new LatLon(ymax, xmin); 241 } 242 243 /** 244 * Returns the latitude of top-left point. 245 * @return The latitude of top-left point 246 * @since 6203 247 */ 248 public double getTopLeftLat() { 249 return ymax; 250 } 251 252 /** 253 * Returns the longitude of top-left point. 254 * @return The longitude of top-left point 255 * @since 6203 256 */ 257 public double getTopLeftLon() { 258 return xmin; 259 } 260 261 /** 262 * Returns the bottom-right point. 263 * @return The bottom-right point 264 */ 265 public LatLon getBottomRight() { 266 return new LatLon(ymin, xmax); 267 } 268 269 /** 270 * Returns the latitude of bottom-right point. 271 * @return The latitude of bottom-right point 272 * @since 6203 273 */ 274 public double getBottomRightLat() { 275 return ymin; 276 } 277 278 /** 279 * Returns the longitude of bottom-right point. 280 * @return The longitude of bottom-right point 281 * @since 6203 282 */ 283 public double getBottomRightLon() { 284 return xmax; 285 } 286 287 /** 288 * Gets the center of this BBox. 289 * @return The center. 290 */ 291 public LatLon getCenter() { 292 return new LatLon(ymin + (ymax-ymin)/2.0, xmin + (xmax-xmin)/2.0); 293 } 294 295 byte getIndex(final int level) { 296 297 byte idx1 = QuadTiling.index(ymin, xmin, level); 298 299 final byte idx2 = QuadTiling.index(ymin, xmax, level); 300 if (idx1 == -1) idx1 = idx2; 301 else if (idx1 != idx2) return -1; 302 303 final byte idx3 = QuadTiling.index(ymax, xmin, level); 304 if (idx1 == -1) idx1 = idx3; 305 else if (idx1 != idx3) return -1; 306 307 final byte idx4 = QuadTiling.index(ymax, xmax, level); 308 if (idx1 == -1) idx1 = idx4; 309 else if (idx1 != idx4) return -1; 310 311 return idx1; 312 } 313 314 /** 315 * Converts the bounds to a rectangle 316 * @return The rectangle in east/north space. 317 */ 318 public Rectangle2D toRectangle() { 319 return new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin); 320 } 321 322 @Override 323 public int hashCode() { 324 return Objects.hash(xmin, xmax, ymin, ymax); 325 } 326 327 @Override 328 public boolean equals(Object o) { 329 if (this == o) return true; 330 if (o == null || getClass() != o.getClass()) return false; 331 BBox b = (BBox) o; 332 return Double.compare(b.xmax, xmax) == 0 && Double.compare(b.ymax, ymax) == 0 333 && Double.compare(b.xmin, xmin) == 0 && Double.compare(b.ymin, ymin) == 0; 334 } 335 336 /** 337 * @return true if the bbox covers a part of the planets surface 338 * Height and width must be non-negative, but may (both) be 0. 339 * @since 11269 340 */ 341 public boolean isValid() { 342 return xmin <= xmax && ymin <= ymax; 343 } 344 345 /** 346 * @return true if the bbox is avalid and covers a part of the planets surface 347 * @since 11269 348 */ 349 public boolean isInWorld() { 350 return xmin >= -180.0 && xmax <= 180.0 && ymin >= -90.0 && ymax <= 90.0 && isValid(); 351 } 352 353 @Override 354 public String toString() { 355 return "[ x: " + xmin + " -> " + xmax + ", y: " + ymin + " -> " + ymax + " ]"; 356 } 357 358 /** 359 * Creates a CSV string for this bbox 360 * @param separator The separator to use 361 * @return A string 362 */ 363 public String toStringCSV(String separator) { 364 return Utils.join(separator, Arrays.asList( 365 LatLon.cDdFormatter.format(xmin), 366 LatLon.cDdFormatter.format(ymin), 367 LatLon.cDdFormatter.format(xmax), 368 LatLon.cDdFormatter.format(ymax))); 369 } 370}