7#include "LambertAzimuthalEqualArea.h"
16#include "IException.h"
18#include "TProjection.h"
21#include "PvlKeyword.h"
22#include "SpecialPixel.h"
62 LambertAzimuthalEqualArea::LambertAzimuthalEqualArea(
65 TProjection::TProjection(label) {
71 PvlGroup &mapGroup = label.findGroup(
"Mapping", Pvl::Traverse);
75 if (!mapGroup.hasKeyword(
"CenterLongitude")) {
77 double centerLon = (MinimumLongitude() + MaximumLongitude()) / 2.0;
78 mapGroup += PvlKeyword(
"CenterLongitude",
toString(centerLon),
"Degrees");
81 QString message =
"Cannot project using Lambert Azimuthal equal-area";
82 message +=
" without [CenterLongitude] value. Keyword does not exist";
83 message +=
" in labels and defaults are not allowed.";
84 throw IException(IException::Unknown, message, _FILEINFO_);
90 if (!mapGroup.hasKeyword(
"CenterLatitude")) {
92 double centerLat = (MinimumLatitude() + MaximumLatitude()) / 2.0;
93 mapGroup += PvlKeyword(
"CenterLatitude",
toString(centerLat),
"Degrees");
96 QString message =
"Cannot project using Lambert Azimuthal equal-area";
97 message +=
" without [CenterLatitude] value. Keyword does not exist";
98 message +=
" in labels and defaults are not allowed.";
99 throw IException(IException::Unknown, message, _FILEINFO_);
104 double centerLongitude = mapGroup[
"CenterLongitude"];
105 double centerLatitude = mapGroup[
"CenterLatitude"];
107 if (fabs(MinimumLongitude() - centerLongitude) > 360.0 ||
108 fabs(MaximumLongitude() - centerLongitude) > 360.0) {
109 IString message =
"[MinimumLongitude,MaximumLongitude] range of [";
110 message += IString(MinimumLongitude())+
","+IString(MaximumLongitude());
111 message +=
"] is invalid. No more than 360 degrees from the "
112 "CenterLongitude [" + IString(centerLongitude) +
"] is allowed.";
113 throw IException(IException::Unknown, message, _FILEINFO_);
116 if (MaximumLongitude() - MinimumLongitude() > 360.0) {
117 IString message =
"[MinimumLongitude,MaximumLongitude] range of ["
118 + IString(MinimumLongitude()) +
","
119 + IString(MaximumLongitude()) +
"] is invalid. "
120 "No more than 360 degree range width is allowed.";
121 throw IException(IException::Unknown, message, _FILEINFO_);
125 init(centerLatitude, centerLongitude);
127 catch(IException &e) {
128 IString message =
"Invalid label group [Mapping]";
129 throw IException(e, IException::Unknown, message, _FILEINFO_);
140 LambertAzimuthalEqualArea::~LambertAzimuthalEqualArea() {
155 bool LambertAzimuthalEqualArea::operator== (
const Projection &proj) {
156 if (!Projection::operator==(proj))
return false;
159 LambertAzimuthalEqualArea *lama = (LambertAzimuthalEqualArea *) &proj;
160 if ((lama->m_phi1 != m_phi1) ||
161 (lama->m_lambda0 != m_lambda0) ||
162 (lama->m_a != m_a) ||
163 (lama->m_e != m_e))
return false;
177 QString LambertAzimuthalEqualArea::Name()
const {
178 return "LambertAzimuthalEqualArea";
186 QString LambertAzimuthalEqualArea::Version()
const {
204 double LambertAzimuthalEqualArea::TrueScaleLatitude()
const {
207 return m_phi1 * 180.0 /
PI;
232 void LambertAzimuthalEqualArea::init(
double centerLatitude,
233 double centerLongitude) {
237 m_minimumX = DBL_MAX;
238 m_maximumX = -DBL_MAX;
239 m_minimumY = DBL_MAX;
240 m_maximumY = -DBL_MAX;
243 if (fabs(centerLongitude) > 360.0) {
244 IString message =
"CenterLongitude [" + IString(centerLongitude);
245 message +=
"] is outside the range of [-360, 360]";
246 throw IException(IException::Unknown, message, _FILEINFO_);
250 if (fabs(centerLatitude) > 90.0) {
251 IString message =
"CenterLatitude [" + IString(centerLatitude);
252 message +=
"] is outside the range of [-90, 90]";
253 throw IException(IException::Unknown, message, _FILEINFO_);
258 if (IsPlanetocentric()) {
259 centerLatitude = ToPlanetographic(centerLatitude);
264 if (m_longitudeDirection == PositiveWest) {
265 centerLongitude = -1.0*centerLongitude;
270 m_a = EquatorialRadius();
271 m_e = Eccentricity();
275 m_lambda0 = centerLongitude*
PI / 180.0;
276 m_phi1 = centerLatitude*
PI / 180.0;
277 m_sinPhi1 = sin(m_phi1);
278 m_cosPhi1 = cos(m_phi1);
282 m_northPolarAspect =
false;
283 m_southPolarAspect =
false;
284 m_equatorialAspect =
false;
286 if (qFuzzyCompare(0.0, m_e)) {
290 if (qFuzzyCompare(HALFPI, m_phi1)) {
292 m_northPolarAspect =
true;
294 if (qFuzzyCompare(-HALFPI, m_phi1)) {
296 m_southPolarAspect =
true;
298 if (qFuzzyCompare(0.0, m_phi1)) {
300 m_equatorialAspect =
true;
331 if (!m_northPolarAspect && !m_southPolarAspect) {
332 if (-centerLatitude >= MinimumLatitude()
333 && -centerLatitude <= MaximumLatitude()
334 && ( (MinimumLongitude() <= centerLongitude - 180 &&
335 MaximumLongitude() >= centerLongitude - 180)
336 || (MinimumLongitude() <= centerLongitude + 180 &&
337 MaximumLongitude() >= centerLongitude + 180) ) ) {
338 IString message =
"Invalid latitude and/or longitude range. ";
339 message +=
"Non-polar Lambert Azimuthal equal-area "
340 "projections can not project the antipodal point on "
342 throw IException(IException::Unknown, message, _FILEINFO_);
354 void LambertAzimuthalEqualArea::initEllipsoid() {
360 m_qp = 1 - (1 - m_e * m_e) / (2 * m_e) * log( (1 - m_e) / (1 + m_e) );
364 if (!m_northPolarAspect && !m_southPolarAspect) {
367 m_q1 = qCompute(m_sinPhi1);
368 m_m1 = mCompute(m_sinPhi1, m_cosPhi1);
370 m_beta1 = asin(m_q1 / m_qp);
389 m_sinBeta1 = sin(m_beta1);
390 m_cosBeta1 = cos(m_beta1);
391 m_Rq = m_a * sqrt(m_qp / 2);
393 m_D = m_a * m_m1 / (m_Rq * m_cosBeta1);
421 bool LambertAzimuthalEqualArea::SetGround(
const double lat,
424 if (fabs(lat) - 90.0 > DBL_EPSILON || lat == Null || lon == Null) {
425 if (!qFuzzyCompare(90.0, fabs(lat))) {
436 double phi = lat *
PI / 180.0;
437 double lambda = lon *
PI / 180.0;
440 if (lat > 90.0 && qFuzzyCompare(90.0, lat)) {
443 if (lat < -90.0 && qFuzzyCompare(-90.0, lat)) {
447 if (m_longitudeDirection == PositiveWest) lambda *= -1.0;
448 if (IsPlanetocentric()) phi = ToPlanetographic(phi);
450 if (!m_spherical)
return setGroundEllipsoid(phi, lambda);
453 double sinPhi = sin(phi);
454 double cosPhi = cos(phi);
455 double sinLambdaDiff = sin(lambda - m_lambda0);
456 double cosLambdaDiff = cos(lambda - m_lambda0);
464 if (m_northPolarAspect ) {
465 double sinQuarterPiMinusHalfPhi = sin(PI/4-phi/2);
466 x = 2*R*sinQuarterPiMinusHalfPhi*sinLambdaDiff;
467 y = -2*R*sinQuarterPiMinusHalfPhi*cosLambdaDiff;
468 m_h = cos(PI/4-phi/2);
472 else if (m_southPolarAspect ) {
473 double cosQuarterPiMinusHalfPhi = cos(PI/4-phi/2);
474 x = 2*R*cosQuarterPiMinusHalfPhi*sinLambdaDiff;
475 y = 2*R*cosQuarterPiMinusHalfPhi*cosLambdaDiff;
476 m_h = sin(PI/4-phi/2);
483 if (qFuzzyCompare(-m_phi1, phi)
484 && fabs(fmod(lambda-m_lambda0, PI)) < DBL_EPSILON
485 && fabs(fmod(lambda-m_lambda0, 2*PI)) > DBL_EPSILON) {
495 if (m_equatorialAspect) {
500 trigTerms = cosPhi*cosLambdaDiff;
504 double sinProduct = m_sinPhi1 * sinPhi;
505 double cosProduct = m_cosPhi1 * cosPhi * cosLambdaDiff;
506 trigTerms = sinProduct + cosProduct;
509 if (qFuzzyCompare(-1.0, trigTerms)) {
513 double denominator = 1 + trigTerms;
514 double kprime = sqrt(2/denominator);
515 x = R*kprime*cosPhi*sinLambdaDiff;
516 if (m_equatorialAspect) {
524 y = R*kprime*(m_cosPhi1*sinPhi - m_sinPhi1*cosPhi*cosLambdaDiff);
550 bool LambertAzimuthalEqualArea::setGroundEllipsoid(
double phi,
553 double sinPhi = sin(phi);
554 double cosPhi = cos(phi);
555 double sinLambdaDiff = sin(lambda - m_lambda0);
556 double cosLambdaDiff = cos(lambda - m_lambda0);
562 double q = qCompute(sinPhi);
563 if (qFuzzyCompare(PI/2, phi)) {
566 if (qFuzzyCompare(-PI/2, phi)) {
571 double m = mCompute(sinPhi, cosPhi);
572 if (m_northPolarAspect) {
573 double rho = m_a* sqrt(m_qp - q);
575 x = rho * sinLambdaDiff;
576 y = -1.0 * rho * cosLambdaDiff;
577 if (qFuzzyCompare(m_phi1, phi)) {
581 m_k = rho / (m_a * m);
586 else if (m_southPolarAspect) {
587 double rho = m_a* sqrt(m_qp + q);
589 x = rho * sinLambdaDiff;
590 y = rho * cosLambdaDiff;
591 if (qFuzzyCompare(m_phi1, phi)) {
595 m_k = rho / (m_a * m);
605 double beta = asin( q/m_qp);
610 double sinBeta = sin(beta);
611 double cosBeta = cos(beta);
613 if (m_equatorialAspect) {
617 double trigTerm = cosBeta*cosLambdaDiff;
618 if (qFuzzyCompare(-1, trigTerm)) {
628 double denominator = 1 + trigTerm;
629 x = m_a * cosBeta * sinLambdaDiff * sqrt(2/denominator);
631 y = (m_Rq*m_Rq/m_a) * sinBeta * sqrt(2/denominator);
647 double sinProduct = m_sinBeta1 * sinBeta;
648 double cosProduct = m_cosBeta1 * cosBeta * cosLambdaDiff;
649 double trigTerms = sinProduct + cosProduct;
650 if (qFuzzyCompare(-1, trigTerms)) {
663 double denominator = 1 + trigTerms;
664 double kprime = sqrt(2/denominator);
665 double B = m_Rq * kprime;
667 x = B * m_D * cosBeta*sinLambdaDiff;
668 y = (B/m_D) * (m_cosBeta1*sinBeta - m_sinBeta1*cosBeta*cosLambdaDiff);
704 bool LambertAzimuthalEqualArea::SetCoordinate(
const double x,
706 if (x == Null || y == Null) {
715 if (!m_spherical)
return setCoordinateEllipsoid(x,y);
724 double rho = sqrt(x*x+y*y);
725 if (rho < DBL_EPSILON) {
731 if (fabs(rho/(2*R)) > 1 + DBL_EPSILON) {
738 else if (fabs(rho/(2*R)) > 1) {
743 double c = 2*asin(rho/(2*R));
745 double sinC = sin(c);
746 double cosC = cos(c);
749 if (fabs(cosC*m_sinPhi1+y*sinC*m_cosPhi1/rho) > 1) {
753 phi = asin(cosC*m_sinPhi1+y*sinC*m_cosPhi1/rho);
754 if (m_northPolarAspect ) {
755 lambda = m_lambda0 + atan2(x,-y);
759 else if (m_southPolarAspect ) {
760 lambda = m_lambda0 + atan2(x,y);
763 double denominator = rho*m_cosPhi1*cosC - y*m_sinPhi1*sinC;
766 lambda = m_lambda0 + atan2(x * sinC, denominator);
771 m_latitude = phi * 180.0 /
PI;
772 m_longitude = lambda * 180.0 /
PI;
775 if (m_longitudeDirection == PositiveWest) {
779 if (m_longitudeDomain == 180) {
780 m_longitude = To180Domain(m_longitude);
784 m_longitude = To360Domain(m_longitude);
788 if (IsPlanetocentric()) {
789 m_latitude = ToPlanetocentric(m_latitude);
813 bool LambertAzimuthalEqualArea::setCoordinateEllipsoid(
const double x,
821 double rho = sqrt(x*x+y*y);
822 if (m_northPolarAspect ) {
823 q = m_qp - (rho*rho/(m_a*m_a));
824 lambda = m_lambda0 + atan2(x,-y);
826 else if (m_southPolarAspect ) {
827 q = -1.0*(m_qp - (rho*rho/(m_a*m_a)));
828 lambda = m_lambda0 + atan2(x,y);
834 double rho = sqrt((xD*xD)+(Dy*Dy));
835 if (fabs(rho) > fabs(2*m_Rq)) {
839 double Ce = 2*asin(rho/(2*m_Rq));
843 double sinCe = sin(Ce);
844 double cosCe = cos(Ce);
846 if (rho < DBL_EPSILON) {
850 q = m_qp * m_sinBeta1;
854 q = m_qp * (cosCe * m_sinBeta1 + m_D * y * sinCe * m_cosBeta1 / rho);
856 double numerator = x * sinCe;
857 double denominator = m_D * rho * m_cosBeta1 * cosCe
858 - m_D * m_D * y * m_sinBeta1 * sinCe;
861 lambda = m_lambda0 + atan2(numerator, denominator);
866 if (qFuzzyCompare(fabs(q), fabs(m_qp))) {
867 phi = copysign(HALFPI,q);
879 bool converge =
false;
880 double tolerance = 1e-10;
882 int maximumIterations = 100;
887 double sinPhi = sin(phi);
888 double eSinPhi = m_e*sinPhi;
889 double cosPhi = cos(phi);
890 double squareESinPhi = eSinPhi*eSinPhi;
891 double oneMinusSquareESinPhi = 1 - squareESinPhi;
893 phi += oneMinusSquareESinPhi*oneMinusSquareESinPhi / (2 * cosPhi)
894 * (q / (1 - m_e * m_e)
895 - sinPhi / (oneMinusSquareESinPhi)
896 + log( (1 - eSinPhi) / (1 + eSinPhi) ) / (2 * m_e));
898 if (fabs(phiOld - phi) < tolerance) {
901 if (i > maximumIterations) {
908 m_latitude = phi * 180.0 /
PI;
909 m_longitude = lambda * 180.0 /
PI;
912 if (m_longitudeDirection == PositiveWest) {
916 if (m_longitudeDomain == 180) {
917 m_longitude = To180Domain(m_longitude);
921 m_longitude = To360Domain(m_longitude);
925 if (IsPlanetocentric()) {
926 m_latitude = ToPlanetocentric(m_latitude);
971 bool LambertAzimuthalEqualArea::XYRange(
double &minX,
double &maxX,
972 double &minY,
double &maxY) {
974 if ((m_northPolarAspect && qFuzzyCompare(-90.0, MinimumLatitude()))
975 || (m_southPolarAspect && qFuzzyCompare(90.0, MaximumLatitude()))) {
979 double eRad = EquatorialRadius();
980 double pRad = PolarRadius();
981 double maxCoordValue = 0;
984 maxCoordValue = 2*EquatorialRadius();
987 maxCoordValue = sqrt(2*eRad*eRad
988 + pRad*pRad*log((1+m_e)/(1-m_e))/m_e);
990 m_minimumX = -maxCoordValue;
991 m_maximumX = maxCoordValue;
992 m_minimumY = -maxCoordValue;
993 m_maximumY = maxCoordValue;
996 else if (m_northPolarAspect || m_southPolarAspect) {
997 return xyRangeLambertAzimuthalPolar(minX, maxX, minY, maxY);
1001 if (xyRangeOblique(minX, maxX, minY, maxY)) {
1005 double maxCoordValue = 2*LocalRadius(-m_phi1*180/PI);
1006 if (m_minimumX < -maxCoordValue) m_minimumX = -maxCoordValue;
1007 if (m_maximumX > maxCoordValue) m_maximumX = maxCoordValue;
1008 if (m_minimumX < -maxCoordValue) m_minimumX = -maxCoordValue;
1009 if (m_maximumY > maxCoordValue) m_maximumY = maxCoordValue;
1020 if (m_minimumX >= m_maximumX)
return false;
1021 if (m_minimumY >= m_maximumY)
return false;
1067 bool LambertAzimuthalEqualArea::xyRangeLambertAzimuthalPolar(
double &minX,
1072 XYRangeCheck(MinimumLatitude(), MinimumLongitude());
1073 XYRangeCheck(MinimumLatitude(), MaximumLongitude());
1074 XYRangeCheck(MaximumLatitude(), MinimumLongitude());
1075 XYRangeCheck(MaximumLatitude(), MaximumLongitude());
1077 double centerLonDeg = m_lambda0 * 180.0 /
PI;
1078 if (m_longitudeDirection == PositiveWest) centerLonDeg = centerLonDeg * -1.0;
1090 for (
double lon = centerLonDeg; lon < centerLonDeg + 360; lon += 90) {
1091 checkLongitude(lon);
1095 if (m_minimumX >= m_maximumX)
return false;
1096 if (m_minimumY >= m_maximumY)
return false;
1123 void LambertAzimuthalEqualArea::checkLongitude(
double longitude) {
1125 double centerLatDeg = m_phi1 * 180.0 /
PI;
1127 double innerLatitude, outerLatitude;
1128 if (m_northPolarAspect) {
1129 innerLatitude = MaximumLatitude();
1130 outerLatitude = MinimumLatitude();
1132 else if (m_southPolarAspect) {
1133 innerLatitude = MinimumLatitude();
1134 outerLatitude = MaximumLatitude();
1137 IString message =
"checkLongitude() should only be called for a "
1138 "polar aspect projection. CenterLatitude is [";
1139 message = message + IString(centerLatDeg) +
"] degrees.";
1140 throw IException(IException::Programmer, message, _FILEINFO_);
1144 bool thisLongitudeInMinMaxRange =
false;
1145 if (MaximumLongitude() - MinimumLongitude() == 360) {
1146 thisLongitudeInMinMaxRange =
true;
1148 double adjustedLon = To360Domain(longitude);
1149 double adjustedMinLon = To360Domain(MinimumLongitude());
1150 double adjustedMaxLon = To360Domain(MaximumLongitude());
1152 if (adjustedMinLon > adjustedMaxLon) {
1153 if (adjustedLon > adjustedMinLon) {
1156 adjustedMinLon -= 360;
1159 if (adjustedMinLon <= adjustedLon
1160 && adjustedLon <= adjustedMaxLon) {
1161 thisLongitudeInMinMaxRange =
true;
1164 thisLongitudeInMinMaxRange =
false;
1167 if (thisLongitudeInMinMaxRange) {
1169 XYRangeCheck(outerLatitude, longitude);
1174 double distToMinLon = fabs(adjustedMinLon - adjustedLon);
1175 double distToMaxLon = fabs(adjustedMaxLon - adjustedLon);
1177 if (distToMinLon >= 180 ) {
1178 distToMinLon = fabs(360 - distToMinLon);
1180 if (distToMaxLon >= 180 ) {
1181 distToMaxLon = fabs(360 - distToMaxLon);
1184 double nearestBoundary = 0;
1185 if (distToMinLon < distToMaxLon) {
1186 nearestBoundary = MinimumLongitude();
1189 nearestBoundary = MaximumLongitude();
1193 if (distToMinLon <= 90 + DBL_EPSILON
1194 || distToMaxLon <= 90 + DBL_EPSILON) {
1195 XYRangeCheck(outerLatitude, nearestBoundary);
1198 else if (qFuzzyCompare(MaximumLatitude(), centerLatDeg)) {
1199 XYRangeCheck(centerLatDeg,longitude);
1203 XYRangeCheck(innerLatitude, nearestBoundary);
1236 PvlGroup LambertAzimuthalEqualArea::Mapping() {
1237 PvlGroup mapping = TProjection::Mapping();
1239 mapping += m_mappingGrp[
"CenterLatitude"];
1240 mapping += m_mappingGrp[
"CenterLongitude"];
1262 PvlGroup LambertAzimuthalEqualArea::MappingLatitudes() {
1263 PvlGroup mapping = TProjection::MappingLatitudes();
1265 mapping += m_mappingGrp[
"CenterLatitude"];
1287 PvlGroup LambertAzimuthalEqualArea::MappingLongitudes() {
1288 PvlGroup mapping = TProjection::MappingLongitudes();
1290 mapping += m_mappingGrp[
"CenterLongitude"];
1317 double LambertAzimuthalEqualArea::relativeScaleFactorLongitude()
const {
1318 validateRelativeScaleFactor();
1336 double LambertAzimuthalEqualArea::relativeScaleFactorLatitude()
const {
1337 validateRelativeScaleFactor();
1363 void LambertAzimuthalEqualArea::validateRelativeScaleFactor()
const {
1365 IString message =
"Projection failed or ground and coordinates have not "
1366 "been set. Relative scale factor can not be computed.";
1367 throw IException(IException::Unknown, message, _FILEINFO_);
1369 if (!m_spherical && !(m_northPolarAspect || m_southPolarAspect)) {
1370 IString message =
"For ellipsidal bodies, relative scale factor can only "
1371 "be computed for polar aspect projections.";
1372 throw IException(IException::Unknown, message, _FILEINFO_);
1374 if (m_northPolarAspect && qFuzzyCompare(-90.0, m_latitude)) {
1375 IString message =
"Relative scale factor can not be computed for north "
1376 "polar aspect projection when ground is set to "
1378 throw IException(IException::Unknown, message, _FILEINFO_);
1380 if (m_southPolarAspect && qFuzzyCompare(90.0, m_latitude)) {
1381 IString message =
"Relative scale factor can not be computed for south "
1382 "polar aspect projection when ground is set to "
1384 throw IException(IException::Unknown, message, _FILEINFO_);
1386 if (m_k == Null || m_h == Null) {
1387 IString message =
"Relative scale factor can not be computed.";
1388 throw IException(IException::Unknown, message, _FILEINFO_);
1412 bool allowDefaults) {
Lambert Azimuthal Equal Area Map Projection.
Base class for Map Projections.
Container for cube-like labels.
This is free and unencumbered software released into the public domain.
QString toString(bool boolToConvert)
Global function to convert a boolean to a string.
const double Null
Value for an Isis Null pixel.
const double HALFPI
The mathematical constant PI/2.
const double PI
The mathematical constant PI.
Namespace for the standard library.