/* * Copyright (c) 1998 - 2009. 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.projection.proj4; import ucar.nc2.constants.CDM; import ucar.nc2.constants.CF; import ucar.unidata.geoloc.*; import ucar.unidata.util.Parameter; import java.util.Formatter; /** * Adapted from com.jhlabs.map.proj.AlbersProjection * * @see "http://www.jhlabs.com/java/maps/proj/index.html" * @see "http://trac.osgeo.org/proj/" * * @author caron * @since Oct 8, 2009 */ public class AlbersEqualAreaEllipse extends ProjectionImpl { private final static double EPS10 = 1.e-10; private final static double TOL7 = 1.e-7; private final static int N_ITER = 15; private final static double EPSILON = 1.0e-7; private final static double TOL = 1.0e-10; // projection parameters private double lat0deg, lon0deg; // projection origin, degrees private double lat0rad, lon0rad; // projection origin, radians private double par1deg, par2deg; // standard parellels, degrees private double phi1, phi2; // standard parellels, radians private double falseEasting, falseNorthing; // km // earth shape private Earth earth; private double e; // earth.getEccentricitySquared private double es; // earth.getEccentricitySquared private double one_es; // 1-es private double totalScale; // scale to convert cartesian coords in km private double ec; private double n; private double c; private double dd; private double n2; private double rho0; @Override public ProjectionImpl constructCopy() { ProjectionImpl result = new AlbersEqualAreaEllipse(getOriginLat(), getOriginLon(), getParallelOne(), getParallelTwo(), getFalseEasting(), getFalseNorthing(), getEarth()); result.setDefaultMapArea(defaultMapArea); result.setName(name); return result; } /** * Constructor with default parameters */ public AlbersEqualAreaEllipse() { this(23.0, -96.0, 29.5, 45.5, 0, 0, new Earth(6378137.0, 0.0, 298.257222101)); } /** * Construct a AlbersEqualAreaEllipse Projection, two standard parellels. * For the one standard parellel case, set them both to the same value. * * @param lat0 lat origin of the coord. system on the projection plane * @param lon0 lon origin of the coord. system on the projection plane * @param par1 standard parallel 1 * @param par2 standard parallel 2 * @param falseEasting false easting in km * @param falseNorthing false easting in km * @param earth shape of the earth * @throws IllegalArgumentException if Math.abs(par1 + par2) < 1.e-10 */ public AlbersEqualAreaEllipse(double lat0, double lon0, double par1, double par2, double falseEasting, double falseNorthing, Earth earth) { super("AlbersEqualAreaEllipse", false); this.lat0deg = lat0; this.lon0deg = lon0; this.lat0rad = Math.toRadians(lat0); this.lon0rad = Math.toRadians(lat0); this.par1deg = par1; this.par2deg = par2; this.phi1 = Math.toRadians(par1); this.phi2 = Math.toRadians(par2); this.falseEasting = falseEasting; this.falseNorthing = falseNorthing; this.earth = earth; this.e = earth.getEccentricity(); this.es = earth.getEccentricitySquared(); this.one_es = 1.0-es; this.totalScale = earth.getMajor() * .001; // scale factor for cartesion coords in km. precalculate(); addParameter(CF.GRID_MAPPING_NAME, CF.ALBERS_CONICAL_EQUAL_AREA); addParameter(CF.LATITUDE_OF_PROJECTION_ORIGIN, lat0); addParameter(CF.LONGITUDE_OF_CENTRAL_MERIDIAN, lon0); if (par2 == par1) { addParameter(CF.STANDARD_PARALLEL, par1); } else { double[] data = new double[2]; data[0] = par1; data[1] = par2; addParameter(new Parameter(CF.STANDARD_PARALLEL, data)); } if ((falseEasting != 0.0) || (falseNorthing != 0.0)) { addParameter(CF.FALSE_EASTING, falseEasting); addParameter(CF.FALSE_NORTHING, falseNorthing); addParameter(CDM.UNITS, "km"); } addParameter(CF.SEMI_MAJOR_AXIS, earth.getMajor()); addParameter(CF.INVERSE_FLATTENING, 1.0/earth.getFlattening()); } private void precalculate() { if (Math.abs(phi1 + phi2) < EPS10) throw new IllegalArgumentException("Math.abs(par1 + par2) < 1.e-10"); double sinphi = Math.sin(phi1); n = sinphi; double cosphi = Math.cos(phi1); boolean secant = Math.abs(phi1 - phi2) >= EPS10; if (!earth.isSpherical()) { // not spherical LOOK CHANGE // if (!(P->en = pj_enfn(P->es))) E_ERROR_0; ?? if ((MapMath.enfn(es)) == null) throw new IllegalArgumentException("0"); double m1 = MapMath.msfn(sinphi, cosphi, es); double ml1 = MapMath.qsfn(sinphi, e, one_es); if (secant) { /* secant cone */ sinphi = Math.sin(phi2); cosphi = Math.cos(phi2); double m2 = MapMath.msfn(sinphi, cosphi, es); double ml2 = MapMath.qsfn(sinphi, e, one_es); n = (m1 * m1 - m2 * m2) / (ml2 - ml1); } ec = 1. - .5 * one_es * Math.log((1. - e) / (1. + e)) / e; c = m1 * m1 + n * ml1; dd = 1. / n; rho0 = dd * Math.sqrt(c - n * MapMath.qsfn(Math.sin(lat0rad), e, one_es)); } else { // sphere if (secant) n = .5 * (n + Math.sin(phi2)); n2 = n + n; c = cosphi * cosphi + n2 * sinphi; dd = 1. / n; rho0 = dd * Math.sqrt(c - n2 * Math.sin(lat0rad)); } } // public AlbersEqualAreaEllipse(double lat0, double lon0, double par1, double par2, double falseEasting, double falseNorthing, Earth earth) { @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AlbersEqualAreaEllipse that = (AlbersEqualAreaEllipse) o; if (Double.compare(that.falseEasting, falseEasting) != 0) return false; if (Double.compare(that.falseNorthing, falseNorthing) != 0) return false; if (Double.compare(that.lat0deg, lat0deg) != 0) return false; if (Double.compare(that.lon0deg, lon0deg) != 0) return false; if (Double.compare(that.par1deg, par1deg) != 0) return false; if (Double.compare(that.par2deg, par2deg) != 0) return false; return earth.equals(that.earth); } @Override public int hashCode() { int result; long temp; temp = Double.doubleToLongBits(lat0deg); result = (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(lon0deg); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(par1deg); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(par2deg); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(falseEasting); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(falseNorthing); result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + earth.hashCode(); return result; } /* * Check for equality with the Object in question * * @param proj object to check * @return true if they are equal * public boolean equals2(Object proj) { if (!(proj instanceof AlbersEqualAreaEllipse)) { return false; } AlbersEqualAreaEllipse oo = (AlbersEqualAreaEllipse) proj; if ((this.getDefaultMapArea() == null) != (oo.defaultMapArea == null)) return false; // common case is that these are null if (this.getDefaultMapArea() != null && !this.defaultMapArea.equals(oo.defaultMapArea)) return false; return ((this.getParallelOne() == oo.getParallelOne()) && (this.getParallelTwo() == oo.getParallelTwo()) && (this.getOriginLat() == oo.getOriginLat()) && (this.getOriginLon() == oo.getOriginLon()) && this.earth.equals(oo.earth)); } */ // bean properties public Earth getEarth() { return earth; } /** * Get the second standard parallel * * @return the second standard parallel in degrees */ public double getParallelTwo() { return par2deg; } /** * Get the first standard parallel * * @return the first standard parallel in degrees */ public double getParallelOne() { return par1deg; } /** * Get the origin longitude. * * @return the origin longitude in degrees */ public double getOriginLon() { return lon0deg; } /** * Get the origin latitude. * * @return the origin latitude in degrees */ public double getOriginLat() { return lat0deg; } /** * Get the false easting, in km. * * @return the false easting in km */ public double getFalseEasting() { return falseEasting; } /** * Get the false northing, in km. * * @return the false northing in km */ public double getFalseNorthing() { return falseNorthing; } /** * Get the label to be used in the gui for this type of projection * * @return Type label */ public String getProjectionTypeLabel() { return "Albers Equal Area Ellipsoidal Earth"; } /** * Create a String of the parameters. * * @return a String of the parameters */ public String paramsToString() { Formatter f = new Formatter(); f.format("origin lat,lon=%f,%f parellels=%f,%f earth=%s", lat0deg, lon0deg, par1deg, par2deg, earth ); return f.toString(); } /** * This returns true when the line between pt1 and pt2 crosses the seam. * When the cone is flattened, the "seam" is lon0 +- 180. * * @param pt1 point 1 * @param pt2 point 2 * @return true when the line between pt1 and pt2 crosses the seam. */ public boolean crossSeam(ProjectionPoint pt1, ProjectionPoint pt2) { if (ProjectionPointImpl.isInfinite(pt1) || ProjectionPointImpl.isInfinite(pt2)) { return true; } return false; /* opposite signed X values, larger then 5000 km LOOK ???? return (pt1.getX() * pt2.getX() < 0) && (Math.abs(pt1.getX() - pt2.getX()) > 5000.0); */ } /* * Convert a LatLonPoint to projection coordinates * * @param latLon convert from these lat, lon coordinates * @param result the object to write to * @return the given result * public ProjectionPoint latLonToProj(LatLonPoint latLon, ProjectionPointImpl result) { double toX, toY; double fromLat = latLon.getLatitude(); double fromLon = latLon.getLongitude(); fromLat = Math.toRadians(fromLat); fromLon = Math.toRadians(fromLon); double rho = computeRho(fromLat); double theta = computeTheta(fromLon); toX = rho * Math.sin(theta) + falseEasting; toY = rho0 - rho * Math.cos(theta) + falseNorthing; result.setLocation(toX, toY); return result; } /* public Point2D.Double project(double lplam, double lpphi, Point2D.Double out) { double rho; if ((rho = c - (!spherical ? n * MapMath.qsfn(Math.sin(lpphi), e, one_es) : n2 * Math.sin(lpphi))) < 0.) throw new ProjectionException("F"); rho = dd * Math.sqrt(rho); out.x = rho * Math.sin( lplam *= n ); out.y = rho0 - rho * Math.cos(lplam); return out; } */ private double computeTheta(double lon) { double dlon = LatLonPointImpl.lonNormal(lon - lon0deg); return n * Math.toRadians(dlon); } // also see Snyder p 101 public ProjectionPoint latLonToProj(LatLonPoint latLon, ProjectionPointImpl result) { double fromLat = Math.toRadians(latLon.getLatitude()); double theta = computeTheta(latLon.getLongitude()); double term = earth.isSpherical() ? n2 * Math.sin(fromLat) : n * MapMath.qsfn(Math.sin(fromLat), e, one_es); double rho = c - term; if (rho < 0.0) throw new RuntimeException("F"); rho = dd * Math.sqrt(rho); double toX = rho * Math.sin(theta); double toY = rho0 - rho * Math.cos(theta); result.setLocation(totalScale * toX + falseEasting, totalScale * toY + falseNorthing); return result; } /** * Convert projection coordinates to a LatLonPoint * Note: a new object is not created on each call for the return value. * * @param world convert from these projection coordinates * @param result the object to write to * @return LatLonPoint convert to these lat/lon coordinates * public LatLonPoint projToLatLon(ProjectionPoint world, LatLonPointImpl result) { double toLat, toLon; double fromX = world.getX() - falseEasting; double fromY = world.getY() - falseNorthing; double rrho0 = rho0; if (n < 0) { rrho0 *= -1.0; fromX *= -1.0; fromY *= -1.0; } double yd = rrho0 - fromY; double rho = Math.sqrt(fromX * fromX + yd * yd); double theta = Math.atan2(fromX, yd); if (n < 0) { rho *= -1.0; } toLat = Math.toDegrees(Math.asin((C - Math.pow((rho * n / EARTH_RADIUS), 2)) / (2 * n))); toLon = Math.toDegrees(theta / n + lon0); result.setLatitude(toLat); result.setLongitude(toLon); return result; } public Point2D.Double projectInverse(double xyx, double xyy, Point2D.Double out) { double rho; if ((rho = MapMath.distance(xyx, xyy = rho0 - xyy)) != 0) { double lpphi, lplam; if (n < 0.) { rho = -rho; xyx = -xyx; xyy = -xyy; } lpphi = rho / dd; if (!spherical) { lpphi = (c - lpphi * lpphi) / n; if (Math.abs(ec - Math.abs(lpphi)) > TOL7) { if ((lpphi = phi1_(lpphi, e, one_es)) == Double.MAX_VALUE) throw new ProjectionException("I"); } else lpphi = lpphi < 0. ? -MapMath.HALFPI : MapMath.HALFPI; } else if (Math.abs(out.y = (c - lpphi * lpphi) / n2) <= 1.) lpphi = Math.asin(lpphi); else lpphi = lpphi < 0. ? -MapMath.HALFPI : MapMath.HALFPI; lplam = Math.atan2(xyx, xyy) / n; out.x = lplam; out.y = lpphi; } else { out.x = 0.; out.y = n > 0. ? MapMath.HALFPI : - MapMath.HALFPI; } return out; } */ public LatLonPoint projToLatLon(ProjectionPoint world, LatLonPointImpl result) { double toLat, toLon; double fromX = (world.getX() - falseEasting) / totalScale; // assumes cartesion coords in km double fromY = (world.getY() - falseNorthing) / totalScale; fromY = rho0 - fromY; double rho = MapMath.distance(fromX, fromY); if (rho == 0.0) { toLon = 0.0; toLat = n > 0.0 ? MapMath.HALFPI : -MapMath.HALFPI; } else { if (n < 0.0) { rho = -rho; fromX = -fromX; fromY = -fromY; } double lpphi = rho / dd; if (!earth.isSpherical()) { lpphi = (c - lpphi * lpphi) / n; if (Math.abs(ec - Math.abs(lpphi)) > TOL7) { if (Math.abs(lpphi) > 2.0) throw new IllegalArgumentException("AlbersEqualAreaEllipse x,y="+world); lpphi = phi1_(lpphi, e, one_es); if (lpphi == Double.MAX_VALUE) throw new RuntimeException("I"); } else { lpphi = (lpphi < 0.) ? -MapMath.HALFPI : MapMath.HALFPI; } } else { // spherical case lpphi = (c - lpphi * lpphi) / n2; if (Math.abs(lpphi) <= 1.0) { lpphi = Math.asin(lpphi); } else { lpphi = (lpphi < 0.) ? -MapMath.HALFPI : MapMath.HALFPI; } } toLon = Math.atan2(fromX, fromY) / n; //coverity[swapped_arguments] toLat = lpphi; } result.setLatitude(Math.toDegrees(toLat)); result.setLongitude(Math.toDegrees(toLon) + lon0deg); return result; } private static double phi1_(double qs, double Te, double Tone_es) { double phi, sinpi, cospi, con, com, dphi; phi = Math.asin(.5 * qs); if (Te < EPSILON) return (phi); int countIter = N_ITER; do { sinpi = Math.sin(phi); cospi = Math.cos(phi); con = Te * sinpi; com = 1. - con * con; dphi = .5 * com * com / cospi * (qs / Tone_es - sinpi / com + .5 / Te * Math.log((1. - con) / (1. + con))); phi += dphi; } while (Math.abs(dphi) > TOL && --countIter != 0); return (countIter != 0 ? phi : Double.MAX_VALUE); } /* proj +inv +proj=aea +lat_0=23.0 +lat_1=29.5 +lat_2=45.5 +a=6378137.0 +rf=298.257222101 +b=6356752.31414 +lon_0=-96.0 Input X,Y: 1730692.593817677 1970917.991173046 results in: Output Lon,Lat: -75.649278 39.089117 * */ private static void toProj(ProjectionImpl p, double lat, double lon) { System.out.printf("lon,lat = %f %f%n", lon, lat); ProjectionPoint pt = p.latLonToProj(lat, lon); System.out.printf("x,y = %f %f%n", pt.getX(), pt.getY()); LatLonPoint ll = p.projToLatLon(pt); System.out.printf("lon,lat = %f %f%n%n", ll.getLongitude(), ll.getLatitude()); } private static void fromProj(ProjectionImpl p, double x, double y) { System.out.printf("x,y = %f %f%n", x,y); LatLonPoint ll = p.projToLatLon(x, y); System.out.printf("lon,lat = %f %f%n", ll.getLongitude(), ll.getLatitude()); ProjectionPoint pt = p.latLonToProj(ll); System.out.printf("x,y = %f %f%n%n", pt.getX(), pt.getY()); } public static void main(String[] args) { AlbersEqualAreaEllipse a = new AlbersEqualAreaEllipse(23.0, -96.0, 29.5, 45.5, 0, 0, new Earth(6378137.0, 0.0, 298.257222101)); System.out.printf("proj = %s %s%n%n", a.getName(), a.paramsToString()); //fromProj(a, 1730.692593817677, 1970.917991173046); //toProj(a, 39.089117, -75.649278); fromProj(a, 5747, 13470); } }