// License: GPL. Copyright 2007 by Immanuel Scholz and others package org.openstreetmap.josm.data; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.geom.Rectangle2D; import java.text.DecimalFormat; import java.text.MessageFormat; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.tools.CheckParameterUtil; /** * This is a simple data class for "rectangular" areas of the world, given in * lat/lon min/max values. The values are rounded to LatLon.OSM_SERVER_PRECISION * * @author imi */ public class Bounds { /** * The minimum and maximum coordinates. */ private double minLat, minLon, maxLat, maxLon; public LatLon getMin() { return new LatLon(minLat, minLon); } public LatLon getMax() { return new LatLon(maxLat, maxLon); } /** * Construct bounds out of two points */ public Bounds(LatLon min, LatLon max) { this(min.lat(), min.lon(), max.lat(), max.lon()); } public Bounds(LatLon b) { this(b, b); } public Bounds(double minlat, double minlon, double maxlat, double maxlon) { this.minLat = roundToOsmPrecision(minlat); this.minLon = roundToOsmPrecision(minlon); this.maxLat = roundToOsmPrecision(maxlat); this.maxLon = roundToOsmPrecision(maxlon); } public Bounds(double [] coords) { CheckParameterUtil.ensureParameterNotNull(coords, "coords"); if (coords.length != 4) throw new IllegalArgumentException(MessageFormat.format("Expected array of length 4, got {0}", coords.length)); this.minLat = roundToOsmPrecision(coords[0]); this.minLon = roundToOsmPrecision(coords[1]); this.maxLat = roundToOsmPrecision(coords[2]); this.maxLon = roundToOsmPrecision(coords[3]); } public Bounds(String asString, String separator) throws IllegalArgumentException { CheckParameterUtil.ensureParameterNotNull(asString, "asString"); String[] components = asString.split(separator); if (components.length != 4) throw new IllegalArgumentException(MessageFormat.format("Exactly four doubles excpected in string, got {0}", components.length)); double[] values = new double[4]; for (int i=0; i<4; i++) { try { values[i] = Double.parseDouble(components[i]); } catch(NumberFormatException e) { throw new IllegalArgumentException(MessageFormat.format("Illegal double value ''{0}''", components[i])); } } if (!LatLon.isValidLat(values[0])) throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", values[0])); if (!LatLon.isValidLon(values[1])) throw new IllegalArgumentException(tr("Illegal longitude value ''{0}''", values[1])); if (!LatLon.isValidLat(values[2])) throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", values[2])); if (!LatLon.isValidLon(values[3])) throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", values[3])); this.minLat = roundToOsmPrecision(values[0]); this.minLon = roundToOsmPrecision(values[1]); this.maxLat = roundToOsmPrecision(values[2]); this.maxLon = roundToOsmPrecision(values[3]); } public Bounds(Bounds other) { this(other.getMin(), other.getMax()); } public Bounds(Rectangle2D rect) { this(rect.getMinY(), rect.getMinX(), rect.getMaxY(), rect.getMaxX()); } /** * Creates new bounds around a coordinate pair <code>center</code>. The * new bounds shall have an extension in latitude direction of <code>latExtent</code>, * and in longitude direction of <code>lonExtent</code>. * * @param center the center coordinate pair. Must not be null. * @param latExtent the latitude extent. > 0 required. * @param lonExtent the longitude extent. > 0 required. * @throws IllegalArgumentException thrown if center is null * @throws IllegalArgumentException thrown if latExtent <= 0 * @throws IllegalArgumentException thrown if lonExtent <= 0 */ public Bounds(LatLon center, double latExtent, double lonExtent) { CheckParameterUtil.ensureParameterNotNull(center, "center"); if (latExtent <= 0.0) throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 exptected, got {1}", "latExtent", latExtent)); if (lonExtent <= 0.0) throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0.0 exptected, got {1}", "lonExtent", lonExtent)); this.minLat = roundToOsmPrecision(center.lat() - latExtent / 2); this.minLon = roundToOsmPrecision(center.lon() - lonExtent / 2); this.maxLat = roundToOsmPrecision(center.lat() + latExtent / 2); this.maxLon = roundToOsmPrecision(center.lon() + lonExtent / 2); } @Override public String toString() { return "Bounds["+minLat+","+minLon+","+maxLat+","+maxLon+"]"; } public String toShortString(DecimalFormat format) { return format.format(minLat) + " " + format.format(minLon) + " / " + format.format(maxLat) + " " + format.format(maxLon); } /** * @return Center of the bounding box. */ public LatLon getCenter() { return getMin().getCenter(getMax()); } /** * Extend the bounds if necessary to include the given point. */ public void extend(LatLon ll) { if (ll.lat() < minLat) { minLat = roundToOsmPrecision(ll.lat()); } if (ll.lon() < minLon) { minLon = roundToOsmPrecision(ll.lon()); } if (ll.lat() > maxLat) { maxLat = roundToOsmPrecision(ll.lat()); } if (ll.lon() > maxLon) { maxLon = roundToOsmPrecision(ll.lon()); } } public void extend(Bounds b) { extend(b.getMin()); extend(b.getMax()); } /** * Is the given point within this bounds? */ public boolean contains(LatLon ll) { if (ll.lat() < minLat || ll.lon() < minLon) return false; if (ll.lat() > maxLat || ll.lon() > maxLon) return false; return true; } /** * The two bounds intersect? Compared to java Shape.intersects, if does not use * the interior but the closure. (">=" instead of ">") */ public boolean intersects(Bounds b) { return b.getMax().lat() >= minLat && b.getMax().lon() >= minLon && b.getMin().lat() <= maxLat && b.getMin().lon() <= maxLon; } /** * Converts the lat/lon bounding box to an object of type Rectangle2D.Double * @return the bounding box to Rectangle2D.Double */ public Rectangle2D.Double asRect() { return new Rectangle2D.Double(minLon, minLat, maxLon-minLon, maxLat-minLat); } public double getArea() { return (maxLon - minLon) * (maxLat - minLat); } public String encodeAsString(String separator) { StringBuffer sb = new StringBuffer(); sb.append(minLat).append(separator).append(minLon) .append(separator).append(maxLat).append(separator) .append(maxLon); return sb.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(maxLat); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(maxLon); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(minLat); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(minLon); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Bounds other = (Bounds) obj; if (Double.doubleToLongBits(maxLat) != Double.doubleToLongBits(other.maxLat)) return false; if (Double.doubleToLongBits(maxLon) != Double.doubleToLongBits(other.maxLon)) return false; if (Double.doubleToLongBits(minLat) != Double.doubleToLongBits(other.minLat)) return false; if (Double.doubleToLongBits(minLon) != Double.doubleToLongBits(other.minLon)) return false; return true; } /** * Returns the value rounded to OSM precisions, i.e. to * LatLon.MAX_SERVER_PRECISION * * @return rounded value */ private double roundToOsmPrecision(double value) { return Math.round(value / LatLon.MAX_SERVER_PRECISION) * LatLon.MAX_SERVER_PRECISION; } }