USGS

Isis 3.0 Object Programmers' Reference

Home

TProjection.cpp
Go to the documentation of this file.
1 
22 #include "TProjection.h"
23 
24 #include <QObject>
25 
26 #include <cfloat>
27 #include <cmath>
28 #include <iomanip>
29 #include <sstream>
30 #include <vector>
31 
32 #include <SpiceUsr.h>
33 
34 #include "Constants.h"
35 #include "Displacement.h"
36 #include "FileName.h"
37 #include "IException.h"
38 #include "IString.h"
39 #include "Longitude.h"
40 #include "NaifStatus.h"
41 #include "Pvl.h"
42 #include "PvlGroup.h"
43 #include "PvlKeyword.h"
44 #include "SpecialPixel.h"
45 #include "WorldMapper.h"
46 
47 using namespace std;
48 namespace Isis {
104  TProjection::TProjection(Pvl &label) : Projection::Projection(label) {
105  try {
106  // Mapping group is read by the parent
107  // Get the radii from the EquatorialRadius and PolarRadius keywords
108  if ((m_mappingGrp.hasKeyword("EquatorialRadius")) &&
109  (m_mappingGrp.hasKeyword("PolarRadius"))) {
110  m_equatorialRadius = m_mappingGrp["EquatorialRadius"];
111  m_polarRadius = m_mappingGrp["PolarRadius"];
112  }
113  // Get the radii using the "TargetName" keyword and NAIF
114  else if (m_mappingGrp.hasKeyword("TargetName")) {
115  PvlGroup radii = TargetRadii((QString) m_mappingGrp["TargetName"]);
116  m_equatorialRadius = radii["EquatorialRadius"];
117  m_polarRadius = radii["PolarRadius"];
118  }
119  else {
120  IString msg = "Projection failed. No target radii are available "
121  "through keywords [EquatorialRadius and PolarRadius] "
122  "or [TargetName].";
124  }
125 
126  // Check the radii for validity
127  if (m_equatorialRadius <= 0.0) {
128  IString msg = "Projection failed. Invalid value for keyword "
129  "[EquatorialRadius]. It must be greater than zero";
131  }
132  if (m_polarRadius <= 0.0) {
133  IString msg = "Projection failed. Invalid value for keyword "
134  "[PolarRadius]. It must be greater than zero";
136  }
137 
138  // Get the LatitudeType
139  if ((QString) m_mappingGrp["LatitudeType"] == "Planetographic") {
141  }
142  else if ((QString) m_mappingGrp["LatitudeType"] == "Planetocentric") {
144  }
145  else {
146  IString msg = "Projection failed. Invalid value for keyword "
147  "[LatitudeType] must be "
148  "[Planetographic or Planetocentric]";
150  }
151 
152  // Get the LongitudeDirection
153  if ((QString) m_mappingGrp["LongitudeDirection"] == "PositiveWest") {
155  }
156  else if ((QString) m_mappingGrp["LongitudeDirection"] == "PositiveEast") {
158  }
159  else {
160  IString msg = "Projection failed. Invalid value for keyword "
161  "[LongitudeDirection] must be "
162  "[PositiveWest or PositiveEast]";
164  }
165 
166  // Get the LongitudeDomain
167  if ((QString) m_mappingGrp["LongitudeDomain"] == "360") {
168  m_longitudeDomain = 360;
169  }
170  else if ((QString) m_mappingGrp["LongitudeDomain"] == "180") {
171  m_longitudeDomain = 180;
172  }
173  else {
174  IString msg = "Projection failed. Invalid value for keyword "
175  "[LongitudeDomain] must be [180 or 360]";
177  }
178 
179  // Get the ground range if it exists
180  m_groundRangeGood = false;
181  if ((m_mappingGrp.hasKeyword("MinimumLatitude")) &&
182  (m_mappingGrp.hasKeyword("MaximumLatitude")) &&
183  (m_mappingGrp.hasKeyword("MinimumLongitude")) &&
184  (m_mappingGrp.hasKeyword("MaximumLongitude"))) {
185  m_minimumLatitude = m_mappingGrp["MinimumLatitude"];
186  m_maximumLatitude = m_mappingGrp["MaximumLatitude"];
187  m_minimumLongitude = m_mappingGrp["MinimumLongitude"];
188  m_maximumLongitude = m_mappingGrp["MaximumLongitude"];
189 
190  if ((m_minimumLatitude < -90.0) || (m_minimumLatitude > 90.0)) {
191  IString msg = "Projection failed. "
192  "[MinimumLatitude] of [" + IString(m_minimumLatitude)
193  + "] is outside the range of [-90:90]";
195  }
196 
197  if ((m_maximumLatitude < -90.0) || (m_maximumLatitude > 90.0)) {
198  IString msg = "Projection failed. "
199  "[MaximumLatitude] of [" + IString(m_maximumLatitude)
200  + "] is outside the range of [-90:90]";
202  }
203 
205  IString msg = "Projection failed. "
206  "[MinimumLatitude,MaximumLatitude] of ["
207  + IString(m_minimumLatitude) + ","
208  + IString(m_maximumLatitude) + "] are not "
209  + "properly ordered";
211  }
212 
214  IString msg = "Projection failed. "
215  "[MinimumLongitude,MaximumLongitude] of ["
216  + IString(m_minimumLongitude) + ","
217  + IString(m_maximumLongitude) + "] are not "
218  + "properly ordered";
220  }
221 
222  m_groundRangeGood = true;
223  }
224  else {
225  // if no ground range is given, initialize the min/max lat/lon to 0
226  m_minimumLatitude = 0.0;
227  m_maximumLatitude = 0.0;
228  m_minimumLongitude = 0.0;
229  m_maximumLongitude = 0.0;
230  }
231 
232  // Initialize miscellaneous protected data elements
234  IString msg = "Projection failed. Invalid keyword value(s). "
235  "[EquatorialRadius] = " + IString(m_equatorialRadius)
236  + " must be greater than or equal to [PolarRadius] = "
239  }
240  else {
241  m_eccentricity = 1.0 -
245  }
246 
247  // initialize the rest of the x,y,lat,lon member variables
248  m_latitude = Null;
249  m_longitude = Null;
250 
251  // If we made it to this point, we have what we need for a triaxial projection
253  }
254  catch(IException &e) {
255  IString msg = "Projection failed. Invalid label group [Mapping]";
256  throw IException(e, IException::Unknown, msg, _FILEINFO_);
257  }
258  }
259 
262  }
263 
275  if (!Projection::operator==(proj)) return false;
276  TProjection *tproj = (TProjection *) &proj;
277  if (EquatorialRadius() != tproj->EquatorialRadius()) return false;
278  if (PolarRadius() != tproj->PolarRadius()) return false;
279  if (IsPlanetocentric() != tproj->IsPlanetocentric()) return false;
280  if (IsPositiveWest() != tproj->IsPositiveWest()) return false;
281  return true;
282  }
283 
284 
292  return m_equatorialRadius;
293  }
294 
301  double TProjection::PolarRadius() const {
302  return m_polarRadius;
303  }
304 
318  double TProjection::Eccentricity() const {
319  return m_eccentricity;
320  }
321 
340  double TProjection::LocalRadius(double latitude) const {
341  if (latitude == Null) {
343  "Unable to calculate local radius. The given latitude value ["
344  + IString(latitude) + "] is invalid.",
345  _FILEINFO_);
346  }
347  double a = m_equatorialRadius;
348  double c = m_polarRadius;
349  // to save calculations, if the target is spherical, return the eq. rad
350  if (a - c < DBL_EPSILON) {
351  return a;
352  }
353  else {
354  double lat = latitude * PI / 180.0;
355  return a * c / sqrt(pow(c * cos(lat), 2) + pow(a * sin(lat), 2));
356  }
357  }
358 
367  double TProjection::LocalRadius() const {
368  return LocalRadius(m_latitude);
369  }
370 
386  static QMap<QString, PvlGroup> cachedResults;
387 
388  PvlGroup mapping("Mapping");
389  if (!cachedResults.contains(target)) {
390  // Convert the target name to a NAIF code
391  SpiceInt code;
392  SpiceBoolean found;
393  bodn2c_c(target.toAscii().data(), &code, &found);
394  if (!found) {
395  QString msg = "Could not convert target name [" + target +"] to NAIF code";
396  throw IException(IException::Io, msg, _FILEINFO_);
397  }
398 
399  // Load the most recent target attitude and shape kernel for NAIF
400  FileName kern("$Base/kernels/pck/pck?????.tpc");
401  kern = kern.highestVersion();
402  QString kernName(kern.expanded());
403  furnsh_c(kernName.toAscii().data());
404 
405  // Get the radii from NAIF
407  SpiceInt n;
408  SpiceDouble radii[3];
409  bodvar_c(code, "RADII", &n, radii);
410  unload_c(kernName.toAscii().data());
411 
412  try {
414  }
415  catch (IException &e) {
416  throw IException(e,
418  QObject::tr("The target name [%1] does not correspond to a target body "
419  "with known radii").arg(target),
420  _FILEINFO_);
421  }
422 
423  mapping += PvlKeyword("TargetName", target);
424  mapping += PvlKeyword("EquatorialRadius", toString(radii[0] * 1000.0), "meters");
425  mapping += PvlKeyword("PolarRadius", toString(radii[2] * 1000.0), "meters");
426  cachedResults[target] = mapping;
427  }
428  else {
429  mapping = cachedResults[target];
430  }
431 
432  return mapping;
433  }
434 
448  //Check to see if the mapGroup already has the target radii.
449  //If BOTH radii are already in the mapGroup then just return back the map
450  //Group.
451  if (mapGroup.hasKeyword("EquatorialRadius")
452  && mapGroup.hasKeyword("PolarRadius")) {
453  return mapGroup;
454  }
455  //If the mapping group only has one or the other of the radii keywords, then
456  //we are going to replace both, so delete which ever one it does have.
457  if (mapGroup.hasKeyword("EquatorialRadius")
458  && !mapGroup.hasKeyword("PolarRadius")) {
459  mapGroup.deleteKeyword("EquatorialRadius");
460  }
461  if (!mapGroup.hasKeyword("EquatorialRadius")
462  && mapGroup.hasKeyword("PolarRadius")) {
463  mapGroup.deleteKeyword("PolarRadius");
464  }
465 
466  PvlGroup inst = cubeLab.findGroup("Instrument", Pvl::Traverse);
467  QString target = inst["TargetName"];
468  PvlGroup radii = TProjection::TargetRadii(target);
469  //Now INSERT the EquatorialRadius and PolorRadius into the mapGroup pvl.
470  mapGroup += PvlKeyword("EquatorialRadius",
471  radii.findKeyword("EquatorialRadius")[0], "meters");
472  mapGroup += PvlKeyword("PolarRadius",
473  radii.findKeyword("PolarRadius")[0], "meters");
474 
475  return mapGroup;
476  }
477 
489  return 0.0;
490  }
491 
502  return false;
503  }
504 
513  return m_latitudeType == Planetocentric;
514  }
515 
524  return m_latitudeType == Planetographic;
525  }
526 
538  double TProjection::ToPlanetocentric(const double lat) const {
540  }
541 
554  double TProjection::ToPlanetocentric(const double lat,
555  double eRadius, double pRadius) {
556  if (lat == Null || abs(lat) > 90.0) {
558  "Unable to convert to Planetocentric. The given latitude value ["
559  + IString(lat) + "] is invalid.",
560  _FILEINFO_);
561  }
562  double mylat = lat;
563  if (abs(mylat) < 90.0) { // So tan doesn't fail
564  mylat *= PI / 180.0;
565  mylat = atan(tan(mylat) * (pRadius / eRadius) *
566  (pRadius / eRadius));
567  mylat *= 180.0 / PI;
568  }
569  return mylat;
570  }
571 
583  double TProjection::ToPlanetographic(const double lat) const {
585  }
586 
600  double TProjection::ToPlanetographic(double lat,
601  double eRadius, double pRadius) {
602  //Account for double rounding error.
603  if (qFuzzyCompare(fabs(lat), 90.0)) {
604  lat = qRound(lat);
605  }
606  if (lat == Null || fabs(lat) > 90.0) {
608  "Unable to convert to Planetographic. The given latitude value ["
609  + IString(lat) + "] is invalid.",
610  _FILEINFO_);
611  }
612  double mylat = lat;
613  if (fabs(mylat) < 90.0) { // So tan doesn't fail
614  mylat *= PI / 180.0;
615  mylat = atan(tan(mylat) * (eRadius / pRadius) *
616  (eRadius / pRadius));
617  mylat *= 180.0 / PI;
618  }
619  return mylat;
620  }
621 
629  if (m_latitudeType == Planetographic) return "Planetographic";
630  return "Planetocentric";
631  }
632 
642  }
643 
653  }
654 
668  double TProjection::ToPositiveEast(const double lon, const int domain) {
669  if (lon == Null) {
671  "Unable to convert to PositiveEast. The given longitude value ["
672  + IString(lon) + "] is invalid.",
673  _FILEINFO_);
674  }
675  double mylon = lon;
676 
677  mylon *= -1;
678 
679  if (domain == 360) {
680  mylon = To360Domain(mylon);
681  }
682  else if (domain == 180) {
683  mylon = To180Domain(mylon);
684  }
685  else {
686  IString msg = "Unable to convert longitude. Domain [" + IString(domain)
687  + "] is not 180 or 360.";
689  }
690 
691  return mylon;
692  }
693 
707  double TProjection::ToPositiveWest(const double lon, const int domain) {
708  if (lon == Null) {
710  "Unable to convert to PositiveWest. The given longitude value ["
711  + IString(lon) + "] is invalid.",
712  _FILEINFO_);
713  }
714  double mylon = lon;
715 
716  mylon *= -1;
717 
718  if (domain == 360) {
719  mylon = To360Domain(mylon);
720  }
721  else if (domain == 180) {
722  mylon = To180Domain(mylon);
723  }
724  else {
725  IString msg = "Unable to convert longitude. Domain [" + IString(domain)
726  + "] is not 180 or 360.";
728  }
729 
730  return mylon;
731  }
732 
741  if (m_longitudeDirection == PositiveEast) return "PositiveEast";
742  return "PositiveWest";
743  }
744 
753  return m_longitudeDomain == 180;
754  }
755 
764  return m_longitudeDomain == 360;
765  }
766 
777  double TProjection::To180Domain(const double lon) {
778  if (lon == Null) {
780  "Unable to convert to 180 degree domain. The given longitude value ["
781  + IString(lon) + "] is invalid.",
782  _FILEINFO_);
783  }
785  }
786 
795  double TProjection::To360Domain(const double lon) {
796  if (lon == Null) {
798  "Unable to convert to 360 degree domain. The given longitude value ["
799  + IString(lon) + "] is invalid.",
800  _FILEINFO_);
801  }
802  double result = lon;
803 
804  if ( (lon < 0.0 || lon > 360.0) &&
805  !qFuzzyCompare(lon, 0.0) && !qFuzzyCompare(lon, 360.0)) {
807  }
808 
809  return result;
810  }
811 
819  if (m_longitudeDomain == 360) return "360";
820  return "180";
821  }
822 
831  return m_minimumLatitude;
832  }
833 
842  return m_maximumLatitude;
843  }
844 
853  return m_minimumLongitude;
854  }
855 
864  return m_maximumLongitude;
865  }
866 
880  bool TProjection::SetGround(const double lat, const double lon) {
881  if (lat == Null || lon == Null) {
882  m_good = false;
883  return m_good;
884  }
885  else {
886  m_latitude = lat;
887  m_longitude = lon;
888  m_good = true;
889  SetComputedXY(lon, lat);
890  }
891  return m_good;
892  }
893 
909  bool TProjection::SetCoordinate(const double x, const double y) {
910  if (x == Null || y == Null) {
911  m_good = false;
912  }
913  else {
914  m_good = true;
915  SetXY(x, y);
916  m_latitude = XCoord();
917  m_longitude = YCoord();
918  }
919  return m_good;
920  }
921 
922 
931  double TProjection::Latitude() const {
932  return m_latitude;
933  }
934 
943  double TProjection::Longitude() const {
944  return m_longitude;
945  }
946 
947 
959  bool TProjection::SetUniversalGround(const double lat, const double lon) {
960  if (lat == Null || lon == Null) {
961  m_good = false;
962  return m_good;
963  }
964  // Deal with the longitude first
965  m_longitude = lon;
967  if (m_longitudeDomain == 180) {
969  }
970  else {
971  // Do this because longitudeDirection could cause (-360,0)
973  }
974 
975  // Deal with the latitude
978  }
979  else {
980  m_latitude = lat;
981  }
982 
983  // Now the lat/lon are in user defined coordinates so set them
985  }
986 
995  double lat = m_latitude;
997  return lat;
998  }
999 
1009  double lon = m_longitude;
1010  if (m_longitudeDirection == PositiveWest) lon = -lon;
1011  lon = To360Domain(lon);
1012  return lon;
1013  }
1014 
1015 
1026  double TProjection::Scale() const {
1027  if (m_mapper != NULL) {
1028  double lat = TrueScaleLatitude() * PI / 180.0;
1029  double a = m_polarRadius * cos(lat);
1030  double b = m_equatorialRadius * sin(lat);
1031  double localRadius = m_equatorialRadius * m_polarRadius /
1032  sqrt(a * a + b * b);
1033 
1034  return localRadius / m_mapper->Resolution();
1035  }
1036  else {
1037  return 1.0;
1038  }
1039  }
1040 
1041 
1079  bool TProjection::XYRange(double &minX, double &maxX,
1080  double &minY, double &maxY) {
1081  if (minX == Null || maxX == Null || minY == Null || maxY == Null) {
1082  return false;
1083  }
1084  if (m_groundRangeGood) {
1085  minX = m_minimumLongitude;
1086  maxX = m_maximumLongitude;
1087  minY = m_minimumLatitude;
1088  maxY = m_maximumLatitude;
1089  return true;
1090  }
1091  return false;
1092  }
1093 
1131  void TProjection::XYRangeCheck(const double latitude, const double longitude) {
1132  if (latitude == Null || longitude == Null) {
1133  m_good = false;
1134  return;
1135  }
1136  SetGround(latitude, longitude);
1137  if (!IsGood()) return;
1138 
1139  if (XCoord() < m_minimumX) m_minimumX = XCoord();
1140  if (XCoord() > m_maximumX) m_maximumX = XCoord();
1141  if (YCoord() < m_minimumY) m_minimumY = YCoord();
1142  if (YCoord() > m_maximumY) m_maximumY = YCoord();
1143  return;
1144  }
1145 
1168  bool TProjection::xyRangeOblique(double &minX, double &maxX,
1169  double &minY, double &maxY) {
1170  if (minX == Null || maxX == Null || minY == Null || maxY == Null) {
1171  return false;
1172  }
1173  //For oblique, we'll have to walk all 4 sides to find out min/max x/y values
1174  if (!HasGroundRange()) return false; // Don't have min/max lat/lon,
1175  //can't continue
1176 
1177  m_specialLatCases.clear();
1178  m_specialLonCases.clear();
1179 
1180  // First, search longitude for min X/Y
1181  double minFoundX1, minFoundX2;
1182  double minFoundY1, minFoundY2;
1183 
1184  // Search for minX between minlat and maxlat along minlon
1186  minFoundX1, MinimumLongitude(), true, true, true);
1187  // Search for minX between minlat and maxlat along maxlon
1189  minFoundX2, MaximumLongitude(), true, true, true);
1190  // Search for minY between minlat and maxlat along minlon
1192  minFoundY1, MinimumLongitude(), false, true, true);
1193  // Search for minY between minlat and maxlat along maxlon
1195  minFoundY2, MaximumLongitude(), false, true, true);
1196 
1197  // Second, search latitude for min X/Y
1198  double minFoundX3, minFoundX4;
1199  double minFoundY3, minFoundY4;
1200 
1201  // Search for minX between minlon and maxlon along minlat
1203  minFoundX3, MinimumLatitude(), true, false, true);
1204  // Search for minX between minlon and maxlon along maxlat
1206  minFoundX4, MaximumLatitude(), true, false, true);
1207  // Search for minY between minlon and maxlon along minlat
1209  minFoundY3, MinimumLatitude(), false, false, true);
1210  // Search for minY between minlon and maxlon along maxlat
1212  minFoundY4, MaximumLatitude(), false, false, true);
1213 
1214  // We've searched all possible minimums, go ahead and store the lowest
1215  double minFoundX5 = min(minFoundX1, minFoundX2);
1216  double minFoundX6 = min(minFoundX3, minFoundX4);
1217  m_minimumX = min(minFoundX5, minFoundX6);
1218 
1219  double minFoundY5 = min(minFoundY1, minFoundY2);
1220  double minFoundY6 = min(minFoundY3, minFoundY4);
1221  m_minimumY = min(minFoundY5, minFoundY6);
1222 
1223  // Search longitude for max X/Y
1224  double maxFoundX1, maxFoundX2;
1225  double maxFoundY1, maxFoundY2;
1226 
1227  // Search for maxX between minlat and maxlat along minlon
1229  maxFoundX1, MinimumLongitude(), true, true, false);
1230  // Search for maxX between minlat and maxlat along maxlon
1232  maxFoundX2, MaximumLongitude(), true, true, false);
1233  // Search for maxY between minlat and maxlat along minlon
1235  maxFoundY1, MinimumLongitude(), false, true, false);
1236  // Search for maxY between minlat and maxlat along maxlon
1238  maxFoundY2, MaximumLongitude(), false, true, false);
1239 
1240  // Search latitude for max X/Y
1241  double maxFoundX3, maxFoundX4;
1242  double maxFoundY3, maxFoundY4;
1243 
1244  // Search for maxX between minlon and maxlon along minlat
1246  maxFoundX3, MinimumLatitude(), true, false, false);
1247  // Search for maxX between minlon and maxlon along maxlat
1249  maxFoundX4, MaximumLatitude(), true, false, false);
1250  // Search for maxY between minlon and maxlon along minlat
1252  maxFoundY3, MinimumLatitude(), false, false, false);
1253  // Search for maxY between minlon and maxlon along maxlat
1255  maxFoundY4, MaximumLatitude(), false, false, false);
1256 
1257  // We've searched all possible maximums, go ahead and store the highest
1258  double maxFoundX5 = max(maxFoundX1, maxFoundX2);
1259  double maxFoundX6 = max(maxFoundX3, maxFoundX4);
1260  m_maximumX = max(maxFoundX5, maxFoundX6);
1261 
1262  double maxFoundY5 = max(maxFoundY1, maxFoundY2);
1263  double maxFoundY6 = max(maxFoundY3, maxFoundY4);
1264  m_maximumY = max(maxFoundY5, maxFoundY6);
1265 
1266  // Look along discontinuities for more extremes
1267  vector<double> specialLatCases = m_specialLatCases;
1268  for (unsigned int specialLatCase = 0;
1269  specialLatCase < specialLatCases.size();
1270  specialLatCase ++) {
1271  double minX, maxX, minY, maxY;
1272 
1273  // Search for minX between minlon and maxlon along latitude discontinuities
1275  minX, specialLatCases[specialLatCase], true, false, true);
1276  // Search for minY between minlon and maxlon along latitude discontinuities
1278  minY, specialLatCases[specialLatCase], false, false, true);
1279  // Search for maxX between minlon and maxlon along latitude discontinuities
1281  maxX, specialLatCases[specialLatCase], true, false, false);
1282  // Search for maxX between minlon and maxlon along latitude discontinuities
1284  maxY, specialLatCases[specialLatCase], false, false, false);
1285 
1286  m_minimumX = min(minX, m_minimumX);
1287  m_maximumX = max(maxX, m_maximumX);
1288  m_minimumY = min(minY, m_minimumY);
1289  m_maximumY = max(maxY, m_maximumY);
1290  }
1291 
1292  vector<double> specialLonCases = m_specialLonCases;
1293  for (unsigned int specialLonCase = 0;
1294  specialLonCase < specialLonCases.size();
1295  specialLonCase ++) {
1296  double minX, maxX, minY, maxY;
1297 
1298  // Search for minX between minlat and maxlat along longitude discontinuities
1300  minX, specialLonCases[specialLonCase], true, true, true);
1301  // Search for minY between minlat and maxlat along longitude discontinuities
1303  minY, specialLonCases[specialLonCase], false, true, true);
1304  // Search for maxX between minlat and maxlat along longitude discontinuities
1306  maxX, specialLonCases[specialLonCase], true, true, false);
1307  // Search for maxY between minlat and maxlat along longitude discontinuities
1309  maxY, specialLonCases[specialLonCase], false, true, false);
1310 
1311  m_minimumX = min(minX, m_minimumX);
1312  m_maximumX = max(maxX, m_maximumX);
1313  m_minimumY = min(minY, m_minimumY);
1314  m_maximumY = max(maxY, m_maximumY);
1315  }
1316 
1317  m_specialLatCases.clear();
1318  m_specialLonCases.clear();
1319 
1320  // Make sure everything is ordered
1321  if (m_minimumX >= m_maximumX) return false;
1322  if (m_minimumY >= m_maximumY) return false;
1323 
1324  // Return X/Y min/maxs
1325  minX = m_minimumX;
1326  maxX = m_maximumX;
1327  minY = m_minimumY;
1328  maxY = m_maximumY;
1329 
1330  return true;
1331  }
1332 
1370  void TProjection::doSearch(double minBorder, double maxBorder,
1371  double &extremeVal, const double constBorder,
1372  bool searchX, bool searchLongitude, bool findMin) {
1373  if (minBorder == Null || maxBorder == Null || constBorder == Null) {
1374  return;
1375  }
1376  const double TOLERANCE = PixelResolution()/2;
1377  const int NUM_ATTEMPTS = (unsigned int)DBL_DIG; // It's unsafe to go past
1378  // this precision
1379 
1380  double minBorderX, minBorderY, maxBorderX, maxBorderY;
1381  int attempts = 0;
1382 
1383  do {
1384  findExtreme(minBorder, maxBorder, minBorderX, minBorderY, maxBorderX,
1385  maxBorderY, constBorder, searchX, searchLongitude, findMin);
1386  if (minBorderX == Null && maxBorderX == Null
1387  && minBorderY == Null && maxBorderY == Null ) {
1388  attempts = NUM_ATTEMPTS;
1389  continue;
1390  }
1391  attempts ++;
1392  }
1393  while ((fabs(minBorderX - maxBorderX) > TOLERANCE
1394  || fabs(minBorderY - maxBorderY) > TOLERANCE)
1395  && (attempts < NUM_ATTEMPTS));
1396  // check both x and y distance in case symmetry of map
1397  // For example, if minBorderX = maxBorderX but minBorderY = -maxBorderY,
1398  // these points may not be close enough.
1399 
1400  if (attempts >= NUM_ATTEMPTS) {
1401  // We zoomed in on a discontinuity because our range never shrank, this
1402  // will need to be rechecked later.
1403  // *min and max border should be nearly identical, so it doesn't matter
1404  // which is used here
1405  if (searchLongitude) {
1406  m_specialLatCases.push_back(minBorder);
1407  }
1408  else {
1409  m_specialLonCases.push_back(minBorder);
1410  }
1411  }
1412 
1413  // These values will always be accurate, even over a discontinuity
1414  if (findMin) {
1415  if (searchX) extremeVal = min(minBorderX, maxBorderX);
1416  else extremeVal = min(minBorderY, maxBorderY);
1417  }
1418  else {
1419  if (searchX) extremeVal = max(minBorderX, maxBorderX);
1420  else extremeVal = max(minBorderY, maxBorderY);
1421  }
1422  return;
1423  }
1424 
1484  void TProjection::findExtreme(double &minBorder, double &maxBorder,
1485  double &minBorderX, double &minBorderY,
1486  double &maxBorderX, double &maxBorderY,
1487  const double constBorder, bool searchX,
1488  bool searchLongitude, bool findMin) {
1489  if (minBorder == Null || maxBorder == Null || constBorder == Null) {
1490  minBorderX = Null;
1491  minBorderY = minBorderX;
1492  minBorderX = minBorderX;
1493  minBorderY = minBorderX;
1494  return;
1495  }
1496  if (!searchLongitude && (fabs(fabs(constBorder) - 90.0) < DBL_EPSILON)) {
1497  // it is impossible to search "along" a pole
1498  setSearchGround(minBorder, constBorder, searchLongitude);
1499  minBorderX = XCoord();
1500  minBorderY = YCoord();
1501  maxBorderX = minBorderX;
1502  maxBorderY = minBorderY;
1503  return;
1504  }
1505  // Always do 10 steps
1506  const double STEP_SIZE = (maxBorder - minBorder) / 10.0;
1507  const double LOOP_END = maxBorder + (STEP_SIZE / 2.0); // This ensures we do
1508  // all of the steps
1509  // properly
1510  double currBorderVal = minBorder;
1511  setSearchGround(minBorder, constBorder, searchLongitude);
1512 
1513  // this makes sure that the initial currBorderVal is valid before entering
1514  // the loop below
1515  if (!m_good){
1516  // minBorder = currBorderVal+STEP_SIZE < LOOP_END until setGround is good?
1517  // then, if still not good return?
1518  while (!m_good && currBorderVal <= LOOP_END) {
1519  currBorderVal+=STEP_SIZE;
1520  if (searchLongitude && (currBorderVal - 90.0 > DBL_EPSILON)) {
1521  currBorderVal = 90.0;
1522  }
1523  setSearchGround(currBorderVal, constBorder, searchLongitude);
1524  }
1525  if (!m_good) {
1526  minBorderX = Null;
1527  minBorderY = minBorderX;
1528  minBorderX = minBorderX;
1529  minBorderY = minBorderX;
1530  return;
1531  }
1532  }
1533 
1534  // save the values of three consecutive steps from the minBorder towards
1535  // the maxBorder along the constBorder. initialize these three border
1536  // values (the non-constant lat or lon)
1537  double border1 = currBorderVal;
1538  double border2 = currBorderVal;
1539  double border3 = currBorderVal;
1540 
1541  // save the coordinate (x or y) values that correspond to the first
1542  // two borders that are being saved.
1543  // initialize these two coordinate values (x or y)
1544  double value1 = (searchX) ? XCoord() : YCoord();
1545  double value2 = value1;
1546 
1547  // initialize the extreme coordinate value
1548  // -- this is the largest coordinate value found so far
1549  double extremeVal2 = value2;
1550 
1551  // initialize the extreme border values
1552  // -- these are the borders on either side of the extreme coordinate value
1553  double extremeBorder1 = minBorder;
1554  double extremeBorder3 = minBorder;
1555 
1556  while (currBorderVal <= LOOP_END) {
1557 
1558  // this conditional was added to prevent trying to SetGround with an
1559  // invalid latitude greater than 90 degrees. There is no need check for
1560  // latitude less than -90 since we start at the minBorder (already
1561  // assumed to be valid) and step forward toward (and possibly past)
1562  // maxBorder
1563  if (searchLongitude && (currBorderVal - 90.0 > DBL_EPSILON)) {
1564  currBorderVal = 90.0;
1565  }
1566 
1567  // update the current border value along constBorder
1568  currBorderVal += STEP_SIZE;
1569  setSearchGround(currBorderVal, constBorder, searchLongitude);
1570  if (!m_good){
1571  continue;
1572  }
1573 
1574  // update the border and coordinate values
1575  border3 = border2;
1576  border2 = border1;
1577  border1 = currBorderVal;
1578  value2 = value1;
1579  value1 = (searchX) ? XCoord() : YCoord();
1580 
1581  if ((findMin && value2 < extremeVal2)
1582  || (!findMin && value2 > extremeVal2)) {
1583  // Compare the coordinate value associated with the center border with
1584  // the current extreme. If the updated coordinate value is more extreme
1585  // (smaller or larger, depending on findMin), then we update the
1586  // extremeVal and it's borders.
1587  extremeVal2 = value2;
1588 
1589  extremeBorder3 = border3;
1590  extremeBorder1 = border1;
1591  }
1592  }
1593 
1594  // update min/max border values to the values on either side of the most
1595  // extreme coordinate found in this call to this method
1596 
1597  minBorder = extremeBorder3; // Border 3 is lagging and thus smaller
1598 
1599  // since the loop steps past the original maxBorder, we want to retain
1600  // the original maxBorder value so we don't go outside of the original
1601  // min/max range given
1602  if (extremeBorder1 <= maxBorder ) {
1603  maxBorder = extremeBorder1; // Border 1 is leading and thus larger
1604  }
1605 
1606  // update minBorder coordinate values
1607  setSearchGround(minBorder, constBorder, searchLongitude);
1608  // if (!m_good){
1609  // this should not happen since minBorder has already been verified in
1610  // the while loop above
1611  // }
1612 
1613  minBorderX = XCoord();
1614  minBorderY = YCoord();
1615 
1616  // update maxBorder coordinate values
1617  setSearchGround(maxBorder, constBorder, searchLongitude);
1618  // if (!m_good){
1619  // this should not happen since maxBorder has already been verified in
1620  // the while loop above
1621  // }
1622 
1623  maxBorderX = XCoord();
1624  maxBorderY = YCoord();
1625  return;
1626  }
1627 
1650  void TProjection::setSearchGround(const double variableBorder,
1651  const double constBorder,
1652  bool variableIsLat) {
1653  if (variableBorder == Null || constBorder == Null) {
1654  return;
1655  }
1656  double lat, lon;
1657  if (variableIsLat) {
1658  lat = variableBorder;
1659  lon = constBorder;
1660  }
1661  else {
1662  lat = constBorder;
1663  lon = variableBorder;
1664  }
1665  SetGround(lat, lon);
1666  return;
1667  }
1668 
1669 
1676  PvlGroup mapping("Mapping");
1677 
1678  QStringList keyNames;
1679  keyNames << "TargetName" << "ProjectionName" << "EquatorialRadius" << "PolarRadius"
1680  << "LatitudeType" << "LongitudeDirection" << "LongitudeDomain"
1681  << "PixelResolution" << "Scale" << "UpperLeftCornerX" << "UpperLeftCornerY"
1682  << "MinimumLatitude" << "MaximumLatitude" << "MinimumLongitude" << "MaximumLongitude"
1683  << "Rotation";
1684 
1685  foreach (QString keyName, keyNames) {
1686  if (m_mappingGrp.hasKeyword(keyName)) {
1687  mapping += m_mappingGrp[keyName];
1688  }
1689  }
1690 
1691  return mapping;
1692  }
1693 
1694 
1701  PvlGroup mapping("Mapping");
1702 
1703  if (HasGroundRange()) {
1704  mapping += m_mappingGrp["MinimumLatitude"];
1705  mapping += m_mappingGrp["MaximumLatitude"];
1706  }
1707 
1708  return mapping;
1709  }
1710 
1717  PvlGroup mapping("Mapping");
1718 
1719  if (HasGroundRange()) {
1720  mapping += m_mappingGrp["MinimumLongitude"];
1721  mapping += m_mappingGrp["MaximumLongitude"];
1722  }
1723 
1724  return mapping;
1725  }
1726 
1747  double TProjection::qCompute(const double sinPhi) const {
1748  if (m_eccentricity < DBL_EPSILON) {
1749  IString msg = "Snyder's q variable should only be computed for "
1750  "ellipsoidal projections.";
1752  }
1753  double eSinPhi = m_eccentricity * sinPhi;
1754  return (1 - m_eccentricity * m_eccentricity)
1755  * (sinPhi / (1 - eSinPhi * eSinPhi)
1756  - 1 / (2 * m_eccentricity) * log( (1 - eSinPhi) / (1 + eSinPhi) ));
1757  // Note: We know that q is well defined since
1758  // 0 < e < 1 and -1 <= sin(phi) <= 1
1759  // implies that -1 < e*sin(phi) < 1
1760  // Thus, there are no 0 denominators and the log domain is
1761  // satisfied, (1-e*sin(phi))/(1+e*sin(phi)) > 0
1762  }
1763 
1780  double TProjection::phi2Compute(const double t) const {
1781  double localPhi = HALFPI - 2.0 * atan(t);
1782  double halfEcc = 0.5 * Eccentricity();
1783  double difference = DBL_MAX;
1784  int iteration = 0;
1785  // a failure in this loop means an exception will be thrown, which is
1786  // an expensive operation. So letting let the loop iterate quite a bit
1787  // is not a big deal. Also, the user will be unable to project at all
1788  // if this fails so more reason to let this loop iterate: better to
1789  // function slow than not at all.
1790  const int MAX_ITERATIONS = 45;
1791 
1792  while ((iteration < MAX_ITERATIONS) && (difference > 0.0000000001)) {
1793  double eccTimesSinphi = Eccentricity() * sin(localPhi);
1794  double newPhi = HALFPI -
1795  2.0 * atan(t * pow((1.0 - eccTimesSinphi) /
1796  (1.0 + eccTimesSinphi), halfEcc));
1797  difference = fabs(newPhi - localPhi);
1798  localPhi = newPhi;
1799  iteration++;
1800  }
1801 
1802  if (iteration >= MAX_ITERATIONS) {
1803  IString msg = "Failed to converge in TProjection::phi2Compute()";
1805  }
1806 
1807  return localPhi;
1808  }
1809 
1824  double TProjection::mCompute(const double sinphi, const double cosphi) const {
1825  double eccTimesSinphi = Eccentricity() * sinphi;
1826  double denominator = sqrt(1.0 - eccTimesSinphi * eccTimesSinphi);
1827  return cosphi / denominator;
1828  }
1829 
1847  double TProjection::tCompute(const double phi, const double sinphi) const {
1848  if ((HALFPI) - fabs(phi) < DBL_EPSILON) return 0.0;
1849 
1850  double eccTimesSinphi = Eccentricity() * sinphi;
1851  double denominator = pow((1.0 - eccTimesSinphi) /
1852  (1.0 + eccTimesSinphi),
1853  0.5 * Eccentricity());
1854  return tan(0.5 * (HALFPI - phi)) / denominator;
1855  }
1856 
1868  double TProjection::e4Compute() const {
1869  double onePlusEcc = 1.0 + Eccentricity();
1870  double oneMinusEcc = 1.0 - Eccentricity();
1871 
1872  return sqrt(pow(onePlusEcc, onePlusEcc) *
1873  pow(oneMinusEcc, oneMinusEcc));
1874  }
1875 
1876 } //end namespace isis
1877 
1878