Isis 3 Programmer Reference
LambertAzimuthalEqualArea.cpp
Go to the documentation of this file.
1 
24 
25 #include <cmath>
26 #include <cfloat>
27 
28 #include <iostream>
29 #include <iomanip>
30 
31 #include "Constants.h"
32 #include "IException.h"
33 #include "IString.h"
34 #include "TProjection.h"
35 #include "Pvl.h"
36 #include "PvlGroup.h"
37 #include "PvlKeyword.h"
38 #include "SpecialPixel.h"
39 
40 using namespace std;
41 namespace Isis {
78  LambertAzimuthalEqualArea::LambertAzimuthalEqualArea(
79  Pvl &label,
80  bool allowDefaults) :
81  TProjection::TProjection(label) {
82  try {
83  // This algorithm can be found in the USGS Professional Paper 1395
84  // Map Projections--A Working Manual by John P. Snyder
85 
86  // Try to read the mapping group
87  PvlGroup &mapGroup = label.findGroup("Mapping", Pvl::Traverse);
88 
89  // Compute and write the default center longitude if allowed and
90  // necessary
91  if (!mapGroup.hasKeyword("CenterLongitude")) {
92  if (allowDefaults) {
93  double centerLon = (MinimumLongitude() + MaximumLongitude()) / 2.0;
94  mapGroup += PvlKeyword("CenterLongitude", toString(centerLon), "Degrees");
95  }
96  else {
97  QString message = "Cannot project using Lambert Azimuthal equal-area";
98  message += " without [CenterLongitude] value. Keyword does not exist";
99  message += " in labels and defaults are not allowed.";
100  throw IException(IException::Unknown, message, _FILEINFO_);
101  }
102  }
103 
104  // Compute and write the default center latitude if allowed and
105  // necessary
106  if (!mapGroup.hasKeyword("CenterLatitude")) {
107  if (allowDefaults) {
108  double centerLat = (MinimumLatitude() + MaximumLatitude()) / 2.0;
109  mapGroup += PvlKeyword("CenterLatitude", toString(centerLat), "Degrees");
110  }
111  else {
112  QString message = "Cannot project using Lambert Azimuthal equal-area";
113  message += " without [CenterLatitude] value. Keyword does not exist";
114  message += " in labels and defaults are not allowed.";
115  throw IException(IException::Unknown, message, _FILEINFO_);
116  }
117  }
118 
119  // Get the center longitude & latitude
120  double centerLongitude = mapGroup["CenterLongitude"];
121  double centerLatitude = mapGroup["CenterLatitude"];
122 
123  if (fabs(MinimumLongitude() - centerLongitude) > 360.0 ||
124  fabs(MaximumLongitude() - centerLongitude) > 360.0) {
125  IString message = "[MinimumLongitude,MaximumLongitude] range of [";
126  message += IString(MinimumLongitude())+","+IString(MaximumLongitude());
127  message += "] is invalid. No more than 360 degrees from the "
128  "CenterLongitude [" + IString(centerLongitude) + "] is allowed.";
129  throw IException(IException::Unknown, message, _FILEINFO_);
130  }
131 
132  if (MaximumLongitude() - MinimumLongitude() > 360.0) {
133  IString message = "[MinimumLongitude,MaximumLongitude] range of ["
134  + IString(MinimumLongitude()) + ","
135  + IString(MaximumLongitude()) + "] is invalid. "
136  "No more than 360 degree range width is allowed.";
137  throw IException(IException::Unknown, message, _FILEINFO_);
138  }
139 
140  // initialize member variables
141  init(centerLatitude, centerLongitude);
142  }
143  catch(IException &e) {
144  IString message = "Invalid label group [Mapping]";
145  throw IException(e, IException::Unknown, message, _FILEINFO_);
146  }
147  }
148 
156  LambertAzimuthalEqualArea::~LambertAzimuthalEqualArea() {
157  }
158 
171  bool LambertAzimuthalEqualArea::operator== (const Projection &proj) {
172  if (!Projection::operator==(proj)) return false;
173  // don't do the below it is a recursive plunge
174  // if (Projection::operator!=(proj)) return false;
175  LambertAzimuthalEqualArea *lama = (LambertAzimuthalEqualArea *) &proj;
176  if ((lama->m_phi1 != m_phi1) ||
177  (lama->m_lambda0 != m_lambda0) ||
178  (lama->m_a != m_a) ||
179  (lama->m_e != m_e)) return false;
180  // all other data members calculated using m_lambda0, m_phi1, m_a and m_e.
181  return true;
182  }
183 
193  QString LambertAzimuthalEqualArea::Name() const {
194  return "LambertAzimuthalEqualArea";
195  }
196 
202  QString LambertAzimuthalEqualArea::Version() const {
203  return "1.0";
204  }
205 
221  //Snyder pg.
222  // no distortion at center of projection (centerLatitude, centerLongitude)
223  return m_phi1 * 180.0 / PI;
224  }
225 
226 
248  void LambertAzimuthalEqualArea::init(double centerLatitude,
249  double centerLongitude) {
250  // Initialize miscellaneous protected data elements
251  m_good = false;
252 
253  m_minimumX = DBL_MAX;
254  m_maximumX = -DBL_MAX;
255  m_minimumY = DBL_MAX;
256  m_maximumY = -DBL_MAX;
257 
258  // Test to make sure center longitude is valid
259  if (fabs(centerLongitude) > 360.0) {
260  IString message = "CenterLongitude [" + IString(centerLongitude);
261  message += "] is outside the range of [-360, 360]";
262  throw IException(IException::Unknown, message, _FILEINFO_);
263  }
264 
265  // Test to make sure center lat is valid
266  if (fabs(centerLatitude) > 90.0) {
267  IString message = "CenterLatitude [" + IString(centerLatitude);
268  message += "] is outside the range of [-90, 90]";
269  throw IException(IException::Unknown, message, _FILEINFO_);
270  }
271 
272  // We need to convert to planetographic since phi is geographic latitude
273  // (see phi and phi1 - Snyder pg ix)
274  if (IsPlanetocentric()) {
275  centerLatitude = ToPlanetographic(centerLatitude);
276  }
277 
278  // adjust for positive east longitude direction
279  // (see lambda and lambda0 - Snyder pg ix)
281  centerLongitude = -1.0*centerLongitude;
282  }
283 
284  // Descriptions of the variables a, e, lambda0, and phi1
285  // can be found in Snyder text on pgs viii-ix, pg 187
286  m_a = EquatorialRadius();
287  m_e = Eccentricity(); // e = sqrt(1 - (PolarRadius/EqRadius)^2)
288  // must be 0 <= e < 1
289 
290  // Snyder variables used for spherical projection
291  m_lambda0 = centerLongitude*PI / 180.0; // convert to radians
292  m_phi1 = centerLatitude*PI / 180.0; // convert to radians
293  m_sinPhi1 = sin(m_phi1);
294  m_cosPhi1 = cos(m_phi1);
295 
296  // flags for determinining which formulas to use
297  m_spherical = false;
298  m_northPolarAspect = false;
299  m_southPolarAspect = false;
300  m_equatorialAspect = false;
301 
302  if (qFuzzyCompare(0.0, m_e)) {
303  m_e = 0.0;
304  m_spherical = true;
305  }
306  if (qFuzzyCompare(HALFPI, m_phi1)) {
307  m_phi1 = HALFPI;
308  m_northPolarAspect = true;
309  }
310  if (qFuzzyCompare(-HALFPI, m_phi1)) {
311  m_phi1 = -HALFPI;
312  m_southPolarAspect = true;
313  }
314  if (qFuzzyCompare(0.0, m_phi1)) {
315  m_phi1 = 0.0;
316  m_equatorialAspect = true;
317  }
318 
319  // Snyder ellipsoid variables will not be used
320  m_qp = Null;
321  m_q1 = Null;
322  m_m1 = Null;
323  m_beta1 = Null;
324  m_sinBeta1 = Null;
325  m_cosBeta1 = Null;
326  m_Rq = Null;
327  m_D = Null;
328 
329  // other Snyder variables
330  m_h = Null;
331  m_k = Null;
332 
333  // if eccentricity = 0, we are projecting a sphere.
334  if (!m_spherical) {
335  // Calculate Snyder ellipsoid variables
336  initEllipsoid();
337  }
338 
339  // Check if the antipodal point is in the lat/lon ranges.
340  //
341  // We can only allow this if we have a polar projection.
342  // Otherwise, we cannot SetGround() for the antipodal point since it is
343  // projected as a circle, not a single point with (x,y) value.
344  //
345  // The antipodal point is defined by the coordinates
346  // (-centerLat, centerLon-180) or (-centerLat, centerLon+180)
348  if (-centerLatitude >= MinimumLatitude()
349  && -centerLatitude <= MaximumLatitude()
350  && ( (MinimumLongitude() <= centerLongitude - 180 &&
351  MaximumLongitude() >= centerLongitude - 180)
352  || (MinimumLongitude() <= centerLongitude + 180 &&
353  MaximumLongitude() >= centerLongitude + 180) ) ) {
354  IString message = "Invalid latitude and/or longitude range. ";
355  message += "Non-polar Lambert Azimuthal equal-area "
356  "projections can not project the antipodal point on "
357  "the body.";
358  throw IException(IException::Unknown, message, _FILEINFO_);
359  }
360  }
361  }
362 
370  void LambertAzimuthalEqualArea::initEllipsoid() {
371  m_spherical = false;
372 
373  // Decided not to use qCompute method here to reduce chance of roundoff
374  // error since q for polar simplifies
375  // m_qp = qCompute(1.0); //q for polar, sin(phi) = sin(pi/2) = 1.0
376  m_qp = 1 - (1 - m_e * m_e) / (2 * m_e) * log( (1 - m_e) / (1 + m_e) );
377  // Snyder eqn (3-12) pg 187, use phi = pi/2 (polar)
378  // Note: We know that qp is well defined since 0 < e < 1, thus
379  // 1+e != 0 (no 0 denominator) and (1-e)/(1+e) > 0 (log domain)
381  // we only need to compute these variables for oblique projections
382 
383  m_q1 = qCompute(m_sinPhi1); // Snyder eqn (3-12) pg 187, use phi = phi1
384  m_m1 = mCompute(m_sinPhi1, m_cosPhi1); // Snyder eqn (14-15) pg 187,
385  //use phi = phi1, thus m1 = cosPhi1/sqrt(1-eSinPhi1*eSinPhi1)
386  m_beta1 = asin(m_q1 / m_qp); // Snyder eqn (3-11) pg 187, use q = q1
387  // Note: We can show mathematically that beta1 is well defined
388  // First, since 0 < eccentricity < 1, we have (1-e)/(1+e) < 1
389  // Thus, ln((1-e)/(1+e)) < 0 and so qp > 1 (no 0 denominator)
390  // Next, verify the domain for arcsine, -1 <= q1/qp <= 1
391  // Since not polar, |phi1| < 90, and since ellipsoid 0 < e < 1
392  // Then |sinPhi1| < 1, so e|sinPhi1| < e
393  // So, 1 < (1+e|sinPhi1|)/(1-e|sinPhi1|) < (1+e)/(1-e).
394  // 0 < ln[(1+e|sinPhi1|)/(1-e|sinPhi1|)] < ln[(1+e)/(1-e)]
395  // Note that (1-e^2)/(2e) > 0 and recall -ln(x) = ln(1/x)
396  // So |q1| = |sinPhi1 - (1-e^2)/(2e)ln[(1-eSinPhi1)/(1+eSinPhi1)]|
397  // = |sinPhi1 + (1-e^2)/(2e)ln[(1+eSinPhi1)/(1-eSinPhi1)]|
398  // <= |sinPhi1| + |(1-e^2)/(2e)ln[(1+eSinPhi1)/(1-eSinPhi1)]|
399  // = |sinPhi1| +(1-e^2)/(2e) |ln[(1+eSinPhi1)/(1-eSinPhi1)]|
400  // < 1 + (1-e^2)/(2e) |ln[(1+eSinPhi1)/(1-eSinPhi1)]|
401  // <= 1 + (1-e^2)/(2e) ln[(1+e|SinPhi1|)/(1-e|SinPhi1|)]
402  // < 1 + (1-e^2)/(2e) ln[(1+e)/(1-e)]
403  // = 1 - (1-e^2)/(2e)ln[(1-e)/(1+e)]
404  // = qp
405  m_sinBeta1 = sin(m_beta1);
406  m_cosBeta1 = cos(m_beta1);
407  m_Rq = m_a * sqrt(m_qp / 2); // Snyder eqn (3-13) pg 16, 187
408  // Note: We know Rq is well defined since qp > 1 (see m_beta1)
409  m_D = m_a * m_m1 / (m_Rq * m_cosBeta1); // Snyder eqn (24-20) pg 187
410  // Note: We know D is well-defined since
411  // cosBeta1 = 0 implies beta1 = +/-pi/2,
412  // which in turn implies q1 = +/-qp
413  // This should only happen if polar aspect,
414  // Furthermore, Rq = 0 is impossible since a > 0 and q > 1
415  }
416  }
417 
437  bool LambertAzimuthalEqualArea::SetGround(const double lat,
438  const double lon) {
439  // when lat/lon is Null or lat is "beyond" the poles, we can't set ground
440  if (fabs(lat) - 90.0 > DBL_EPSILON || lat == Null || lon == Null) {
441  if (!qFuzzyCompare(90.0, fabs(lat))) {
442  m_good = false;
443  return m_good;
444  }
445  }
446 
447  m_longitude = lon;
448  m_latitude = lat;
449 
450  // assign input values to Snyder's phi and lambda variables
451  // convert to radians, positive east longitude, planetographic
452  double phi = lat * PI / 180.0;
453  double lambda = lon * PI / 180.0;
454 
455  // when lat just barely beyond pole, set equal to pole
456  if (lat > 90.0 && qFuzzyCompare(90.0, lat)) {
457  phi = HALFPI;
458  }
459  if (lat < -90.0 && qFuzzyCompare(-90.0, lat)) {
460  phi = -HALFPI;
461  }
462 
463  if (m_longitudeDirection == PositiveWest) lambda *= -1.0;
464  if (IsPlanetocentric()) phi = ToPlanetographic(phi);
465 
466  if (!m_spherical) return setGroundEllipsoid(phi, lambda);
467 
468  // calculate the following once to reduce computations
469  double sinPhi = sin(phi);
470  double cosPhi = cos(phi);
471  double sinLambdaDiff = sin(lambda - m_lambda0);
472  double cosLambdaDiff = cos(lambda - m_lambda0);
473 
474  // Use the variables defined above to compute the x,y coordinate
475  double x,y;
476 
477  // In this case, Spherical Radius = Equitorial Radius,
478  // so Snyder's R = Snyder's a variable pg ix
479  double R = m_a;
480  if (m_northPolarAspect ) {
481  double sinQuarterPiMinusHalfPhi = sin(PI/4-phi/2);
482  x = 2*R*sinQuarterPiMinusHalfPhi*sinLambdaDiff; //Snyder eqn (24-3) pg 186
483  y = -2*R*sinQuarterPiMinusHalfPhi*cosLambdaDiff;//Snyder eqn (24-4) pg 186
484  m_h = cos(PI/4-phi/2); // Snyder eqn (24-6) pg 186
485  m_k = 1/m_h; // Snyder eqn (24-5) and (24-6) pg 186
486  // - recall sec(theta) = 1/cos(theta)
487  }
488  else if (m_southPolarAspect ) {
489  double cosQuarterPiMinusHalfPhi = cos(PI/4-phi/2);
490  x = 2*R*cosQuarterPiMinusHalfPhi*sinLambdaDiff; //Snyder eqn (24-8) pg 186
491  y = 2*R*cosQuarterPiMinusHalfPhi*cosLambdaDiff; //Snyder eqn (24-9) pg 186
492  m_h = sin(PI/4-phi/2); // Snyder eqn (24-11) pg 186
493  m_k = 1/m_h; // Snyder eqn (24-10) and (24-11) pg 186
494  }
495  else { // spherical oblique aspect (this includes equatorial)
496 
497  // Check if (phi, lambda) is the antipodal point (point diametrically
498  // opposite the center point of projection)
499  if (qFuzzyCompare(-m_phi1, phi) // phi = -phi1
500  && fabs(fmod(lambda-m_lambda0, PI)) < DBL_EPSILON //lambda-lambda0=k*PI
501  && fabs(fmod(lambda-m_lambda0, 2*PI)) > DBL_EPSILON) { // k is odd
502  // We don't allow this for oblique aspect projections. This point,
503  // at the opposite side of the planet, is projected as a circle of
504  // radius 2R - we can't return a unique (x,y) value.
505  m_good = false;
506  return m_good;
507  }
508 
509  // First, be sure that the denominator will not be zero
510  double trigTerms;
511  if (m_equatorialAspect) {
512  // If m_phi1 == 0, the general case below cancels alegbraically
513  // to the following equations, however, the simplified equations
514  // are used here to reduce chance of roundoff errors
515  // -- Snyder eq (24-14) pg 186
516  trigTerms = cosPhi*cosLambdaDiff;
517  }
518  else {
519  // general case for oblique projections -- Snyder eq (24-2) pg 185
520  double sinProduct = m_sinPhi1 * sinPhi;
521  double cosProduct = m_cosPhi1 * cosPhi * cosLambdaDiff;
522  trigTerms = sinProduct + cosProduct;
523  }
524  // make sure when we add 1 to trigTerms that we are not near zero
525  if (qFuzzyCompare(-1.0, trigTerms)) {
526  m_good = false;
527  return m_good;
528  }
529  double denominator = 1 + trigTerms;
530  double kprime = sqrt(2/denominator); // Snyder eqn (24-2) or (24-14) pg 185-186
531  x = R*kprime*cosPhi*sinLambdaDiff; // Snyder eqn (22-4) pg 185
532  if (m_equatorialAspect) {
533  // If m_phi1 == 0, the general case below cancels alegbraically
534  // to the following, however, use the simplified equations for y
535  // to reduce the likelihood of roundoff errors
536  // -- Snyder eq (24-13) pg 186
537  y = R*kprime*sinPhi;
538  }
539  else { // spherical general oblique aspect -- Snyder eqn (22-5) pg 185
540  y = R*kprime*(m_cosPhi1*sinPhi - m_sinPhi1*cosPhi*cosLambdaDiff);
541  }
542  // these are the relative scale factors for oblique aspect
543  m_k = kprime;
544  m_h = 1/m_k;
545  }
546 
547  SetComputedXY(x, y); // sets m_x = x and m_y = y and handles rotation
548  m_good = true;
549  return m_good;
550  }
551 
566  bool LambertAzimuthalEqualArea::setGroundEllipsoid(double phi,
567  double lambda) {
568  // calculate the following once to reduce computations
569  double sinPhi = sin(phi);
570  double cosPhi = cos(phi);
571  double sinLambdaDiff = sin(lambda - m_lambda0);
572  double cosLambdaDiff = cos(lambda - m_lambda0);
573 
574  // Use the variables passed in to compute the x,y coordinate
575  double x,y;
576 
577  // projecting an ellipsoid
578  double q = qCompute(sinPhi); //q for given lat
579  if (qFuzzyCompare(PI/2, phi)) {
580  q = m_qp; // reduce roundoff error
581  }
582  if (qFuzzyCompare(-PI/2, phi)) {
583  q = -m_qp; // reduce roundoff error
584  }
585  // q = (1-e^2)*(sinphi/(1-e^2sinphi^2))-1/(2e)*log((1-esinphi)/(1+esinphi)))
586 
587  double m = mCompute(sinPhi, cosPhi); // m = cosPhi/sqrt(1-eSinPhi*eSinPhi)
588  if (m_northPolarAspect) {
589  double rho = m_a* sqrt(m_qp - q); // Snyder eqn (24-23) pg 188
590  // Note: Rho is well defined (qp >= q) as shown in initEllipsoid()
591  x = rho * sinLambdaDiff; // Snyder eqn (21-30) pg 188
592  y = -1.0 * rho * cosLambdaDiff; // Snyder eqn (21-31) pg 188
593  if (qFuzzyCompare(m_phi1, phi)) {
594  m_k = 1; // Since True scale at clat/clon - see Snyder page 190, table 29
595  }
596  else{
597  m_k = rho / (m_a * m); // Snyder eqn (21-32) pg 188
598  }
599  m_h = 1 / m_k; // Snyder paragraph after eqn (24-23) pg 188
600 
601  }
602  else if (m_southPolarAspect) {
603  double rho = m_a* sqrt(m_qp + q); // Snyder eqn (24-25) pg 188
604  // Note: Rho is well defined (qp >= -q) as shown in initEllipsoid()
605  x = rho * sinLambdaDiff; // Snyder eqn (21-30) pg 188
606  y = rho * cosLambdaDiff; // Snyder eqn (24-24) pg 188
607  if (qFuzzyCompare(m_phi1, phi)) {
608  m_k = 1; // Since True scale at clat/clon - see Snyder page 190, table 29
609  }
610  else{
611  m_k = rho / (m_a * m); // Snyder eqn (21-32) pg 188
612  }
613  m_h = 1 / m_k;
614  }
615  else { // ellipsoidal oblique aspect
616  // following comment is impossible -- see initEllipsoid(), |q1| > |qp|
617  // if (fabs(q) > fabs(m_qp)) {
618  // m_good = false;
619  // return m_good;
620  // }
621  double beta = asin( q/m_qp); // Snyder eqn (3-11) pg 187
622  // Note: beta is well defined- see initEllipsoid() m_beta1
623 
624  // calculate the following once to reduce computations when defining
625  // Snyder's variable B
626  double sinBeta = sin(beta);
627  double cosBeta = cos(beta);
628 
629  if (m_equatorialAspect) {
630  // If phi1 == 0, the general case below cancels alegbraically to
631  // the following equations, however, the simplified equations are
632  // used here to reduce chance of roundoff errors
633  double trigTerm = cosBeta*cosLambdaDiff;
634  if (qFuzzyCompare(-1, trigTerm)) { // avoid divide by zero & possible roundoff errors
635  // This happens when trying to project the antipodal point.
636  // lambda = lambda0 +/- 180 and beta = 0 (that is, phi = 0)
637  // Thus denominator = 1 + (1)(-1) = 0
638  // We don't allow this for equatorial aspect projections. This point,
639  // at the opposite side of the planet, is projected as a circle of
640  // radius 2R - we can't return a unique (x,y) value.
641  m_good = false;
642  return m_good;
643  }
644  double denominator = 1 + trigTerm;
645  x = m_a * cosBeta * sinLambdaDiff * sqrt(2/denominator);
646  // Snyder eqn (24-21) pg 187
647  y = (m_Rq*m_Rq/m_a) * sinBeta * sqrt(2/denominator);
648  // Snyder eqn (24-22) pg 187
649  // Note: Clearly 1 + cosBeta*cosLambdaDiff >= 0
650  // 1 + cosBeta*cosLambdaDiff = 0 implies 2 cases
651  // 1) cosBeta = 1 and cosLambdaDiff = -1
652  // So, beta = 0 and lambdaDiff = 180
653  // Then, q = 0, which implies phi = 0 = phi1
654  // this only happens when projecting opposite side of equator
655  // 2) cosBeta = -1 and cosLambdaDiff = 1
656  // So, beta = 180 impossible since beta is defined by asin
657  // (range of asin is -90 to 90)
658  // Then, q = 0, which implies phi = 0 = phi1
659  // this only happens when projecting opposite side of equator
660  // Therefore x and y are well defined
661  }
662  else { // ellipsoidal General oblique aspect
663  double sinProduct = m_sinBeta1 * sinBeta;
664  double cosProduct = m_cosBeta1 * cosBeta * cosLambdaDiff;
665  double trigTerms = sinProduct + cosProduct;
666  if (qFuzzyCompare(-1, trigTerms)) { // avoid divide by zero & possible roundoff errors
667  // This happens when trying to project the antipodal point.
668  // lambda = lambda0 + 180 and beta = -beta1 (that is, phi = -phi1)
669  // Then, by odd symmetry of sine function, sinBeta = -sinBeta1,
670  // by even symmetry of cosine, cosBeta = cosBeta1
671  // and cosLambdaDiff = cos(180) = -1
672  // Thus denominator = 1 - sin^2(beta) - cos^2(beta) = 1-1 = 0
673  // We don't allow this for oblique aspect projections. This point,
674  // at the opposite side of the planet, is projected as a circle of
675  // radius 2R - we can't return a unique (x,y) value.
676  m_good = false;
677  return m_good;
678  }
679  double denominator = 1 + trigTerms;
680  double kprime = sqrt(2/denominator); // Snyder eqn (24-2) page 185
681  double B = m_Rq * kprime; // Snyder eqns (24-19) pg 187
682 
683  x = B * m_D * cosBeta*sinLambdaDiff; // Snyder eqn (24-17) pg 187
684  y = (B/m_D) * (m_cosBeta1*sinBeta - m_sinBeta1*cosBeta*cosLambdaDiff);
685  // Snyder eqn (24-18) pg 187
686  // Note: y is well defined since D = 0 implies a = 0 or m1 = 0
687  // we know a > 0,
688  // so m1 = 0 would imply cosPhi1 = 0, thus phi1 = +/- 90
689  // but this calculation is not used for polar aspect,
690  // so y doesn't have zero denominator
691  }
692  // There are no ellipsoidal values for the scale factors h and k unless
693  // using the polar aspects, Snyder pg 26.
694  }
695  SetComputedXY(x, y); // sets m_x = x and m_y = y and handles rotation
696 
697  m_good = true;
698  return m_good;
699  }
700 
720  bool LambertAzimuthalEqualArea::SetCoordinate(const double x,
721  const double y) {
722  if (x == Null || y == Null) {
723  m_good = false;
724  return m_good;
725  }
726  // Save the coordinate
727  SetXY(x, y);
728 
729  // 2012-06-11
730  // Uncomment following line if ellipsoid projection is implemented
731  if (!m_spherical) return setCoordinateEllipsoid(x,y);
732 
733  double phi; // latitude value to be calculated
734  double lambda; // longitude value to be calculated
735 
736  // Spherical Radius = Equatorial Radius, so
737  // Snyder's R = Snyder's a variable pg ix
738  double R = m_a;
739 
740  double rho = sqrt(x*x+y*y); // Snyder eqn (20-18) pg 187
741  if (rho < DBL_EPSILON) { // this implies (x,y) = (0,0),
742  // so project to center lat/lon values
743  phi = m_phi1;
744  lambda = m_lambda0;
745  }
746  else {
747  if (fabs(rho/(2*R)) > 1 + DBL_EPSILON) {// given (x,y) point is off planet
748  // projection if distance from
749  // origin (rho) is greater than
750  // 2*radius of sphere
751  m_good = false;
752  return m_good;
753  }
754  else if (fabs(rho/(2*R)) > 1) { // if ratio is slightly larger than 1
755  // due to rounding error, then fix so we
756  // can take the arcsin
757  rho = 2*R;
758  }
759  double c = 2*asin(rho/(2*R)); // c is angular distance,
760  //Snyder eqn (24-16) pg 187
761  double sinC = sin(c);
762  double cosC = cos(c);
763 
764  // verify the following is in the domain of the arcsin function
765  if (fabs(cosC*m_sinPhi1+y*sinC*m_cosPhi1/rho) > 1) {
766  m_good = false;
767  return m_good;
768  }
769  phi = asin(cosC*m_sinPhi1+y*sinC*m_cosPhi1/rho);//Snyder eq (20-14) pg 186
770  if (m_northPolarAspect ) { // spherical north polar aspect
771  lambda = m_lambda0 + atan2(x,-y); // Snyder eqn (20-16) pg 187,
772  // need to use atan2 to correct for
773  // quadrant - see pg 150
774  }
775  else if (m_southPolarAspect ) { // spherical north polar aspect
776  lambda = m_lambda0 + atan2(x,y); // Snyder eqn (20-17) pg 187, 150
777  }
778  else { // spherical oblique aspect
779  double denominator = rho*m_cosPhi1*cosC - y*m_sinPhi1*sinC;
780  // atan2 well defined for denominator = 0
781  // if numerator is also 0, returns 0
782  lambda = m_lambda0 + atan2(x * sinC, denominator);
783  // Snyder eqn (20-15) pg 186
784  }
785  }
786 
787  m_latitude = phi * 180.0 / PI;
788  m_longitude = lambda * 180.0 / PI;
789 
790  // Cleanup the longitude
792  m_longitude *= -1.0;
793  }
794 
795  if (m_longitudeDomain == 180) {
797  }
798  else {
799  // Do this because longitudeDirection could cause (-360,0)
801  }
802 
803  // Cleanup the latitude
804  if (IsPlanetocentric()) {
806  }
807 
808  m_good = true;
809  return m_good;
810  }
811 
829  bool LambertAzimuthalEqualArea::setCoordinateEllipsoid(const double x,
830  const double y) {
831  double phi; // latitude value to be calculated
832  double lambda; // longitude value to be calculated
833 
834  // ellipsoidal projection
835  double q;
836  // for polar aspects
837  double rho = sqrt(x*x+y*y); // Snyder eqn (20-18) pg 190
838  if (m_northPolarAspect ) { // ellipsoidal north polar aspect
839  q = m_qp - (rho*rho/(m_a*m_a)); // Snyder eqn (24-31) pg 190
840  lambda = m_lambda0 + atan2(x,-y); // Snyder eqn (20-16) pgs 190, 150
841  }
842  else if (m_southPolarAspect ) { // ellipsoidal south polar aspect
843  q = -1.0*(m_qp - (rho*rho/(m_a*m_a))); // Snyder eqn (24-31) pg 190
844  lambda = m_lambda0 + atan2(x,y); // Snyder eqn (20-17) pg 190, 150
845  }
846  else {// ellipsoidal oblique aspect
847  double xD = x/m_D; // m_D = 0 implies polar aspect
848  // (see setGroundEllipsoid() general oblique case
849  double Dy = m_D*y;
850  double rho = sqrt((xD*xD)+(Dy*Dy)); // Snyder eqn (24-28) pg 189
851  if (fabs(rho) > fabs(2*m_Rq)) {
852  m_good = false;
853  return m_good;
854  }
855  double Ce = 2*asin(rho/(2*m_Rq)); // Snyder eqn (24-29) pg 189
856  // TYPO IN TEXT, parentheses required
857  // around denominator (see usage pg 335)
858  // Note: Ce well defined since Rq = 0 is impossible (a > 0 and q > 1)
859  double sinCe = sin(Ce);
860  double cosCe = cos(Ce);
861 
862  if (rho < DBL_EPSILON) { // pg 189, first line appears to be typo in text,
863  // below is our interpretation.
864  // if rho = 0, then x=0 and (D=0 or y=0), so it
865  // makes sense that lambda = lambda0 in this case.
866  q = m_qp * m_sinBeta1;
867  lambda = m_lambda0;
868  }
869  else {
870  q = m_qp * (cosCe * m_sinBeta1 + m_D * y * sinCe * m_cosBeta1 / rho);
871  // Snyder eqn (24-27) pg 188
872  double numerator = x * sinCe;
873  double denominator = m_D * rho * m_cosBeta1 * cosCe
874  - m_D * m_D * y * m_sinBeta1 * sinCe;
875  // atan2 well defined for denominator = 0
876  // if numerator is also 0, returns 0
877  lambda = m_lambda0 + atan2(numerator, denominator);
878  // Snyder eqn (24-26) pg 188
879  }
880  }
881 
882  if (qFuzzyCompare(fabs(q), fabs(m_qp))) {
883  phi = copysign(HALFPI,q);// Snyder eqn (14-20) pg 189,
884  //(see definition of qp on pg 187)
885  }
886  else {
887  if (fabs(q) > 2) {
888  // verify that q/2 is in the domain of arcsin
889  m_good = false;
890  return m_good;
891  }
892  phi = asin(q/2); // Snyder pg 189 above (14-20) describes iteration
893  // process and initial value for phi
894  double phiOld = phi;
895  bool converge = false;
896  double tolerance = 1e-10; // tolerance same as Projection::phi2Compute()
897  // should we attempt to relate tolerance to pixel resolution ???
898  int maximumIterations = 100;
899  int i = 0;
900  while (!converge) {
901  i++;
902  // calculate values to reduce calculations:
903  double sinPhi = sin(phi);
904  double eSinPhi = m_e*sinPhi;
905  double cosPhi = cos(phi);
906  double squareESinPhi = eSinPhi*eSinPhi;
907  double oneMinusSquareESinPhi = 1 - squareESinPhi;
908  // find next iteration of phi
909  phi += oneMinusSquareESinPhi*oneMinusSquareESinPhi / (2 * cosPhi)
910  * (q / (1 - m_e * m_e)
911  - sinPhi / (oneMinusSquareESinPhi)
912  + log( (1 - eSinPhi) / (1 + eSinPhi) ) / (2 * m_e));
913  //eqn (3-16) pg 188
914  if (fabs(phiOld - phi) < tolerance) {
915  converge = true;
916  }
917  if (i > maximumIterations) {
918  m_good = false;
919  return m_good;
920  }
921  phiOld = phi;
922  }
923  }
924  m_latitude = phi * 180.0 / PI;
925  m_longitude = lambda * 180.0 / PI;
926 
927  // Cleanup the longitude
929  m_longitude *= -1.0;
930  }
931 
932  if (m_longitudeDomain == 180) {
934  }
935  else {
936  // Do this because longitudeDirection could cause (-360,0)
938  }
939 
940  // Cleanup the latitude
941  if (IsPlanetocentric()) {
943  }
944 
945  m_good = true;
946  return m_good;
947  }
948 
987  bool LambertAzimuthalEqualArea::XYRange(double &minX, double &maxX,
988  double &minY, double &maxY) {
989  // Global Polar projection
990  if ((m_northPolarAspect && qFuzzyCompare(-90.0, MinimumLatitude()))
991  || (m_southPolarAspect && qFuzzyCompare(90.0, MaximumLatitude()))) {
992  // for polar aspect projections, the antipodal point is the opposite pole.
993  // if it is included in lat range, the bounding circle will exist, no
994  // matter the longitude range.
995  double eRad = EquatorialRadius();
996  double pRad = PolarRadius();
997  double maxCoordValue = 0;
998 
999  if (m_spherical) {
1000  maxCoordValue = 2*EquatorialRadius();
1001  }
1002  else {
1003  maxCoordValue = sqrt(2*eRad*eRad
1004  + pRad*pRad*log((1+m_e)/(1-m_e))/m_e);
1005  }
1006  m_minimumX = -maxCoordValue;
1007  m_maximumX = maxCoordValue;
1008  m_minimumY = -maxCoordValue;
1009  m_maximumY = maxCoordValue;
1010  }
1011  // Polar projection, not including antipodal point
1012  else if (m_northPolarAspect || m_southPolarAspect) {
1013  return xyRangeLambertAzimuthalPolar(minX, maxX, minY, maxY);
1014  }
1015  // Oblique projection
1016  else { // oblique cases (includes equatorial aspect)
1017  if (xyRangeOblique(minX, maxX, minY, maxY)) {
1018  // Make sure that the calculations didn't go beyond the accepatable x,y
1019  // values. As far as we know, the x, y values should not exceed
1020  // 2*LocalRadius(-phi)
1021  double maxCoordValue = 2*LocalRadius(-m_phi1*180/PI);
1022  if (m_minimumX < -maxCoordValue) m_minimumX = -maxCoordValue;
1023  if (m_maximumX > maxCoordValue) m_maximumX = maxCoordValue;
1024  if (m_minimumX < -maxCoordValue) m_minimumX = -maxCoordValue;
1025  if (m_maximumY > maxCoordValue) m_maximumY = maxCoordValue;
1026  }
1027  else {
1028  return false;
1029  }
1030  }
1031 
1032  // If we haven't already returned, we have a Global Polar projection or an
1033  // Oblique projection that did not fail xyRangeOblique
1034 
1035  // Make sure everything is ordered
1036  if (m_minimumX >= m_maximumX) return false;
1037  if (m_minimumY >= m_maximumY) return false;
1038 
1039  // Return X/Y min/maxs
1040  minX = m_minimumX;
1041  maxX = m_maximumX;
1042  minY = m_minimumY;
1043  maxY = m_maximumY;
1044  return true;
1045  }
1046 
1083  bool LambertAzimuthalEqualArea::xyRangeLambertAzimuthalPolar(double &minX,
1084  double &maxX,
1085  double &minY,
1086  double &maxY) {
1087  // Test the four combinations of min/max lat/lon
1092 
1093  double centerLonDeg = m_lambda0 * 180.0 / PI;
1094  if (m_longitudeDirection == PositiveWest) centerLonDeg = centerLonDeg * -1.0;
1095 
1096  // Since this projection is Polar aspect, 4 longitudes will lie
1097  // directly on the horizontal and vertical axes of the
1098  // projection.
1099  // test combinations with each of these longitudes:
1100  //
1101  // down (negative vertical axis) - center long for north polar
1102  // up (positive vertical axis) - center long for south polar
1103  // left (negative horizontal axis)
1104  // right (positive horizontal axis)
1105  //
1106  for (double lon = centerLonDeg; lon < centerLonDeg + 360; lon += 90) {
1107  checkLongitude(lon);
1108  }
1109 
1110  // Make sure everything is ordered
1111  if (m_minimumX >= m_maximumX) return false;
1112  if (m_minimumY >= m_maximumY) return false;
1113 
1114  // Return X/Y min/maxs
1115  minX = m_minimumX;
1116  maxX = m_maximumX;
1117  minY = m_minimumY;
1118  maxY = m_maximumY;
1119  return true;
1120  }
1121 
1139  void LambertAzimuthalEqualArea::checkLongitude(double longitude) {
1140 
1141  double centerLatDeg = m_phi1 * 180.0 / PI;
1142 
1143  double innerLatitude, outerLatitude;
1144  if (m_northPolarAspect) {
1145  innerLatitude = MaximumLatitude();
1146  outerLatitude = MinimumLatitude();
1147  }
1148  else if (m_southPolarAspect) {
1149  innerLatitude = MinimumLatitude();
1150  outerLatitude = MaximumLatitude();
1151  }
1152  else {
1153  IString message = "checkLongitude() should only be called for a "
1154  "polar aspect projection. CenterLatitude is [";
1155  message = message + IString(centerLatDeg) + "] degrees.";
1156  throw IException(IException::Programmer, message, _FILEINFO_);
1157  }
1158 
1159  // Check whether longitude is in the min/max longitude range
1160  bool thisLongitudeInMinMaxRange = false;
1161  if (MaximumLongitude() - MinimumLongitude() == 360) {
1162  thisLongitudeInMinMaxRange = true;
1163  }
1164  double adjustedLon = To360Domain(longitude);
1165  double adjustedMinLon = To360Domain(MinimumLongitude());
1166  double adjustedMaxLon = To360Domain(MaximumLongitude());
1167 
1168  if (adjustedMinLon > adjustedMaxLon) {
1169  if (adjustedLon > adjustedMinLon) {
1170  adjustedLon -= 360;
1171  }
1172  adjustedMinLon -= 360;
1173  }
1174  // if lon value for this axis is between min lon and max lon
1175  if (adjustedMinLon <= adjustedLon
1176  && adjustedLon <= adjustedMaxLon) {
1177  thisLongitudeInMinMaxRange = true;
1178  }
1179  else {
1180  thisLongitudeInMinMaxRange = false;
1181  }
1182 
1183  if (thisLongitudeInMinMaxRange) {
1184  // any shape that includes this longitude
1185  XYRangeCheck(outerLatitude, longitude);
1186  }
1187  else {
1188  // determine which boundary value (minlon or maxlon)
1189  // is closer to given longitude
1190  double distToMinLon = fabs(adjustedMinLon - adjustedLon);
1191  double distToMaxLon = fabs(adjustedMaxLon - adjustedLon);
1192 
1193  if (distToMinLon >= 180 ) {
1194  distToMinLon = fabs(360 - distToMinLon);
1195  }
1196  if (distToMaxLon >= 180 ) {
1197  distToMaxLon = fabs(360 - distToMaxLon);
1198  }
1199 
1200  double nearestBoundary = 0;
1201  if (distToMinLon < distToMaxLon) {
1202  nearestBoundary = MinimumLongitude();
1203  }
1204  else {
1205  nearestBoundary = MaximumLongitude();
1206  }
1207 
1208  // shapes that come within 90 degrees of the given longitude
1209  if (distToMinLon <= 90 + DBL_EPSILON
1210  || distToMaxLon <= 90 + DBL_EPSILON) {
1211  XYRangeCheck(outerLatitude, nearestBoundary);
1212  }
1213  // shapes more than 90 degrees from the longitude that include the origin
1214  else if (qFuzzyCompare(MaximumLatitude(), centerLatDeg)) {
1215  XYRangeCheck(centerLatDeg,longitude);
1216  }
1217  // shapes more than 90 degrees from the longitude without the origin
1218  else {
1219  XYRangeCheck(innerLatitude, nearestBoundary);
1220  }
1221  }
1222  return;
1223  }
1224 
1253  PvlGroup mapping = TProjection::Mapping();
1254 
1255  mapping += m_mappingGrp["CenterLatitude"];
1256  mapping += m_mappingGrp["CenterLongitude"];
1257 
1258  return mapping;
1259  }
1260 
1279  PvlGroup mapping = TProjection::MappingLatitudes();
1280 
1281  mapping += m_mappingGrp["CenterLatitude"];
1282 
1283  return mapping;
1284  }
1285 
1304  PvlGroup mapping = TProjection::MappingLongitudes();
1305 
1306  mapping += m_mappingGrp["CenterLongitude"];
1307 
1308  return mapping;
1309  }
1310 
1311  // There are no ellipsoidal values for the scale factors h and k unless
1312  // using the polar aspects, Snyder pg 26.
1313 
1314  // These methods are not currently used in other Projection classes. However,
1315  // if similar methods are implemented for other projections, we should create
1316  // a virtual prototype in the parent Projection. Currently since they do not
1317  // exist in Projection, we can not call them if ProjectionFactory was used to
1318  // create the object.
1333  double LambertAzimuthalEqualArea::relativeScaleFactorLongitude() const {
1334  validateRelativeScaleFactor();
1335  return m_h;
1336  };
1337 
1352  double LambertAzimuthalEqualArea::relativeScaleFactorLatitude() const {
1353  validateRelativeScaleFactor();
1354  return m_k;
1355  }
1356 
1379  void LambertAzimuthalEqualArea::validateRelativeScaleFactor() const {
1380  if (!m_good) {
1381  IString message = "Projection failed or ground and coordinates have not "
1382  "been set. Relative scale factor can not be computed.";
1383  throw IException(IException::Unknown, message, _FILEINFO_);
1384  }
1386  IString message = "For ellipsidal bodies, relative scale factor can only "
1387  "be computed for polar aspect projections.";
1388  throw IException(IException::Unknown, message, _FILEINFO_);
1389  }
1390  if (m_northPolarAspect && qFuzzyCompare(-90.0, m_latitude)) {
1391  IString message = "Relative scale factor can not be computed for north "
1392  "polar aspect projection when ground is set to "
1393  "latitude -90.";
1394  throw IException(IException::Unknown, message, _FILEINFO_);
1395  }
1396  if (m_southPolarAspect && qFuzzyCompare(90.0, m_latitude)) {
1397  IString message = "Relative scale factor can not be computed for south "
1398  "polar aspect projection when ground is set to "
1399  "latitude 90.";
1400  throw IException(IException::Unknown, message, _FILEINFO_);
1401  }
1402  if (m_k == Null || m_h == Null) {
1403  IString message = "Relative scale factor can not be computed.";
1404  throw IException(IException::Unknown, message, _FILEINFO_);
1405  }
1406  }
1407 
1408 } // end namespace isis
1409 
1427 extern "C" Isis::Projection *LambertAzimuthalEqualAreaPlugin(Isis::Pvl &lab,
1428  bool allowDefaults) {
1429  return new Isis::LambertAzimuthalEqualArea(lab, allowDefaults);
1430 }
1431 
double EquatorialRadius() const
This returns the equatorial radius of the target.
double m_cosPhi1
The cosine of the center latitude.
double m_cosBeta1
The cosine of m_beta1.
bool m_northPolarAspect
Indicates whether this is a north polar aspect projection.
double m_sinPhi1
The sine of the center latitude.
PvlGroup MappingLongitudes()
This function returns the longitude keywords that this projection uses.
double MaximumLongitude() const
This returns the maximum longitude of the area of interest.
const double Null
Value for an Isis Null pixel.
Definition: SpecialPixel.h:110
double mCompute(const double sinphi, const double cosphi) const
A convience method to compute Snyder&#39;s m equation (14-15) for a given latitude, . ...
double m_lambda0
The center longitude for the map projection measured positive east, in radians.
bool xyRangeOblique(double &minX, double &maxX, double &minY, double &maxY)
This method is used to find the XY range for oblique aspect projections (non-polar projections) by "w...
double m_beta1
The authalaic latitude.
double TrueScaleLatitude() const
This method returns the latitude of true scale.
const double PI
The mathematical constant PI.
Definition: Constants.h:56
Longitude values increase in the westerly direction.
Definition: TProjection.h:241
double m_e
Eccentricity of the ellipsoid.
bool SetCoordinate(const double x, const double y)
This method is used to set the projection x/y.
double m_Rq
The radius of the sphere having the same surface area as the ellipsoid.
const double HALFPI
The mathematical constant PI/2.
Definition: Constants.h:57
Namespace for the standard library.
double m_q1
Snyder&#39;s q variable from equation (3-12) on page 187, computed for the center latitude, phi = m_phi1.
Search child objects.
Definition: PvlObject.h:170
double m_minimumX
The data elements m_minimumX, m_minimumY, m_maximumX, and m_maximumY are convience data elements when...
Definition: Projection.h:333
QString Version() const
This method returns the Version of the map projection.
QString toString(bool boolToConvert)
Global function to convert a boolean to a string.
Definition: IString.cpp:226
double m_D
Value used to correct scale in all directions at the center of the projection.
This error is for when a programmer made an API call that was illegal.
Definition: IException.h:162
void SetComputedXY(double x, double y)
This protected method is a helper for derived classes.
Definition: Projection.cpp:795
double m_latitude
This contains the currently set latitude value.
Definition: TProjection.h:332
virtual PvlGroup MappingLatitudes()
This function returns the latitude keywords that this projection uses.
double m_h
Relative scale factor along a meridian of longitude.
double MinimumLongitude() const
This returns the minimum longitude of the area of interest.
double m_qp
Snyder&#39;s q variable from equation (3-12) on page 187, computed for the north pole, phi = 90.
double m_maximumY
See minimumX description.
Definition: Projection.h:344
PvlGroup Mapping()
This function returns the keywords that this projection uses.
double m_longitude
This contains the currently set longitude value.
Definition: TProjection.h:334
bool IsPlanetocentric() const
This indicates if the latitude type is planetocentric (as opposed to planetographic).
double m_a
Equitorial Radius of the ellipsoid.
Base class for Map Projections.
Definition: Projection.h:171
double m_minimumY
See minimumX description.
Definition: Projection.h:343
double Eccentricity() const
This returns the eccentricity of the target,.
int m_longitudeDomain
This integer is either 180 or 360 and is read from the labels.
Definition: TProjection.h:347
static double To180Domain(const double lon)
This method converts a longitude into the -180 to 180 domain.
#define _FILEINFO_
Macro for the filename and line number.
Definition: IException.h:40
double LocalRadius() const
This method returns the local radius in meters at the current latitude position.
A type of error that cannot be classified as any of the other error types.
Definition: IException.h:134
bool operator==(const Projection &proj)
This method determines whether two map projection objects are equal by comparing the equatorial radiu...
Lambert Azimuthal Equal Area Map Projection.
bool SetGround(const double lat, const double lon)
This method is used to set the latitude/longitude (assumed to be of the correct LatitudeType, LongitudeDirection, and LongitudeDomain.
double m_m1
Snyder&#39;s m variable from equation (14-15) on page 187, computed from the center latitude, phi = m_phi1.
Container for cube-like labels.
Definition: Pvl.h:135
double ToPlanetographic(const double lat) const
This method converts a planetocentric latitude to a planetographic latitude.
double MinimumLatitude() const
This returns the minimum latitude of the area of interest.
virtual PvlGroup Mapping()
This function returns the keywords that this projection uses.
bool m_spherical
Indicates whether the body to be projected is spherical.
bool m_good
Indicates if the contents of m_x, m_y, m_latitude, and m_longitude are valid.
Definition: Projection.h:316
static double To360Domain(const double lon)
This method converts a longitude into the 0 to 360 domain.
LongitudeDirection m_longitudeDirection
An enumerated type indicating the LongitudeDirection read from the labels.
Definition: TProjection.h:340
double m_sinBeta1
The sine of m_beta1.
double m_phi1
The center latitude for the map projection, in radians.
double PolarRadius() const
This returns the polar radius of the target.
bool m_southPolarAspect
Indicates whether this is a south polar aspect projection.
Namespace for ISIS/Bullet specific routines.
Definition: Apollo.h:31
double m_k
Relative scale factor along a parallel of latitude.
PvlGroup MappingLatitudes()
This function returns the latitude keywords that this projection uses.
double ToPlanetocentric(const double lat) const
This method converts a planetographic latitude to a planetocentric latitude.
virtual PvlGroup MappingLongitudes()
This function returns the longitude keywords that this projection uses.
void SetXY(double x, double y)
This protected method is a helper for derived classes.
Definition: Projection.cpp:819
QString Name() const
This method returns the name of the map projection.
void XYRangeCheck(const double latitude, const double longitude)
This convience function is established to assist in the development of the XYRange virtual method...
bool XYRange(double &minX, double &maxX, double &minY, double &maxY)
This method is used to determine the x/y range which completely covers the area of interest specified...
double m_maximumX
See minimumX description.
Definition: Projection.h:342
double qCompute(const double sinPhi) const
A convience method to compute Snyder&#39;s q equation (3-12) for a given latitude, .
PvlGroup m_mappingGrp
Mapping group that created this projection.
Definition: Projection.h:345
bool m_equatorialAspect
Indicates whether this is a equatorial aspect projection.
double MaximumLatitude() const
This returns the maximum latitude of the area of interest.