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}