/////////////////////////////////////////////////////////////////////////////// // // Project: C++ Test Suite for GDAL/OGR // Purpose: Test coordinate transformations. Ported from osr/osr_ct.py. // Author: Mateusz Loskot // /////////////////////////////////////////////////////////////////////////////// // Copyright (c) 2006, Mateusz Loskot /* * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "gdal_unit_test.h" #include "cpl_conv.h" #include "cpl_error.h" #include "ogr_api.h" #include "ogr_srs_api.h" #include "ogr_spatialref.h" #include #include #include #include "gtest_include.h" namespace { // Common fixture with test data struct test_osr_ct : public ::testing::Test { OGRErr err_ = OGRERR_NONE; OGRSpatialReferenceH srs_utm_ = nullptr; OGRSpatialReferenceH srs_ll_ = nullptr; OGRCoordinateTransformationH ct_ = nullptr; void SetUp() override { srs_utm_ = OSRNewSpatialReference(nullptr); srs_ll_ = OSRNewSpatialReference(nullptr); OSRSetAxisMappingStrategy(srs_utm_, OAMS_TRADITIONAL_GIS_ORDER); OSRSetAxisMappingStrategy(srs_ll_, OAMS_TRADITIONAL_GIS_ORDER); } void TearDown() override { OSRDestroySpatialReference(srs_utm_); srs_utm_ = nullptr; OSRDestroySpatialReference(srs_ll_); srs_ll_ = nullptr; OCTDestroyCoordinateTransformation(ct_); ct_ = nullptr; } }; TEST_F(test_osr_ct, basic) { err_ = OSRSetUTM(srs_utm_, 11, TRUE); ASSERT_EQ(err_, OGRERR_NONE); err_ = OSRSetWellKnownGeogCS(srs_utm_, "WGS84"); ASSERT_EQ(err_, OGRERR_NONE); err_ = OSRSetWellKnownGeogCS(srs_ll_, "WGS84"); ASSERT_EQ(err_, OGRERR_NONE); ct_ = OCTNewCoordinateTransformation(srs_ll_, srs_utm_); ASSERT_TRUE(nullptr != ct_); } // Actually perform a simple LL to UTM conversion TEST_F(test_osr_ct, LL_to_UTM) { err_ = OSRSetUTM(srs_utm_, 11, TRUE); ASSERT_EQ(err_, OGRERR_NONE); err_ = OSRSetWellKnownGeogCS(srs_utm_, "WGS84"); ASSERT_EQ(err_, OGRERR_NONE); err_ = OSRSetWellKnownGeogCS(srs_ll_, "WGS84"); ASSERT_EQ(err_, OGRERR_NONE); ct_ = OCTNewCoordinateTransformation(srs_ll_, srs_utm_); ASSERT_TRUE(nullptr != ct_); const int size = 1; double x[size] = {-117.5}; double y[size] = {32.0}; double z[size] = {0.0}; ASSERT_EQ(OCTTransform(ct_, size, x, y, z), TRUE); EXPECT_NEAR(x[0], 452772.06, 0.01); EXPECT_NEAR(y[0], 3540544.89, 0.01); EXPECT_NEAR(z[0], 0.0, 0.01); } // Transform an OGR geometry. // This is mostly aimed at ensuring that the OGRCoordinateTransformation // target SRS isn't deleted till the output geometry which also // uses it is deleted. TEST_F(test_osr_ct, OGR_G_Transform) { err_ = OSRSetUTM(srs_utm_, 11, TRUE); ASSERT_EQ(err_, OGRERR_NONE); err_ = OSRSetWellKnownGeogCS(srs_utm_, "WGS84"); ASSERT_EQ(err_, OGRERR_NONE); err_ = OSRSetWellKnownGeogCS(srs_ll_, "WGS84"); ASSERT_EQ(err_, OGRERR_NONE); ct_ = OCTNewCoordinateTransformation(srs_ll_, srs_utm_); ASSERT_TRUE(nullptr != ct_); const char *wkt = "POINT(-117.5 32.0)"; OGRGeometryH geom = nullptr; err_ = OGR_G_CreateFromWkt((char **)&wkt, nullptr, &geom); EXPECT_EQ(OGRERR_NONE, err_); EXPECT_TRUE(nullptr != geom); if (geom) { err_ = OGR_G_Transform(geom, ct_); ASSERT_EQ(err_, OGRERR_NONE); OGRSpatialReferenceH srs = nullptr; srs = OGR_G_GetSpatialReference(geom); char *wktSrs = nullptr; err_ = OSRExportToPrettyWkt(srs, &wktSrs, FALSE); EXPECT_TRUE(nullptr != wktSrs); if (wktSrs) { std::string pretty(wktSrs); EXPECT_EQ(pretty.substr(0, 6), std::string("PROJCS")); } CPLFree(wktSrs); OGR_G_DestroyGeometry(geom); } } // Test OGRCoordinateTransformation::GetInverse() TEST_F(test_osr_ct, GetInverse) { OGRSpatialReference oSRSSource; oSRSSource.SetAxisMappingStrategy(OAMS_AUTHORITY_COMPLIANT); oSRSSource.importFromEPSG(4267); OGRSpatialReference oSRSTarget; oSRSTarget.SetAxisMappingStrategy(OAMS_AUTHORITY_COMPLIANT); oSRSTarget.importFromEPSG(4269); auto poCT = std::unique_ptr( OGRCreateCoordinateTransformation(&oSRSSource, &oSRSTarget)); ASSERT_TRUE(poCT != nullptr); ASSERT_TRUE(poCT->GetSourceCS() != nullptr); ASSERT_TRUE(poCT->GetSourceCS()->IsSame(&oSRSSource)); ASSERT_TRUE(poCT->GetTargetCS() != nullptr); ASSERT_TRUE(poCT->GetTargetCS()->IsSame(&oSRSTarget)); auto poInverse = std::unique_ptr(poCT->GetInverse()); ASSERT_TRUE(poInverse != nullptr); ASSERT_TRUE(poInverse->GetSourceCS() != nullptr); ASSERT_TRUE(poInverse->GetSourceCS()->IsSame(&oSRSTarget)); ASSERT_TRUE(poInverse->GetTargetCS() != nullptr); ASSERT_TRUE(poInverse->GetTargetCS()->IsSame(&oSRSSource)); double x = 40; double y = -100; ASSERT_TRUE(poCT->Transform(1, &x, &y)); // Check that the transformed point is different but not too far EXPECT_TRUE(fabs(x - 40) > 1e-10); EXPECT_TRUE(fabs(y - -100) > 1e-10); EXPECT_NEAR(x, 40, 1e-3); EXPECT_NEAR(y, -100, 1e-3); const double xTransformed = x; const double yTransformed = y; poCT.reset(); // Check that the transformed point with the inverse transformation // matches the source ASSERT_TRUE(poInverse->Transform(1, &x, &y)); EXPECT_NEAR(x, 40, 1e-8); EXPECT_NEAR(y, -100, 1e-8); auto poInvOfInv = std::unique_ptr(poInverse->GetInverse()); ASSERT_TRUE(poInvOfInv != nullptr); ASSERT_TRUE(poInvOfInv->GetSourceCS() != nullptr); ASSERT_TRUE(poInvOfInv->GetSourceCS()->IsSame(&oSRSSource)); ASSERT_TRUE(poInvOfInv->GetTargetCS() != nullptr); ASSERT_TRUE(poInvOfInv->GetTargetCS()->IsSame(&oSRSTarget)); ASSERT_TRUE(poInvOfInv->Transform(1, &x, &y)); // Check that the transformed point is different but not too far EXPECT_NEAR(x, xTransformed, 1e-8); EXPECT_NEAR(y, yTransformed, 1e-8); } // Test OGRCoordinateTransformation::GetInverse() with a specified coordinate // operation TEST_F(test_osr_ct, GetInverse_with_ct) { OGRCoordinateTransformationOptions options; options.SetCoordinateOperation("+proj=affine +xoff=10", false); auto poCT = std::unique_ptr( OGRCreateCoordinateTransformation(nullptr, nullptr, options)); ASSERT_TRUE(poCT != nullptr); auto poInverse = std::unique_ptr(poCT->GetInverse()); ASSERT_TRUE(poInverse != nullptr); ASSERT_TRUE(poInverse->GetSourceCS() == nullptr); ASSERT_TRUE(poInverse->GetTargetCS() == nullptr); poCT.reset(); double x = 100; double y = 200; ASSERT_TRUE(poInverse->Transform(1, &x, &y)); EXPECT_NEAR(x, 90, 1e-12); EXPECT_NEAR(y, 200.0, 1e-12); } // Test OGRCoordinateTransformation::Clone() static void test_clone(OGRCoordinateTransformation *poCT, OGRSpatialReference *poSRSSource, OGRSpatialReference *poSRSTarget, const double xSrc, const double ySrc) { ASSERT_TRUE(poCT != nullptr); ASSERT_TRUE((poCT->GetSourceCS() == nullptr) == (poSRSSource == nullptr)); if (poSRSSource != nullptr) { ASSERT_TRUE(poCT->GetSourceCS()->IsSame(poSRSSource)); } ASSERT_TRUE((poCT->GetTargetCS() == nullptr) == (poSRSTarget == nullptr)); if (poSRSTarget != nullptr) { ASSERT_TRUE(poCT->GetTargetCS()->IsSame(poSRSTarget)); } double x = xSrc; double y = ySrc; ASSERT_TRUE(poCT->Transform(1, &x, &y)); const double xTransformed = x; const double yTransformed = y; auto poClone = std::unique_ptr(poCT->Clone()); ASSERT_TRUE(poClone != nullptr); ASSERT_TRUE((poClone->GetSourceCS() == nullptr) == (poSRSSource == nullptr)); if (poSRSSource != nullptr) { ASSERT_TRUE(poClone->GetSourceCS()->IsSame(poSRSSource)); } ASSERT_TRUE((poClone->GetTargetCS() == nullptr) == (poSRSTarget == nullptr)); if (poSRSTarget != nullptr) { ASSERT_TRUE(poClone->GetTargetCS()->IsSame(poSRSTarget)); } x = xSrc; y = ySrc; ASSERT_TRUE(poClone->Transform(1, &x, &y)); EXPECT_NEAR(x, xTransformed, 1e-15); EXPECT_NEAR(y, yTransformed, 1e-15); } // Test OGRCoordinateTransformation::Clone() with usual case TEST_F(test_osr_ct, Clone) { OGRSpatialReference oSRSSource; oSRSSource.importFromEPSG(4267); oSRSSource.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); OGRSpatialReference oSRSTarget; oSRSTarget.importFromEPSG(4269); oSRSTarget.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); auto poCT = std::unique_ptr( OGRCreateCoordinateTransformation(&oSRSSource, &oSRSTarget)); test_clone(poCT.get(), &oSRSSource, &oSRSTarget, 44, -60); } // Test OGRCoordinateTransformation::Clone() with a specified coordinate // operation TEST_F(test_osr_ct, Clone_with_ct) { OGRCoordinateTransformationOptions options; options.SetCoordinateOperation("+proj=affine +xoff=10", false); auto poCT = std::unique_ptr( OGRCreateCoordinateTransformation(nullptr, nullptr, options)); test_clone(poCT.get(), nullptr, nullptr, 90, 200); } // Test OGRCoordinateTransformation::Clone() with WebMercator->WGS84 special // case TEST_F(test_osr_ct, Clone_WebMercator_to_WGS84) { OGRSpatialReference oSRSSource; oSRSSource.importFromEPSG(3857); oSRSSource.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); OGRSpatialReference oSRSTarget; oSRSTarget.SetWellKnownGeogCS("WGS84"); oSRSTarget.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); auto poCT = std::unique_ptr( OGRCreateCoordinateTransformation(&oSRSSource, &oSRSTarget)); test_clone(poCT.get(), &oSRSSource, &oSRSTarget, 44, -60); } // Test OGRCoordinateTransformation in pure "C" API // OCTClone/OCTGetSourceCS/OCTGetTargetCS/OCTGetInverse TEST_F(test_osr_ct, OGRCoordinateTransformation_C_API) { OGRSpatialReferenceH hSource = OSRNewSpatialReference(nullptr); OGRSpatialReferenceH hTarget = OSRNewSpatialReference(nullptr); EXPECT_TRUE(hSource != nullptr); EXPECT_TRUE(hTarget != nullptr); if (hSource && hTarget) { EXPECT_TRUE(OGRERR_NONE == OSRImportFromEPSG(hSource, 32637)); EXPECT_TRUE(OGRERR_NONE == OSRSetWellKnownGeogCS(hTarget, "WGS84")); OGRCoordinateTransformationH hTransform = OCTNewCoordinateTransformation(hSource, hTarget); EXPECT_TRUE(hTransform != nullptr); if (hTransform) { OGRCoordinateTransformationH hClone = OCTClone(hTransform); EXPECT_TRUE(hClone != nullptr); OGRCoordinateTransformationH hInvTransform = OCTGetInverse(hTransform); EXPECT_TRUE(hInvTransform != nullptr); if (hClone && hInvTransform) { OGRSpatialReferenceH hSourceInternal = OCTGetSourceCS(hTransform); EXPECT_TRUE(hSourceInternal != nullptr); OGRSpatialReferenceH hTargetInternal = OCTGetTargetCS(hTransform); EXPECT_TRUE(hTargetInternal != nullptr); if (hSourceInternal) { EXPECT_TRUE(OSRIsSame(hSource, hSourceInternal)); } if (hTargetInternal) { EXPECT_TRUE(OSRIsSame(hTarget, hTargetInternal)); } } OCTDestroyCoordinateTransformation(hInvTransform); OCTDestroyCoordinateTransformation(hClone); } OCTDestroyCoordinateTransformation(hTransform); } OSRDestroySpatialReference(hSource); OSRDestroySpatialReference(hTarget); } } // namespace