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

Last change on this file since 5228 was 4580, checked in by Don-vip, 12 years ago

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

  • Property svn:eol-style set to native
File size: 12.1 KB
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.