diff --git a/CHANGELOG.md b/CHANGELOG.md index d02a0a20e6..03ebef1fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ release. ctest FunctionalTestJigsawApollo to validate this output. [#5710](https://github.com/DOI-USGS/ISIS3/issues/5710) - Added OFFBODY and OFFBODYTRIM parameters to cam2cam. Added tests and updated documentation. [#3602] (https://github.com/DOI-USGS/ISIS3/issues/3602) - Added support for reading, writing, and viewing GeoTIFFs in ISIS. [#5618](https://github.com/DOI-USGS/ISIS3/pull/5618) +- Added GDAL SRS propagation for systems outside of ISIS to display projected GTiffs. [#5736](https://github.com/DOI-USGS/ISIS3/pull/5736) ### Changed - Enhanced csminit by removing the need to specify model and plugin [#5585](https://github.com/DOI-USGS/ISIS3/issues/5585) diff --git a/isis/src/base/objs/Blob/Blob.cpp b/isis/src/base/objs/Blob/Blob.cpp index 5dfd0d38c0..976bfbfbef 100644 --- a/isis/src/base/objs/Blob/Blob.cpp +++ b/isis/src/base/objs/Blob/Blob.cpp @@ -477,7 +477,7 @@ namespace Isis { } // update metadata - string jsonblobstr = pvl.toJson().dump(); + string jsonblobstr = pvl.toJson()["Root"].dump(); string key = this->Type().toStdString() + "_" + this->Name().toStdString(); dataset->SetMetadataItem(key.c_str(), jsonblobstr.c_str(), "USGS"); } diff --git a/isis/src/base/objs/Cube/Cube.cpp b/isis/src/base/objs/Cube/Cube.cpp index 9c842ba877..1edb9cf777 100644 --- a/isis/src/base/objs/Cube/Cube.cpp +++ b/isis/src/base/objs/Cube/Cube.cpp @@ -1020,80 +1020,92 @@ namespace Isis { isiscube.addObject(core); - if (dataset->GetSpatialRef()) { - char ** projStr = new char*[1]; - const OGRSpatialReference &oSRS = *dataset->GetSpatialRef(); - oSRS.exportToProj4(projStr); - QString qProjStr = QString::fromStdString(std::string(projStr[0]) + " +type=crs"); - delete[] projStr[0]; - delete[] projStr; - - char ** projJsonStr = new char*[1]; - oSRS.exportToPROJJSON(projJsonStr, nullptr); - nlohmann::json projJson = nlohmann::json::parse(projJsonStr[0]); - CPLFree(projJsonStr); - - PvlGroup mappingGroup("Mapping"); - mappingGroup.addKeyword(PvlKeyword("ProjectionName", "IProj")); - mappingGroup.addKeyword(PvlKeyword("EquatorialRadius", toString(oSRS.GetSemiMajor()), "meters")); - mappingGroup.addKeyword(PvlKeyword("PolarRadius", toString(oSRS.GetSemiMinor()), "meters")); - - if (projJson.contains("base_crs")) { - projJson = projJson["base_crs"]; - } + m_label->addObject(isiscube); + } - std::string direction = projJson["coordinate_system"]["axis"][1]["direction"]; - if (direction == "east") { - mappingGroup.addKeyword(PvlKeyword("LongitudeDirection", "PositiveEast")); - } - else if (direction == "west") { - mappingGroup.addKeyword(PvlKeyword("LongitudeDirection", "PositiveWest")); - } - else { - QString msg = "Unknown direction [" + QString::fromStdString(direction) + "]"; - throw IException(IException::Programmer, msg, _FILEINFO_); - } - mappingGroup.addKeyword(PvlKeyword("LongitudeDomain", "180")); + if (dataset->GetSpatialRef()) { + char ** projStr = new char*[1]; + const OGRSpatialReference &oSRS = *dataset->GetSpatialRef(); + oSRS.exportToProj4(projStr); + QString qProjStr = QString::fromStdString(std::string(projStr[0]) + " +type=crs"); + delete[] projStr[0]; + delete[] projStr; + + char ** projJsonStr = new char*[1]; + oSRS.exportToPROJJSON(projJsonStr, nullptr); + nlohmann::json projJson = nlohmann::json::parse(projJsonStr[0]); + CPLFree(projJsonStr); + + PvlGroup mappingGroup("Mapping"); + mappingGroup.addKeyword(PvlKeyword("ProjectionName", "IProj")); + mappingGroup.addKeyword(PvlKeyword("EquatorialRadius", toString(oSRS.GetSemiMajor()), "meters")); + mappingGroup.addKeyword(PvlKeyword("PolarRadius", toString(oSRS.GetSemiMinor()), "meters")); + + if (projJson.contains("base_crs")) { + projJson = projJson["base_crs"]; + } + + std::string direction = projJson["coordinate_system"]["axis"][1]["direction"]; + if (direction == "east") { + mappingGroup.addKeyword(PvlKeyword("LongitudeDirection", "PositiveEast")); + } + else if (direction == "west") { + mappingGroup.addKeyword(PvlKeyword("LongitudeDirection", "PositiveWest")); + } + else { + QString msg = "Unknown direction [" + QString::fromStdString(direction) + "]"; + throw IException(IException::Programmer, msg, _FILEINFO_); + } + + if (oSRS.GetSemiMajor() == oSRS.GetSemiMinor()) { mappingGroup.addKeyword(PvlKeyword("LatitudeType", "Planetocentric")); - mappingGroup.addKeyword(PvlKeyword("ProjStr", qProjStr)); - - // Read the GeoTransform and get the elements we care about - double *padfTransform = new double[6]; - dataset->GetGeoTransform(padfTransform); - if (abs(padfTransform[1]) != abs(padfTransform[5])) { - delete[] padfTransform; - QString msg = "Vertical and horizontal resolution do not match"; - throw IException(IException::Io, msg, _FILEINFO_); - } + } + else { + mappingGroup.addKeyword(PvlKeyword("LatitudeType", "Planetographic")); + } - double dfScale; - double dfRes; - double upperLeftX; - double upperLeftY; - dfRes = padfTransform[1] * oSRS.GetLinearUnits(); - upperLeftX = padfTransform[0]; - upperLeftY = padfTransform[3]; - if (oSRS.IsProjected()) { - const double dfDegToMeter = oSRS.GetSemiMajor() * M_PI / 180.0; - dfScale = dfDegToMeter / dfRes; - mappingGroup.addKeyword(PvlKeyword("PixelResolution", toString(dfRes), "meters/pixel")); - } - else if (oSRS.IsGeographic()) { - dfScale = 1.0 / dfRes; - mappingGroup.addKeyword(PvlKeyword("PixelResolution", toString(dfRes), "degrees/pixel")); - } - else { - QString msg = "Gdal spatial reference is not Geographic or Projected"; - throw IException(IException::Io, msg, _FILEINFO_); - } - mappingGroup.addKeyword(PvlKeyword("Scale", toString(dfScale), "pixels/degree")); - mappingGroup.addKeyword(PvlKeyword("UpperLeftCornerX", toString(upperLeftX))); - mappingGroup.addKeyword(PvlKeyword("UpperLeftCornerY", toString(upperLeftY))); + mappingGroup.addKeyword(PvlKeyword("LongitudeDomain", "180")); + mappingGroup.addKeyword(PvlKeyword("ProjStr", qProjStr)); + + // Read the GeoTransform and get the elements we care about + double *padfTransform = new double[6]; + dataset->GetGeoTransform(padfTransform); + if (abs(padfTransform[1]) != abs(padfTransform[5])) { delete[] padfTransform; + QString msg = "Vertical and horizontal resolution do not match"; + throw IException(IException::Io, msg, _FILEINFO_); + } - isiscube.addGroup(mappingGroup); + double dfScale; + double dfRes; + double upperLeftX; + double upperLeftY; + dfRes = padfTransform[1] * oSRS.GetLinearUnits(); + upperLeftX = padfTransform[0]; + upperLeftY = padfTransform[3]; + if (oSRS.IsProjected()) { + const double dfDegToMeter = oSRS.GetSemiMajor() * M_PI / 180.0; + dfScale = dfDegToMeter / dfRes; + mappingGroup.addKeyword(PvlKeyword("PixelResolution", toString(dfRes), "meters/pixel")); } - m_label->addObject(isiscube); + else if (oSRS.IsGeographic()) { + dfScale = 1.0 / dfRes; + mappingGroup.addKeyword(PvlKeyword("PixelResolution", toString(dfRes), "degrees/pixel")); + } + else { + QString msg = "Gdal spatial reference is not Geographic or Projected"; + throw IException(IException::Io, msg, _FILEINFO_); + } + mappingGroup.addKeyword(PvlKeyword("Scale", toString(dfScale), "pixels/degree")); + mappingGroup.addKeyword(PvlKeyword("UpperLeftCornerX", toString(upperLeftX))); + mappingGroup.addKeyword(PvlKeyword("UpperLeftCornerY", toString(upperLeftY))); + delete[] padfTransform; + + PvlObject &isiscube = m_label->findObject("IsisCube"); + if (isiscube.hasGroup("Mapping")) { + isiscube.deleteGroup("Mapping"); + } + isiscube.addGroup(mappingGroup); } GDALClose(dataset); @@ -1122,7 +1134,7 @@ namespace Isis { * */ void Cube::reopen(QString access) { - if (!m_labelFile) { + if (!isOpen()) { QString msg = "Cube has not been opened yet. The filename to re-open is " "unknown"; throw IException(IException::Programmer, msg, _FILEINFO_); @@ -3024,7 +3036,6 @@ namespace Isis { // update metadata nlohmann::ordered_json jsonblob = this->label()->toJson()["Root"]; nlohmann::ordered_json jsonOut; - // std::cout << jsonblob << std::endl; for (auto& [key, val] : jsonblob.items()) { if (!val.contains("Bytes") || key == "Label") { jsonOut[key] = val; @@ -3033,6 +3044,31 @@ namespace Isis { std::string jsonblobstr = jsonOut.dump(); std::string name = "CubeLabel"; gdalDataset()->SetMetadataItem(name.c_str(), jsonblobstr.c_str(), "USGS"); + + if (this->label()->findObject("IsisCube").hasGroup("Mapping")) { + PvlGroup &mappingGroup = this->label()->findObject("IsisCube").findGroup("Mapping"); + + if (mappingGroup.hasKeyword("ProjStr")) { + OGRSpatialReference *oSRS = new OGRSpatialReference(); + oSRS->SetFromUserInput(mappingGroup.findKeyword("ProjStr")[0].toStdString().c_str()); + + gdalDataset()->SetSpatialRef(oSRS); + + double dfRes = (double)mappingGroup.findKeyword("PixelResolution") / oSRS->GetLinearUnits(); + double upperLeftX = (double) mappingGroup.findKeyword("UpperLeftCornerX"); + double upperLeftY = (double) mappingGroup.findKeyword("UpperLeftCornerY"); + double *padfTransform = new double[6]; + padfTransform[1] = dfRes; + padfTransform[5] = -dfRes; + padfTransform[0] = upperLeftX; + padfTransform[3] = upperLeftY; + gdalDataset()->SetGeoTransform(padfTransform); + delete oSRS; + delete[] padfTransform; + } + } + + return; } diff --git a/isis/src/base/objs/Pvl/Pvl.cpp b/isis/src/base/objs/Pvl/Pvl.cpp index 318913bca1..8989d1a72b 100644 --- a/isis/src/base/objs/Pvl/Pvl.cpp +++ b/isis/src/base/objs/Pvl/Pvl.cpp @@ -155,12 +155,7 @@ namespace Isis { return jsonobj; }; - if (this->name() == "Root" && this->objects() == 1) { - return pvlobject_to_json(this->object(0)); - } - else { - return pvlobject_to_json(*this); - } + return pvlobject_to_json(*this); } /** diff --git a/isis/tests/GTiffTests.cpp b/isis/tests/GTiffTests.cpp index 704c8c0679..fc6906ef99 100644 --- a/isis/tests/GTiffTests.cpp +++ b/isis/tests/GTiffTests.cpp @@ -18,6 +18,7 @@ using json = nlohmann::json; #include "TableRecord.h" #include "CubeFixtures.h" +#include "TiffFixtures.h" #include "TestUtilities.h" #include "gmock/gmock.h" @@ -174,9 +175,7 @@ TEST_P(GdalDnTypeGenerator, TestGTiffCreateWrite) { in.close(); } -// Add Test for GTiff with no ISIS metadata - -TEST_F(TempTestingFiles, TableTestsWriteReadGdal) { +TEST_F(TempTestingFiles, TestGTiffTableWriteRead) { TableField f1("Column1", TableField::Integer); TableField f2("Column2", TableField::Double); TableField f3("Column3", TableField::Text, 10); @@ -222,4 +221,73 @@ TEST_F(TempTestingFiles, TableTestsWriteReadGdal) { for (int i = 0; i < t.Records(); i++) { EXPECT_EQ(TableRecord::toString(t[i]).toStdString(), TableRecord::toString(t2[i]).toStdString()); } +} + +TEST_F(ReadWriteTiff, TestGTiffSRS) { + PvlGroup mapping("Mapping"); + mapping.addKeyword(PvlKeyword("ProjectionName", "IProj")); + mapping.addKeyword(PvlKeyword("EquatorialRadius", "3396190.0", "meters")); + mapping.addKeyword(PvlKeyword("PolarRadius", "3396190.0", "meters")); + mapping.addKeyword(PvlKeyword("LongitudeDirection", "PositiveEast")); + mapping.addKeyword(PvlKeyword("LatitudeType", "Planetocentric")); + mapping.addKeyword(PvlKeyword("LongitudeDomain", "180")); + mapping.addKeyword(PvlKeyword("ProjStr", "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +R=3396190 +units=m +no_defs +type=crs")); + mapping.addKeyword(PvlKeyword("PixelResolution", "20.0", "meters/pixel")); + mapping.addKeyword(PvlKeyword("Scale", "2963.7348761653", "pixels/degree")); + mapping.addKeyword(PvlKeyword("UpperLeftCornerX", "8120050.0")); + mapping.addKeyword(PvlKeyword("UpperLeftCornerY", "-230230.0")); + + createTiff(SignedWord); + + Cube tiff; + tiff.open(path, "rw"); + tiff.putGroup(mapping); + tiff.reopen(); + + Pvl &label = *tiff.label(); + PvlGroup returnMapping = label.findObject("IsisCube").findGroup("Mapping"); + EXPECT_TRUE(returnMapping == mapping); + + dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly)); + + char ** projStr = new char*[1]; + ASSERT_TRUE(dataset->GetSpatialRef() != nullptr); + const OGRSpatialReference &oSRS = *dataset->GetSpatialRef(); + oSRS.exportToProj4(projStr); + std::string projAsCPPStr = projStr[0]; + EXPECT_EQ(projAsCPPStr, "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +R=3396190 +units=m +no_defs"); + + char ** projJsonStr = new char*[1]; + oSRS.exportToPROJJSON(projJsonStr, nullptr); + nlohmann::ordered_json projJson = nlohmann::ordered_json::parse(projJsonStr[0]); + CPLFree(projJsonStr); + + ASSERT_TRUE(projJson.contains("base_crs")); + ASSERT_TRUE(projJson["base_crs"].contains("coordinate_system")); + ASSERT_TRUE(projJson["base_crs"].contains("datum")); + ASSERT_TRUE(projJson["base_crs"]["coordinate_system"].contains("axis")); + ASSERT_TRUE(projJson["base_crs"]["datum"].contains("ellipsoid")); + ASSERT_TRUE(projJson["base_crs"]["datum"]["ellipsoid"].contains("radius")); + + EXPECT_EQ(projJson["base_crs"]["coordinate_system"]["axis"][1]["direction"], "east"); + EXPECT_EQ(projJson["base_crs"]["datum"]["ellipsoid"]["radius"], 3396190); + + ASSERT_TRUE(projJson.contains("conversion")); + ASSERT_TRUE(projJson["conversion"].contains("name")); + ASSERT_TRUE(projJson["conversion"].contains("parameters")); + ASSERT_EQ(projJson["conversion"]["parameters"].size(), 5); + + EXPECT_EQ(projJson["conversion"]["name"], "Equidistant Cylindrical"); + EXPECT_EQ(projJson["conversion"]["parameters"][0]["name"], "Latitude of 1st standard parallel"); + EXPECT_EQ(projJson["conversion"]["parameters"][0]["value"], 0); + EXPECT_EQ(projJson["conversion"]["parameters"][1]["name"], "Latitude of natural origin"); + EXPECT_EQ(projJson["conversion"]["parameters"][1]["value"], 0); + EXPECT_EQ(projJson["conversion"]["parameters"][2]["name"], "Longitude of natural origin"); + EXPECT_EQ(projJson["conversion"]["parameters"][2]["value"], 0); + EXPECT_EQ(projJson["conversion"]["parameters"][3]["name"], "False easting"); + EXPECT_EQ(projJson["conversion"]["parameters"][3]["value"], 0); + EXPECT_EQ(projJson["conversion"]["parameters"][4]["name"], "False northing"); + EXPECT_EQ(projJson["conversion"]["parameters"][4]["value"], 0); + + dataset->Close(); } \ No newline at end of file