blob: 03a65a798015a08d9e3e8729c1321ad9481f201e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.lucene.geo;
import static java.lang.Math.PI;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static org.apache.lucene.geo.GeoUtils.checkLatitude;
import static org.apache.lucene.geo.GeoUtils.checkLongitude;
import static org.apache.lucene.geo.GeoUtils.MAX_LAT_INCL;
import static org.apache.lucene.geo.GeoUtils.MIN_LAT_INCL;
import static org.apache.lucene.geo.GeoUtils.MAX_LAT_RADIANS;
import static org.apache.lucene.geo.GeoUtils.MAX_LON_RADIANS;
import static org.apache.lucene.geo.GeoUtils.MIN_LAT_RADIANS;
import static org.apache.lucene.geo.GeoUtils.MIN_LON_RADIANS;
import static org.apache.lucene.geo.GeoUtils.EARTH_MEAN_RADIUS_METERS;
import static org.apache.lucene.geo.GeoUtils.sloppySin;
import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
import static org.apache.lucene.util.SloppyMath.asin;
import static org.apache.lucene.util.SloppyMath.cos;
import static org.apache.lucene.util.SloppyMath.toDegrees;
import static org.apache.lucene.util.SloppyMath.toRadians;
/** Represents a lat/lon rectangle. */
public class Rectangle extends LatLonGeometry {
/** maximum longitude value (in degrees) */
public final double minLat;
/** minimum longitude value (in degrees) */
public final double minLon;
/** maximum latitude value (in degrees) */
public final double maxLat;
/** minimum latitude value (in degrees) */
public final double maxLon;
/**
* Constructs a bounding box by first validating the provided latitude and longitude coordinates
*/
public Rectangle(double minLat, double maxLat, double minLon, double maxLon) {
GeoUtils.checkLatitude(minLat);
GeoUtils.checkLatitude(maxLat);
GeoUtils.checkLongitude(minLon);
GeoUtils.checkLongitude(maxLon);
this.minLon = minLon;
this.maxLon = maxLon;
this.minLat = minLat;
this.maxLat = maxLat;
assert maxLat >= minLat;
// NOTE: cannot assert maxLon >= minLon since this rect could cross the dateline
}
@Override
protected Component2D toComponent2D() {
return Rectangle2D.create(this);
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("Rectangle(lat=");
b.append(minLat);
b.append(" TO ");
b.append(maxLat);
b.append(" lon=");
b.append(minLon);
b.append(" TO ");
b.append(maxLon);
if (maxLon < minLon) {
b.append(" [crosses dateline!]");
}
b.append(")");
return b.toString();
}
/** Returns true if this bounding box crosses the dateline */
public boolean crossesDateline() {
return maxLon < minLon;
}
/** returns true if rectangle (defined by minLat, maxLat, minLon, maxLon) contains the lat lon point */
public static boolean containsPoint(final double lat, final double lon,
final double minLat, final double maxLat,
final double minLon, final double maxLon) {
return lat >= minLat && lat <= maxLat && lon >= minLon && lon <= maxLon;
}
/** Compute Bounding Box for a circle using WGS-84 parameters */
public static Rectangle fromPointDistance(final double centerLat, final double centerLon, final double radiusMeters) {
checkLatitude(centerLat);
checkLongitude(centerLon);
final double radLat = toRadians(centerLat);
final double radLon = toRadians(centerLon);
// LUCENE-7143
double radDistance = (radiusMeters + 7E-2) / EARTH_MEAN_RADIUS_METERS;
double minLat = radLat - radDistance;
double maxLat = radLat + radDistance;
double minLon;
double maxLon;
if (minLat > MIN_LAT_RADIANS && maxLat < MAX_LAT_RADIANS) {
double deltaLon = asin(sloppySin(radDistance) / cos(radLat));
minLon = radLon - deltaLon;
if (minLon < MIN_LON_RADIANS) {
minLon += 2d * PI;
}
maxLon = radLon + deltaLon;
if (maxLon > MAX_LON_RADIANS) {
maxLon -= 2d * PI;
}
} else {
// a pole is within the distance
minLat = max(minLat, MIN_LAT_RADIANS);
maxLat = min(maxLat, MAX_LAT_RADIANS);
minLon = MIN_LON_RADIANS;
maxLon = MAX_LON_RADIANS;
}
return new Rectangle(toDegrees(minLat), toDegrees(maxLat), toDegrees(minLon), toDegrees(maxLon));
}
/** maximum error from {@link #axisLat(double, double)}. logic must be prepared to handle this */
public static final double AXISLAT_ERROR = 0.1D / EARTH_MEAN_RADIUS_METERS * TO_DEGREES;
/**
* Calculate the latitude of a circle's intersections with its bbox meridians.
* <p>
* <b>NOTE:</b> the returned value will be +/- {@link #AXISLAT_ERROR} of the actual value.
* @param centerLat The latitude of the circle center
* @param radiusMeters The radius of the circle in meters
* @return A latitude
*/
public static double axisLat(double centerLat, double radiusMeters) {
// A spherical triangle with:
// r is the radius of the circle in radians
// l1 is the latitude of the circle center
// l2 is the latitude of the point at which the circle intersect's its bbox longitudes
// We know r is tangent to the bbox meridians at l2, therefore it is a right angle.
// So from the law of cosines, with the angle of l1 being 90, we have:
// cos(l1) = cos(r) * cos(l2) + sin(r) * sin(l2) * cos(90)
// The second part cancels out because cos(90) == 0, so we have:
// cos(l1) = cos(r) * cos(l2)
// Solving for l2, we get:
// l2 = acos( cos(l1) / cos(r) )
// We ensure r is in the range (0, PI/2) and l1 in the range (0, PI/2]. This means we
// cannot divide by 0, and we will always get a positive value in the range [0, 1) as
// the argument to arc cosine, resulting in a range (0, PI/2].
final double PIO2 = Math.PI / 2D;
double l1 = toRadians(centerLat);
double r = (radiusMeters + 7E-2) / EARTH_MEAN_RADIUS_METERS;
// if we are within radius range of a pole, the lat is the pole itself
if (Math.abs(l1) + r >= MAX_LAT_RADIANS) {
return centerLat >= 0 ? MAX_LAT_INCL : MIN_LAT_INCL;
}
// adjust l1 as distance from closest pole, to form a right triangle with bbox meridians
// and ensure it is in the range (0, PI/2]
l1 = centerLat >= 0 ? PIO2 - l1 : l1 + PIO2;
double l2 = Math.acos(Math.cos(l1) / Math.cos(r));
assert !Double.isNaN(l2);
// now adjust back to range [-pi/2, pi/2], ie latitude in radians
l2 = centerLat >= 0 ? PIO2 - l2 : l2 - PIO2;
return toDegrees(l2);
}
/** Returns the bounding box over an array of polygons */
public static Rectangle fromPolygon(Polygon[] polygons) {
// compute bounding box
double minLat = Double.POSITIVE_INFINITY;
double maxLat = Double.NEGATIVE_INFINITY;
double minLon = Double.POSITIVE_INFINITY;
double maxLon = Double.NEGATIVE_INFINITY;
for (int i = 0;i < polygons.length; i++) {
minLat = Math.min(polygons[i].minLat, minLat);
maxLat = Math.max(polygons[i].maxLat, maxLat);
minLon = Math.min(polygons[i].minLon, minLon);
maxLon = Math.max(polygons[i].maxLon, maxLon);
}
return new Rectangle(minLat, maxLat, minLon, maxLon);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Rectangle rectangle = (Rectangle) o;
if (Double.compare(rectangle.minLat, minLat) != 0) return false;
if (Double.compare(rectangle.minLon, minLon) != 0) return false;
if (Double.compare(rectangle.maxLat, maxLat) != 0) return false;
return Double.compare(rectangle.maxLon, maxLon) == 0;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(minLat);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(minLon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(maxLat);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(maxLon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}