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

Last change on this file since 5266 was 5235, checked in by bastiK, 12 years ago

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

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