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

Last change on this file since 10809 was 10806, checked in by Don-vip, 8 years ago

fix #13303 - Fixes for hatched texture (modified patch by michael2402) - gsoc-core

  • Property svn:eol-style set to native
File size: 17.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
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;
9import java.util.Objects;
10import java.util.function.Consumer;
11
12import org.openstreetmap.josm.data.coor.LatLon;
13import org.openstreetmap.josm.data.osm.BBox;
14import org.openstreetmap.josm.data.projection.Projection;
15import org.openstreetmap.josm.tools.CheckParameterUtil;
16
17/**
18 * This is a simple data class for "rectangular" areas of the world, given in
19 * lat/lon min/max values. The values are rounded to LatLon.OSM_SERVER_PRECISION
20 *
21 * @author imi
22 */
23public class Bounds {
24 /**
25 * The minimum and maximum coordinates.
26 */
27 private double minLat, minLon, maxLat, maxLon;
28
29 public LatLon getMin() {
30 return new LatLon(minLat, minLon);
31 }
32
33 /**
34 * Returns min latitude of bounds. Efficient shortcut for {@code getMin().lat()}.
35 *
36 * @return min latitude of bounds.
37 * @since 6203
38 */
39 public double getMinLat() {
40 return minLat;
41 }
42
43 /**
44 * Returns min longitude of bounds. Efficient shortcut for {@code getMin().lon()}.
45 *
46 * @return min longitude of bounds.
47 * @since 6203
48 */
49 public double getMinLon() {
50 return minLon;
51 }
52
53 public LatLon getMax() {
54 return new LatLon(maxLat, maxLon);
55 }
56
57 /**
58 * Returns max latitude of bounds. Efficient shortcut for {@code getMax().lat()}.
59 *
60 * @return max latitude of bounds.
61 * @since 6203
62 */
63 public double getMaxLat() {
64 return maxLat;
65 }
66
67 /**
68 * Returns max longitude of bounds. Efficient shortcut for {@code getMax().lon()}.
69 *
70 * @return max longitude of bounds.
71 * @since 6203
72 */
73 public double getMaxLon() {
74 return maxLon;
75 }
76
77 public enum ParseMethod {
78 MINLAT_MINLON_MAXLAT_MAXLON,
79 LEFT_BOTTOM_RIGHT_TOP
80 }
81
82 /**
83 * Construct bounds out of two points. Coords will be rounded.
84 * @param min min lat/lon
85 * @param max max lat/lon
86 */
87 public Bounds(LatLon min, LatLon max) {
88 this(min.lat(), min.lon(), max.lat(), max.lon());
89 }
90
91 public Bounds(LatLon min, LatLon max, boolean roundToOsmPrecision) {
92 this(min.lat(), min.lon(), max.lat(), max.lon(), roundToOsmPrecision);
93 }
94
95 /**
96 * Constructs bounds out a single point.
97 * @param b lat/lon
98 */
99 public Bounds(LatLon b) {
100 this(b, true);
101 }
102
103 /**
104 * Single point Bounds defined by lat/lon {@code b}.
105 * Coordinates will be rounded to osm precision if {@code roundToOsmPrecision} is true.
106 *
107 * @param b lat/lon of given point.
108 * @param roundToOsmPrecision defines if lat/lon will be rounded.
109 */
110 public Bounds(LatLon b, boolean roundToOsmPrecision) {
111 this(b.lat(), b.lon(), roundToOsmPrecision);
112 }
113
114 /**
115 * Single point Bounds defined by point [lat,lon].
116 * Coordinates will be rounded to osm precision if {@code roundToOsmPrecision} is true.
117 *
118 * @param lat latitude of given point.
119 * @param lon longitude of given point.
120 * @param roundToOsmPrecision defines if lat/lon will be rounded.
121 * @since 6203
122 */
123 public Bounds(double lat, double lon, boolean roundToOsmPrecision) {
124 // Do not call this(b, b) to avoid GPX performance issue (see #7028) until roundToOsmPrecision() is improved
125 if (roundToOsmPrecision) {
126 this.minLat = LatLon.roundToOsmPrecision(lat);
127 this.minLon = LatLon.roundToOsmPrecision(lon);
128 } else {
129 this.minLat = lat;
130 this.minLon = lon;
131 }
132 this.maxLat = this.minLat;
133 this.maxLon = this.minLon;
134 }
135
136 public Bounds(double minlat, double minlon, double maxlat, double maxlon) {
137 this(minlat, minlon, maxlat, maxlon, true);
138 }
139
140 public Bounds(double minlat, double minlon, double maxlat, double maxlon, boolean roundToOsmPrecision) {
141 if (roundToOsmPrecision) {
142 this.minLat = LatLon.roundToOsmPrecision(minlat);
143 this.minLon = LatLon.roundToOsmPrecision(minlon);
144 this.maxLat = LatLon.roundToOsmPrecision(maxlat);
145 this.maxLon = LatLon.roundToOsmPrecision(maxlon);
146 } else {
147 this.minLat = minlat;
148 this.minLon = minlon;
149 this.maxLat = maxlat;
150 this.maxLon = maxlon;
151 }
152 }
153
154 public Bounds(double ... coords) {
155 this(coords, true);
156 }
157
158 public Bounds(double[] coords, boolean roundToOsmPrecision) {
159 CheckParameterUtil.ensureParameterNotNull(coords, "coords");
160 if (coords.length != 4)
161 throw new IllegalArgumentException(MessageFormat.format("Expected array of length 4, got {0}", coords.length));
162 if (roundToOsmPrecision) {
163 this.minLat = LatLon.roundToOsmPrecision(coords[0]);
164 this.minLon = LatLon.roundToOsmPrecision(coords[1]);
165 this.maxLat = LatLon.roundToOsmPrecision(coords[2]);
166 this.maxLon = LatLon.roundToOsmPrecision(coords[3]);
167 } else {
168 this.minLat = coords[0];
169 this.minLon = coords[1];
170 this.maxLat = coords[2];
171 this.maxLon = coords[3];
172 }
173 }
174
175 public Bounds(String asString, String separator) {
176 this(asString, separator, ParseMethod.MINLAT_MINLON_MAXLAT_MAXLON);
177 }
178
179 public Bounds(String asString, String separator, ParseMethod parseMethod) {
180 this(asString, separator, parseMethod, true);
181 }
182
183 public Bounds(String asString, String separator, ParseMethod parseMethod, boolean roundToOsmPrecision) {
184 CheckParameterUtil.ensureParameterNotNull(asString, "asString");
185 String[] components = asString.split(separator);
186 if (components.length != 4)
187 throw new IllegalArgumentException(
188 MessageFormat.format("Exactly four doubles expected in string, got {0}: {1}", components.length, asString));
189 double[] values = new double[4];
190 for (int i = 0; i < 4; i++) {
191 try {
192 values[i] = Double.parseDouble(components[i]);
193 } catch (NumberFormatException e) {
194 throw new IllegalArgumentException(MessageFormat.format("Illegal double value ''{0}''", components[i]), e);
195 }
196 }
197
198 switch (parseMethod) {
199 case LEFT_BOTTOM_RIGHT_TOP:
200 this.minLat = initLat(values[1], roundToOsmPrecision);
201 this.minLon = initLon(values[0], roundToOsmPrecision);
202 this.maxLat = initLat(values[3], roundToOsmPrecision);
203 this.maxLon = initLon(values[2], roundToOsmPrecision);
204 break;
205 case MINLAT_MINLON_MAXLAT_MAXLON:
206 default:
207 this.minLat = initLat(values[0], roundToOsmPrecision);
208 this.minLon = initLon(values[1], roundToOsmPrecision);
209 this.maxLat = initLat(values[2], roundToOsmPrecision);
210 this.maxLon = initLon(values[3], roundToOsmPrecision);
211 }
212 }
213
214 protected static double initLat(double value, boolean roundToOsmPrecision) {
215 if (!LatLon.isValidLat(value))
216 throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", value));
217 return roundToOsmPrecision ? LatLon.roundToOsmPrecision(value) : value;
218 }
219
220 protected static double initLon(double value, boolean roundToOsmPrecision) {
221 if (!LatLon.isValidLon(value))
222 throw new IllegalArgumentException(tr("Illegal longitude value ''{0}''", value));
223 return roundToOsmPrecision ? LatLon.roundToOsmPrecision(value) : value;
224 }
225
226 /**
227 * Creates new {@code Bounds} from an existing one.
228 * @param other The bounds to copy
229 */
230 public Bounds(final Bounds other) {
231 this(other.minLat, other.minLon, other.maxLat, other.maxLon);
232 }
233
234 public Bounds(Rectangle2D rect) {
235 this(rect.getMinY(), rect.getMinX(), rect.getMaxY(), rect.getMaxX());
236 }
237
238 /**
239 * Creates new bounds around a coordinate pair <code>center</code>. The
240 * new bounds shall have an extension in latitude direction of <code>latExtent</code>,
241 * and in longitude direction of <code>lonExtent</code>.
242 *
243 * @param center the center coordinate pair. Must not be null.
244 * @param latExtent the latitude extent. &gt; 0 required.
245 * @param lonExtent the longitude extent. &gt; 0 required.
246 * @throws IllegalArgumentException if center is null
247 * @throws IllegalArgumentException if latExtent &lt;= 0
248 * @throws IllegalArgumentException if lonExtent &lt;= 0
249 */
250 public Bounds(LatLon center, double latExtent, double lonExtent) {
251 CheckParameterUtil.ensureParameterNotNull(center, "center");
252 if (latExtent <= 0.0)
253 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 expected, got {1}", "latExtent", latExtent));
254 if (lonExtent <= 0.0)
255 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 expected, got {1}", "lonExtent", lonExtent));
256
257 this.minLat = LatLon.roundToOsmPrecision(LatLon.toIntervalLat(center.lat() - latExtent / 2));
258 this.minLon = LatLon.roundToOsmPrecision(LatLon.toIntervalLon(center.lon() - lonExtent / 2));
259 this.maxLat = LatLon.roundToOsmPrecision(LatLon.toIntervalLat(center.lat() + latExtent / 2));
260 this.maxLon = LatLon.roundToOsmPrecision(LatLon.toIntervalLon(center.lon() + lonExtent / 2));
261 }
262
263 /**
264 * Creates BBox with same coordinates.
265 *
266 * @return BBox with same coordinates.
267 * @since 6203
268 */
269 public BBox toBBox() {
270 return new BBox(minLon, minLat, maxLon, maxLat);
271 }
272
273 @Override
274 public String toString() {
275 return "Bounds["+minLat+','+minLon+','+maxLat+','+maxLon+']';
276 }
277
278 public String toShortString(DecimalFormat format) {
279 return format.format(minLat) + ' '
280 + format.format(minLon) + " / "
281 + format.format(maxLat) + ' '
282 + format.format(maxLon);
283 }
284
285 /**
286 * @return Center of the bounding box.
287 */
288 public LatLon getCenter() {
289 if (crosses180thMeridian()) {
290 double lat = (minLat + maxLat) / 2;
291 double lon = (minLon + maxLon - 360.0) / 2;
292 if (lon < -180.0) {
293 lon += 360.0;
294 }
295 return new LatLon(lat, lon);
296 } else {
297 return new LatLon((minLat + maxLat) / 2, (minLon + maxLon) / 2);
298 }
299 }
300
301 /**
302 * Extend the bounds if necessary to include the given point.
303 * @param ll The point to include into these bounds
304 */
305 public void extend(LatLon ll) {
306 extend(ll.lat(), ll.lon());
307 }
308
309 /**
310 * Extend the bounds if necessary to include the given point [lat,lon].
311 * Good to use if you know coordinates to avoid creation of LatLon object.
312 * @param lat Latitude of point to include into these bounds
313 * @param lon Longitude of point to include into these bounds
314 * @since 6203
315 */
316 public void extend(final double lat, final double lon) {
317 if (lat < minLat) {
318 minLat = LatLon.roundToOsmPrecision(lat);
319 }
320 if (lat > maxLat) {
321 maxLat = LatLon.roundToOsmPrecision(lat);
322 }
323 if (crosses180thMeridian()) {
324 if (lon > maxLon && lon < minLon) {
325 if (Math.abs(lon - minLon) <= Math.abs(lon - maxLon)) {
326 minLon = LatLon.roundToOsmPrecision(lon);
327 } else {
328 maxLon = LatLon.roundToOsmPrecision(lon);
329 }
330 }
331 } else {
332 if (lon < minLon) {
333 minLon = LatLon.roundToOsmPrecision(lon);
334 }
335 if (lon > maxLon) {
336 maxLon = LatLon.roundToOsmPrecision(lon);
337 }
338 }
339 }
340
341 public void extend(Bounds b) {
342 extend(b.minLat, b.minLon);
343 extend(b.maxLat, b.maxLon);
344 }
345
346 /**
347 * Determines if the given point {@code ll} is within these bounds.
348 * @param ll The lat/lon to check
349 * @return {@code true} if {@code ll} is within these bounds, {@code false} otherwise
350 */
351 public boolean contains(LatLon ll) {
352 if (ll.lat() < minLat || ll.lat() > maxLat)
353 return false;
354 if (crosses180thMeridian()) {
355 if (ll.lon() > maxLon && ll.lon() < minLon)
356 return false;
357 } else {
358 if (ll.lon() < minLon || ll.lon() > maxLon)
359 return false;
360 }
361 return true;
362 }
363
364 private static boolean intersectsLonCrossing(Bounds crossing, Bounds notCrossing) {
365 return notCrossing.minLon <= crossing.maxLon || notCrossing.maxLon >= crossing.minLon;
366 }
367
368 /**
369 * The two bounds intersect? Compared to java Shape.intersects, if does not use
370 * the interior but the closure. ("&gt;=" instead of "&gt;")
371 * @param b other bounds
372 * @return {@code true} if the two bounds intersect
373 */
374 public boolean intersects(Bounds b) {
375 if (b.maxLat < minLat || b.minLat > maxLat)
376 return false;
377
378 if (crosses180thMeridian() && !b.crosses180thMeridian()) {
379 return intersectsLonCrossing(this, b);
380 } else if (!crosses180thMeridian() && b.crosses180thMeridian()) {
381 return intersectsLonCrossing(b, this);
382 } else if (crosses180thMeridian() && b.crosses180thMeridian()) {
383 return true;
384 } else {
385 return b.maxLon >= minLon && b.minLon <= maxLon;
386 }
387 }
388
389 /**
390 * Determines if this Bounds object crosses the 180th Meridian.
391 * See http://wiki.openstreetmap.org/wiki/180th_meridian
392 * @return true if this Bounds object crosses the 180th Meridian.
393 */
394 public boolean crosses180thMeridian() {
395 return this.minLon > this.maxLon;
396 }
397
398 /**
399 * Converts the lat/lon bounding box to an object of type Rectangle2D.Double
400 * @return the bounding box to Rectangle2D.Double
401 */
402 public Rectangle2D.Double asRect() {
403 double w = getWidth();
404 return new Rectangle2D.Double(minLon, minLat, w, maxLat-minLat);
405 }
406
407 private double getWidth() {
408 return maxLon-minLon + (crosses180thMeridian() ? 360.0 : 0.0);
409 }
410
411 public double getArea() {
412 double w = getWidth();
413 return w * (maxLat - minLat);
414 }
415
416 public String encodeAsString(String separator) {
417 StringBuilder sb = new StringBuilder();
418 sb.append(minLat).append(separator).append(minLon)
419 .append(separator).append(maxLat).append(separator)
420 .append(maxLon);
421 return sb.toString();
422 }
423
424 /**
425 * <p>Replies true, if this bounds are <em>collapsed</em>, i.e. if the min
426 * and the max corner are equal.</p>
427 *
428 * @return true, if this bounds are <em>collapsed</em>
429 */
430 public boolean isCollapsed() {
431 return Double.doubleToLongBits(minLat) == Double.doubleToLongBits(maxLat)
432 && Double.doubleToLongBits(minLon) == Double.doubleToLongBits(maxLon);
433 }
434
435 public boolean isOutOfTheWorld() {
436 return
437 minLat < -90 || minLat > 90 ||
438 maxLat < -90 || maxLat > 90 ||
439 minLon < -180 || minLon > 180 ||
440 maxLon < -180 || maxLon > 180;
441 }
442
443 public void normalize() {
444 minLat = LatLon.toIntervalLat(minLat);
445 maxLat = LatLon.toIntervalLat(maxLat);
446 minLon = LatLon.toIntervalLon(minLon);
447 maxLon = LatLon.toIntervalLon(maxLon);
448 }
449
450 /**
451 * Visit points along the edge of this bounds instance.
452 * @param projection The projection that should be used to determine how often the edge should be split along a given corner.
453 * @param visitor A function to call for the points on the edge.
454 * @since 10806
455 */
456 public void visitEdge(Projection projection, Consumer<LatLon> visitor) {
457 double width = getWidth();
458 double height = maxLat - minLat;
459 //TODO: Use projection to see if there is any need for doing this along each axis.
460 int splitX = Math.max((int) width / 10, 10);
461 int splitY = Math.max((int) height / 10, 10);
462
463 for (int step = 0; step < splitX; step++) {
464 visitor.accept(new LatLon(minLat, minLon + width * step / splitX));
465 }
466 for (int step = 0; step < splitY; step++) {
467 visitor.accept(new LatLon(minLat + height * step / splitY, maxLon));
468 }
469 for (int step = 0; step < splitX; step++) {
470 visitor.accept(new LatLon(maxLat, maxLon - width * step / splitX));
471 }
472 for (int step = 0; step < splitY; step++) {
473 visitor.accept(new LatLon(maxLat - height * step / splitY, minLon));
474 }
475 }
476
477 @Override
478 public int hashCode() {
479 return Objects.hash(minLat, minLon, maxLat, maxLon);
480 }
481
482 @Override
483 public boolean equals(Object obj) {
484 if (this == obj) return true;
485 if (obj == null || getClass() != obj.getClass()) return false;
486 Bounds bounds = (Bounds) obj;
487 return Double.compare(bounds.minLat, minLat) == 0 &&
488 Double.compare(bounds.minLon, minLon) == 0 &&
489 Double.compare(bounds.maxLat, maxLat) == 0 &&
490 Double.compare(bounds.maxLon, maxLon) == 0;
491 }
492}
Note: See TracBrowser for help on using the repository browser.