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