/** * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * @author Chris Whitney * */ package org.geowebcache.grid; import java.io.Serializable; import java.text.NumberFormat; import java.util.Arrays; import java.util.Locale; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class BoundingBox implements Serializable { private static final long serialVersionUID = -2555598825074884627L; private static NumberFormat COORD_FORMATTER = NumberFormat.getNumberInstance(Locale.ENGLISH); static { COORD_FORMATTER.setMinimumFractionDigits(1); COORD_FORMATTER.setGroupingUsed(false); COORD_FORMATTER.setMaximumFractionDigits(16); } private static Log log = LogFactory.getLog(org.geowebcache.grid.BoundingBox.class); private static String DELIMITER = ","; private static double EQUALITYTHRESHOLD = 0.03; public static final BoundingBox WORLD4326 = new BoundingBox(-180.0, -90.0, 180.0, 90.0); public static final BoundingBox WORLD3857 = new BoundingBox(-20037508.34, -20037508.34, 20037508.34, 20037508.34); // minx, miny, maxx, maxy private double[] coords = new double[4]; BoundingBox() { // default constructor for XStream } public BoundingBox(BoundingBox bbox) { coords[0] = bbox.coords[0]; coords[1] = bbox.coords[1]; coords[2] = bbox.coords[2]; coords[3] = bbox.coords[3]; } public BoundingBox(String BBOX) { setFromBBOXString(BBOX, 0); if (log.isTraceEnabled()) { log.trace("Created BBOX: " + getReadableString()); } } public BoundingBox(String[] BBOX) { setFromStringArray(BBOX); if (log.isTraceEnabled()) { log.trace("Created BBOX: " + getReadableString()); } } public BoundingBox(double minx, double miny, double maxx, double maxy) { coords[0] = minx; coords[1] = miny; coords[2] = maxx; coords[3] = maxy; if (log.isTraceEnabled()) { log.trace("Created BBOX: " + getReadableString()); } } public double getMinX() { return coords[0]; } public void setMinX(double minx) { coords[0] = minx; } public double getMinY() { return coords[1]; } public void setMinY(double miny) { coords[1] = miny; } public double getMaxX() { return coords[2]; } public void setMaxX(double maxx) { coords[2] = maxx; } public double getMaxY() { return coords[3]; } public void setMaxY(double maxy) { coords[3] = maxy; } /** * @return [minx, miny, maxx, maxy] */ public double[] getCoords() { return coords.clone(); } public double getWidth() { return coords[2] - coords[0]; } public double getHeight() { return coords[3] - coords[1]; } /** * Sets from an array of strings * * @param BBOX */ public void setFromStringArray(String[] BBOX) { setFromStringArray(BBOX, 0); } public void setFromStringArray(String[] BBOX, int recWatch) { if (BBOX.length == 4) { coords[0] = Double.parseDouble(BBOX[0]); coords[1] = Double.parseDouble(BBOX[1]); coords[2] = Double.parseDouble(BBOX[2]); coords[3] = Double.parseDouble(BBOX[3]); } else if (recWatch < 4) { setFromBBOXString(BBOX[0], recWatch); } else { log.error("Doesnt understand " + Arrays.toString(BBOX)); } } /** * Parses the BBOX parameters from a comma separted value list * * @param BBOX */ public void setFromBBOXString(String BBOX, int recWatch) { String[] tokens = BBOX.split(DELIMITER); setFromStringArray(tokens, recWatch + 1); } /** * Outputs a string suitable for logging and other human-readable tasks * * @return a readable string */ public String getReadableString() { return "Min X: " + coords[0] + " Min Y: " + coords[1] + " Max X: " + coords[2] + " Max Y: " + coords[3]; } /** * Returns a comma separated value String suitable for URL output */ @Override public String toString() { StringBuilder buff = new StringBuilder(40); buff.append(COORD_FORMATTER.format(coords[0])); buff.append(','); buff.append(COORD_FORMATTER.format(coords[1])); buff.append(','); buff.append(COORD_FORMATTER.format(coords[2])); buff.append(','); buff.append(COORD_FORMATTER.format(coords[3])); return buff.toString(); } public String toKMLLatLonBox() { return "<LatLonBox>" + "<north>" + Double.toString(coords[3]) + "</north>" + "<south>" + Double.toString(coords[1]) + "</south>" + "<east>" + Double.toString(coords[2]) + "</east>" + "<west>" + Double.toString(coords[0]) + "</west>" + "</LatLonBox>"; } public String toKMLLatLonAltBox() { return "<LatLonAltBox>" + "<north>" + Double.toString(coords[3]) + "</north>" + "<south>" + Double.toString(coords[1]) + "</south>" + "<east>" + Double.toString(coords[2]) + "</east>" + "<west>" + Double.toString(coords[0]) + "</west>" + "</LatLonAltBox>"; } /** * Comparing whether the differences between the bounding boxes can be ignored. * * @param other * @return whether the boxes are equal */ @Override public boolean equals(Object obj) { if (obj != null && obj.getClass() == this.getClass()) { BoundingBox other = (BoundingBox) obj; return this.equals(other, EQUALITYTHRESHOLD); } return false; } public boolean equals(BoundingBox other, double threshold) { return Math.abs(getMinX() - other.getMinX()) < threshold && Math.abs(getMinY() - other.getMinY()) < threshold && Math.abs(getWidth() - other.getWidth()) < threshold && Math.abs(getHeight() - other.getHeight()) < threshold; } /** * Check whether this bbox contains the bbox * * @param other * @return whether other is contained by this */ public boolean contains(BoundingBox other) { return (coords[0] - EQUALITYTHRESHOLD <= other.coords[0] && coords[1] - EQUALITYTHRESHOLD <= other.coords[1] && coords[2] + EQUALITYTHRESHOLD >= other.coords[2] && coords[3] + EQUALITYTHRESHOLD >= other.coords[3]); } /** * Minimal sanity check * * @return whether min x < max x, min y < max y */ public boolean isSane() { return (coords[0] < coords[2] && coords[1] < coords[3]); } public boolean isNull() { return (coords[0] > coords[2] || coords[1] > coords[3]); } @Override public int hashCode() { return Float.floatToIntBits((float) coords[0]) ^ Float.floatToIntBits((float) coords[1]); } public boolean intersects(BoundingBox other) { if (isNull() || other.isNull()) { return false; } return !(other.getMinX() > getMaxX() || other.getMaxX() < getMinX() || other.getMinY() > getMaxY() || other.getMaxY() < getMinY()); } public BoundingBox intersection(BoundingBox bboxB) { return intersection(this, bboxB); } public static BoundingBox intersection(BoundingBox bboxA, BoundingBox bboxB) { BoundingBox retBbox = new BoundingBox(0, 0, -1, -1); if (bboxA.intersects(bboxB)) { for (int i = 0; i < 2; i++) { if (bboxA.coords[i] > bboxB.coords[i]) { retBbox.coords[i] = bboxA.coords[i]; } else { retBbox.coords[i] = bboxB.coords[i]; } } for (int i = 2; i < 4; i++) { if (bboxA.coords[i] < bboxB.coords[i]) { retBbox.coords[i] = bboxA.coords[i]; } else { retBbox.coords[i] = bboxB.coords[i]; } } } return retBbox; } public void scale(double xFactor, double yFactor) { double x = coords[2] - coords[0]; double xdiff = (x * xFactor - x) / 2; double y = coords[3] - coords[1]; double ydiff = (y * yFactor - y) / 2; coords[0] = coords[0] - xdiff; coords[1] = coords[1] - ydiff; coords[2] = coords[2] + xdiff; coords[3] = coords[3] + ydiff; } public void scale(double factor) { scale(factor, factor); } }