ucar.unidata.geoloc.projection.proj4.EquidistantAzimuthalProjection Maven / Gradle / Ivy
The newest version!
/*
Copyright 2006 Jerry Huxtable
Licensed 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.
*/
/*
* This file was semi-automatically converted from the public-domain USGS PROJ source.
*/
package ucar.unidata.geoloc.projection.proj4;
import java.util.Objects;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.unidata.geoloc.*;
/**
* AzimuthalEquidistant Projection.
* Port from proj4.
*/
public class EquidistantAzimuthalProjection extends ProjectionImpl {
public final static int NORTH_POLE = 1;
public final static int SOUTH_POLE = 2;
public final static int EQUATOR = 3;
public final static int OBLIQUE = 4;
private final static double TOL = 1.e-8;
private double lat0, lon0; // degrees
private double projectionLatitude, projectionLongitude; // radians
private double falseEasting;
private double falseNorthing;
private Earth earth;
private double e, es, one_es;
private double totalScale;
private int mode;
private double[] en;
private double N1;
private double Mp;
private double He;
private double G;
private double sinphi0, cosphi0;
public EquidistantAzimuthalProjection() {
this(90, 0, 0, 0, new Earth());
}
public EquidistantAzimuthalProjection(double lat0, double lon0, double falseEasting, double falseNorthing, Earth earth) {
super("EquidistantAzimuthalProjection", false);
Objects.requireNonNull(earth, "Azimuthal equidistant constructor requires non-null Earth");
this.lat0 = lat0;
this.lon0 = lon0;
this.projectionLatitude = Math.toRadians(lat0);
this.projectionLongitude = Math.toRadians(lon0);
this.falseEasting = falseEasting;
this.falseNorthing = falseNorthing;
this.earth = earth;
this.e = earth.getEccentricity();
this.es = earth.getEccentricitySquared();
this.one_es = 1 - es;
this.totalScale = earth.getMajor() * .001; // scale factor for cartesion coords in km.
addParameter(CF.GRID_MAPPING_NAME, CF.AZIMUTHAL_EQUIDISTANT);
addParameter(CF.LATITUDE_OF_PROJECTION_ORIGIN, lat0);
addParameter(CF.LONGITUDE_OF_CENTRAL_MERIDIAN, lon0);
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());
initialize();
}
@Override
public ProjectionImpl constructCopy() {
ProjectionImpl result = new EquidistantAzimuthalProjection(lat0, lon0, falseEasting, falseNorthing, earth);
result.setDefaultMapArea(defaultMapArea);
result.setName(name);
return result;
}
private void initialize() {
if (Math.abs(Math.abs(projectionLatitude) - MapMath.HALFPI) < MapMath.EPS10) {
mode = projectionLatitude < 0. ? SOUTH_POLE : NORTH_POLE;
sinphi0 = projectionLatitude < 0. ? -1. : 1.;
cosphi0 = 0.;
} else if (Math.abs(projectionLatitude) < MapMath.EPS10) {
mode = EQUATOR;
sinphi0 = 0.;
cosphi0 = 1.;
} else {
mode = OBLIQUE;
sinphi0 = Math.sin(projectionLatitude);
cosphi0 = Math.cos(projectionLatitude);
}
if (!earth.isSpherical()) {
en = MapMath.enfn(es);
switch (mode) {
case NORTH_POLE:
Mp = MapMath.mlfn(MapMath.HALFPI, 1., 0., en);
break;
case SOUTH_POLE:
Mp = MapMath.mlfn(-MapMath.HALFPI, -1., 0., en);
break;
case EQUATOR:
case OBLIQUE:
N1 = 1. / Math.sqrt(1. - es * sinphi0 * sinphi0);
G = sinphi0 * (He = e / Math.sqrt(one_es));
He *= cosphi0;
break;
}
}
}
@Override
public ProjectionPoint latLonToProj(LatLonPoint latlon, ProjectionPointImpl xy) {
double lam = Math.toRadians(latlon.getLongitude() - lon0);
double phi = Math.toRadians(latlon.getLatitude());
if (earth.isSpherical()) {
double coslam, cosphi, sinphi;
sinphi = Math.sin(phi);
cosphi = Math.cos(phi);
coslam = Math.cos(lam);
switch (mode) {
case EQUATOR:
case OBLIQUE:
if (mode == EQUATOR)
xy.setY(cosphi * coslam);
else
xy.setY(sinphi0 * sinphi + cosphi0 * cosphi * coslam);
if (Math.abs(Math.abs(xy.getY()) - 1.) < TOL) {
if (xy.getY() < 0.)
throw new IllegalStateException();
else
xy.setLocation(0, 0);
} else {
double y = Math.acos(xy.getY());
y /= Math.sin(y);
double x = y * cosphi * Math.sin(lam);
y *= (mode == EQUATOR) ? sinphi : cosphi0 * sinphi - sinphi0 * cosphi * coslam;
xy.setLocation(x, y);
}
break;
case NORTH_POLE:
phi = -phi;
coslam = -coslam;
case SOUTH_POLE:
if (Math.abs(phi - MapMath.HALFPI) < MapMath.EPS10)
throw new IllegalStateException();
double y = (MapMath.HALFPI + phi);
double x = y * Math.sin(lam);
y *= coslam;
xy.setLocation(x, y);
break;
}
} else {
double coslam, cosphi, sinphi, rho, s, H, H2, c, Az, t, ct, st, cA, sA;
coslam = Math.cos(lam);
cosphi = Math.cos(phi);
sinphi = Math.sin(phi);
switch (mode) {
case NORTH_POLE:
coslam = -coslam;
//coverity[missing_break]
case SOUTH_POLE:
double x = (rho = Math.abs(Mp - MapMath.mlfn(phi, sinphi, cosphi, en))) * Math.sin(lam);
double y = rho * coslam;
xy.setLocation(x, y);
break;
case EQUATOR:
case OBLIQUE:
if (Math.abs(lam) < MapMath.EPS10 && Math.abs(phi - projectionLatitude) < MapMath.EPS10) {
xy.setLocation(0, 0);
break;
}
t = Math.atan2(one_es * sinphi + es * N1 * sinphi0 * Math.sqrt(1. - es * sinphi * sinphi), cosphi);
ct = Math.cos(t);
st = Math.sin(t);
Az = Math.atan2(Math.sin(lam) * ct, cosphi0 * st - sinphi0 * coslam * ct);
cA = Math.cos(Az);
sA = Math.sin(Az);
s = MapMath.asin(Math.abs(sA) < TOL ?
(cosphi0 * st - sinphi0 * coslam * ct) / cA :
Math.sin(lam) * ct / sA);
H = He * cA;
H2 = H * H;
c = N1 * s * (1. + s * s * (-H2 * (1. - H2) / 6. +
s * (G * H * (1. - 2. * H2 * H2) / 8. +
s * ((H2 * (4. - 7. * H2) - 3. * G * G * (1. - 7. * H2)) /
120. - s * G * H / 48.))));
xy.setLocation(c * sA, c * cA);
break;
}
}
xy.setLocation(totalScale * xy.getX() + falseEasting, totalScale * xy.getY() + falseNorthing);
return xy;
}
@Override
public LatLonPoint projToLatLon(ProjectionPoint ppt, LatLonPointImpl lp) {
double x = (ppt.getX() - falseEasting) / totalScale; // assumes cartesion coords in km
double y = (ppt.getY() - falseNorthing) / totalScale;
if (earth.isSpherical()) {
double cosc, c_rh, sinc;
if ((c_rh = MapMath.distance(x, y)) > Math.PI) {
if (c_rh - MapMath.EPS10 > Math.PI)
throw new IllegalStateException();
c_rh = Math.PI;
} else if (c_rh < MapMath.EPS10) {
lp.setLatitude(lat0);
lp.setLongitude(0.0);
return lp;
}
if (mode == OBLIQUE || mode == EQUATOR) {
sinc = Math.sin(c_rh);
cosc = Math.cos(c_rh);
if (mode == EQUATOR) {
lp.setLatitude(Math.toDegrees(MapMath.asin(y * sinc / c_rh)));
x *= sinc;
y = cosc * c_rh;
} else {
lp.setLatitude(Math.toDegrees(MapMath.asin(cosc * sinphi0 + y * sinc * cosphi0 / c_rh)));
y = (cosc - sinphi0 * MapMath.sind(lp.getLatitude())) * c_rh;
x *= sinc * cosphi0;
}
lp.setLongitude(Math.toDegrees(y == 0. ? 0. : Math.atan2(x, y)));
} else if (mode == NORTH_POLE) {
lp.setLatitude(Math.toDegrees(MapMath.HALFPI - c_rh));
lp.setLongitude(Math.toDegrees(Math.atan2(x, -y)));
} else {
lp.setLatitude(Math.toDegrees(c_rh - MapMath.HALFPI));
lp.setLongitude(Math.toDegrees(Math.atan2(x, y)));
}
} else {
double c, Az, cosAz, A, B, D, E, F, psi, t;
if ((c = MapMath.distance(x, y)) < MapMath.EPS10) {
lp.setLatitude(lat0);
lp.setLongitude(0.0);
return (lp);
}
if (mode == OBLIQUE || mode == EQUATOR) {
cosAz = Math.cos(Az = Math.atan2(x, y));
t = cosphi0 * cosAz;
B = es * t / one_es;
A = -B * t;
B *= 3. * (1. - A) * sinphi0;
D = c / N1;
E = D * (1. - D * D * (A * (1. + A) / 6. + B * (1. + 3. * A) * D / 24.));
F = 1. - E * E * (A / 2. + B * E / 6.);
psi = MapMath.asin(sinphi0 * Math.cos(E) + t * Math.sin(E));
lp.setLongitude(Math.toDegrees(MapMath.asin(Math.sin(Az) * Math.sin(E) / Math.cos(psi))));
if ((t = Math.abs(psi)) < MapMath.EPS10)
lp.setLatitude(0.0);
else if (Math.abs(t - MapMath.HALFPI) < 0.)
lp.setLatitude(Math.toDegrees(MapMath.HALFPI));
else
lp.setLatitude(Math.toDegrees(Math.atan((1. - es * F * sinphi0 / Math.sin(psi)) * Math.tan(psi) / one_es)));
} else {
lp.setLatitude(Math.toDegrees(MapMath.inv_mlfn(mode == NORTH_POLE ? Mp - c : Mp + c, es, en)));
lp.setLongitude(Math.toDegrees(Math.atan2(x, mode == NORTH_POLE ? -y : y)));
}
}
lp.setLongitude(lp.getLongitude() + lon0);
return lp;
}
@Override
public String paramsToString() {
return null;
}
@Override
public boolean crossSeam(ProjectionPoint pt1, ProjectionPoint pt2) {
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EquidistantAzimuthalProjection that = (EquidistantAzimuthalProjection) o;
if (Double.compare(that.falseEasting, falseEasting) != 0) return false;
if (Double.compare(that.falseNorthing, falseNorthing) != 0) return false;
if (Double.compare(that.projectionLatitude, projectionLatitude) != 0) return false;
if (Double.compare(that.projectionLongitude, projectionLongitude) != 0) return false;
if (earth != null ? !earth.equals(that.earth) : that.earth != null) return false;
if ((defaultMapArea == null) != (that.defaultMapArea == null)) return false; // common case is that these are null
if (defaultMapArea != null && !that.defaultMapArea.equals(defaultMapArea)) return false;
return true;
}
@Override
public int hashCode() {
int result;
long temp;
temp = projectionLatitude != +0.0d ? Double.doubleToLongBits(projectionLatitude) : 0L;
result = (int) (temp ^ (temp >>> 32));
temp = projectionLongitude != +0.0d ? Double.doubleToLongBits(projectionLongitude) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = falseEasting != +0.0d ? Double.doubleToLongBits(falseEasting) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = falseNorthing != +0.0d ? Double.doubleToLongBits(falseNorthing) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + (earth != null ? earth.hashCode() : 0);
return result;
}
}