/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.util.shapes;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.graphhopper.util.Helper;
import com.graphhopper.util.NumHelper;
import java.util.ArrayList;
import java.util.List;
/**
* A simple bounding box defined as follows: minLon, maxLon followed by minLat which is south(!) and
* maxLat. Equally to EX_GeographicBoundingBox in the ISO 19115 standard see
* http://osgeo-org.1560.n6.nabble.com/Boundingbox-issue-for-discussion-td3875533.html
* <p>
* Nice German overview:
* http://www.geoinf.uni-jena.de/fileadmin/Geoinformatik/Lehre/Diplomarbeiten/DA_Andres.pdf
* <p>
*
* @author Peter Karich
*/
public class BBox implements Shape, Cloneable {
private final boolean elevation;
// longitude (theta) = x, latitude (phi) = y, elevation = z
public double minLon;
public double maxLon;
public double minLat;
public double maxLat;
public double minEle;
public double maxEle;
@JsonCreator
public BBox(double[] coords) {
this(coords[0],coords[2],coords[1],coords[3]);
}
public BBox(double minLon, double maxLon, double minLat, double maxLat) {
this(minLon, maxLon, minLat, maxLat, Double.NaN, Double.NaN, false);
}
public BBox(double minLon, double maxLon, double minLat, double maxLat, double minEle, double maxEle) {
this(minLon, maxLon, minLat, maxLat, minEle, maxEle, true);
}
public BBox(double minLon, double maxLon, double minLat, double maxLat, double minEle, double maxEle, boolean elevation) {
this.elevation = elevation;
this.maxLat = maxLat;
this.minLon = minLon;
this.minLat = minLat;
this.maxLon = maxLon;
this.minEle = minEle;
this.maxEle = maxEle;
}
/**
* Prefills BBox with minimum values so that it can increase.
*/
public static BBox createInverse(boolean elevation) {
if (elevation) {
return new BBox(Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, -Double.MAX_VALUE,
Double.MAX_VALUE, -Double.MAX_VALUE, true);
} else {
return new BBox(Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, -Double.MAX_VALUE,
Double.NaN, Double.NaN, false);
}
}
public boolean hasElevation() {
return elevation;
}
public void update(double lat, double lon) {
if (lat > maxLat) {
maxLat = lat;
}
if (lat < minLat) {
minLat = lat;
}
if (lon > maxLon) {
maxLon = lon;
}
if (lon < minLon) {
minLon = lon;
}
}
public void update(double lat, double lon, double elev) {
if (elevation) {
if (elev > maxEle) {
maxEle = elev;
}
if (elev < minEle) {
minEle = elev;
}
} else {
throw new IllegalStateException("No BBox with elevation to update");
}
update(lat, lon);
}
/**
* Calculates the intersecting BBox between this and the specified BBox
*
* @return the intersecting BBox or null if not intersecting
*/
public BBox calculateIntersection(BBox bBox) {
if (!this.intersect(bBox))
return null;
double minLon = Math.max(this.minLon, bBox.minLon);
double maxLon = Math.min(this.maxLon, bBox.maxLon);
double minLat = Math.max(this.minLat, bBox.minLat);
double maxLat = Math.min(this.maxLat, bBox.maxLat);
return new BBox(minLon, maxLon, minLat, maxLat);
}
@Override
public BBox clone() {
return new BBox(minLon, maxLon, minLat, maxLat, minEle, maxEle, elevation);
}
@Override
public boolean intersect(Shape s) {
if (s instanceof BBox) {
return intersect((BBox) s);
} else if (s instanceof Circle) {
return ((Circle) s).intersect(this);
}
throw new UnsupportedOperationException("unsupported shape");
}
@Override
public boolean contains(Shape s) {
if (s instanceof BBox) {
return contains((BBox) s);
} else if (s instanceof Circle) {
return contains((Circle) s);
}
throw new UnsupportedOperationException("unsupported shape");
}
public boolean intersect(Circle s) {
return ((Circle) s).intersect(this);
}
public boolean intersect(BBox o) {
// return (o.minLon < minLon && o.maxLon > minLon || o.minLon < maxLon && o.minLon >= minLon)
// && (o.maxLat < maxLat && o.maxLat >= minLat || o.maxLat >= maxLat && o.minLat < maxLat);
return minLon < o.maxLon && minLat < o.maxLat && o.minLon < maxLon && o.minLat < maxLat;
}
@Override
public boolean contains(double lat, double lon) {
return lat <= maxLat && lat >= minLat && lon <= maxLon && lon >= minLon;
}
public boolean contains(BBox b) {
return maxLat >= b.maxLat && minLat <= b.minLat && maxLon >= b.maxLon && minLon <= b.minLon;
}
public boolean contains(Circle c) {
return contains(c.getBounds());
}
@Override
public String toString() {
String str = minLon + "," + maxLon + "," + minLat + "," + maxLat;
if (elevation)
str += "," + minEle + "," + maxEle;
return str;
}
public String toLessPrecisionString() {
return (float) minLon + "," + (float) maxLon + "," + (float) minLat + "," + (float) maxLat;
}
@Override
public BBox getBounds() {
return this;
}
@Override
public GHPoint getCenter() {
return new GHPoint((maxLat + minLat) / 2, (maxLon + minLon) / 2);
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
BBox b = (BBox) obj;
// equals within a very small range
return NumHelper.equalsEps(minLat, b.minLat) && NumHelper.equalsEps(maxLat, b.maxLat)
&& NumHelper.equalsEps(minLon, b.minLon) && NumHelper.equalsEps(maxLon, b.maxLon);
}
@Override
public int hashCode() {
int hash = 3;
hash = 17 * hash + (int) (Double.doubleToLongBits(this.minLon) ^ (Double.doubleToLongBits(this.minLon) >>> 32));
hash = 17 * hash + (int) (Double.doubleToLongBits(this.maxLon) ^ (Double.doubleToLongBits(this.maxLon) >>> 32));
hash = 17 * hash + (int) (Double.doubleToLongBits(this.minLat) ^ (Double.doubleToLongBits(this.minLat) >>> 32));
hash = 17 * hash + (int) (Double.doubleToLongBits(this.maxLat) ^ (Double.doubleToLongBits(this.maxLat) >>> 32));
return hash;
}
public boolean isValid() {
// second longitude should be bigger than the first
if (minLon >= maxLon)
return false;
// second latitude should be smaller than the first
if (minLat >= maxLat)
return false;
if (elevation) {
// equal elevation is okay
if (minEle > maxEle)
return false;
if (Double.compare(maxEle, -Double.MAX_VALUE) == 0
|| Double.compare(minEle, Double.MAX_VALUE) == 0)
return false;
}
return Double.compare(maxLat, -Double.MAX_VALUE) != 0
&& Double.compare(minLat, Double.MAX_VALUE) != 0
&& Double.compare(maxLon, -Double.MAX_VALUE) != 0
&& Double.compare(minLon, Double.MAX_VALUE) != 0;
}
/**
* @return array containing this bounding box. Attention: GeoJson is lon,lat! If 3D is gets even
* worse: lon,lat,ele
*/
public List<Double> toGeoJson() {
List<Double> list = new ArrayList<Double>(4);
list.add(Helper.round6(minLon));
list.add(Helper.round6(minLat));
// hmh
if (elevation)
list.add(Helper.round2(minEle));
list.add(Helper.round6(maxLon));
list.add(Helper.round6(maxLat));
if (elevation)
list.add(Helper.round2(maxEle));
return list;
}
/**
* @return an estimated area in m^2 using the mean value of latitudes for longitude distance
*/
@Override
public double calculateArea() {
double meanLat = (maxLat + minLat) / 2;
return Helper.DIST_PLANE.calcDist(meanLat, minLon, meanLat, maxLon)
// left side should be equal to right side no mean value necessary
* Helper.DIST_PLANE.calcDist(minLat, minLon, maxLat, minLon);
}
/**
* This method creates a BBox out of a string in format lat1,lon1,lat2,lon2
*/
public static BBox parseTwoPoints(String objectAsString) {
String[] splittedObject = objectAsString.split(",");
if (splittedObject.length != 4)
throw new IllegalArgumentException("BBox should have 4 parts but was " + objectAsString);
double minLat = Double.parseDouble(splittedObject[0]);
double minLon = Double.parseDouble(splittedObject[1]);
double maxLat = Double.parseDouble(splittedObject[2]);
double maxLon = Double.parseDouble(splittedObject[3]);
if (minLat > maxLat) {
double tmp = minLat;
minLat = maxLat;
maxLat = tmp;
}
if (minLon > maxLon) {
double tmp = minLon;
minLon = maxLon;
maxLon = tmp;
}
return new BBox(minLon, maxLon, minLat, maxLat);
}
/**
* This method creates a BBox out of a string in format lon1,lon2,lat1,lat2
*/
public static BBox parseBBoxString(String objectAsString) {
String[] splittedObject = objectAsString.split(",");
if (splittedObject.length != 4)
throw new IllegalArgumentException("BBox should have 4 parts but was " + objectAsString);
double minLon = Double.parseDouble(splittedObject[0]);
double maxLon = Double.parseDouble(splittedObject[1]);
double minLat = Double.parseDouble(splittedObject[2]);
double maxLat = Double.parseDouble(splittedObject[3]);
return new BBox(minLon, maxLon, minLat, maxLat);
}
}