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

Revision 4580, 12.1 KB checked in by Don-vip, 3 months ago (diff)

see #2212 - Allow JOSM to download data crossing the 180th meridian

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