ucar.unidata.geoloc.LatLonRect Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cdm Show documentation
Show all versions of cdm Show documentation
The NetCDF-Java Library is a Java interface to NetCDF files,
as well as to many other types of scientific data formats.
The newest version!
/*
* Copyright 1998-2014 University Corporation for Atmospheric Research/Unidata
*
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package ucar.unidata.geoloc;
import com.google.common.base.Preconditions;
import ucar.unidata.util.Format;
import java.util.StringTokenizer;
/**
* Bounding box for latitude/longitude points. This is a rectangle
* in lat/lon coordinates.
* This class handles the longitude wrapping problem.
* Note that LatLonPoint always has lon in the range +/-180.
*
* @author Russ Rew
* @author John Caron
*/
public class LatLonRect {
/**
* Inverse of LatLon.toString().
* @param s "ll: 63.45S 180.0W+ ur: 74.65N 180.0E"
* @see #LatLonRect(LatLonPoint p1, double deltaLat, double deltaLon)
*
static public LatLonRect parse(String s) {
StringTokenizer stoker = new StringTokenizer( spec, " ,");
int n = stoker.countTokens();
if (n != 4) throw new IllegalArgumentException("Must be 4 numbers = lat, lon, latWidth, lonWidth");
double lat = Double.parseDouble(stoker.nextToken());
double lon = Double.parseDouble(stoker.nextToken());
double deltaLat = Double.parseDouble(stoker.nextToken());
double deltaLon = Double.parseDouble(stoker.nextToken());
init(new LatLonPointImpl(lat, lon), deltaLat, deltaLon);
} */
public static LatLonRect INVALID = new LatLonRect(LatLonPointImmutable.INVALID, LatLonPointImmutable.INVALID);
/**
* upper right corner
*/
private LatLonPointImpl upperRight;
/**
* lower left corner
*/
private LatLonPointImpl lowerLeft;
/**
* flag for dateline cross
*/
private boolean crossDateline = false;
/**
* All longitudes are included
*/
private boolean allLongitude = false;
/**
* width and initial longitude
*/
private double width, lon0;
/**
* Construct a lat/lon bounding box from a point, and a delta lat, lon.
* This disambiguates which way the box wraps around the globe.
*
* @param p1 one corner of the box
* @param deltaLat delta lat from p1. (may be positive or negetive)
* @param deltaLon delta lon from p1. (may be positive or negetive)
*/
public LatLonRect(LatLonPoint p1, double deltaLat, double deltaLon) {
init(p1, deltaLat, deltaLon);
}
private void init(LatLonPoint p1, double deltaLat, double deltaLon) {
double lonmin, lonmax;
double latmin = Math.min(p1.getLatitude(), p1.getLatitude() + deltaLat);
double latmax = Math.max(p1.getLatitude(), p1.getLatitude() + deltaLat);
double lonpt = p1.getLongitude();
if (deltaLon > 0) {
lonmin = lonpt;
lonmax = lonpt + deltaLon;
crossDateline = (lonmax > 180.0);
} else {
lonmax = lonpt;
lonmin = lonpt + deltaLon;
crossDateline = (lonmin < -180.0);
}
this.lowerLeft = new LatLonPointImpl(latmin, lonmin);
this.upperRight = new LatLonPointImpl(latmax, lonmax);
// these are an alternative way to view the longitude range
this.width = Math.abs(deltaLon);
this.lon0 = LatLonPointImpl.lonNormal(p1.getLongitude() + deltaLon / 2);
this.allLongitude = (this.width >= 360.0);
}
/**
* Construct a lat/lon bounding box from two points.
* The order of longitude coord of the two points matters:
* pt1.lon is always the "left" point, then points contained within the box
* increase (unless crossing the Dateline, in which case they jump to -180, but
* then start increasing again) until pt2.lon
* The order of lat doesnt matter: smaller will go to "lower" point (further south)
*
* @param left left corner
* @param right right corner
*/
public LatLonRect(LatLonPoint left, LatLonPoint right) {
this(left, right.getLatitude() - left.getLatitude(),
LatLonPointImpl.lonNormal360(right.getLongitude() - left.getLongitude()));
}
/**
* Construct a lat/lon bounding box from a string.
* @param spec "lat, lon, deltaLat, deltaLon"
* @see #LatLonRect(LatLonPoint p1, double deltaLat, double deltaLon)
*/
public LatLonRect(String spec) {
StringTokenizer stoker = new StringTokenizer( spec, " ,");
int n = stoker.countTokens();
if (n != 4) throw new IllegalArgumentException("Must be 4 numbers = lat, lon, latWidth, lonWidth");
double lat = Double.parseDouble(stoker.nextToken());
double lon = Double.parseDouble(stoker.nextToken());
double deltaLat = Double.parseDouble(stoker.nextToken());
double deltaLon = Double.parseDouble(stoker.nextToken());
init(new LatLonPointImpl(lat, lon), deltaLat, deltaLon);
}
/**
* Copy Constructor
*
* @param r rectangle to copy
*/
public LatLonRect(LatLonRect r) {
this(r.getLowerLeftPoint(),
r.getUpperRightPoint().getLatitude()
- r.getLowerLeftPoint().getLatitude(), r.getWidth());
}
/**
* Create a LatLonRect that covers the whole world.
*/
public LatLonRect() {
this(new LatLonPointImpl(-90, -180), 180, 360);
}
/**
* Get the upper right corner of the bounding box.
*
* @return upper right corner of the bounding box
*/
public LatLonPointImpl getUpperRightPoint() {
return upperRight;
}
/**
* Get the lower left corner of the bounding box.
*
* @return lower left corner of the bounding box
*/
public LatLonPointImpl getLowerLeftPoint() {
return lowerLeft;
}
/**
* Get the upper left corner of the bounding box.
*
* @return upper left corner of the bounding box
*/
public LatLonPointImpl getUpperLeftPoint() {
return new LatLonPointImpl(upperRight.getLatitude(),
lowerLeft.getLongitude());
}
/**
* Get the lower left corner of the bounding box.
*
* @return lower left corner of the bounding box
*/
public LatLonPointImpl getLowerRightPoint() {
return new LatLonPointImpl(lowerLeft.getLatitude(),
upperRight.getLongitude());
}
/**
* Get whether the bounding box crosses the +/- 180 seam
*
* @return true if the bounding box crosses the +/- 180 seam
*/
public boolean crossDateline() {
return crossDateline;
}
/**
* determine whether two bounding boxes are equal in values
*
* @param other other bounding box
* @return true if this represents the same bounding box as other
*/
public boolean equals(LatLonRect other) {
return lowerLeft.equals(other.getLowerLeftPoint())
&& upperRight.equals(other.getUpperRightPoint());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return equals((LatLonRect) o);
}
@Override
public int hashCode() {
int result = upperRight.hashCode();
result = 31 * result + lowerLeft.hashCode();
return result;
}
/**
* return width of bounding box, always between 0 and 360 degrees.
*
* @return width of bounding box in degrees longitude
*/
public double getWidth() {
return width;
}
/**
* return height of bounding box, always between 0 and 180 degrees.
*
* @return height of bounding box in degrees latitude
*/
public double getHeight() {
return getLatMax() - getLatMin();
}
/**
* return center Longitude, always in the range +/-180
*
* @return center Longitude
*/
public double getCenterLon() {
return lon0;
}
/**
* Get minimum longitude, aka "west" edge
*
* @return minimum longitude
*/
public double getLonMin() {
return lowerLeft.getLongitude();
}
/**
* Get maximum longitude, aka "east" edge
*
* @return maximum longitude
*/
public double getLonMax() {
return lowerLeft.getLongitude() + width;
}
/**
* Get minimum latitude, aka "south" edge
*
* @return minimum latitude
*/
public double getLatMin() {
return lowerLeft.getLatitude();
}
/**
* Get maximum latitude, aka "north" edge
*
* @return maximum latitude
*/
public double getLatMax() {
return upperRight.getLatitude();
}
/**
* Determine if a specified LatLonPoint is contained in this bounding box.
*
* @param p the specified point to be tested
* @return true if point is contained in this bounding box
*/
public boolean contains(LatLonPoint p) {
return contains(p.getLatitude(), p.getLongitude());
}
/**
* Determine if the given lat/lon point is contined inside this rectangle.
*
* @param lat lat of point
* @param lon lon of point
* @return true if the given lat/lon point is contined inside this rectangle
*/
public boolean contains(double lat, double lon) {
// check lat first
if ((lat + eps < lowerLeft.getLatitude())
|| (lat - eps > upperRight.getLatitude())) {
return false;
}
if (allLongitude)
return true;
if (crossDateline) {
// bounding box crosses the +/- 180 seam
return ((lon >= lowerLeft.getLongitude()) || (lon <= upperRight.getLongitude()));
} else {
// check "normal" lon case
return ((lon >= lowerLeft.getLongitude()) && (lon <= upperRight.getLongitude()));
}
}
private double eps = 1.0e-9;
/**
* Determine if this bounding box is contained in another LatLonRect.
*
* @param b the other box to see if it contains this one
* @return true if b contained in this bounding box
*/
public boolean containedIn(LatLonRect b) {
return (b.getWidth() >= width) && b.contains(upperRight)
&& b.contains(lowerLeft);
}
/*
* Determine if a specified LatLonRect intersects this
* @param b the specified box to be tested
*
* @param p
* @return true if b intersects this bounding box
*
* public boolean intersects(LatLonRect b) {
* if (b.getUpperRightPoint().getLatitude() < lowerLeft.getLatitude())
* return false;
* if (b.getLowerLeftPoint().getLatitude() > upperRight.getLatitude())
* return false;
*
* double blon0 = b.getCenterLon();
* double normal = (blon0 + lon0) / 2;
* if (Math.abs(blon0-lon0) > 180.0)
* normal += 180;
* blon0 = LatLonPoint.lonNormal(blon0, normal);
* double mylon0 = LatLonPoint.lonNormal(lon0, normal);
*
* if (blon0 + b.getWidth() < mylon0 - width)
* return false;
* if (blon0 - b.getWidth() > mylon0 + width)
* return false;
*
* return true;
* }
*/
/**
* Extend the bounding box to contain this point
*
* @param p point to include
*/
public void extend(LatLonPoint p) {
if (contains(p))
return;
double lat = p.getLatitude();
double lon = p.getLongitude();
// lat is easy to deal with
if (lat > upperRight.getLatitude()) {
upperRight.setLatitude(lat);
}
if (lat < lowerLeft.getLatitude()) {
lowerLeft.setLatitude(lat);
}
// lon is uglier
if (allLongitude) {
// do nothing
} else if (crossDateline) {
// bounding box crosses the +/- 180 seam
double d1 = lon - upperRight.getLongitude();
double d2 = lowerLeft.getLongitude() - lon;
if ((d1 > 0.0) && (d2 > 0.0)) { // needed ?
if (d1 > d2) {
lowerLeft.setLongitude(lon);
} else {
upperRight.setLongitude(lon);
}
}
} else {
// normal case
if (lon > upperRight.getLongitude()) {
if (lon - upperRight.getLongitude() > lowerLeft.getLongitude() - lon + 360) {
crossDateline = true;
lowerLeft.setLongitude(lon);
} else {
upperRight.setLongitude(lon);
}
} else if (lon < lowerLeft.getLongitude()) {
if (lowerLeft.getLongitude() - lon > lon + 360.0 - upperRight.getLongitude()) {
crossDateline = true;
upperRight.setLongitude(lon);
} else {
lowerLeft.setLongitude(lon);
}
}
}
// recalc delta, center
width = upperRight.getLongitude() - lowerLeft.getLongitude();
lon0 = (upperRight.getLongitude() + lowerLeft.getLongitude()) / 2;
if (crossDateline) {
width += 360;
lon0 -= 180;
}
this.allLongitude = this.allLongitude || (this.width >= 360.0);
}
/**
* Extend the bounding box to contain the given rectangle
*
* @param r rectangle to include
*/
public void extend(LatLonRect r) {
Preconditions.checkNotNull(r);
// lat is easy
double latMin = r.getLatMin();
double latMax = r.getLatMax();
if (latMax > upperRight.getLatitude()) {
upperRight.setLatitude(latMax);
}
if (latMin < lowerLeft.getLatitude()) {
lowerLeft.setLatitude(latMin);
}
// lon is uglier
if (allLongitude)
return;
// everything is reletive to current LonMin
double lonMin = getLonMin();
double lonMax = getLonMax();
double nlonMin = LatLonPointImpl.lonNormal( r.getLonMin(), lonMin);
double nlonMax = nlonMin + r.getWidth();
lonMin = Math.min(lonMin, nlonMin);
lonMax = Math.max(lonMax, nlonMax);
width = lonMax - lonMin;
allLongitude = width >= 360.0;
if (allLongitude) {
width = 360.0;
lonMin = -180.0;
} else {
lonMin = LatLonPointImpl.lonNormal(lonMin);
}
lowerLeft.setLongitude(lonMin);
upperRight.setLongitude(lonMin+width);
lon0 = lonMin+width/2;
crossDateline = lowerLeft.getLongitude() > upperRight.getLongitude();
}
/**
* Create the instersection of this LatLon with the given one
*
* @param clip intersect with this
* @return intersection, or null if there is no intersection
*/
public LatLonRect intersect(LatLonRect clip) {
double latMin = Math.max(getLatMin(), clip.getLatMin());
double latMax = Math.min(getLatMax(), clip.getLatMax());
double deltaLat = latMax - latMin;
if (deltaLat < 0)
return null;
// lon as always is a pain : if not intersection, try +/- 360
double lon1min = getLonMin();
double lon1max = getLonMax();
double lon2min = clip.getLonMin();
double lon2max = clip.getLonMax();
if (!intersect(lon1min, lon1max, lon2min, lon2max)) {
lon2min = clip.getLonMin() + 360;
lon2max = clip.getLonMax() + 360;
if (!intersect(lon1min, lon1max, lon2min, lon2max)) {
lon2min = clip.getLonMin() - 360;
lon2max = clip.getLonMax() - 360;
}
}
// we did our best to find an intersection
double lonMin = Math.max(lon1min, lon2min);
double lonMax = Math.min(lon1max, lon2max);
double deltaLon = lonMax - lonMin;
if (deltaLon < 0)
return null;
return new LatLonRect(new LatLonPointImpl(latMin, lonMin), deltaLat, deltaLon);
}
private boolean intersect(double min1, double max1, double min2, double max2) {
double min = Math.max(min1, min2);
double max = Math.min(max1, max2);
return min < max;
}
/**
* Return a String representation of this object.
* eg: ll: 90.0S .0E+ ur: 90.0N .0E
*
* @return a String representation of this object.
*/
public String toString() {
return " ll: " + lowerLeft + "+ ur: " + upperRight;
}
/**
* Return a String representation of this object.
* lat= [-90.00,90.00] lon= [0.00,360.00
*
* @return a String representation of this object.
*/
public String toString2() {
return " lat= [" + Format.dfrac(getLatMin(), 2) + "," + Format.dfrac(getLatMax(), 2) +
"] lon= [" + Format.dfrac(getLonMin(), 2) + "," + Format.dfrac(getLonMax(), 2) + "]";
}
/* public static void main(String args[]) {
// ll: 63.45S 180.0W+ ur: 74.65N 180.0E does not contains point 74.65N 50.26E
LatLonRect rect = LatLonRect.parse("ll: 63.45S 180.0W+ ur: 74.65N 180.0E");
LatLonPointImpl pt = LatLonPointImpl.parse("74.65N 50.26E");
} */
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy