| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.data;
|
|---|
| 3 |
|
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
|---|
| 5 |
|
|---|
| 6 | import java.awt.geom.Rectangle2D;
|
|---|
| 7 | import java.text.DecimalFormat;
|
|---|
| 8 | import java.text.MessageFormat;
|
|---|
| 9 | import java.util.Arrays;
|
|---|
| 10 |
|
|---|
| 11 | import org.openstreetmap.josm.data.coor.ILatLon;
|
|---|
| 12 | import org.openstreetmap.josm.data.coor.LatLon;
|
|---|
| 13 | import org.openstreetmap.josm.data.osm.BBox;
|
|---|
| 14 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
|---|
| 15 |
|
|---|
| 16 | /**
|
|---|
| 17 | * This is a simple data class for "rectangular" areas of the world, given in
|
|---|
| 18 | * lat/lon min/max values. The values are {@linkplain LatLon#roundToOsmPrecision(double) rounded to server precision}
|
|---|
| 19 | *
|
|---|
| 20 | * @author imi
|
|---|
| 21 | *
|
|---|
| 22 | * @see BBox to represent invalid areas.
|
|---|
| 23 | */
|
|---|
| 24 | public class Bounds implements IBounds {
|
|---|
| 25 | /**
|
|---|
| 26 | * The minimum and maximum coordinates.
|
|---|
| 27 | */
|
|---|
| 28 | private double minLat, minLon, maxLat, maxLon;
|
|---|
| 29 |
|
|---|
| 30 | /**
|
|---|
| 31 | * Gets the point that has both the minimal lat and lon coordinate
|
|---|
| 32 | * @return The point
|
|---|
| 33 | */
|
|---|
| 34 | @Override
|
|---|
| 35 | public LatLon getMin() {
|
|---|
| 36 | return new LatLon(minLat, minLon);
|
|---|
| 37 | }
|
|---|
| 38 |
|
|---|
| 39 | /**
|
|---|
| 40 | * Returns min latitude of bounds. Efficient shortcut for {@code getMin().lat()}.
|
|---|
| 41 | *
|
|---|
| 42 | * @return min latitude of bounds.
|
|---|
| 43 | * @since 6203
|
|---|
| 44 | */
|
|---|
| 45 | @Override
|
|---|
| 46 | public double getMinLat() {
|
|---|
| 47 | return minLat;
|
|---|
| 48 | }
|
|---|
| 49 |
|
|---|
| 50 | /**
|
|---|
| 51 | * Returns min longitude of bounds. Efficient shortcut for {@code getMin().lon()}.
|
|---|
| 52 | *
|
|---|
| 53 | * @return min longitude of bounds.
|
|---|
| 54 | * @since 6203
|
|---|
| 55 | */
|
|---|
| 56 | @Override
|
|---|
| 57 | public double getMinLon() {
|
|---|
| 58 | return minLon;
|
|---|
| 59 | }
|
|---|
| 60 |
|
|---|
| 61 | /**
|
|---|
| 62 | * Gets the point that has both the maximum lat and lon coordinate
|
|---|
| 63 | * @return The point
|
|---|
| 64 | */
|
|---|
| 65 | @Override
|
|---|
| 66 | public LatLon getMax() {
|
|---|
| 67 | return new LatLon(maxLat, maxLon);
|
|---|
| 68 | }
|
|---|
| 69 |
|
|---|
| 70 | /**
|
|---|
| 71 | * Returns max latitude of bounds. Efficient shortcut for {@code getMax().lat()}.
|
|---|
| 72 | *
|
|---|
| 73 | * @return max latitude of bounds.
|
|---|
| 74 | * @since 6203
|
|---|
| 75 | */
|
|---|
| 76 | @Override
|
|---|
| 77 | public double getMaxLat() {
|
|---|
| 78 | return maxLat;
|
|---|
| 79 | }
|
|---|
| 80 |
|
|---|
| 81 | /**
|
|---|
| 82 | * Returns max longitude of bounds. Efficient shortcut for {@code getMax().lon()}.
|
|---|
| 83 | *
|
|---|
| 84 | * @return max longitude of bounds.
|
|---|
| 85 | * @since 6203
|
|---|
| 86 | */
|
|---|
| 87 | @Override
|
|---|
| 88 | public double getMaxLon() {
|
|---|
| 89 | return maxLon;
|
|---|
| 90 | }
|
|---|
| 91 |
|
|---|
| 92 | /**
|
|---|
| 93 | * The method used by the {@link Bounds#Bounds(String, String, ParseMethod)} constructor
|
|---|
| 94 | */
|
|---|
| 95 | public enum ParseMethod {
|
|---|
| 96 | /**
|
|---|
| 97 | * Order: minlat, minlon, maxlat, maxlon
|
|---|
| 98 | */
|
|---|
| 99 | MINLAT_MINLON_MAXLAT_MAXLON,
|
|---|
| 100 | /**
|
|---|
| 101 | * Order: left, bottom, right, top
|
|---|
| 102 | */
|
|---|
| 103 | LEFT_BOTTOM_RIGHT_TOP
|
|---|
| 104 | }
|
|---|
| 105 |
|
|---|
| 106 | /**
|
|---|
| 107 | * Construct bounds out of two points. Coords will be rounded.
|
|---|
| 108 | * @param min min lat/lon
|
|---|
| 109 | * @param max max lat/lon
|
|---|
| 110 | */
|
|---|
| 111 | public Bounds(LatLon min, LatLon max) {
|
|---|
| 112 | this(min.lat(), min.lon(), max.lat(), max.lon());
|
|---|
| 113 | }
|
|---|
| 114 |
|
|---|
| 115 | /**
|
|---|
| 116 | * Constructs bounds out of two points.
|
|---|
| 117 | * @param min min lat/lon
|
|---|
| 118 | * @param max max lat/lon
|
|---|
| 119 | * @param roundToOsmPrecision defines if lat/lon will be rounded
|
|---|
| 120 | */
|
|---|
| 121 | public Bounds(LatLon min, LatLon max, boolean roundToOsmPrecision) {
|
|---|
| 122 | this(min.lat(), min.lon(), max.lat(), max.lon(), roundToOsmPrecision);
|
|---|
| 123 | }
|
|---|
| 124 |
|
|---|
| 125 | /**
|
|---|
| 126 | * Constructs bounds out a single point. Coords will be rounded.
|
|---|
| 127 | * @param b lat/lon
|
|---|
| 128 | */
|
|---|
| 129 | public Bounds(LatLon b) {
|
|---|
| 130 | this(b, true);
|
|---|
| 131 | }
|
|---|
| 132 |
|
|---|
| 133 | /**
|
|---|
| 134 | * Single point Bounds defined by lat/lon {@code b}.
|
|---|
| 135 | * Coordinates will be rounded to osm precision if {@code roundToOsmPrecision} is true.
|
|---|
| 136 | *
|
|---|
| 137 | * @param b lat/lon of given point.
|
|---|
| 138 | * @param roundToOsmPrecision defines if lat/lon will be rounded.
|
|---|
| 139 | */
|
|---|
| 140 | public Bounds(LatLon b, boolean roundToOsmPrecision) {
|
|---|
| 141 | this(b.lat(), b.lon(), roundToOsmPrecision);
|
|---|
| 142 | }
|
|---|
| 143 |
|
|---|
| 144 | /**
|
|---|
| 145 | * Single point Bounds defined by point [lat,lon].
|
|---|
| 146 | * Coordinates will be rounded to osm precision if {@code roundToOsmPrecision} is true.
|
|---|
| 147 | *
|
|---|
| 148 | * @param lat latitude of given point.
|
|---|
| 149 | * @param lon longitude of given point.
|
|---|
| 150 | * @param roundToOsmPrecision defines if lat/lon will be rounded.
|
|---|
| 151 | * @since 6203
|
|---|
| 152 | */
|
|---|
| 153 | public Bounds(double lat, double lon, boolean roundToOsmPrecision) {
|
|---|
| 154 | // Do not call this(b, b) to avoid GPX performance issue (see #7028) until roundToOsmPrecision() is improved
|
|---|
| 155 | if (roundToOsmPrecision) {
|
|---|
| 156 | this.minLat = LatLon.roundToOsmPrecision(lat);
|
|---|
| 157 | this.minLon = LatLon.roundToOsmPrecision(lon);
|
|---|
| 158 | } else {
|
|---|
| 159 | this.minLat = lat;
|
|---|
| 160 | this.minLon = lon;
|
|---|
| 161 | }
|
|---|
| 162 | this.maxLat = this.minLat;
|
|---|
| 163 | this.maxLon = this.minLon;
|
|---|
| 164 | }
|
|---|
| 165 |
|
|---|
| 166 | /**
|
|---|
| 167 | * Constructs bounds out of two points. Coords will be rounded.
|
|---|
| 168 | * @param minLat min lat
|
|---|
| 169 | * @param minLon min lon
|
|---|
| 170 | * @param maxLat max lat
|
|---|
| 171 | * @param maxLon max lon
|
|---|
| 172 | */
|
|---|
| 173 | public Bounds(double minLat, double minLon, double maxLat, double maxLon) {
|
|---|
| 174 | this(minLat, minLon, maxLat, maxLon, true);
|
|---|
| 175 | }
|
|---|
| 176 |
|
|---|
| 177 | /**
|
|---|
| 178 | * Constructs bounds out of two points.
|
|---|
| 179 | * @param minLat min lat
|
|---|
| 180 | * @param minLon min lon
|
|---|
| 181 | * @param maxLat max lat
|
|---|
| 182 | * @param maxLon max lon
|
|---|
| 183 | * @param roundToOsmPrecision defines if lat/lon will be rounded
|
|---|
| 184 | */
|
|---|
| 185 | public Bounds(double minLat, double minLon, double maxLat, double maxLon, boolean roundToOsmPrecision) {
|
|---|
| 186 | if (roundToOsmPrecision) {
|
|---|
| 187 | this.minLat = LatLon.roundToOsmPrecision(minLat);
|
|---|
| 188 | this.minLon = LatLon.roundToOsmPrecision(minLon);
|
|---|
| 189 | this.maxLat = LatLon.roundToOsmPrecision(maxLat);
|
|---|
| 190 | this.maxLon = LatLon.roundToOsmPrecision(maxLon);
|
|---|
| 191 | } else {
|
|---|
| 192 | this.minLat = minLat;
|
|---|
| 193 | this.minLon = minLon;
|
|---|
| 194 | this.maxLat = maxLat;
|
|---|
| 195 | this.maxLon = maxLon;
|
|---|
| 196 | }
|
|---|
| 197 | }
|
|---|
| 198 |
|
|---|
| 199 | /**
|
|---|
| 200 | * Constructs bounds out of two points. Coords will be rounded.
|
|---|
| 201 | * @param coords exactly 4 values: min lat, min lon, max lat, max lon
|
|---|
| 202 | * @throws IllegalArgumentException if coords does not contain 4 double values
|
|---|
| 203 | */
|
|---|
| 204 | public Bounds(double... coords) {
|
|---|
| 205 | this(coords, true);
|
|---|
| 206 | }
|
|---|
| 207 |
|
|---|
| 208 | /**
|
|---|
| 209 | * Constructs bounds out of two points.
|
|---|
| 210 | * @param coords exactly 4 values: min lat, min lon, max lat, max lon
|
|---|
| 211 | * @param roundToOsmPrecision defines if lat/lon will be rounded
|
|---|
| 212 | * @throws IllegalArgumentException if coords does not contain 4 double values
|
|---|
| 213 | */
|
|---|
| 214 | public Bounds(double[] coords, boolean roundToOsmPrecision) {
|
|---|
| 215 | CheckParameterUtil.ensureParameterNotNull(coords, "coords");
|
|---|
| 216 | if (coords.length != 4)
|
|---|
| 217 | throw new IllegalArgumentException(MessageFormat.format("Expected array of length 4, got {0}", coords.length));
|
|---|
| 218 | if (roundToOsmPrecision) {
|
|---|
| 219 | this.minLat = LatLon.roundToOsmPrecision(coords[0]);
|
|---|
| 220 | this.minLon = LatLon.roundToOsmPrecision(coords[1]);
|
|---|
| 221 | this.maxLat = LatLon.roundToOsmPrecision(coords[2]);
|
|---|
| 222 | this.maxLon = LatLon.roundToOsmPrecision(coords[3]);
|
|---|
| 223 | } else {
|
|---|
| 224 | this.minLat = coords[0];
|
|---|
| 225 | this.minLon = coords[1];
|
|---|
| 226 | this.maxLat = coords[2];
|
|---|
| 227 | this.maxLon = coords[3];
|
|---|
| 228 | }
|
|---|
| 229 | }
|
|---|
| 230 |
|
|---|
| 231 | /**
|
|---|
| 232 | * Parse the bounds in order {@link ParseMethod#MINLAT_MINLON_MAXLAT_MAXLON}
|
|---|
| 233 | * @param asString The string
|
|---|
| 234 | * @param separator The separation regex
|
|---|
| 235 | */
|
|---|
| 236 | public Bounds(String asString, String separator) {
|
|---|
| 237 | this(asString, separator, ParseMethod.MINLAT_MINLON_MAXLAT_MAXLON);
|
|---|
| 238 | }
|
|---|
| 239 |
|
|---|
| 240 | /**
|
|---|
| 241 | * Parse the bounds from a given string and round to OSM precision
|
|---|
| 242 | * @param asString The string
|
|---|
| 243 | * @param separator The separation regex
|
|---|
| 244 | * @param parseMethod The order of the numbers
|
|---|
| 245 | */
|
|---|
| 246 | public Bounds(String asString, String separator, ParseMethod parseMethod) {
|
|---|
| 247 | this(asString, separator, parseMethod, true);
|
|---|
| 248 | }
|
|---|
| 249 |
|
|---|
| 250 | /**
|
|---|
| 251 | * Parse the bounds from a given string
|
|---|
| 252 | * @param asString The string
|
|---|
| 253 | * @param separator The separation regex
|
|---|
| 254 | * @param parseMethod The order of the numbers
|
|---|
| 255 | * @param roundToOsmPrecision Whether to round to OSM precision
|
|---|
| 256 | */
|
|---|
| 257 | public Bounds(String asString, String separator, ParseMethod parseMethod, boolean roundToOsmPrecision) {
|
|---|
| 258 | CheckParameterUtil.ensureParameterNotNull(asString, "asString");
|
|---|
| 259 | String[] components = asString.split(separator, -1);
|
|---|
| 260 | if (components.length != 4)
|
|---|
| 261 | throw new IllegalArgumentException(
|
|---|
| 262 | MessageFormat.format("Exactly four doubles expected in string, got {0}: {1}", components.length, asString));
|
|---|
| 263 | double[] values = new double[4];
|
|---|
| 264 | for (int i = 0; i < 4; i++) {
|
|---|
| 265 | try {
|
|---|
| 266 | values[i] = Double.parseDouble(components[i]);
|
|---|
| 267 | } catch (NumberFormatException e) {
|
|---|
| 268 | throw new IllegalArgumentException(MessageFormat.format("Illegal double value ''{0}''", components[i]), e);
|
|---|
| 269 | }
|
|---|
| 270 | }
|
|---|
| 271 |
|
|---|
| 272 | switch (parseMethod) {
|
|---|
| 273 | case LEFT_BOTTOM_RIGHT_TOP:
|
|---|
| 274 | this.minLat = initLat(values[1], roundToOsmPrecision);
|
|---|
| 275 | this.minLon = initLon(values[0], roundToOsmPrecision);
|
|---|
| 276 | this.maxLat = initLat(values[3], roundToOsmPrecision);
|
|---|
| 277 | this.maxLon = initLon(values[2], roundToOsmPrecision);
|
|---|
| 278 | break;
|
|---|
| 279 | case MINLAT_MINLON_MAXLAT_MAXLON:
|
|---|
| 280 | this.minLat = initLat(values[0], roundToOsmPrecision);
|
|---|
| 281 | this.minLon = initLon(values[1], roundToOsmPrecision);
|
|---|
| 282 | this.maxLat = initLat(values[2], roundToOsmPrecision);
|
|---|
| 283 | this.maxLon = initLon(values[3], roundToOsmPrecision);
|
|---|
| 284 | }
|
|---|
| 285 | }
|
|---|
| 286 |
|
|---|
| 287 | protected static double initLat(double value, boolean roundToOsmPrecision) {
|
|---|
| 288 | if (!LatLon.isValidLat(value))
|
|---|
| 289 | throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", value));
|
|---|
| 290 | return roundToOsmPrecision ? LatLon.roundToOsmPrecision(value) : value;
|
|---|
| 291 | }
|
|---|
| 292 |
|
|---|
| 293 | protected static double initLon(double value, boolean roundToOsmPrecision) {
|
|---|
| 294 | if (!LatLon.isValidLon(value))
|
|---|
| 295 | throw new IllegalArgumentException(tr("Illegal longitude value ''{0}''", value));
|
|---|
| 296 | return roundToOsmPrecision ? LatLon.roundToOsmPrecision(value) : value;
|
|---|
| 297 | }
|
|---|
| 298 |
|
|---|
| 299 | /**
|
|---|
| 300 | * Creates new {@code Bounds} from an existing one.
|
|---|
| 301 | * @param other The bounds to copy
|
|---|
| 302 | */
|
|---|
| 303 | public Bounds(final Bounds other) {
|
|---|
| 304 | this(other.minLat, other.minLon, other.maxLat, other.maxLon);
|
|---|
| 305 | }
|
|---|
| 306 |
|
|---|
| 307 | /**
|
|---|
| 308 | * Creates new {@code Bounds} from a rectangle.
|
|---|
| 309 | * @param rect The rectangle
|
|---|
| 310 | */
|
|---|
| 311 | public Bounds(Rectangle2D rect) {
|
|---|
| 312 | this(rect.getMinY(), rect.getMinX(), rect.getMaxY(), rect.getMaxX());
|
|---|
| 313 | }
|
|---|
| 314 |
|
|---|
| 315 | /**
|
|---|
| 316 | * Creates new bounds around a coordinate pair <code>center</code>. The
|
|---|
| 317 | * new bounds shall have an extension in latitude direction of <code>latExtent</code>,
|
|---|
| 318 | * and in longitude direction of <code>lonExtent</code>.
|
|---|
| 319 | *
|
|---|
| 320 | * @param center the center coordinate pair. Must not be null.
|
|---|
| 321 | * @param latExtent the latitude extent. > 0 required.
|
|---|
| 322 | * @param lonExtent the longitude extent. > 0 required.
|
|---|
| 323 | * @throws IllegalArgumentException if center is null
|
|---|
| 324 | * @throws IllegalArgumentException if latExtent <= 0
|
|---|
| 325 | * @throws IllegalArgumentException if lonExtent <= 0
|
|---|
| 326 | */
|
|---|
| 327 | public Bounds(LatLon center, double latExtent, double lonExtent) {
|
|---|
| 328 | CheckParameterUtil.ensureParameterNotNull(center, "center");
|
|---|
| 329 | if (latExtent <= 0.0)
|
|---|
| 330 | throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 expected, got {1}", "latExtent", latExtent));
|
|---|
| 331 | if (lonExtent <= 0.0)
|
|---|
| 332 | throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 expected, got {1}", "lonExtent", lonExtent));
|
|---|
| 333 |
|
|---|
| 334 | this.minLat = LatLon.roundToOsmPrecision(LatLon.toIntervalLat(center.lat() - latExtent / 2));
|
|---|
| 335 | this.minLon = LatLon.roundToOsmPrecision(LatLon.toIntervalLon(center.lon() - lonExtent / 2));
|
|---|
| 336 | this.maxLat = LatLon.roundToOsmPrecision(LatLon.toIntervalLat(center.lat() + latExtent / 2));
|
|---|
| 337 | this.maxLon = LatLon.roundToOsmPrecision(LatLon.toIntervalLon(center.lon() + lonExtent / 2));
|
|---|
| 338 | }
|
|---|
| 339 |
|
|---|
| 340 | /**
|
|---|
| 341 | * Creates BBox with same coordinates.
|
|---|
| 342 | *
|
|---|
| 343 | * @return BBox with same coordinates.
|
|---|
| 344 | * @since 6203
|
|---|
| 345 | */
|
|---|
| 346 | public BBox toBBox() {
|
|---|
| 347 | return new BBox(minLon, minLat, maxLon, maxLat);
|
|---|
| 348 | }
|
|---|
| 349 |
|
|---|
| 350 | @Override
|
|---|
| 351 | public String toString() {
|
|---|
| 352 | return "Bounds["+minLat+','+minLon+','+maxLat+','+maxLon+']';
|
|---|
| 353 | }
|
|---|
| 354 |
|
|---|
| 355 | /**
|
|---|
| 356 | * Converts this bounds to a human readable short string
|
|---|
| 357 | * @param format The number format to use
|
|---|
| 358 | * @return The string
|
|---|
| 359 | */
|
|---|
| 360 | public String toShortString(DecimalFormat format) {
|
|---|
| 361 | return format.format(minLat) + ' '
|
|---|
| 362 | + format.format(minLon) + " / "
|
|---|
| 363 | + format.format(maxLat) + ' '
|
|---|
| 364 | + format.format(maxLon);
|
|---|
| 365 | }
|
|---|
| 366 |
|
|---|
| 367 | /**
|
|---|
| 368 | * Returns center of the bounding box.
|
|---|
| 369 | * @return Center of the bounding box.
|
|---|
| 370 | */
|
|---|
| 371 | @Override
|
|---|
| 372 | public LatLon getCenter() {
|
|---|
| 373 | if (crosses180thMeridian()) {
|
|---|
| 374 | double lat = (minLat + maxLat) / 2;
|
|---|
| 375 | double lon = (minLon + maxLon - 360.0) / 2;
|
|---|
| 376 | if (lon < -180.0) {
|
|---|
| 377 | lon += 360.0;
|
|---|
| 378 | }
|
|---|
| 379 | return new LatLon(lat, lon);
|
|---|
| 380 | } else {
|
|---|
| 381 | return new LatLon((minLat + maxLat) / 2, (minLon + maxLon) / 2);
|
|---|
| 382 | }
|
|---|
| 383 | }
|
|---|
| 384 |
|
|---|
| 385 | /**
|
|---|
| 386 | * Extend the bounds if necessary to include the given point.
|
|---|
| 387 | * @param ll The point to include into these bounds
|
|---|
| 388 | */
|
|---|
| 389 | public void extend(LatLon ll) {
|
|---|
| 390 | extend(ll.lat(), ll.lon());
|
|---|
| 391 | }
|
|---|
| 392 |
|
|---|
| 393 | /**
|
|---|
| 394 | * Extend the bounds if necessary to include the given point [lat,lon].
|
|---|
| 395 | * Good to use if you know coordinates to avoid creation of LatLon object.
|
|---|
| 396 | * @param lat Latitude of point to include into these bounds
|
|---|
| 397 | * @param lon Longitude of point to include into these bounds
|
|---|
| 398 | * @since 6203
|
|---|
| 399 | */
|
|---|
| 400 | public void extend(final double lat, final double lon) {
|
|---|
| 401 | if (lat < minLat) {
|
|---|
| 402 | minLat = LatLon.roundToOsmPrecision(lat);
|
|---|
| 403 | }
|
|---|
| 404 | if (lat > maxLat) {
|
|---|
| 405 | maxLat = LatLon.roundToOsmPrecision(lat);
|
|---|
| 406 | }
|
|---|
| 407 | if (crosses180thMeridian()) {
|
|---|
| 408 | if (lon > maxLon && lon < minLon) {
|
|---|
| 409 | if (Math.abs(lon - minLon) <= Math.abs(lon - maxLon)) {
|
|---|
| 410 | minLon = LatLon.roundToOsmPrecision(lon);
|
|---|
| 411 | } else {
|
|---|
| 412 | maxLon = LatLon.roundToOsmPrecision(lon);
|
|---|
| 413 | }
|
|---|
| 414 | }
|
|---|
| 415 | } else {
|
|---|
| 416 | if (lon < minLon) {
|
|---|
| 417 | minLon = LatLon.roundToOsmPrecision(lon);
|
|---|
| 418 | }
|
|---|
| 419 | if (lon > maxLon) {
|
|---|
| 420 | maxLon = LatLon.roundToOsmPrecision(lon);
|
|---|
| 421 | }
|
|---|
| 422 | }
|
|---|
| 423 | }
|
|---|
| 424 |
|
|---|
| 425 | /**
|
|---|
| 426 | * Extends this bounds to enclose an other bounding box
|
|---|
| 427 | * @param b The other bounds to enclose
|
|---|
| 428 | */
|
|---|
| 429 | public void extend(Bounds b) {
|
|---|
| 430 | extend(b.minLat, b.minLon);
|
|---|
| 431 | extend(b.maxLat, b.maxLon);
|
|---|
| 432 | }
|
|---|
| 433 |
|
|---|
| 434 | /**
|
|---|
| 435 | * Determines if the given point {@code ll} is within these bounds.
|
|---|
| 436 | * <p>
|
|---|
| 437 | * Points with unknown coordinates are always outside the coordinates.
|
|---|
| 438 | * @param ll The lat/lon to check
|
|---|
| 439 | * @return {@code true} if {@code ll} is within these bounds, {@code false} otherwise
|
|---|
| 440 | */
|
|---|
| 441 | public boolean contains(LatLon ll) {
|
|---|
| 442 | // binary compatibility
|
|---|
| 443 | return contains((ILatLon) ll);
|
|---|
| 444 | }
|
|---|
| 445 |
|
|---|
| 446 | /**
|
|---|
| 447 | * Determines if the given point {@code ll} is within these bounds.
|
|---|
| 448 | * <p>
|
|---|
| 449 | * Points with unknown coordinates are always outside the coordinates.
|
|---|
| 450 | * @param ll The lat/lon to check
|
|---|
| 451 | * @return {@code true} if {@code ll} is within these bounds, {@code false} otherwise
|
|---|
| 452 | * @since 12161
|
|---|
| 453 | */
|
|---|
| 454 | @Override
|
|---|
| 455 | public boolean contains(ILatLon ll) {
|
|---|
| 456 | if (!ll.isLatLonKnown()) {
|
|---|
| 457 | return false;
|
|---|
| 458 | }
|
|---|
| 459 | if (ll.lat() < minLat || ll.lat() > maxLat)
|
|---|
| 460 | return false;
|
|---|
| 461 | if (crosses180thMeridian()) {
|
|---|
| 462 | if (ll.lon() > maxLon && ll.lon() < minLon)
|
|---|
| 463 | return false;
|
|---|
| 464 | } else {
|
|---|
| 465 | if (ll.lon() < minLon || ll.lon() > maxLon)
|
|---|
| 466 | return false;
|
|---|
| 467 | }
|
|---|
| 468 | return true;
|
|---|
| 469 | }
|
|---|
| 470 |
|
|---|
| 471 | private static boolean intersectsLonCrossing(IBounds crossing, IBounds notCrossing) {
|
|---|
| 472 | return notCrossing.getMinLon() <= crossing.getMaxLon() || notCrossing.getMaxLon() >= crossing.getMinLon();
|
|---|
| 473 | }
|
|---|
| 474 |
|
|---|
| 475 | /**
|
|---|
| 476 | * The two bounds intersect? Compared to java Shape.intersects, if does not use
|
|---|
| 477 | * the interior but the closure. (">=" instead of ">")
|
|---|
| 478 | * @param b other bounds
|
|---|
| 479 | * @return {@code true} if the two bounds intersect
|
|---|
| 480 | */
|
|---|
| 481 | public boolean intersects(Bounds b) {
|
|---|
| 482 | return intersects((IBounds) b);
|
|---|
| 483 | }
|
|---|
| 484 |
|
|---|
| 485 | @Override
|
|---|
| 486 | public boolean intersects(IBounds b) {
|
|---|
| 487 | if (b.getMaxLat() < minLat || b.getMinLat() > maxLat)
|
|---|
| 488 | return false;
|
|---|
| 489 |
|
|---|
| 490 | if (crosses180thMeridian() && !b.crosses180thMeridian()) {
|
|---|
| 491 | return intersectsLonCrossing(this, b);
|
|---|
| 492 | } else if (!crosses180thMeridian() && b.crosses180thMeridian()) {
|
|---|
| 493 | return intersectsLonCrossing(b, this);
|
|---|
| 494 | } else if (crosses180thMeridian() && b.crosses180thMeridian()) {
|
|---|
| 495 | return true;
|
|---|
| 496 | } else {
|
|---|
| 497 | return b.getMaxLon() >= minLon && b.getMinLon() <= maxLon;
|
|---|
| 498 | }
|
|---|
| 499 | }
|
|---|
| 500 |
|
|---|
| 501 | /**
|
|---|
| 502 | * Determines if this Bounds object crosses the 180th Meridian.
|
|---|
| 503 | * See <a href="http://wiki.openstreetmap.org/wiki/180th_meridian">180th Meridian</a>.
|
|---|
| 504 | * @return true if this Bounds object crosses the 180th Meridian.
|
|---|
| 505 | */
|
|---|
| 506 | @Override
|
|---|
| 507 | public boolean crosses180thMeridian() {
|
|---|
| 508 | return this.minLon > this.maxLon;
|
|---|
| 509 | }
|
|---|
| 510 |
|
|---|
| 511 | /**
|
|---|
| 512 | * Converts the lat/lon bounding box to an object of type Rectangle2D.Double
|
|---|
| 513 | * @return the bounding box to Rectangle2D.Double
|
|---|
| 514 | */
|
|---|
| 515 | public Rectangle2D.Double asRect() {
|
|---|
| 516 | return new Rectangle2D.Double(minLon, minLat, getWidth(), getHeight());
|
|---|
| 517 | }
|
|---|
| 518 |
|
|---|
| 519 | /**
|
|---|
| 520 | * Returns the bounds width.
|
|---|
| 521 | * @return the bounds width
|
|---|
| 522 | * @since 14521
|
|---|
| 523 | */
|
|---|
| 524 | @Override
|
|---|
| 525 | public double getHeight() {
|
|---|
| 526 | return maxLat-minLat;
|
|---|
| 527 | }
|
|---|
| 528 |
|
|---|
| 529 | /**
|
|---|
| 530 | * Returns the bounds width.
|
|---|
| 531 | * @return the bounds width
|
|---|
| 532 | * @since 14521
|
|---|
| 533 | */
|
|---|
| 534 | @Override
|
|---|
| 535 | public double getWidth() {
|
|---|
| 536 | return maxLon-minLon + (crosses180thMeridian() ? 360.0 : 0.0);
|
|---|
| 537 | }
|
|---|
| 538 |
|
|---|
| 539 | /**
|
|---|
| 540 | * Gets the area of this bounds (in lat/lon space)
|
|---|
| 541 | * @return The area
|
|---|
| 542 | */
|
|---|
| 543 | @Override
|
|---|
| 544 | public double getArea() {
|
|---|
| 545 | return getWidth() * getHeight();
|
|---|
| 546 | }
|
|---|
| 547 |
|
|---|
| 548 | /**
|
|---|
| 549 | * Encodes this as a string so that it may be parsed using the {@link ParseMethod#MINLAT_MINLON_MAXLAT_MAXLON} order
|
|---|
| 550 | * @param separator The separator
|
|---|
| 551 | * @return The string encoded bounds
|
|---|
| 552 | */
|
|---|
| 553 | public String encodeAsString(String separator) {
|
|---|
| 554 | return minLat + separator + minLon + separator +
|
|---|
| 555 | maxLat + separator + maxLon;
|
|---|
| 556 | }
|
|---|
| 557 |
|
|---|
| 558 | /**
|
|---|
| 559 | * <p>Replies true, if this bounds are <em>collapsed</em>, i.e. if the min
|
|---|
| 560 | * and the max corner are equal.</p>
|
|---|
| 561 | *
|
|---|
| 562 | * @return true, if this bounds are <em>collapsed</em>
|
|---|
| 563 | */
|
|---|
| 564 | public boolean isCollapsed() {
|
|---|
| 565 | return Double.doubleToLongBits(minLat) == Double.doubleToLongBits(maxLat)
|
|---|
| 566 | && Double.doubleToLongBits(minLon) == Double.doubleToLongBits(maxLon);
|
|---|
| 567 | }
|
|---|
| 568 |
|
|---|
| 569 | /**
|
|---|
| 570 | * Determines if these bounds are out of the world.
|
|---|
| 571 | * @return true if lat outside of range [-90,90] or lon outside of range [-180,180]
|
|---|
| 572 | */
|
|---|
| 573 | public boolean isOutOfTheWorld() {
|
|---|
| 574 | return
|
|---|
| 575 | !LatLon.isValidLat(minLat) ||
|
|---|
| 576 | !LatLon.isValidLat(maxLat) ||
|
|---|
| 577 | !LatLon.isValidLon(minLon) ||
|
|---|
| 578 | !LatLon.isValidLon(maxLon);
|
|---|
| 579 | }
|
|---|
| 580 |
|
|---|
| 581 | /**
|
|---|
| 582 | * Clamp the bounds to be inside the world.
|
|---|
| 583 | */
|
|---|
| 584 | public void normalize() {
|
|---|
| 585 | minLat = LatLon.toIntervalLat(minLat);
|
|---|
| 586 | maxLat = LatLon.toIntervalLat(maxLat);
|
|---|
| 587 | minLon = LatLon.toIntervalLon(minLon);
|
|---|
| 588 | maxLon = LatLon.toIntervalLon(maxLon);
|
|---|
| 589 | }
|
|---|
| 590 |
|
|---|
| 591 | @Override
|
|---|
| 592 | public int hashCode() {
|
|---|
| 593 | return Arrays.hashCode(new double[] {minLat, minLon, maxLat, maxLon});
|
|---|
| 594 | }
|
|---|
| 595 |
|
|---|
| 596 | @Override
|
|---|
| 597 | public boolean equals(Object obj) {
|
|---|
| 598 | if (this == obj) return true;
|
|---|
| 599 | if (obj == null || getClass() != obj.getClass()) return false;
|
|---|
| 600 | Bounds bounds = (Bounds) obj;
|
|---|
| 601 | return Double.compare(bounds.minLat, minLat) == 0 &&
|
|---|
| 602 | Double.compare(bounds.minLon, minLon) == 0 &&
|
|---|
| 603 | Double.compare(bounds.maxLat, maxLat) == 0 &&
|
|---|
| 604 | Double.compare(bounds.maxLon, maxLon) == 0;
|
|---|
| 605 | }
|
|---|
| 606 | }
|
|---|