From 9e723c5f9e94c520d7e69708f56d4f07413bcb6b Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Thu, 20 Jan 2022 13:43:10 -0500 Subject: [PATCH 01/13] Georeferencing: Add 'gnss_crs_spec' for new WGS84 realization The gnss_crs_spec is EPSG:9755, i.e. WGS84 (G2139). The name for the spec string "+proj=latlong +datum=WGS84" is changed from geographic_crs_spec to ballpark_geographic_crs_spec, because it is retained for compatibility. --- src/core/georeferencing.cpp | 15 ++++++++------- src/core/georeferencing.h | 10 ++++++++-- src/gdal/ogr_template.cpp | 6 +++--- src/gui/select_crs_dialog.cpp | 4 ++-- src/templates/template_track.cpp | 6 +++--- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/core/georeferencing.cpp b/src/core/georeferencing.cpp index 57a4d7b74..83791f817 100644 --- a/src/core/georeferencing.cpp +++ b/src/core/georeferencing.cpp @@ -304,7 +304,7 @@ bool ProjTransform::isGeographic() const QPointF ProjTransform::forward(const LatLon& lat_lon, bool* ok) const { - static auto const geographic_crs = ProjTransform(Georeferencing::geographic_crs_spec); + static auto const geographic_crs = ProjTransform(Georeferencing::ballpark_geographic_crs_spec); auto point = isGeographic() ? QPointF{lat_lon.longitude(), lat_lon.latitude()} @@ -324,7 +324,7 @@ QPointF ProjTransform::forward(const LatLon& lat_lon, bool* ok) const LatLon ProjTransform::inverse(const QPointF& projected_coords, bool* ok) const { - static auto const geographic_crs = ProjTransform(Georeferencing::geographic_crs_spec); + static auto const geographic_crs = ProjTransform(Georeferencing::ballpark_geographic_crs_spec); double easting = projected_coords.x(), northing = projected_coords.y(); if (geographic_crs.isValid()) @@ -376,7 +376,7 @@ ProjTransform::ProjTransform(const QString& crs_spec) if (crs_spec.isEmpty()) return; - static auto const geographic_crs_spec_utf8 = Georeferencing::geographic_crs_spec.toUtf8(); + static auto const geographic_crs_spec_utf8 = Georeferencing::ballpark_geographic_crs_spec.toUtf8(); auto crs_spec_utf8 = crs_spec.toUtf8(); #ifdef PROJ_ISSUE_1573 @@ -386,7 +386,7 @@ ProjTransform::ProjTransform(const QString& crs_spec) #if defined(ACCEPT_USE_OF_DEPRECATED_PROJ_API_H) || (PROJ_VERSION_MAJOR) < 8 pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX, geographic_crs_spec_utf8, crs_spec_utf8, nullptr); #else - static auto const geographic_crs = crs(Georeferencing::geographic_crs_spec); + static auto const geographic_crs = crs(Georeferencing::ballpark_geographic_crs_spec); auto const projected_crs = crs(crs_spec); static const char* const options[] = {"AUTHORITY=any", nullptr}; pj = proj_create_crs_to_crs_from_pj(PJ_DEFAULT_CTX, geographic_crs.pj, projected_crs.pj, nullptr, options); @@ -478,7 +478,8 @@ QString ProjTransform::errorText() const //### Georeferencing ### -const QString Georeferencing::geographic_crs_spec(QString::fromLatin1("+proj=latlong +datum=WGS84")); +const QString Georeferencing::ballpark_geographic_crs_spec(QString::fromLatin1("+proj=latlong +datum=WGS84")); +const QString Georeferencing::gnss_crs_spec(QString::fromLatin1("EPSG:9755")); Georeferencing::Georeferencing() : state(Local), @@ -660,7 +661,7 @@ void Georeferencing::load(QXmlStreamReader& xml, bool load_scale_only) if (language != literal::proj_4) throw FileFormatException(tr("Unknown CRS specification language: %1").arg(language)); QString geographic_crs_spec = xml.readElementText(); - if (Georeferencing::geographic_crs_spec != geographic_crs_spec) + if (Georeferencing::ballpark_geographic_crs_spec != geographic_crs_spec) throw FileFormatException(tr("Unsupported geographic CRS specification: %1").arg(geographic_crs_spec)); } else if (xml.name() == literal::ref_point) @@ -770,7 +771,7 @@ void Georeferencing::save(QXmlStreamWriter& xml) const { XmlElementWriter spec_element(xml, literal::spec); spec_element.writeAttribute(literal::language, literal::proj_4); - xml.writeCharacters(geographic_crs_spec); + xml.writeCharacters(ballpark_geographic_crs_spec); } if (XMLFileFormat::active_version < 6) { diff --git a/src/core/georeferencing.h b/src/core/georeferencing.h index 144295a2d..57de37911 100644 --- a/src/core/georeferencing.h +++ b/src/core/georeferencing.h @@ -133,7 +133,12 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); /** * A shared PROJ specification of a WGS84 geographic CRS. */ - static const QString geographic_crs_spec; + static const QString ballpark_geographic_crs_spec; + + /** + * A shared PROJ specification of an accurate realization of the WGS84 geographic CRS. + */ + static const QString gnss_crs_spec; /** @@ -480,7 +485,8 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); LatLon toGeographicCoords(const MapCoordF& map_coords, bool* ok = 0) const; /** - * Transforms CRS coordinates to geographic coordinates (lat/lon). + * Transforms CRS coordinates to geographic coordinates (lat/lon) + * using a WGS84 datum. */ LatLon toGeographicCoords(const QPointF& projected_coords, bool* ok = 0) const; diff --git a/src/gdal/ogr_template.cpp b/src/gdal/ogr_template.cpp index 6f2c2a1ab..b35813bfd 100644 --- a/src/gdal/ogr_template.cpp +++ b/src/gdal/ogr_template.cpp @@ -296,7 +296,7 @@ try { is_georeferenced = true; // Data is to be transformed to the map CRS directly. - track_crs_spec = Georeferencing::geographic_crs_spec; + track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; return true; } } @@ -318,7 +318,7 @@ try preserveRefPoints(*data_georef, initial_georef); explicit_georef = std::move(data_georef); // Data is to be transformed to the projected CRS. - track_crs_spec = Georeferencing::geographic_crs_spec; + track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; projected_crs_spec = explicit_georef->getProjectedCRSSpec(); } @@ -600,7 +600,7 @@ bool OgrTemplate::finishTypeSpecificTemplateConfiguration() // Data is to be transformed to the map CRS directly. Q_ASSERT(projected_crs_spec.isEmpty()); } - else if (!track_crs_spec.contains(QLatin1String("+proj=latlong"))) + else if (!ProjTransform::crs(track_crs_spec).isGeographic()) { // Nothing to do with this configuration Q_ASSERT(projected_crs_spec.isEmpty()); diff --git a/src/gui/select_crs_dialog.cpp b/src/gui/select_crs_dialog.cpp index 86996eabc..29fcb3127 100644 --- a/src/gui/select_crs_dialog.cpp +++ b/src/gui/select_crs_dialog.cpp @@ -101,7 +101,7 @@ SelectCRSDialog::SelectCRSDialog( crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::TemplateFile)); else if (crs_spec == georef.getProjectedCRSSpec()) crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::SameAsMap)); - else if (crs_spec == Georeferencing::geographic_crs_spec) + else if (crs_spec == Georeferencing::ballpark_geographic_crs_spec) crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::Geographic)); else crs_selector->setCurrentCRS(CRSTemplateRegistry().find(QString::fromLatin1("PROJ.4")), { crs_spec }); @@ -130,7 +130,7 @@ QString SelectCRSDialog::currentCRSSpec() const // nothing break; case SpecialCRS::Geographic: - spec = Georeferencing::geographic_crs_spec; + spec = Georeferencing::ballpark_geographic_crs_spec; break; case SpecialCRS::TemplateFile: spec = options.template_file.crs_spec; diff --git a/src/templates/template_track.cpp b/src/templates/template_track.cpp index b2bcc3184..0808a3509 100644 --- a/src/templates/template_track.cpp +++ b/src/templates/template_track.cpp @@ -152,7 +152,7 @@ TemplateTrack::TemplateTrack(const QString& path, Map* map) : Template(path, map) { // set default value - track_crs_spec = Georeferencing::geographic_crs_spec; + track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; const Georeferencing& georef = map->getGeoreferencing(); connect(&georef, &Georeferencing::projectionChanged, this, &TemplateTrack::updateGeoreferencing); @@ -257,7 +257,7 @@ bool TemplateTrack::loadTemplateFileImpl() return false; } - if (!track_crs_spec.isEmpty() && track_crs_spec != Georeferencing::geographic_crs_spec) + if (!track_crs_spec.isEmpty() && track_crs_spec != Georeferencing::ballpark_geographic_crs_spec) { setErrorString(tr("This template must be loaded with GDAL/OGR.")); return false; @@ -580,7 +580,7 @@ void TemplateTrack::configureForGPSTrack() { is_georeferenced = true; - track_crs_spec = Georeferencing::geographic_crs_spec; + track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; projected_crs_spec.clear(); track.changeMapGeoreferencing(map->getGeoreferencing()); From eb93b05fd8fef4c70513636fb55389b063b440aa Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Sat, 10 Feb 2024 09:34:56 -0500 Subject: [PATCH 02/13] Georeferencing: Use new geographic CRS for pivot, tracks and more Replace Georeferencing::ballpark_geographic_crs_spec with Georeferencing::gns_crs_spec in all occurrences that relate to Mapper internals. The former refers to the long-term evolution of WGS84, while the latter to an up-to-date realization G1762. Georeferencing::ballpark_geographic_crs_spec continues to be used for the map file format, for interoperability with older versions of Mapper. --- src/core/georeferencing.cpp | 8 ++++---- src/gdal/ogr_template.cpp | 4 ++-- src/gui/select_crs_dialog.cpp | 2 +- src/templates/template_track.cpp | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/georeferencing.cpp b/src/core/georeferencing.cpp index 83791f817..07fa1cd9c 100644 --- a/src/core/georeferencing.cpp +++ b/src/core/georeferencing.cpp @@ -304,7 +304,7 @@ bool ProjTransform::isGeographic() const QPointF ProjTransform::forward(const LatLon& lat_lon, bool* ok) const { - static auto const geographic_crs = ProjTransform(Georeferencing::ballpark_geographic_crs_spec); + static auto const geographic_crs = ProjTransform(Georeferencing::gnss_crs_spec); auto point = isGeographic() ? QPointF{lat_lon.longitude(), lat_lon.latitude()} @@ -324,7 +324,7 @@ QPointF ProjTransform::forward(const LatLon& lat_lon, bool* ok) const LatLon ProjTransform::inverse(const QPointF& projected_coords, bool* ok) const { - static auto const geographic_crs = ProjTransform(Georeferencing::ballpark_geographic_crs_spec); + static auto const geographic_crs = ProjTransform(Georeferencing::gnss_crs_spec); double easting = projected_coords.x(), northing = projected_coords.y(); if (geographic_crs.isValid()) @@ -376,7 +376,7 @@ ProjTransform::ProjTransform(const QString& crs_spec) if (crs_spec.isEmpty()) return; - static auto const geographic_crs_spec_utf8 = Georeferencing::ballpark_geographic_crs_spec.toUtf8(); + static auto const geographic_crs_spec_utf8 = Georeferencing::gnss_crs_spec.toUtf8(); auto crs_spec_utf8 = crs_spec.toUtf8(); #ifdef PROJ_ISSUE_1573 @@ -386,7 +386,7 @@ ProjTransform::ProjTransform(const QString& crs_spec) #if defined(ACCEPT_USE_OF_DEPRECATED_PROJ_API_H) || (PROJ_VERSION_MAJOR) < 8 pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX, geographic_crs_spec_utf8, crs_spec_utf8, nullptr); #else - static auto const geographic_crs = crs(Georeferencing::ballpark_geographic_crs_spec); + static auto const geographic_crs = crs(Georeferencing::gnss_crs_spec); auto const projected_crs = crs(crs_spec); static const char* const options[] = {"AUTHORITY=any", nullptr}; pj = proj_create_crs_to_crs_from_pj(PJ_DEFAULT_CTX, geographic_crs.pj, projected_crs.pj, nullptr, options); diff --git a/src/gdal/ogr_template.cpp b/src/gdal/ogr_template.cpp index b35813bfd..b72d1ffc9 100644 --- a/src/gdal/ogr_template.cpp +++ b/src/gdal/ogr_template.cpp @@ -296,7 +296,7 @@ try { is_georeferenced = true; // Data is to be transformed to the map CRS directly. - track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; + track_crs_spec = Georeferencing::gnss_crs_spec; return true; } } @@ -318,7 +318,7 @@ try preserveRefPoints(*data_georef, initial_georef); explicit_georef = std::move(data_georef); // Data is to be transformed to the projected CRS. - track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; + track_crs_spec = Georeferencing::gnss_crs_spec; projected_crs_spec = explicit_georef->getProjectedCRSSpec(); } diff --git a/src/gui/select_crs_dialog.cpp b/src/gui/select_crs_dialog.cpp index 29fcb3127..f1d872a1b 100644 --- a/src/gui/select_crs_dialog.cpp +++ b/src/gui/select_crs_dialog.cpp @@ -130,7 +130,7 @@ QString SelectCRSDialog::currentCRSSpec() const // nothing break; case SpecialCRS::Geographic: - spec = Georeferencing::ballpark_geographic_crs_spec; + spec = Georeferencing::gnss_crs_spec; break; case SpecialCRS::TemplateFile: spec = options.template_file.crs_spec; diff --git a/src/templates/template_track.cpp b/src/templates/template_track.cpp index 0808a3509..953bdb065 100644 --- a/src/templates/template_track.cpp +++ b/src/templates/template_track.cpp @@ -152,7 +152,7 @@ TemplateTrack::TemplateTrack(const QString& path, Map* map) : Template(path, map) { // set default value - track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; + track_crs_spec = Georeferencing::gnss_crs_spec; const Georeferencing& georef = map->getGeoreferencing(); connect(&georef, &Georeferencing::projectionChanged, this, &TemplateTrack::updateGeoreferencing); @@ -580,7 +580,7 @@ void TemplateTrack::configureForGPSTrack() { is_georeferenced = true; - track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; + track_crs_spec = Georeferencing::gnss_crs_spec; projected_crs_spec.clear(); track.changeMapGeoreferencing(map->getGeoreferencing()); From 93c4399df9a4438baaacb5090c21f63d6a0fa06f Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Sat, 10 Feb 2024 11:04:37 -0500 Subject: [PATCH 03/13] OgrFileImport: If data CRS is WGS84, substitute recent realization In OgrFileImport::setSRS that sets the transformation of imported coodinates to the map SRS, if the source SRS is being set to WGS84, substitute Georeferencing::gnss_crs_spec. This makes the resulting transformation more accurate, for example if the map's datum is NAD83. --- src/gdal/ogr_file_format.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/gdal/ogr_file_format.cpp b/src/gdal/ogr_file_format.cpp index 37ac197cd..bc5627d0e 100644 --- a/src/gdal/ogr_file_format.cpp +++ b/src/gdal/ogr_file_format.cpp @@ -1338,6 +1338,21 @@ bool OgrFileImport::setSRS(OGRSpatialReferenceH srs) if (srs && data_srs != srs) { // New SRS, indeed. + if (OSRIsGeographic(srs)) + { + auto ballpark_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) }; + OSRSetWellKnownGeogCS(ballpark_srs.get(), "WGS84"); + + if (!strcmp(OSRGetAuthorityName(srs, nullptr), OSRGetAuthorityName(ballpark_srs.get(), nullptr)) + && !strcmp(OSRGetAuthorityCode(srs, nullptr), OSRGetAuthorityCode(ballpark_srs.get(), nullptr))) + { + // Substitute an accurate, recent realization of WGS84. + auto gnss_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) }; + OSRSetWellKnownGeogCS(gnss_srs.get(), Georeferencing::gnss_crs_spec.toUtf8()); + OSRCopyGeogCSFrom(srs, gnss_srs.get()); + } + } + auto transformation = ogr::unique_transformation{ OCTNewCoordinateTransformation(srs, map_srs.get()) }; if (!transformation) { From dd254064375d3a6533f950a99e8ab2052d9fd4cb Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Sat, 10 Feb 2024 14:28:28 -0500 Subject: [PATCH 04/13] TemplateTest: Update expectations for WGS84 improvements With Mapper now using the WGS84 (G2139) realization for templates and as a pivot for coordinate transformations, template alignment has changed, causing differences in the test results. The test cases TemplateTrack NAD83 OgrTemplate NAD83 in ogrTemplateTest no longer fail, because the tracks are now aligned well enough with the map and its datum to pass the test. In addition, TemplateTrack and OgrTemplate now align tracks the same, causing those same test cases to pass in the templateTypesConsistentTest. On the other hand, the test cases TemplateTrack from v0.8.4 OGRTemplate from v0.9.3 in ogrTemplateTest now fail, because behavior of earlier versions of Mapper did not handle track coordinates as accurately. The position is changed by 0.3mm on the map, or 1.3m on the ground. --- test/template_t.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/template_t.cpp b/test/template_t.cpp index 51cf52f13..a42fcce69 100644 --- a/test/template_t.cpp +++ b/test/template_t.cpp @@ -494,8 +494,8 @@ private slots: QVERIFY(map.getTemplate(template_index)->loadTemplateFile()); QCOMPARE(temp->getTemplateState(), Template::Loaded); - QEXPECT_FAIL("TemplateTrack NAD83", "Unsupported WGS 84 -> NAD 83 transformation", Continue); - QEXPECT_FAIL("OgrTemplate NAD83", "Unsupported WGS 84 -> NAD 83 transformation", Continue); + QEXPECT_FAIL("TemplateTrack from v0.8.4", "WGS 84 -> NAD 83 transformation updated", Continue); + QEXPECT_FAIL("OGRTemplate from v0.9.3", "WGS 84 -> NAD 83 transformation updated", Continue); auto const expected_center = map.calculateExtent().center(); if (QLineF(center(temp), expected_center).length() > 0.25) // 1 m QCOMPARE(center(temp), expected_center); @@ -567,10 +567,6 @@ private slots: ogr_template_center = center(temp); } -#if !defined(ACCEPT_USE_OF_DEPRECATED_PROJ_API_H) || PJ_VERSION >= 600 - QEXPECT_FAIL("TemplateTrack NAD83", "Unsupported WGS 84 -> NAD 83 transformation", Continue); - QEXPECT_FAIL("OgrTemplate NAD83", "Unsupported WGS 84 -> NAD 83 transformation", Continue); -#endif if (QLineF(ogr_template_center, template_track_center).length() > 0.1) // 40 cm QCOMPARE(ogr_template_center, template_track_center); else From 8869ce6455e6c8d013409807e6cc39e25dd51876 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Sun, 11 Feb 2024 06:23:01 -0500 Subject: [PATCH 05/13] Georeferencing: Use EPSG:9057 as the GNSS geographic CRS This changes Georeferencing::gnss_crs_spec from EPSG:9755 to EPSG:9057. PROJ handles EPSG:9057 with with an epoch several years earlier than EPSG:9755, which makes coordinate transformations to NAD83 less accurate, by 20 cm. With EPSG:9755, Mapper was failing tests in Azure pipelines. Changing to the earlier EPSG:9057, which indicates the WGS 84 (G1762) realization, may work with the older version of PROJ that's available in the superbuild. --- src/core/georeferencing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/georeferencing.cpp b/src/core/georeferencing.cpp index 07fa1cd9c..6b7b14ef4 100644 --- a/src/core/georeferencing.cpp +++ b/src/core/georeferencing.cpp @@ -479,7 +479,7 @@ QString ProjTransform::errorText() const //### Georeferencing ### const QString Georeferencing::ballpark_geographic_crs_spec(QString::fromLatin1("+proj=latlong +datum=WGS84")); -const QString Georeferencing::gnss_crs_spec(QString::fromLatin1("EPSG:9755")); +const QString Georeferencing::gnss_crs_spec(QString::fromLatin1("EPSG:9057")); Georeferencing::Georeferencing() : state(Local), From 9f1706a0091ce9e9ab482d19a7c4bd3e301c2336 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Wed, 14 Feb 2024 09:40:28 -0500 Subject: [PATCH 06/13] SelectCRSDialog: Fix Geographic option to match map's georeferencing --- src/gui/select_crs_dialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/select_crs_dialog.cpp b/src/gui/select_crs_dialog.cpp index f1d872a1b..f8e42be17 100644 --- a/src/gui/select_crs_dialog.cpp +++ b/src/gui/select_crs_dialog.cpp @@ -101,7 +101,7 @@ SelectCRSDialog::SelectCRSDialog( crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::TemplateFile)); else if (crs_spec == georef.getProjectedCRSSpec()) crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::SameAsMap)); - else if (crs_spec == Georeferencing::ballpark_geographic_crs_spec) + else if (crs_spec == Georeferencing::gnss_crs_spec) crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::Geographic)); else crs_selector->setCurrentCRS(CRSTemplateRegistry().find(QString::fromLatin1("PROJ.4")), { crs_spec }); From c5673a6220ec1710051590007bf26a3f845d67bd Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Wed, 14 Feb 2024 18:15:13 -0500 Subject: [PATCH 07/13] TemplateTrack: Restore to use ballpark track_crs_spec throughout TemplateTrack will always rely on the map's Georeferencing to convert GPS tracks to map coordinates, which can now be a more accurate realization of the ballpark geographic CRS. At the same time, to save the realization CRS in the file, or to keep it in the TemplateTrack object would provide no benefit. --- src/templates/template_track.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/template_track.cpp b/src/templates/template_track.cpp index 953bdb065..0808a3509 100644 --- a/src/templates/template_track.cpp +++ b/src/templates/template_track.cpp @@ -152,7 +152,7 @@ TemplateTrack::TemplateTrack(const QString& path, Map* map) : Template(path, map) { // set default value - track_crs_spec = Georeferencing::gnss_crs_spec; + track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; const Georeferencing& georef = map->getGeoreferencing(); connect(&georef, &Georeferencing::projectionChanged, this, &TemplateTrack::updateGeoreferencing); @@ -580,7 +580,7 @@ void TemplateTrack::configureForGPSTrack() { is_georeferenced = true; - track_crs_spec = Georeferencing::gnss_crs_spec; + track_crs_spec = Georeferencing::ballpark_geographic_crs_spec; projected_crs_spec.clear(); track.changeMapGeoreferencing(map->getGeoreferencing()); From 9c7633da4b1b6a917f532e8e6bec022a4bf603d1 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Tue, 13 Feb 2024 00:35:25 -0500 Subject: [PATCH 08/13] Georeferencing: Make geographic CRS a member of ProjTransform Each Georeferencing object is given its own geographic_crs value, so that Mapper will be able to support older maps without affecting the alignment of their templates, while simultaneously providing more accurate positioning of georeferenced templates in new maps. For this purpose, ProjTransform objects are enhanced to have an explicit geographic CRS in addition to the projected CRS. This modification to ProjTransform is unsuited to another scenario to which ProjTransform used to be applied, namely when the ProjTransform would do no more than indicate a CRS. The mechanism supporting 'forward' and 'inverse' methods of such objects was not constructed. A new struct, 'ProjCRS' is provided for this specialized need. Splitting off ProjCRS from ProjTransform provides clarity to see which of these utility objects define a transformation from a geographic CRS, and which define just a CRS. --- src/core/georeferencing.cpp | 241 ++++++++++++++++++++---------------- src/core/georeferencing.h | 32 +++-- src/gdal/ogr_template.cpp | 2 +- test/georeferencing_t.cpp | 4 +- 4 files changed, 158 insertions(+), 121 deletions(-) diff --git a/src/core/georeferencing.cpp b/src/core/georeferencing.cpp index 6b7b14ef4..0159ff8f7 100644 --- a/src/core/georeferencing.cpp +++ b/src/core/georeferencing.cpp @@ -251,45 +251,126 @@ namespace -//### ProjTransform ### +//### ProjCRS ### #ifdef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H +ProjCRS::ProjCRS(const QString& crs_spec) +{ + *pj_get_errno_ref() = 0; -ProjTransform::ProjTransform(ProjTransformData* pj) noexcept -: pj{pj} -{} + if (!crs_spec.isEmpty()) + { + auto spec_latin1 = crs_spec.toLatin1(); + if (!spec_latin1.contains("+no_defs")) + spec_latin1.append(" +no_defs"); -ProjTransform::ProjTransform(ProjTransform&& other) noexcept + pj = pj_init_plus(spec_latin1); + } +} + +ProjCRS::~ProjCRS() { - operator=(std::move(other)); + if (pj) + pj_free(pj); } -ProjTransform::ProjTransform(const QString& crs_spec) +bool ProjCRS::isGeographic() const { - auto spec_latin1 = crs_spec.toLatin1(); - if (!spec_latin1.contains("+no_defs")) - spec_latin1.append(" +no_defs"); - - *pj_get_errno_ref() = 0; - pj = pj_init_plus(spec_latin1); + return isValid() && pj_is_latlong(pj); } +#else -ProjTransform::~ProjTransform() +namespace { + +QByteArray withTypeCrs(QByteArray crs_spec_utf8) +{ + if ((crs_spec_utf8.startsWith("+proj=") || crs_spec_utf8.startsWith("+init=")) + && !crs_spec_utf8.contains("+type=crs")) + { + crs_spec_utf8.append(" +type=crs"); + } + return crs_spec_utf8; +} + +} + +ProjCRS::ProjCRS(const QString& crs_spec) +{ + if (!crs_spec.isEmpty()) + { + auto crs_spec_utf8 = withTypeCrs(crs_spec.toUtf8().trimmed()); +#ifdef PROJ_ISSUE_1573 + // Cf. https://github.com/OSGeo/PROJ/pull/1573 + crs_spec_utf8.replace("+datum=potsdam", "+ellps=bessel +nadgrids=@BETA2007.gsb"); +#endif + pj = proj_create(PJ_DEFAULT_CTX, crs_spec_utf8); + } +} + +ProjCRS::~ProjCRS() { if (pj) - pj_free(pj); + proj_destroy(pj); } -ProjTransform& ProjTransform::operator=(ProjTransform&& other) noexcept +bool ProjCRS::isGeographic() const +{ + auto type = pj ? proj_get_type(pj) : PJ_TYPE_UNKNOWN; + if (type == PJ_TYPE_BOUND_CRS) + { + // "Coordinates referring to a BoundCRS are expressed into its source/base CRS." + // (https://proj.org/development/reference/cpp/crs.html) + auto* base_crs = proj_get_source_crs(nullptr, pj); + type = proj_get_type(base_crs); + proj_destroy(base_crs); + } + + switch (type) + { + case PJ_TYPE_GEOGRAPHIC_CRS: + case PJ_TYPE_GEOGRAPHIC_2D_CRS: + case PJ_TYPE_GEOGRAPHIC_3D_CRS: + return true; + default: + return false; + } +} +#endif + +ProjCRS::ProjCRS(ProjCRS&& other) noexcept +{ + std::swap(pj, other.pj); +} + +ProjCRS& ProjCRS::operator=(ProjCRS&& other) noexcept { std::swap(pj, other.pj); return *this; } -// static -ProjTransform ProjTransform::crs(const QString& crs_spec) +bool ProjCRS::isValid() const noexcept { - return ProjTransform(crs_spec); + return pj != nullptr; +} + + +//### ProjTransform ### + +ProjTransform::ProjTransform() noexcept +: geographic_crs(QString()) +{} + +ProjTransform::ProjTransform(ProjTransform&& other) noexcept +: geographic_crs(std::move(other.geographic_crs)) +{ + std::swap(pj, other.pj); +} + +ProjTransform& ProjTransform::operator=(ProjTransform&& other) noexcept +{ + geographic_crs.operator=(std::move(other.geographic_crs)); + std::swap(pj, other.pj); + return *this; } bool ProjTransform::isValid() const noexcept @@ -297,16 +378,28 @@ bool ProjTransform::isValid() const noexcept return pj != nullptr; } -bool ProjTransform::isGeographic() const +#ifdef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H + +ProjTransform::ProjTransform(const QString& crs_spec, const QString& geographic_crs_spec) +: geographic_crs(geographic_crs_spec) { - return isValid() && pj_is_latlong(pj); + auto spec_latin1 = crs_spec.toLatin1(); + if (!spec_latin1.contains("+no_defs")) + spec_latin1.append(" +no_defs"); + + *pj_get_errno_ref() = 0; + pj = pj_init_plus(spec_latin1); +} + +ProjTransform::~ProjTransform() +{ + if (pj) + pj_free(pj); } QPointF ProjTransform::forward(const LatLon& lat_lon, bool* ok) const { - static auto const geographic_crs = ProjTransform(Georeferencing::gnss_crs_spec); - - auto point = isGeographic() + auto point = pj && pj_is_latlong(pj) ? QPointF{lat_lon.longitude(), lat_lon.latitude()} : QPointF{qDegreesToRadians(lat_lon.longitude()), qDegreesToRadians(lat_lon.latitude())}; if (geographic_crs.isValid()) @@ -324,8 +417,6 @@ QPointF ProjTransform::forward(const LatLon& lat_lon, bool* ok) const LatLon ProjTransform::inverse(const QPointF& projected_coords, bool* ok) const { - static auto const geographic_crs = ProjTransform(Georeferencing::gnss_crs_spec); - double easting = projected_coords.x(), northing = projected_coords.y(); if (geographic_crs.isValid()) { @@ -337,7 +428,7 @@ LatLon ProjTransform::inverse(const QPointF& projected_coords, bool* ok) const { *ok = false; } - return isGeographic() ? LatLon{northing, easting} : LatLon::fromRadiant(northing, easting); + return (pj && pj_is_latlong(pj)) ? LatLon{northing, easting} : LatLon::fromRadiant(northing, easting); } QString ProjTransform::errorText() const @@ -348,35 +439,13 @@ QString ProjTransform::errorText() const #else -namespace { - -QByteArray withTypeCrs(QByteArray crs_spec_utf8) -{ - if ((crs_spec_utf8.startsWith("+proj=") || crs_spec_utf8.startsWith("+init=")) - && !crs_spec_utf8.contains("+type=crs")) - { - crs_spec_utf8.append(" +type=crs"); - } - return crs_spec_utf8; -} - -} - -ProjTransform::ProjTransform(ProjTransformData* pj) noexcept -: pj{pj} -{} - -ProjTransform::ProjTransform(ProjTransform&& other) noexcept -{ - operator=(std::move(other)); -} - -ProjTransform::ProjTransform(const QString& crs_spec) +ProjTransform::ProjTransform(const QString& crs_spec, const QString& geographic_crs_spec) +: geographic_crs(geographic_crs_spec) { if (crs_spec.isEmpty()) return; - static auto const geographic_crs_spec_utf8 = Georeferencing::gnss_crs_spec.toUtf8(); + auto const geographic_crs_spec_utf8 = geographic_crs_spec.toUtf8(); auto crs_spec_utf8 = crs_spec.toUtf8(); #ifdef PROJ_ISSUE_1573 @@ -386,13 +455,16 @@ ProjTransform::ProjTransform(const QString& crs_spec) #if defined(ACCEPT_USE_OF_DEPRECATED_PROJ_API_H) || (PROJ_VERSION_MAJOR) < 8 pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX, geographic_crs_spec_utf8, crs_spec_utf8, nullptr); #else - static auto const geographic_crs = crs(Georeferencing::gnss_crs_spec); - auto const projected_crs = crs(crs_spec); + const ProjCRS projected_crs(crs_spec); static const char* const options[] = {"AUTHORITY=any", nullptr}; pj = proj_create_crs_to_crs_from_pj(PJ_DEFAULT_CTX, geographic_crs.pj, projected_crs.pj, nullptr, options); #endif if (pj) - operator=({proj_normalize_for_visualization(PJ_DEFAULT_CTX, pj)}); + { + auto* pjt = pj; + pj = proj_normalize_for_visualization(PJ_DEFAULT_CTX, pjt); + proj_destroy(pjt); + } } ProjTransform::~ProjTransform() @@ -401,53 +473,6 @@ ProjTransform::~ProjTransform() proj_destroy(pj); } -ProjTransform& ProjTransform::operator=(ProjTransform&& other) noexcept -{ - std::swap(pj, other.pj); - return *this; -} - -// static -ProjTransform ProjTransform::crs(const QString& crs_spec) -{ - ProjTransform result; - auto crs_spec_utf8 = withTypeCrs(crs_spec.toUtf8().trimmed()); -#ifdef PROJ_ISSUE_1573 - // Cf. https://github.com/OSGeo/PROJ/pull/1573 - crs_spec_utf8.replace("+datum=potsdam", "+ellps=bessel +nadgrids=@BETA2007.gsb"); -#endif - result.pj = proj_create(PJ_DEFAULT_CTX, crs_spec_utf8); - return result; -} - -bool ProjTransform::isValid() const noexcept -{ - return pj != nullptr; -} - -bool ProjTransform::isGeographic() const -{ - auto type = pj ? proj_get_type(pj) : PJ_TYPE_UNKNOWN; - if (type == PJ_TYPE_BOUND_CRS) - { - // "Coordinates referring to a BoundCRS are expressed into its source/base CRS." - // (https://proj.org/development/reference/cpp/crs.html) - auto* base_crs = proj_get_source_crs(nullptr, pj); - type = proj_get_type(base_crs); - proj_destroy(base_crs); - } - - switch (type) - { - case PJ_TYPE_GEOGRAPHIC_CRS: - case PJ_TYPE_GEOGRAPHIC_2D_CRS: - case PJ_TYPE_GEOGRAPHIC_3D_CRS: - return true; - default: - return false; - } -} - QPointF ProjTransform::forward(const LatLon& lat_lon, bool* ok) const { proj_errno_reset(pj); @@ -517,7 +542,7 @@ Georeferencing::Georeferencing(const Georeferencing& other) projected_crs_id(other.projected_crs_id), projected_crs_spec(other.projected_crs_spec), projected_crs_parameters(other.projected_crs_parameters), - proj_transform(projected_crs_spec), + proj_transform(projected_crs_spec, gnss_crs_spec), geographic_ref_point(other.geographic_ref_point) { updateTransformation(); @@ -547,7 +572,7 @@ Georeferencing& Georeferencing::operator=(const Georeferencing& other) projected_crs_id = other.projected_crs_id; projected_crs_spec = other.projected_crs_spec; projected_crs_parameters = other.projected_crs_parameters; - proj_transform = ProjTransform(other.projected_crs_spec); + proj_transform = ProjTransform(other.projected_crs_spec, gnss_crs_spec); geographic_ref_point = other.geographic_ref_point; emit stateChanged(); @@ -563,7 +588,7 @@ Georeferencing& Georeferencing::operator=(const Georeferencing& other) bool Georeferencing::isGeographic() const { - return getState() == Geospatial && ProjTransform::crs(getProjectedCRSSpec()).isGeographic(); + return getState() == Geospatial && ProjCRS(getProjectedCRSSpec()).isGeographic(); } @@ -696,7 +721,7 @@ void Georeferencing::load(QXmlStreamReader& xml, bool load_scale_only) emit declinationChanged(); if (!projected_crs_spec.isEmpty()) { - proj_transform = {projected_crs_spec}; + proj_transform = ProjTransform(projected_crs_spec, gnss_crs_spec); state = proj_transform.isValid() ? Geospatial : BrokenGeospatial; updateGridCompensation(); if (!georef_element.hasAttribute(literal::auxiliary_scale_factor)) @@ -952,7 +977,7 @@ void Georeferencing::updateGridCompensation() QString local_crs_spec = QString::fromLatin1("+proj=sterea +lat_0=%1 +lon_0=%2 +ellps=WGS84 +units=m") .arg(geographic_ref_point.latitude(), 0, 'f') .arg(geographic_ref_point.longitude(), 0, 'f'); - ProjTransform local_proj_transform(local_crs_spec); + ProjTransform local_proj_transform(local_crs_spec, gnss_crs_spec); if (!local_proj_transform.isValid()) return; @@ -1129,7 +1154,7 @@ bool Georeferencing::setProjectedCRS(const QString& id, QString spec, std::vecto else { projected_crs_parameters.swap(params); // params was passed by value! - proj_transform = {projected_crs_spec}; + proj_transform = ProjTransform(projected_crs_spec, gnss_crs_spec); ok = proj_transform.isValid(); if (ok) setState(Geospatial); diff --git a/src/core/georeferencing.h b/src/core/georeferencing.h index 57de37911..43b076d2a 100644 --- a/src/core/georeferencing.h +++ b/src/core/georeferencing.h @@ -47,24 +47,38 @@ namespace OpenOrienteering { /** - * A utility which encapsulates PROJ API variants and resource management. + * Utilities which encapsulate PROJ API variants and resource management. */ +struct ProjCRS +{ + ProjCRS(const ProjCRS&) = delete; + ProjCRS(ProjCRS&& other) noexcept; + ProjCRS(const QString& crs_spec); + ~ProjCRS(); + + ProjCRS& operator=(const ProjCRS& other) = delete; + ProjCRS& operator=(ProjCRS&& other) noexcept; + + bool isValid() const noexcept; + bool isGeographic() const; +private: + ProjTransformData* pj = nullptr; + + friend struct ProjTransform; +}; + struct ProjTransform { - ProjTransform() noexcept = default; + ProjTransform() noexcept; ProjTransform(const ProjTransform&) = delete; ProjTransform(ProjTransform&& other) noexcept; - ProjTransform(const QString& crs_spec); + ProjTransform(const QString& crs_spec, const QString& geographic_crs_spec); ~ProjTransform(); ProjTransform& operator=(const ProjTransform& other) = delete; ProjTransform& operator=(ProjTransform&& other) noexcept; - /// Create a PROJ CRS object. - static ProjTransform crs(const QString& crs_spec); - bool isValid() const noexcept; - bool isGeographic() const; QPointF forward(const LatLon& lat_lon, bool* ok) const; LatLon inverse(const QPointF& projected, bool* ok) const; @@ -72,10 +86,8 @@ struct ProjTransform QString errorText() const; private: - ProjTransform(ProjTransformData* pj) noexcept; - ProjTransformData* pj = nullptr; - + ProjCRS geographic_crs; }; diff --git a/src/gdal/ogr_template.cpp b/src/gdal/ogr_template.cpp index b72d1ffc9..fab38201c 100644 --- a/src/gdal/ogr_template.cpp +++ b/src/gdal/ogr_template.cpp @@ -600,7 +600,7 @@ bool OgrTemplate::finishTypeSpecificTemplateConfiguration() // Data is to be transformed to the map CRS directly. Q_ASSERT(projected_crs_spec.isEmpty()); } - else if (!ProjTransform::crs(track_crs_spec).isGeographic()) + else if (!ProjCRS(track_crs_spec).isGeographic()) { // Nothing to do with this configuration Q_ASSERT(projected_crs_spec.isEmpty()); diff --git a/test/georeferencing_t.cpp b/test/georeferencing_t.cpp index 44d4b14ae..7429570e5 100644 --- a/test/georeferencing_t.cpp +++ b/test/georeferencing_t.cpp @@ -357,7 +357,7 @@ void GeoreferencingTest::testCRS() #ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H // Test with IDs { - auto t = ProjTransform::crs(id); + ProjCRS t(id); QVERIFY(t.isValid()); QCOMPARE(t.isGeographic(), is_geographic); @@ -369,7 +369,7 @@ void GeoreferencingTest::testCRS() // Test with specs. { - auto t = ProjTransform::crs(spec); + ProjCRS t(spec); QVERIFY(t.isValid()); QCOMPARE(t.isGeographic(), is_geographic); From 826e1f490552c705562e9ee1ebb0c4eda329cf45 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Tue, 13 Feb 2024 22:29:30 -0500 Subject: [PATCH 09/13] Georeferencing: Add getGeographicCRSSpec() method The static value Georeferencing::gnss_crs_spec is made private to the Georeferencing class, so that non-core uses of the map's geographic WGS84 CRS will be sure to depend on the map to obtain the CRS, rather than specify a particular variant of WGS84. Their references to gnss_crs_spec are replaced with calls to getGeographicCRSSpec(), which in the future will depend on the map. The Georeferencing's toGeographicCoords, toProjectedCoords, and toMapCoords methods will be consistent with the CRS returned by getGeographicCRSSpec(). --- src/core/georeferencing.h | 19 ++++++++++++++----- src/gdal/ogr_file_format.cpp | 3 ++- src/gdal/ogr_template.cpp | 4 ++-- src/gui/select_crs_dialog.cpp | 4 ++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/core/georeferencing.h b/src/core/georeferencing.h index 43b076d2a..fc93cd07f 100644 --- a/src/core/georeferencing.h +++ b/src/core/georeferencing.h @@ -147,11 +147,6 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); */ static const QString ballpark_geographic_crs_spec; - /** - * A shared PROJ specification of an accurate realization of the WGS84 geographic CRS. - */ - static const QString gnss_crs_spec; - /** * @brief Returns the precision of the grid scale factor. @@ -444,6 +439,15 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); */ bool setProjectedCRS(const QString& id, QString spec, std::vector< QString > params = std::vector()); + /** + * Returns the specification of the WGS84-based geographic coordinate + * reference system (CRS) used by Georeferencing. + * This is the target CRS of the toGeographicCoords methods, + * and also the source CRS when applicable. + * @return a PROJ specification of the geographic CRS + */ + const QString& getGeographicCRSSpec() const { return gnss_crs_spec; } + /** * Calculates the convergence at the reference point. * @@ -650,6 +654,11 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); void setScaleFactors(double combined_scale_factor, double auxiliary_scale_factor); void setDeclinationAndGrivation(double declination, double grivation); + /** + * PROJ specification of an accurate realization of the WGS84 geographic CRS. + */ + static const QString gnss_crs_spec; + State state; unsigned int scale_denominator; diff --git a/src/gdal/ogr_file_format.cpp b/src/gdal/ogr_file_format.cpp index bc5627d0e..f1b73dfee 100644 --- a/src/gdal/ogr_file_format.cpp +++ b/src/gdal/ogr_file_format.cpp @@ -1338,6 +1338,7 @@ bool OgrFileImport::setSRS(OGRSpatialReferenceH srs) if (srs && data_srs != srs) { // New SRS, indeed. + auto& georef = map->getGeoreferencing(); if (OSRIsGeographic(srs)) { auto ballpark_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) }; @@ -1348,7 +1349,7 @@ bool OgrFileImport::setSRS(OGRSpatialReferenceH srs) { // Substitute an accurate, recent realization of WGS84. auto gnss_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) }; - OSRSetWellKnownGeogCS(gnss_srs.get(), Georeferencing::gnss_crs_spec.toUtf8()); + OSRSetWellKnownGeogCS(gnss_srs.get(), georef.getGeographicCRSSpec().toUtf8()); OSRCopyGeogCSFrom(srs, gnss_srs.get()); } } diff --git a/src/gdal/ogr_template.cpp b/src/gdal/ogr_template.cpp index fab38201c..43d33f153 100644 --- a/src/gdal/ogr_template.cpp +++ b/src/gdal/ogr_template.cpp @@ -296,7 +296,7 @@ try { is_georeferenced = true; // Data is to be transformed to the map CRS directly. - track_crs_spec = Georeferencing::gnss_crs_spec; + track_crs_spec = georef.getGeographicCRSSpec(); return true; } } @@ -318,7 +318,7 @@ try preserveRefPoints(*data_georef, initial_georef); explicit_georef = std::move(data_georef); // Data is to be transformed to the projected CRS. - track_crs_spec = Georeferencing::gnss_crs_spec; + track_crs_spec = explicit_georef->getGeographicCRSSpec(); projected_crs_spec = explicit_georef->getProjectedCRSSpec(); } diff --git a/src/gui/select_crs_dialog.cpp b/src/gui/select_crs_dialog.cpp index f8e42be17..630d5abc5 100644 --- a/src/gui/select_crs_dialog.cpp +++ b/src/gui/select_crs_dialog.cpp @@ -101,7 +101,7 @@ SelectCRSDialog::SelectCRSDialog( crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::TemplateFile)); else if (crs_spec == georef.getProjectedCRSSpec()) crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::SameAsMap)); - else if (crs_spec == Georeferencing::gnss_crs_spec) + else if (crs_spec == georef.getGeographicCRSSpec()) crs_selector->setCurrentIndex(crs_selector->findData(SpecialCRS::Geographic)); else crs_selector->setCurrentCRS(CRSTemplateRegistry().find(QString::fromLatin1("PROJ.4")), { crs_spec }); @@ -130,7 +130,7 @@ QString SelectCRSDialog::currentCRSSpec() const // nothing break; case SpecialCRS::Geographic: - spec = Georeferencing::gnss_crs_spec; + spec = georef.getGeographicCRSSpec(); break; case SpecialCRS::TemplateFile: spec = options.template_file.crs_spec; From 26e840b621a403420ebff9f8a5e6d8b8cd113f60 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Wed, 14 Feb 2024 08:20:23 -0500 Subject: [PATCH 10/13] Georeferencing: Add isDatumBallpark() method The isDatumBallpark() method indicates compatibility for working with old maps. It enables GDAL import to provide compatibility with the way old Mappers aligned template tracks. When compatibility is not called for, track import is tweaked to use a more accurate geographic CRS. --- src/core/georeferencing.h | 7 +++++++ src/gdal/ogr_file_format.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/georeferencing.h b/src/core/georeferencing.h index fc93cd07f..092816439 100644 --- a/src/core/georeferencing.h +++ b/src/core/georeferencing.h @@ -447,6 +447,13 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); * @return a PROJ specification of the geographic CRS */ const QString& getGeographicCRSSpec() const { return gnss_crs_spec; } + + /** + * Returns whether transformations use loose accuracy around the + * WGS84 datum for compatibility with older releases of Mapper. + * @return true if transformations are compatible, false otherwise + */ + bool isDatumBallpark() const { return false; } /** * Calculates the convergence at the reference point. diff --git a/src/gdal/ogr_file_format.cpp b/src/gdal/ogr_file_format.cpp index f1b73dfee..f5a6fc13f 100644 --- a/src/gdal/ogr_file_format.cpp +++ b/src/gdal/ogr_file_format.cpp @@ -1339,7 +1339,7 @@ bool OgrFileImport::setSRS(OGRSpatialReferenceH srs) { // New SRS, indeed. auto& georef = map->getGeoreferencing(); - if (OSRIsGeographic(srs)) + if (OSRIsGeographic(srs) && !georef.isDatumBallpark()) { auto ballpark_srs = ogr::unique_srs { OSRNewSpatialReference(nullptr) }; OSRSetWellKnownGeogCS(ballpark_srs.get(), "WGS84"); From 71468af526b479f96042aa425b0817694ee4fbb4 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Thu, 15 Feb 2024 13:45:37 -0500 Subject: [PATCH 11/13] Georeferencing: Enable datum compatibility mode The attribute 'is_realization' is added to the Georeferencing class and to the geographic_crs_spec of the xml file format. A 'true' value indicates the new, more accurate geographic CRS, while 'false' provides the same geographic CRS as earlier versions of Mapper. The geographic CRS is used as pivot when transforming from one version of map coordinates to another. It is also used for GNSS tracks and for display of geographic coordinates. The Georeferencing::setDatumBallpark() method provides for the compatibility mode to be turned on or off. On loading a map, the user is warned if they haven't decided whether compatibility is wanted. When the user opens the georeferencing dialog, a message box asks the user whether they want extra accuracy, or alignment that is compatible with older Mappers. Example and test maps are updated to indicate the 'is_realization' attribute. The template_t test had two test files as examples of what older versions of Mapper would save. This commit provides the compatibility which makes the corresponding two tests succeed. --- src/core/georeferencing.cpp | 44 +++++++++++++++++++++- src/core/georeferencing.h | 27 ++++++++++--- src/fileformats/xml_file_format.cpp | 9 +++++ src/gui/map/map_editor.cpp | 20 ++++++++++ test/data/templates/geotiff.xmap | 2 +- test/data/templates/ogr-template.xmap | 2 +- test/data/templates/template-track-NA.xmap | 2 +- test/data/templates/template-track.xmap | 2 +- test/data/templates/world-file.xmap | 2 +- test/template_t.cpp | 2 - 10 files changed, 98 insertions(+), 14 deletions(-) diff --git a/src/core/georeferencing.cpp b/src/core/georeferencing.cpp index 0159ff8f7..b7e0790b5 100644 --- a/src/core/georeferencing.cpp +++ b/src/core/georeferencing.cpp @@ -73,6 +73,7 @@ namespace literal static const QLatin1String ref_point_deg("ref_point_deg"); static const QLatin1String projected_crs("projected_crs"); static const QLatin1String geographic_crs("geographic_crs"); + static const QLatin1String is_realization("is_realization"); static const QLatin1String spec("spec"); static const QLatin1String parameter("parameter"); @@ -87,6 +88,8 @@ namespace literal static const QLatin1String proj_4("PROJ.4"); static const QLatin1String geographic_coordinates("Geographic coordinates"); + static const QLatin1String f("false"); + static const QLatin1String t("true"); } @@ -517,7 +520,9 @@ Georeferencing::Georeferencing() grivation_error(0.0), convergence(0.0), map_ref_point(0, 0), - projected_ref_point(0, 0) + projected_ref_point(0, 0), + is_realization(true), + explicit_realization(false) { static ProjSetup run_once; @@ -542,6 +547,8 @@ Georeferencing::Georeferencing(const Georeferencing& other) projected_crs_id(other.projected_crs_id), projected_crs_spec(other.projected_crs_spec), projected_crs_parameters(other.projected_crs_parameters), + is_realization(other.is_realization), + explicit_realization(other.explicit_realization), proj_transform(projected_crs_spec, gnss_crs_spec), geographic_ref_point(other.geographic_ref_point) { @@ -572,6 +579,8 @@ Georeferencing& Georeferencing::operator=(const Georeferencing& other) projected_crs_id = other.projected_crs_id; projected_crs_spec = other.projected_crs_spec; projected_crs_parameters = other.projected_crs_parameters; + is_realization = other.is_realization; + explicit_realization = other.explicit_realization; proj_transform = ProjTransform(other.projected_crs_spec, gnss_crs_spec); geographic_ref_point = other.geographic_ref_point; @@ -621,6 +630,7 @@ void Georeferencing::load(QXmlStreamReader& xml, bool load_scale_only) } else { + explicit_realization = false; if (georef_element.hasAttribute(literal::auxiliary_scale_factor)) { auxiliary_scale_factor = roundScaleFactor(georef_element.attribute(literal::auxiliary_scale_factor)); @@ -677,6 +687,9 @@ void Georeferencing::load(QXmlStreamReader& xml, bool load_scale_only) } else if (xml.name() == literal::geographic_crs) { + XmlElementReader crs_element(xml); + explicit_realization = crs_element.hasAttribute(literal::is_realization); + is_realization = explicit_realization && crs_element.attribute(literal::is_realization); while (xml.readNextStartElement()) { XmlElementReader current_element(xml); @@ -721,7 +734,7 @@ void Georeferencing::load(QXmlStreamReader& xml, bool load_scale_only) emit declinationChanged(); if (!projected_crs_spec.isEmpty()) { - proj_transform = ProjTransform(projected_crs_spec, gnss_crs_spec); + proj_transform = ProjTransform(projected_crs_spec, getGeographicCRSSpec()); state = proj_transform.isValid() ? Geospatial : BrokenGeospatial; updateGridCompensation(); if (!georef_element.hasAttribute(literal::auxiliary_scale_factor)) @@ -793,6 +806,8 @@ void Georeferencing::save(QXmlStreamWriter& xml) const { XmlElementWriter crs_element(xml, literal::geographic_crs); crs_element.writeAttribute(literal::id, literal::geographic_coordinates); + if (explicit_realization) + crs_element.writeAttribute(literal::is_realization, is_realization ? literal::t : literal::f); { XmlElementWriter spec_element(xml, literal::spec); spec_element.writeAttribute(literal::language, literal::proj_4); @@ -1170,6 +1185,31 @@ bool Georeferencing::setProjectedCRS(const QString& id, QString spec, std::vecto return ok; } +bool Georeferencing::setDatumBallpark(bool ballpark) +{ + explicit_realization = true; + + // Default return value if no change is necessary + bool ok = (getState() == Geospatial); + + if (is_realization != !ballpark) + { + is_realization = !ballpark; + proj_transform = ProjTransform(projected_crs_spec, getGeographicCRSSpec()); + ok = proj_transform.isValid(); + if (ok) + setState(Geospatial); + else + setState(BrokenGeospatial); + if (getState() == Geospatial) + updateGridCompensation(); + + emit projectionChanged(); + } + + return ok; +} + QPointF Georeferencing::toProjectedCoords(const MapCoord& map_coords) const { return to_projected.map(QPointF(map_coords)); diff --git a/src/core/georeferencing.h b/src/core/georeferencing.h index 092816439..f915040d1 100644 --- a/src/core/georeferencing.h +++ b/src/core/georeferencing.h @@ -446,14 +446,28 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); * and also the source CRS when applicable. * @return a PROJ specification of the geographic CRS */ - const QString& getGeographicCRSSpec() const { return gnss_crs_spec; } + const QString& getGeographicCRSSpec() const { return is_realization ? gnss_crs_spec : ballpark_geographic_crs_spec; } /** - * Returns whether transformations use loose accuracy around the - * WGS84 datum for compatibility with older releases of Mapper. - * @return true if transformations are compatible, false otherwise + * Returns whether transformations use loose accuracy around the WGS84 + * datum for explicitly requested compatibility with older releases of Mapper. + * @return true if transformations are set compatible, false otherwise */ - bool isDatumBallpark() const { return false; } + bool isDatumBallpark() const { return explicit_realization && !is_realization; } + + /** + * Sets the coordinate reference system (CRS) of the geographical coordinates, + * to either the ballpark CRS or the GNSS CRS. + * + * This setting determines the "pivot" coordinates used to transform one pair + * of projected coordinates to another, also the transformation of map + * coordinates to geographical, also transformation of GNSS coordinates to + * map coordinates. + * + * @param ballpark whether to make the datum "ballpark", otherwise make it a realization of WGS84 + * @return true if the resulting transformation is valid, false otherwise + */ + bool setDatumBallpark(bool ballpark); /** * Calculates the convergence at the reference point. @@ -706,6 +720,9 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); QString projected_crs_id; QString projected_crs_spec; std::vector< QString > projected_crs_parameters; + + bool is_realization; + bool explicit_realization; ProjTransform proj_transform; diff --git a/src/fileformats/xml_file_format.cpp b/src/fileformats/xml_file_format.cpp index 60d1a324b..8889a3331 100644 --- a/src/fileformats/xml_file_format.cpp +++ b/src/fileformats/xml_file_format.cpp @@ -719,6 +719,15 @@ void XMLFileImporter::validateGeoreferencing() auto const& loaded_georef = map->getGeoreferencing(); if (loaded_georef.getState() != Georeferencing::Geospatial) return; + + // Check for 'is_realization' not set. + if (!loaded_georef.isDatumBallpark() + && Georeferencing::ballpark_geographic_crs_spec == loaded_georef.getGeographicCRSSpec()) + { + addWarning(tr("This map was saved by an older version of Mapper, " + "with less accurate template alignment.\n\n" + "Adjust Georeferencing to confirm or change.")); + } // Check for georeferencings with inconsistent declination/grivation, // e.g. from GH-1206 (georef setup bug) diff --git a/src/gui/map/map_editor.cpp b/src/gui/map/map_editor.cpp index 955e5d277..2d7e03223 100644 --- a/src/gui/map/map_editor.cpp +++ b/src/gui/map/map_editor.cpp @@ -2370,6 +2370,26 @@ void MapEditorController::showTagsWindow(bool show) void MapEditorController::editGeoreferencing() { + // Check for 'is_realization' not set. + const Georeferencing& georef = getMap()->getGeoreferencing(); + if (georef.getState() == Georeferencing::Geospatial + && !georef.isDatumBallpark() + && Georeferencing::ballpark_geographic_crs_spec == georef.getGeographicCRSSpec()) + { + auto message = + tr("This map was saved by an older version of Mapper, " + "with less accurate template alignment.\n\n" + "Press Yes for more accurate alignment.\n" + "Press No to retain template alignment."); + int result = QMessageBox::question(getWindow(), tr("Warning"), message, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); + if (QMessageBox::Yes == result || QMessageBox::No == result) + { + auto local_georef = Georeferencing(georef); + local_georef.setDatumBallpark(QMessageBox::No == result); + map->setGeoreferencing(local_georef); + } + } + if (georeferencing_dialog.isNull()) { auto* dialog = new GeoreferencingDialog(this); diff --git a/test/data/templates/geotiff.xmap b/test/data/templates/geotiff.xmap index 7125ce779..6ee77796e 100644 --- a/test/data/templates/geotiff.xmap +++ b/test/data/templates/geotiff.xmap @@ -7,7 +7,7 @@ 2180 - + +proj=latlong +datum=WGS84 diff --git a/test/data/templates/ogr-template.xmap b/test/data/templates/ogr-template.xmap index fb16626d5..b1b810669 100644 --- a/test/data/templates/ogr-template.xmap +++ b/test/data/templates/ogr-template.xmap @@ -7,7 +7,7 @@ 2180 - + +proj=latlong +datum=WGS84 diff --git a/test/data/templates/template-track-NA.xmap b/test/data/templates/template-track-NA.xmap index 78cc4e960..ae3d9dd43 100644 --- a/test/data/templates/template-track-NA.xmap +++ b/test/data/templates/template-track-NA.xmap @@ -7,7 +7,7 @@ 6342 - + +proj=latlong +datum=WGS84 diff --git a/test/data/templates/template-track.xmap b/test/data/templates/template-track.xmap index d3e2724d2..c1272efa7 100644 --- a/test/data/templates/template-track.xmap +++ b/test/data/templates/template-track.xmap @@ -7,7 +7,7 @@ 2180 - + +proj=latlong +datum=WGS84 diff --git a/test/data/templates/world-file.xmap b/test/data/templates/world-file.xmap index 745e0a6c8..704bfd485 100644 --- a/test/data/templates/world-file.xmap +++ b/test/data/templates/world-file.xmap @@ -7,7 +7,7 @@ 2180 - + +proj=latlong +datum=WGS84 diff --git a/test/template_t.cpp b/test/template_t.cpp index a42fcce69..f7cf01d2f 100644 --- a/test/template_t.cpp +++ b/test/template_t.cpp @@ -494,8 +494,6 @@ private slots: QVERIFY(map.getTemplate(template_index)->loadTemplateFile()); QCOMPARE(temp->getTemplateState(), Template::Loaded); - QEXPECT_FAIL("TemplateTrack from v0.8.4", "WGS 84 -> NAD 83 transformation updated", Continue); - QEXPECT_FAIL("OGRTemplate from v0.9.3", "WGS 84 -> NAD 83 transformation updated", Continue); auto const expected_center = map.calculateExtent().center(); if (QLineF(center(temp), expected_center).length() > 0.25) // 1 m QCOMPARE(center(temp), expected_center); From addecfed5cc9c947ca2e4459f3c6eeb610450930 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Thu, 15 Feb 2024 19:07:03 -0500 Subject: [PATCH 12/13] Test: Geographic CRS and compatible template alignment Adds a test map that has a geographic CRS with is_realization="false". This is used with TemplateTest::ogrTemplateTest to check support for template alignment that's compatible with Mapper v0.95. Also, for FileFormatTest::saveAndLoad, getGeographicCRSSpec() is added to the map comparison. --- .../template-track-NA-ballpark-GDAL.xmap | 64 +++++++++++++++++++ test/file_format_t.cpp | 1 + test/template_t.cpp | 1 + 3 files changed, 66 insertions(+) create mode 100644 test/data/templates/template-track-NA-ballpark-GDAL.xmap diff --git a/test/data/templates/template-track-NA-ballpark-GDAL.xmap b/test/data/templates/template-track-NA-ballpark-GDAL.xmap new file mode 100644 index 000000000..b903c2630 --- /dev/null +++ b/test/data/templates/template-track-NA-ballpark-GDAL.xmap @@ -0,0 +1,64 @@ + + + + + + +init=epsg:6342 + 6342 + + + + +proj=latlong +datum=WGS84 + + + + + + + PURPLE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/file_format_t.cpp b/test/file_format_t.cpp index 04309402c..077f48046 100644 --- a/test/file_format_t.cpp +++ b/test/file_format_t.cpp @@ -227,6 +227,7 @@ namespace QCOMPARE(actual_georef.getProjectedCRSName(), expected_georef.getProjectedCRSName()); QCOMPARE(actual_georef.getProjectedCoordinatesName(), expected_georef.getProjectedCoordinatesName()); QCOMPARE(actual_georef.getProjectedCRSSpec(), expected_georef.getProjectedCRSSpec()); + QCOMPARE(actual_georef.getGeographicCRSSpec(), expected_georef.getGeographicCRSSpec()); QVERIFY(qAbs(actual_georef.getGeographicRefPoint().latitude() - expected_georef.getGeographicRefPoint().latitude()) < 0.5e-8); QVERIFY(qAbs(actual_georef.getGeographicRefPoint().longitude() - expected_georef.getGeographicRefPoint().longitude()) < 0.5e-8); diff --git a/test/template_t.cpp b/test/template_t.cpp index f7cf01d2f..883abf78d 100644 --- a/test/template_t.cpp +++ b/test/template_t.cpp @@ -474,6 +474,7 @@ private slots: QTest::newRow("OgrTemplate NAD83") << QStringLiteral("testdata:templates/template-track-NA.xmap") << 1; QTest::newRow("TemplateTrack from v0.8.4") << QStringLiteral("testdata:templates/template-track-NA-084.xmap") << 0; QTest::newRow("OGRTemplate from v0.9.3") << QStringLiteral("testdata:templates/template-track-NA-093-GDAL.xmap") << 0; + QTest::newRow("OGRTemplate NAD83 ballpark") << QStringLiteral("testdata:templates/template-track-NA-ballpark-GDAL.xmap") << 0; } void ogrTemplateTest() From 68c70f5977945d6b82fd535cc87cd0f36cddb373 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Fri, 16 Feb 2024 19:22:11 -0500 Subject: [PATCH 13/13] Georeferencing: Save realization CRS to file Instead of saving _that_ the map was developed with a recent realization of WGS84 for its geographic CRS, save the _spec_ of the geographic CRS itself. --- src/core/georeferencing.cpp | 39 +++++++++++++------ src/core/georeferencing.h | 8 ++-- test/data/templates/geotiff.xmap | 3 +- test/data/templates/ogr-template.xmap | 3 +- .../template-track-NA-ballpark-GDAL.xmap | 3 +- test/data/templates/template-track-NA.xmap | 3 +- test/data/templates/template-track.xmap | 3 +- test/data/templates/world-file.xmap | 3 +- 8 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/core/georeferencing.cpp b/src/core/georeferencing.cpp index b7e0790b5..51fba11cd 100644 --- a/src/core/georeferencing.cpp +++ b/src/core/georeferencing.cpp @@ -73,7 +73,7 @@ namespace literal static const QLatin1String ref_point_deg("ref_point_deg"); static const QLatin1String projected_crs("projected_crs"); static const QLatin1String geographic_crs("geographic_crs"); - static const QLatin1String is_realization("is_realization"); + static const QLatin1String realization_spec("realization_spec"); static const QLatin1String spec("spec"); static const QLatin1String parameter("parameter"); @@ -88,8 +88,6 @@ namespace literal static const QLatin1String proj_4("PROJ.4"); static const QLatin1String geographic_coordinates("Geographic coordinates"); - static const QLatin1String f("false"); - static const QLatin1String t("true"); } @@ -521,7 +519,7 @@ Georeferencing::Georeferencing() convergence(0.0), map_ref_point(0, 0), projected_ref_point(0, 0), - is_realization(true), + realization_crs_spec(gnss_crs_spec), explicit_realization(false) { static ProjSetup run_once; @@ -547,7 +545,7 @@ Georeferencing::Georeferencing(const Georeferencing& other) projected_crs_id(other.projected_crs_id), projected_crs_spec(other.projected_crs_spec), projected_crs_parameters(other.projected_crs_parameters), - is_realization(other.is_realization), + realization_crs_spec(other.realization_crs_spec), explicit_realization(other.explicit_realization), proj_transform(projected_crs_spec, gnss_crs_spec), geographic_ref_point(other.geographic_ref_point) @@ -579,7 +577,7 @@ Georeferencing& Georeferencing::operator=(const Georeferencing& other) projected_crs_id = other.projected_crs_id; projected_crs_spec = other.projected_crs_spec; projected_crs_parameters = other.projected_crs_parameters; - is_realization = other.is_realization; + realization_crs_spec = other.realization_crs_spec; explicit_realization = other.explicit_realization; proj_transform = ProjTransform(other.projected_crs_spec, gnss_crs_spec); geographic_ref_point = other.geographic_ref_point; @@ -688,8 +686,8 @@ void Georeferencing::load(QXmlStreamReader& xml, bool load_scale_only) else if (xml.name() == literal::geographic_crs) { XmlElementReader crs_element(xml); - explicit_realization = crs_element.hasAttribute(literal::is_realization); - is_realization = explicit_realization && crs_element.attribute(literal::is_realization); + explicit_realization = false; + realization_crs_spec = QString(); while (xml.readNextStartElement()) { XmlElementReader current_element(xml); @@ -702,6 +700,16 @@ void Georeferencing::load(QXmlStreamReader& xml, bool load_scale_only) if (Georeferencing::ballpark_geographic_crs_spec != geographic_crs_spec) throw FileFormatException(tr("Unsupported geographic CRS specification: %1").arg(geographic_crs_spec)); } + else if (xml.name() == literal::realization_spec) + { + explicit_realization = true; + QString language{}; + if (current_element.hasAttribute(literal::language)) + language = current_element.attribute(literal::language); + realization_crs_spec = xml.readElementText(); + if (!realization_crs_spec.isEmpty() && language != literal::proj_4) + throw FileFormatException(tr("Unknown realization CRS specification language: %1").arg(language)); + } else if (xml.name() == literal::ref_point) { // Legacy, latitude/longitude in radiant @@ -806,13 +814,20 @@ void Georeferencing::save(QXmlStreamWriter& xml) const { XmlElementWriter crs_element(xml, literal::geographic_crs); crs_element.writeAttribute(literal::id, literal::geographic_coordinates); - if (explicit_realization) - crs_element.writeAttribute(literal::is_realization, is_realization ? literal::t : literal::f); { XmlElementWriter spec_element(xml, literal::spec); spec_element.writeAttribute(literal::language, literal::proj_4); xml.writeCharacters(ballpark_geographic_crs_spec); } + if (explicit_realization) + { + XmlElementWriter realization_element(xml, literal::realization_spec); + if (!realization_crs_spec.isEmpty()) + { + realization_element.writeAttribute(literal::language, literal::proj_4); + xml.writeCharacters(realization_crs_spec); + } + } if (XMLFileFormat::active_version < 6) { // Legacy compatibility @@ -1192,9 +1207,9 @@ bool Georeferencing::setDatumBallpark(bool ballpark) // Default return value if no change is necessary bool ok = (getState() == Geospatial); - if (is_realization != !ballpark) + if (realization_crs_spec.isEmpty() != ballpark) { - is_realization = !ballpark; + realization_crs_spec = ballpark ? QString() : gnss_crs_spec; proj_transform = ProjTransform(projected_crs_spec, getGeographicCRSSpec()); ok = proj_transform.isValid(); if (ok) diff --git a/src/core/georeferencing.h b/src/core/georeferencing.h index f915040d1..07ac75ecd 100644 --- a/src/core/georeferencing.h +++ b/src/core/georeferencing.h @@ -446,14 +446,16 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); * and also the source CRS when applicable. * @return a PROJ specification of the geographic CRS */ - const QString& getGeographicCRSSpec() const { return is_realization ? gnss_crs_spec : ballpark_geographic_crs_spec; } + const QString& getGeographicCRSSpec() const { return realization_crs_spec.isEmpty() + ? ballpark_geographic_crs_spec + : realization_crs_spec; } /** * Returns whether transformations use loose accuracy around the WGS84 * datum for explicitly requested compatibility with older releases of Mapper. * @return true if transformations are set compatible, false otherwise */ - bool isDatumBallpark() const { return explicit_realization && !is_realization; } + bool isDatumBallpark() const { return explicit_realization && realization_crs_spec.isEmpty(); } /** * Sets the coordinate reference system (CRS) of the geographical coordinates, @@ -721,7 +723,7 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); QString projected_crs_spec; std::vector< QString > projected_crs_parameters; - bool is_realization; + QString realization_crs_spec; bool explicit_realization; ProjTransform proj_transform; diff --git a/test/data/templates/geotiff.xmap b/test/data/templates/geotiff.xmap index 6ee77796e..1f31d2a45 100644 --- a/test/data/templates/geotiff.xmap +++ b/test/data/templates/geotiff.xmap @@ -7,8 +7,9 @@ 2180 - + +proj=latlong +datum=WGS84 + EPSG:9057 diff --git a/test/data/templates/ogr-template.xmap b/test/data/templates/ogr-template.xmap index b1b810669..8d9bff3a6 100644 --- a/test/data/templates/ogr-template.xmap +++ b/test/data/templates/ogr-template.xmap @@ -7,8 +7,9 @@ 2180 - + +proj=latlong +datum=WGS84 + EPSG:9057 diff --git a/test/data/templates/template-track-NA-ballpark-GDAL.xmap b/test/data/templates/template-track-NA-ballpark-GDAL.xmap index b903c2630..567c5f9de 100644 --- a/test/data/templates/template-track-NA-ballpark-GDAL.xmap +++ b/test/data/templates/template-track-NA-ballpark-GDAL.xmap @@ -7,8 +7,9 @@ 6342 - + +proj=latlong +datum=WGS84 + diff --git a/test/data/templates/template-track-NA.xmap b/test/data/templates/template-track-NA.xmap index ae3d9dd43..2e49408e4 100644 --- a/test/data/templates/template-track-NA.xmap +++ b/test/data/templates/template-track-NA.xmap @@ -7,8 +7,9 @@ 6342 - + +proj=latlong +datum=WGS84 + EPSG:9057 diff --git a/test/data/templates/template-track.xmap b/test/data/templates/template-track.xmap index c1272efa7..bc55f4d4d 100644 --- a/test/data/templates/template-track.xmap +++ b/test/data/templates/template-track.xmap @@ -7,8 +7,9 @@ 2180 - + +proj=latlong +datum=WGS84 + EPSG:9057 diff --git a/test/data/templates/world-file.xmap b/test/data/templates/world-file.xmap index 704bfd485..79343b077 100644 --- a/test/data/templates/world-file.xmap +++ b/test/data/templates/world-file.xmap @@ -7,8 +7,9 @@ 2180 - + +proj=latlong +datum=WGS84 + EPSG:9057