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.coor.LatLon; 009import org.openstreetmap.josm.data.coor.QuadTiling; 010import org.openstreetmap.josm.tools.Utils; 011 012public class BBox { 013 014 private double xmin = Double.POSITIVE_INFINITY; 015 private double xmax = Double.NEGATIVE_INFINITY; 016 private double ymin = Double.POSITIVE_INFINITY; 017 private double ymax = Double.NEGATIVE_INFINITY; 018 019 /** 020 * Constructs a new {@code BBox} defined by a single point. 021 * 022 * @param x X coordinate 023 * @param y Y coordinate 024 * @since 6203 025 */ 026 public BBox(final double x, final double y) { 027 xmax = xmin = x; 028 ymax = ymin = y; 029 sanity(); 030 } 031 032 /** 033 * Constructs a new {@code BBox} defined by points <code>a</code> and <code>b</code>. 034 * Result is minimal BBox containing both points. 035 * 036 * @param a first point 037 * @param b second point 038 */ 039 public BBox(LatLon a, LatLon b) { 040 this(a.lon(), a.lat(), b.lon(), b.lat()); 041 } 042 043 /** 044 * Constructs a new {@code BBox} from another one. 045 * 046 * @param copy the BBox to copy 047 */ 048 public BBox(BBox copy) { 049 this.xmin = copy.xmin; 050 this.xmax = copy.xmax; 051 this.ymin = copy.ymin; 052 this.ymax = copy.ymax; 053 } 054 055 public BBox(double ax, double ay, double bx, double by) { 056 057 if (ax > bx) { 058 xmax = ax; 059 xmin = bx; 060 } else { 061 xmax = bx; 062 xmin = ax; 063 } 064 065 if (ay > by) { 066 ymax = ay; 067 ymin = by; 068 } else { 069 ymax = by; 070 ymin = ay; 071 } 072 073 sanity(); 074 } 075 076 public BBox(Way w) { 077 for (Node n : w.getNodes()) { 078 LatLon coor = n.getCoor(); 079 if (coor == null) { 080 continue; 081 } 082 add(coor); 083 } 084 } 085 086 public BBox(Node n) { 087 LatLon coor = n.getCoor(); 088 if (coor == null) { 089 xmin = xmax = ymin = ymax = 0; 090 } else { 091 xmin = xmax = coor.lon(); 092 ymin = ymax = coor.lat(); 093 } 094 } 095 096 private void sanity() { 097 if (xmin < -180.0) { 098 xmin = -180.0; 099 } 100 if (xmax > 180.0) { 101 xmax = 180.0; 102 } 103 if (ymin < -90.0) { 104 ymin = -90.0; 105 } 106 if (ymax > 90.0) { 107 ymax = 90.0; 108 } 109 } 110 111 public final void add(LatLon c) { 112 add(c.lon(), c.lat()); 113 } 114 115 /** 116 * Extends this bbox to include the point (x, y) 117 * @param x X coordinate 118 * @param y Y coordinate 119 */ 120 public final void add(double x, double y) { 121 xmin = Math.min(xmin, x); 122 xmax = Math.max(xmax, x); 123 ymin = Math.min(ymin, y); 124 ymax = Math.max(ymax, y); 125 sanity(); 126 } 127 128 public final void add(BBox box) { 129 xmin = Math.min(xmin, box.xmin); 130 xmax = Math.max(xmax, box.xmax); 131 ymin = Math.min(ymin, box.ymin); 132 ymax = Math.max(ymax, box.ymax); 133 sanity(); 134 } 135 136 public void addPrimitive(OsmPrimitive primitive, double extraSpace) { 137 BBox primBbox = primitive.getBBox(); 138 add(primBbox.xmin - extraSpace, primBbox.ymin - extraSpace); 139 add(primBbox.xmax + extraSpace, primBbox.ymax + extraSpace); 140 } 141 142 public double height() { 143 return ymax-ymin; 144 } 145 146 public double width() { 147 return xmax-xmin; 148 } 149 150 /** 151 * Tests, whether the bbox {@code b} lies completely inside this bbox. 152 * @param b bounding box 153 * @return {@code true} if {@code b} lies completely inside this bbox 154 */ 155 public boolean bounds(BBox b) { 156 return xmin <= b.xmin && xmax >= b.xmax 157 && ymin <= b.ymin && ymax >= b.ymax; 158 } 159 160 /** 161 * Tests, whether the Point {@code c} lies within the bbox. 162 * @param c point 163 * @return {@code true} if {@code c} lies within the bbox 164 */ 165 public boolean bounds(LatLon c) { 166 return xmin <= c.lon() && xmax >= c.lon() 167 && ymin <= c.lat() && ymax >= c.lat(); 168 } 169 170 /** 171 * Tests, whether two BBoxes intersect as an area. 172 * I.e. whether there exists a point that lies in both of them. 173 * @param b other bounding box 174 * @return {@code true} if this bbox intersects with the other 175 */ 176 public boolean intersects(BBox b) { 177 if (xmin > b.xmax) 178 return false; 179 if (xmax < b.xmin) 180 return false; 181 if (ymin > b.ymax) 182 return false; 183 if (ymax < b.ymin) 184 return false; 185 return true; 186 } 187 188 /** 189 * Returns the top-left point. 190 * @return The top-left point 191 */ 192 public LatLon getTopLeft() { 193 return new LatLon(ymax, xmin); 194 } 195 196 /** 197 * Returns the latitude of top-left point. 198 * @return The latitude of top-left point 199 * @since 6203 200 */ 201 public double getTopLeftLat() { 202 return ymax; 203 } 204 205 /** 206 * Returns the longitude of top-left point. 207 * @return The longitude of top-left point 208 * @since 6203 209 */ 210 public double getTopLeftLon() { 211 return xmin; 212 } 213 214 /** 215 * Returns the bottom-right point. 216 * @return The bottom-right point 217 */ 218 public LatLon getBottomRight() { 219 return new LatLon(ymin, xmax); 220 } 221 222 /** 223 * Returns the latitude of bottom-right point. 224 * @return The latitude of bottom-right point 225 * @since 6203 226 */ 227 public double getBottomRightLat() { 228 return ymin; 229 } 230 231 /** 232 * Returns the longitude of bottom-right point. 233 * @return The longitude of bottom-right point 234 * @since 6203 235 */ 236 public double getBottomRightLon() { 237 return xmax; 238 } 239 240 public LatLon getCenter() { 241 return new LatLon(ymin + (ymax-ymin)/2.0, xmin + (xmax-xmin)/2.0); 242 } 243 244 int getIndex(final int level) { 245 246 int idx1 = QuadTiling.index(ymin, xmin, level); 247 248 final int idx2 = QuadTiling.index(ymin, xmax, level); 249 if (idx1 == -1) idx1 = idx2; 250 else if (idx1 != idx2) return -1; 251 252 final int idx3 = QuadTiling.index(ymax, xmin, level); 253 if (idx1 == -1) idx1 = idx3; 254 else if (idx1 != idx3) return -1; 255 256 final int idx4 = QuadTiling.index(ymax, xmax, level); 257 if (idx1 == -1) idx1 = idx4; 258 else if (idx1 != idx4) return -1; 259 260 return idx1; 261 } 262 263 public Rectangle2D toRectangle() { 264 return new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin); 265 } 266 267 @Override 268 public int hashCode() { 269 return Objects.hash(xmin, xmax, ymin, ymax); 270 } 271 272 @Override 273 public boolean equals(Object o) { 274 if (this == o) return true; 275 if (o == null || getClass() != o.getClass()) return false; 276 BBox b = (BBox) o; 277 return Double.compare(b.xmax, xmax) == 0 && Double.compare(b.ymax, ymax) == 0 278 && Double.compare(b.xmin, xmin) == 0 && Double.compare(b.ymin, ymin) == 0; 279 } 280 281 @Override 282 public String toString() { 283 return "[ x: " + xmin + " -> " + xmax + ", y: " + ymin + " -> " + ymax + " ]"; 284 } 285 286 public String toStringCSV(String separator) { 287 return Utils.join(separator, Arrays.asList( 288 LatLon.cDdFormatter.format(xmin), 289 LatLon.cDdFormatter.format(ymin), 290 LatLon.cDdFormatter.format(xmax), 291 LatLon.cDdFormatter.format(ymax))); 292 } 293}