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}