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 ax, double ay, double bx, double by) { 055 056 if (ax > bx) { 057 xmax = ax; 058 xmin = bx; 059 } else { 060 xmax = bx; 061 xmin = ax; 062 } 063 064 if (ay > by) { 065 ymax = ay; 066 ymin = by; 067 } else { 068 ymax = by; 069 ymin = ay; 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 return xmin <= b.xmin && xmax >= b.xmax 156 && ymin <= b.ymin && ymax >= b.ymax; 157 } 158 159 /** 160 * Tests, whether the Point {@code c} lies within the bbox. 161 * @param c point 162 * @return {@code true} if {@code c} lies within the bbox 163 */ 164 public boolean bounds(LatLon c) { 165 return xmin <= c.lon() && xmax >= c.lon() 166 && ymin <= c.lat() && ymax >= c.lat(); 167 } 168 169 /** 170 * Tests, whether two BBoxes intersect as an area. 171 * I.e. whether there exists a point that lies in both of them. 172 * @param b other bounding box 173 * @return {@code true} if this bbox intersects with the other 174 */ 175 public boolean intersects(BBox b) { 176 if (xmin > b.xmax) 177 return false; 178 if (xmax < b.xmin) 179 return false; 180 if (ymin > b.ymax) 181 return false; 182 if (ymax < b.ymin) 183 return false; 184 return true; 185 } 186 187 /** 188 * Returns the top-left point. 189 * @return The top-left point 190 */ 191 public LatLon getTopLeft() { 192 return new LatLon(ymax, xmin); 193 } 194 195 /** 196 * Returns the latitude of top-left point. 197 * @return The latitude of top-left point 198 * @since 6203 199 */ 200 public double getTopLeftLat() { 201 return ymax; 202 } 203 204 /** 205 * Returns the longitude of top-left point. 206 * @return The longitude of top-left point 207 * @since 6203 208 */ 209 public double getTopLeftLon() { 210 return xmin; 211 } 212 213 /** 214 * Returns the bottom-right point. 215 * @return The bottom-right point 216 */ 217 public LatLon getBottomRight() { 218 return new LatLon(ymin, xmax); 219 } 220 221 /** 222 * Returns the latitude of bottom-right point. 223 * @return The latitude of bottom-right point 224 * @since 6203 225 */ 226 public double getBottomRightLat() { 227 return ymin; 228 } 229 230 /** 231 * Returns the longitude of bottom-right point. 232 * @return The longitude of bottom-right point 233 * @since 6203 234 */ 235 public double getBottomRightLon() { 236 return xmax; 237 } 238 239 public LatLon getCenter() { 240 return new LatLon(ymin + (ymax-ymin)/2.0, xmin + (xmax-xmin)/2.0); 241 } 242 243 int getIndex(final int level) { 244 245 int idx1 = QuadTiling.index(ymin, xmin, level); 246 247 final int idx2 = QuadTiling.index(ymin, xmax, level); 248 if (idx1 == -1) idx1 = idx2; 249 else if (idx1 != idx2) return -1; 250 251 final int idx3 = QuadTiling.index(ymax, xmin, level); 252 if (idx1 == -1) idx1 = idx3; 253 else if (idx1 != idx3) return -1; 254 255 final int idx4 = QuadTiling.index(ymax, xmax, level); 256 if (idx1 == -1) idx1 = idx4; 257 else if (idx1 != idx4) return -1; 258 259 return idx1; 260 } 261 262 public Rectangle2D toRectangle() { 263 return new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin); 264 } 265 266 @Override 267 public int hashCode() { 268 return (int) (ymin * xmin); 269 } 270 271 @Override 272 public boolean equals(Object o) { 273 if (o instanceof BBox) { 274 BBox b = (BBox) o; 275 return b.xmax == xmax && b.ymax == ymax 276 && b.xmin == xmin && b.ymin == ymin; 277 } else 278 return false; 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}