001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.Arrays;
005
006import org.openstreetmap.josm.data.coor.LatLon;
007import org.openstreetmap.josm.data.coor.QuadTiling;
008import org.openstreetmap.josm.tools.Utils;
009
010public class BBox {
011
012    private double xmin = Double.POSITIVE_INFINITY;
013    private double xmax = Double.NEGATIVE_INFINITY;
014    private double ymin = Double.POSITIVE_INFINITY;
015    private double ymax = Double.NEGATIVE_INFINITY;
016
017    /**
018     * Constructs a new {@code BBox} defined by a single point.
019     * 
020     * @param x X coordinate
021     * @param y Y coordinate
022     * @since 6203
023     */
024    public BBox(final double x, final double y) {
025        xmax = xmin = x;
026        ymax = ymin = y;
027        sanity();
028    }
029
030    /**
031     * Constructs a new {@code BBox} defined by points <code>a</code> and <code>b</code>.
032     * Result is minimal BBox containing both points.
033     * 
034     * @param a 
035     * @param b
036     */
037    public BBox(LatLon a, LatLon b) {
038        this(a.lon(), a.lat(), b.lon(), b.lat());
039    }
040
041    /**
042     * Constructs a new {@code BBox} from another one.
043     * 
044     * @param copy the BBox to copy
045     */
046    public BBox(BBox copy) {
047        this.xmin = copy.xmin;
048        this.xmax = copy.xmax;
049        this.ymin = copy.ymin;
050        this.ymax = copy.ymax;
051    }
052
053    public BBox(double a_x, double a_y, double b_x, double b_y)  {
054        
055        if (a_x > b_x) {
056            xmax = a_x;
057            xmin = b_x;
058        } else {
059            xmax = b_x;
060            xmin = a_x;
061        }
062        
063        if (a_y > b_y) {
064            ymax = a_y;
065            ymin = b_y;
066        } else {
067            ymax = b_y;
068            ymin = a_y;
069        }
070        
071        sanity();
072    }
073
074    public BBox(Way w) {
075        for (Node n : w.getNodes()) {
076            LatLon coor = n.getCoor();
077            if (coor == null) {
078                continue;
079            }
080            add(coor);
081        }
082    }
083
084    public BBox(Node n) {
085        LatLon coor = n.getCoor();
086        if (coor == null) {
087            xmin = xmax = ymin = ymax = 0;
088        } else {
089            xmin = xmax = coor.lon();
090            ymin = ymax = coor.lat();
091        }
092    }
093
094    private void sanity()  {
095        if (xmin < -180.0) {
096            xmin = -180.0;
097        }
098        if (xmax >  180.0) {
099            xmax =  180.0;
100        }
101        if (ymin <  -90.0) {
102            ymin =  -90.0;
103        }
104        if (ymax >   90.0) {
105            ymax =   90.0;
106        }
107    }
108
109    public void add(LatLon c) {
110        add(c.lon(), c.lat());
111    }
112
113    /**
114     * Extends this bbox to include the point (x, y)
115     */
116    public void add(double x, double y) {
117        xmin = Math.min(xmin, x);
118        xmax = Math.max(xmax, x);
119        ymin = Math.min(ymin, y);
120        ymax = Math.max(ymax, y);
121        sanity();
122    }
123
124    public void add(BBox box) {
125        xmin = Math.min(xmin, box.xmin);
126        xmax = Math.max(xmax, box.xmax);
127        ymin = Math.min(ymin, box.ymin);
128        ymax = Math.max(ymax, box.ymax);
129        sanity();
130    }
131
132    public void addPrimitive(OsmPrimitive primitive, double extraSpace) {
133        BBox primBbox = primitive.getBBox();
134        add(primBbox.xmin - extraSpace, primBbox.ymin - extraSpace);
135        add(primBbox.xmax + extraSpace, primBbox.ymax + extraSpace);
136    }
137
138    public double height() {
139        return ymax-ymin;
140    }
141
142    public double width() {
143        return xmax-xmin;
144    }
145
146    /**
147     * Tests, weather the bbox b lies completely inside
148     * this bbox.
149     */
150    public boolean bounds(BBox b) {
151        if (!(xmin <= b.xmin) ||
152                !(xmax >= b.xmax) ||
153                !(ymin <= b.ymin) ||
154                !(ymax >= b.ymax))
155            return false;
156        return true;
157    }
158
159    /**
160     * Tests, weather the Point c lies within the bbox.
161     */
162    public boolean bounds(LatLon c) {
163        if ((xmin <= c.lon()) &&
164                (xmax >= c.lon()) &&
165                (ymin <= c.lat()) &&
166                (ymax >= c.lat()))
167            return true;
168        return false;
169    }
170
171    /**
172     * Tests, weather two BBoxes intersect as an area.
173     * I.e. whether there exists a point that lies in both of them.
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    @Override
263    public int hashCode() {
264        return (int)(ymin * xmin);
265    }
266
267    @Override
268    public boolean equals(Object o) {
269        if (o instanceof BBox) {
270            BBox b = (BBox)o;
271            return b.xmax == xmax && b.ymax == ymax && b.xmin == xmin && b.ymin == ymin;
272        } else
273            return false;
274    }
275
276    @Override
277    public String toString() {
278        return "[ x: " + xmin + " -> " + xmax +
279        ", y: " + ymin + " -> " + ymax + " ]";
280    }
281
282    public String toStringCSV(String separator) {
283        return Utils.join(separator, Arrays.asList(
284                LatLon.cDdFormatter.format(xmin),
285                LatLon.cDdFormatter.format(ymin),
286                LatLon.cDdFormatter.format(xmax),
287                LatLon.cDdFormatter.format(ymax)));
288    }
289}