source: josm/trunk/src/org/openstreetmap/josm/data/Bounds.java @ 5241

Revision 5235, 13.7 KB checked in by bastiK, 9 days ago (diff)

no rounding for projection bounds, to avoid 42 being dispayed as 41.99999999999

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Rectangle2D;
7import java.text.DecimalFormat;
8import java.text.MessageFormat;
9
10import org.openstreetmap.josm.data.coor.LatLon;
11import org.openstreetmap.josm.tools.CheckParameterUtil;
12
13/**
14 * This is a simple data class for "rectangular" areas of the world, given in
15 * lat/lon min/max values.  The values are rounded to LatLon.OSM_SERVER_PRECISION
16 *
17 * @author imi
18 */
19public class Bounds {
20    /**
21     * The minimum and maximum coordinates.
22     */
23    private double minLat, minLon, maxLat, maxLon;
24
25    public LatLon getMin() {
26        return new LatLon(minLat, minLon);
27    }
28
29    public LatLon getMax() {
30        return new LatLon(maxLat, maxLon);
31    }
32   
33    public enum ParseMethod {
34        MINLAT_MINLON_MAXLAT_MAXLON,
35        LEFT_BOTTOM_RIGHT_TOP
36    }
37   
38    /**
39     * Construct bounds out of two points
40     */
41    public Bounds(LatLon min, LatLon max) {
42        this(min.lat(), min.lon(), max.lat(), max.lon());
43    }
44
45    public Bounds(LatLon min, LatLon max, boolean roundToOsmPrecision) {
46        this(min.lat(), min.lon(), max.lat(), max.lon(), roundToOsmPrecision);
47    }
48
49    public Bounds(LatLon b) {
50        this(b, true);
51    }
52   
53    public Bounds(LatLon b, boolean roundToOsmPrecision) {
54        // Do not call this(b, b) to avoid GPX performance issue (see #7028) until roundToOsmPrecision() is improved
55        if (roundToOsmPrecision) {
56            this.minLat = LatLon.roundToOsmPrecision(b.lat());
57            this.minLon = LatLon.roundToOsmPrecision(b.lon());
58        } else {
59            this.minLat = b.lat();
60            this.minLon = b.lon();
61        }
62        this.maxLat = this.minLat;
63        this.maxLon = this.minLon;
64    }
65
66    public Bounds(double minlat, double minlon, double maxlat, double maxlon) {
67        this(minlat, minlon, maxlat, maxlon, true);
68    }
69
70    public Bounds(double minlat, double minlon, double maxlat, double maxlon, boolean roundToOsmPrecision) {
71        if (roundToOsmPrecision) {
72            this.minLat = LatLon.roundToOsmPrecision(minlat);
73            this.minLon = LatLon.roundToOsmPrecision(minlon);
74            this.maxLat = LatLon.roundToOsmPrecision(maxlat);
75            this.maxLon = LatLon.roundToOsmPrecision(maxlon);
76        } else {
77            this.minLat = minlat;
78            this.minLon = minlon;
79            this.maxLat = maxlat;
80            this.maxLon = maxlon;
81        }
82    }
83
84    public Bounds(double [] coords) {
85        this(coords, true);
86    }
87
88    public Bounds(double [] coords, boolean roundToOsmPrecision) {
89        CheckParameterUtil.ensureParameterNotNull(coords, "coords");
90        if (coords.length != 4)
91            throw new IllegalArgumentException(MessageFormat.format("Expected array of length 4, got {0}", coords.length));
92        if (roundToOsmPrecision) {
93            this.minLat = LatLon.roundToOsmPrecision(coords[0]);
94            this.minLon = LatLon.roundToOsmPrecision(coords[1]);
95            this.maxLat = LatLon.roundToOsmPrecision(coords[2]);
96            this.maxLon = LatLon.roundToOsmPrecision(coords[3]);
97        } else {
98            this.minLat = coords[0];
99            this.minLon = coords[1];
100            this.maxLat = coords[2];
101            this.maxLon = coords[3];
102        }
103    }
104
105    public Bounds(String asString, String separator) throws IllegalArgumentException {
106        this(asString, separator, ParseMethod.MINLAT_MINLON_MAXLAT_MAXLON);
107    }
108
109    public Bounds(String asString, String separator, ParseMethod parseMethod) throws IllegalArgumentException {
110        this(asString, separator, parseMethod, true);
111    }
112
113    public Bounds(String asString, String separator, ParseMethod parseMethod, boolean roundToOsmPrecision) throws IllegalArgumentException {
114        CheckParameterUtil.ensureParameterNotNull(asString, "asString");
115        String[] components = asString.split(separator);
116        if (components.length != 4)
117            throw new IllegalArgumentException(MessageFormat.format("Exactly four doubles expected in string, got {0}: {1}", components.length, asString));
118        double[] values = new double[4];
119        for (int i=0; i<4; i++) {
120            try {
121                values[i] = Double.parseDouble(components[i]);
122            } catch(NumberFormatException e) {
123                throw new IllegalArgumentException(MessageFormat.format("Illegal double value ''{0}''", components[i]));
124            }
125        }
126       
127        switch (parseMethod) {
128            case LEFT_BOTTOM_RIGHT_TOP:
129                this.minLat = initLat(values[1], roundToOsmPrecision);
130                this.minLon = initLon(values[0], roundToOsmPrecision);
131                this.maxLat = initLat(values[3], roundToOsmPrecision);
132                this.maxLon = initLon(values[2], roundToOsmPrecision);
133                break;
134            case MINLAT_MINLON_MAXLAT_MAXLON:
135            default:
136                this.minLat = initLat(values[0], roundToOsmPrecision);
137                this.minLon = initLon(values[1], roundToOsmPrecision);
138                this.maxLat = initLat(values[2], roundToOsmPrecision);
139                this.maxLon = initLon(values[3], roundToOsmPrecision);
140        }
141    }
142   
143    protected static double initLat(double value, boolean roundToOsmPrecision) {
144        if (!LatLon.isValidLat(value))
145            throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", value));
146        return roundToOsmPrecision ? LatLon.roundToOsmPrecision(value) : value;
147    }
148
149    protected static double initLon(double value, boolean roundToOsmPrecision) {
150        if (!LatLon.isValidLon(value))
151            throw new IllegalArgumentException(tr("Illegal longitude value ''{0}''", value));
152        return roundToOsmPrecision ? LatLon.roundToOsmPrecision(value) : value;
153    }
154
155    public Bounds(Bounds other) {
156        this(other.getMin(), other.getMax());
157    }
158
159    public Bounds(Rectangle2D rect) {
160        this(rect.getMinY(), rect.getMinX(), rect.getMaxY(), rect.getMaxX());
161    }
162
163    /**
164     * Creates new bounds around a coordinate pair <code>center</code>. The
165     * new bounds shall have an extension in latitude direction of <code>latExtent</code>,
166     * and in longitude direction of <code>lonExtent</code>.
167     *
168     * @param center  the center coordinate pair. Must not be null.
169     * @param latExtent the latitude extent. > 0 required.
170     * @param lonExtent the longitude extent. > 0 required.
171     * @throws IllegalArgumentException thrown if center is null
172     * @throws IllegalArgumentException thrown if latExtent <= 0
173     * @throws IllegalArgumentException thrown if lonExtent <= 0
174     */
175    public Bounds(LatLon center, double latExtent, double lonExtent) {
176        CheckParameterUtil.ensureParameterNotNull(center, "center");
177        if (latExtent <= 0.0)
178            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 exptected, got {1}", "latExtent", latExtent));
179        if (lonExtent <= 0.0)
180            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 exptected, got {1}", "lonExtent", lonExtent));
181
182        this.minLat = LatLon.roundToOsmPrecision(LatLon.toIntervalLat(center.lat() - latExtent / 2));
183        this.minLon = LatLon.roundToOsmPrecision(LatLon.toIntervalLon(center.lon() - lonExtent / 2));
184        this.maxLat = LatLon.roundToOsmPrecision(LatLon.toIntervalLat(center.lat() + latExtent / 2));
185        this.maxLon = LatLon.roundToOsmPrecision(LatLon.toIntervalLon(center.lon() + lonExtent / 2));
186    }
187
188    @Override public String toString() {
189        return "Bounds["+minLat+","+minLon+","+maxLat+","+maxLon+"]";
190    }
191
192    public String toShortString(DecimalFormat format) {
193        return
194        format.format(minLat) + " "
195        + format.format(minLon) + " / "
196        + format.format(maxLat) + " "
197        + format.format(maxLon);
198    }
199
200    /**
201     * @return Center of the bounding box.
202     */
203    public LatLon getCenter()
204    {
205        if (crosses180thMeridian()) {
206            LatLon result = new LatLon(minLat, minLon-360.0).getCenter(getMax());
207            if (result.lon() < -180.0) {
208                result.setLocation(result.lon()+360.0, result.lat());
209            }
210            return result;
211        } else {
212            return getMin().getCenter(getMax());
213        }
214    }
215
216    /**
217     * Extend the bounds if necessary to include the given point.
218     */
219    public void extend(LatLon ll) {
220        if (ll.lat() < minLat) {
221            minLat = LatLon.roundToOsmPrecision(ll.lat());
222        }
223        if (ll.lat() > maxLat) {
224            maxLat = LatLon.roundToOsmPrecision(ll.lat());
225        }
226        if (crosses180thMeridian()) {
227            if (ll.lon() > maxLon && ll.lon() < minLon) {
228                if (Math.abs(ll.lon() - minLon) <= Math.abs(ll.lon() - maxLon)) {
229                    minLon = LatLon.roundToOsmPrecision(ll.lon());
230                } else {
231                    maxLon = LatLon.roundToOsmPrecision(ll.lon());
232                }
233            }
234        } else {
235            if (ll.lon() < minLon) {
236                minLon = LatLon.roundToOsmPrecision(ll.lon());
237            }
238            if (ll.lon() > maxLon) {
239                maxLon = LatLon.roundToOsmPrecision(ll.lon());
240            }
241        }
242    }
243
244    public void extend(Bounds b) {
245        extend(b.getMin());
246        extend(b.getMax());
247    }
248   
249    /**
250     * Is the given point within this bounds?
251     */
252    public boolean contains(LatLon ll) {
253        if (ll.lat() < minLat || ll.lat() > maxLat)
254            return false;
255        if (crosses180thMeridian()) {
256            if (ll.lon() > maxLon && ll.lon() < minLon)
257                return false;
258        } else {
259            if (ll.lon() < minLon || ll.lon() > maxLon)
260                return false;
261        }
262        return true;
263    }
264
265    private static boolean intersectsLonCrossing(Bounds crossing, Bounds notCrossing) {
266        return notCrossing.minLon <= crossing.maxLon || notCrossing.maxLon >= crossing.minLon;
267    }
268   
269    /**
270     * The two bounds intersect? Compared to java Shape.intersects, if does not use
271     * the interior but the closure. (">=" instead of ">")
272     */
273    public boolean intersects(Bounds b) {
274        if (b.maxLat < minLat || b.minLat > maxLat)
275            return false;
276       
277        if (crosses180thMeridian() && !b.crosses180thMeridian()) {
278            return intersectsLonCrossing(this, b);
279        } else if (!crosses180thMeridian() && b.crosses180thMeridian()) {
280            return intersectsLonCrossing(b, this);
281        } else if (crosses180thMeridian() && b.crosses180thMeridian()) {
282            return true;
283        } else {
284            return b.maxLon >= minLon && b.minLon <= maxLon;
285        }
286    }
287
288    /**
289     * Determines if this Bounds object crosses the 180th Meridian.
290     * See http://wiki.openstreetmap.org/wiki/180th_meridian
291     * @return true if this Bounds object crosses the 180th Meridian.
292     */
293    public boolean crosses180thMeridian() {
294        return this.minLon > this.maxLon;
295    }
296   
297    /**
298     * Converts the lat/lon bounding box to an object of type Rectangle2D.Double
299     * @return the bounding box to Rectangle2D.Double
300     */
301    public Rectangle2D.Double asRect() {
302        double w = maxLon-minLon + (crosses180thMeridian() ? 360.0 : 0.0);
303        return new Rectangle2D.Double(minLon, minLat, w, maxLat-minLat);
304    }
305
306    public double getArea() {
307        double w = maxLon-minLon + (crosses180thMeridian() ? 360.0 : 0.0);
308        return w * (maxLat - minLat);
309    }
310
311    public String encodeAsString(String separator) {
312        StringBuffer sb = new StringBuffer();
313        sb.append(minLat).append(separator).append(minLon)
314        .append(separator).append(maxLat).append(separator)
315        .append(maxLon);
316        return sb.toString();
317    }
318
319    /**
320     * <p>Replies true, if this bounds are <em>collapsed</em>, i.e. if the min
321     * and the max corner are equal.</p>
322     *
323     * @return true, if this bounds are <em>collapsed</em>
324     */
325    public boolean isCollapsed() {
326        return getMin().equals(getMax());
327    }
328
329    public boolean isOutOfTheWorld() {
330        return
331        minLat < -90 || minLat > 90 ||
332        maxLat < -90 || maxLat > 90 ||
333        minLon < -180 || minLon > 180 ||
334        maxLon < -180 || maxLon > 180;
335    }
336
337    public void normalize() {
338        minLat = LatLon.toIntervalLat(minLat);
339        maxLat = LatLon.toIntervalLat(maxLat);
340        minLon = LatLon.toIntervalLon(minLon);
341        maxLon = LatLon.toIntervalLon(maxLon);
342    }
343
344    @Override
345    public int hashCode() {
346        final int prime = 31;
347        int result = 1;
348        long temp;
349        temp = Double.doubleToLongBits(maxLat);
350        result = prime * result + (int) (temp ^ (temp >>> 32));
351        temp = Double.doubleToLongBits(maxLon);
352        result = prime * result + (int) (temp ^ (temp >>> 32));
353        temp = Double.doubleToLongBits(minLat);
354        result = prime * result + (int) (temp ^ (temp >>> 32));
355        temp = Double.doubleToLongBits(minLon);
356        result = prime * result + (int) (temp ^ (temp >>> 32));
357        return result;
358    }
359
360    @Override
361    public boolean equals(Object obj) {
362        if (this == obj)
363            return true;
364        if (obj == null)
365            return false;
366        if (getClass() != obj.getClass())
367            return false;
368        Bounds other = (Bounds) obj;
369        if (Double.doubleToLongBits(maxLat) != Double.doubleToLongBits(other.maxLat))
370            return false;
371        if (Double.doubleToLongBits(maxLon) != Double.doubleToLongBits(other.maxLon))
372            return false;
373        if (Double.doubleToLongBits(minLat) != Double.doubleToLongBits(other.minLat))
374            return false;
375        if (Double.doubleToLongBits(minLon) != Double.doubleToLongBits(other.minLon))
376            return false;
377        return true;
378    }
379}
Note: See TracBrowser for help on using the repository browser.