From fc29fb8eadf9e48f3e8b0fb2a2aeccc22d85baa3 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Fri, 7 Jan 2022 12:01:56 -0500 Subject: [PATCH 1/5] Georeferencing: Regression test of orthographic projection Add a map that uses an orthographic projection, such as Mapper sets up for an import from OpenStreetMap. Test that a track template is positioned correctly. Behavior of PROJ with +proj=ortho changes at version 7.2. A change in alignment within a map is undesirable. GH-2016 --- test/data/templates/template-track-ortho.xmap | 92 +++++++++++++++++++ test/template_t.cpp | 4 + 2 files changed, 96 insertions(+) create mode 100644 test/data/templates/template-track-ortho.xmap diff --git a/test/data/templates/template-track-ortho.xmap b/test/data/templates/template-track-ortho.xmap new file mode 100644 index 000000000..1b8e85a29 --- /dev/null +++ b/test/data/templates/template-track-ortho.xmap @@ -0,0 +1,92 @@ + + + This map to demonstrate issue GH-2016, trimmed for testing purposes. +In order to get an orthographic projection, this map was initially created by import from a .osm map similar to 'map.osm', but in the region of template-track.gpx. +Then the OpenStreetMap objects were deleted, and the template-track.xmap was imported to get its line objects. Templates were added for template-track.gpx so that template_t.cpp could easily be expanded to validate the handling of this projection. + + + +proj=ortho +datum=WGS84 +ellps=WGS84 +units=m +lat_0=51.394000 +lon_0=21.199000 +no_defs + + + +proj=latlong +datum=WGS84 + + + + + + + PURPLE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/template_t.cpp b/test/template_t.cpp index 8eeca94e2..4558594fa 100644 --- a/test/template_t.cpp +++ b/test/template_t.cpp @@ -424,6 +424,8 @@ 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("TemplateTrack from v0.9.3") << QStringLiteral("testdata:templates/template-track-NA-093-PROJ.xmap") << 0; + QTest::newRow("TemplateTrack ortho") << QStringLiteral("testdata:templates/template-track-ortho.xmap") << 0; + QTest::newRow("OgrTemplate ortho") << QStringLiteral("testdata:templates/template-track-ortho.xmap") << 1; } void templateTrackTest() @@ -476,6 +478,8 @@ 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("TemplateTrack ortho") << QStringLiteral("testdata:templates/template-track-ortho.xmap") << 0; + QTest::newRow("OgrTemplate ortho") << QStringLiteral("testdata:templates/template-track-ortho.xmap") << 1; } void ogrTemplateTest() From dd9dac1f316258e008db3569c11aaf641e5c75bb Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Fri, 7 Jan 2022 12:10:22 -0500 Subject: [PATCH 2/5] Georeferencing: When spec has +proj=ortho, use +f=0 PROJ changes its interpretation of +proj=ortho at version 7.2. Using +f=0 in the spec string forces a spheroidal variant of the projection, which is compatible with all earlier versions of PROJ. This commit changes all explicit occurrences of +proj=ortho. --- src/gdal/ogr_file_format.cpp | 2 +- src/gdal/ogr_template.cpp | 4 ++-- src/templates/template_track.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gdal/ogr_file_format.cpp b/src/gdal/ogr_file_format.cpp index 37ac197cd..e7d20dea6 100644 --- a/src/gdal/ogr_file_format.cpp +++ b/src/gdal/ogr_file_format.cpp @@ -1007,7 +1007,7 @@ ogr::unique_srs OgrFileImport::importGeoreferencing(OGRDataSourceH data_source) auto ortho_georef = Georeferencing(); ortho_georef.setScaleDenominator(int(map->getScaleDenominator())); ortho_georef.setProjectedCRS(QString{}, - QString::fromLatin1("+proj=ortho +datum=WGS84 +ellps=WGS84 +units=m +lat_0=%1 +lon_0=%2 +no_defs") + QString::fromLatin1("+proj=ortho +f=0 +datum=WGS84 +ellps=WGS84 +units=m +lat_0=%1 +lon_0=%2 +no_defs") .arg(latitude, 0, 'f') .arg(longitude, 0, 'f') ); ortho_georef.setProjectedRefPoint({}, false, false); diff --git a/src/gdal/ogr_template.cpp b/src/gdal/ogr_template.cpp index 6f2c2a1ab..ba2c41143 100644 --- a/src/gdal/ogr_template.cpp +++ b/src/gdal/ogr_template.cpp @@ -201,12 +201,12 @@ std::unique_ptr OgrTemplate::makeOrthographicGeoreferencing(cons /// \todo Use the template's datum etc. instead of WGS84? auto georef = std::make_unique(); georef->setScaleDenominator(int(map->getGeoreferencing().getScaleDenominator())); - georef->setProjectedCRS(QString{}, QStringLiteral("+proj=ortho +datum=WGS84 +ellps=WGS84 +units=m +no_defs")); + georef->setProjectedCRS(QString{}, QStringLiteral("+proj=ortho +f=0 +datum=WGS84 +ellps=WGS84 +units=m +no_defs")); if (OgrFileImport::checkGeoreferencing(path, *georef)) { auto center = OgrFileImport::calcAverageLatLon(path); georef->setProjectedCRS(QString{}, - QString::fromLatin1("+proj=ortho +datum=WGS84 +ellps=WGS84 +units=m +lat_0=%1 +lon_0=%2 +no_defs") + QString::fromLatin1("+proj=ortho +f=0 +datum=WGS84 +ellps=WGS84 +units=m +lat_0=%1 +lon_0=%2 +no_defs") .arg(center.latitude()).arg(center.longitude())); georef->setProjectedRefPoint({}, false, false); georef->setCombinedScaleFactor(1.0); diff --git a/src/templates/template_track.cpp b/src/templates/template_track.cpp index b2bcc3184..ecf6df3b3 100644 --- a/src/templates/template_track.cpp +++ b/src/templates/template_track.cpp @@ -601,7 +601,7 @@ void TemplateTrack::updateGeoreferencing() QString TemplateTrack::calculateLocalGeoreferencing() const { LatLon proj_center = track.calcAveragePosition(); - return QString::fromLatin1("+proj=ortho +datum=WGS84 +lat_0=%1 +lon_0=%2") + return QString::fromLatin1("+proj=ortho +f=0 +datum=WGS84 +lat_0=%1 +lon_0=%2") .arg(proj_center.latitude(), 0, 'f') .arg(proj_center.longitude(), 0, 'f'); From 6450e11a9e7dd41100325da5e75500bb838af799 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Fri, 7 Jan 2022 12:16:06 -0500 Subject: [PATCH 3/5] Georeferencing: Add ensureOrthoIsSpheroidal method Add the ensureOrthoIsSpheroidal method which will change a PROJ spec string that uses the 'ortho' projection into one which is explicitly spheroidal, with 0 flattenting, by ensuring that '+f=0' is part of the spec string. --- src/core/georeferencing.cpp | 13 +++++++++++++ src/core/georeferencing.h | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/src/core/georeferencing.cpp b/src/core/georeferencing.cpp index 43b28fec6..244fbd0fb 100644 --- a/src/core/georeferencing.cpp +++ b/src/core/georeferencing.cpp @@ -1239,6 +1239,19 @@ QString Georeferencing::degToDMS(double val) return ret; } +void Georeferencing::ensureOrthoIsSpheroidal(QString &spec) +{ + const QString ortho_proj = QLatin1String("+proj=ortho "); + auto flattening_param = QLatin1String(" +f="); + auto i = spec.indexOf(ortho_proj); + if (i >= 0 && !spec.contains(flattening_param)) + { + auto pos = i + ortho_proj.length() - 1; + // Insert to make "+proj=ortho +f=0", no flattening. + spec.insert(pos, flattening_param + QLatin1String("0")); + } +} + QDebug operator<<(QDebug dbg, const Georeferencing &georef) { auto state = [](auto state) -> const char* { diff --git a/src/core/georeferencing.h b/src/core/georeferencing.h index 144295a2d..dde7a1c79 100644 --- a/src/core/georeferencing.h +++ b/src/core/georeferencing.h @@ -527,6 +527,11 @@ friend QDebug operator<<(QDebug dbg, const Georeferencing& georef); */ static QString degToDMS(double val); + /** + * Ensure specs using ortho projection are explicitly spheroidal. + */ + static void ensureOrthoIsSpheroidal(QString &spec); + /** * Updates the transformation parameters between map coordinates and From cf66fcbda74d66e3d785172e8230dfa53a566baa Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Fri, 7 Jan 2022 12:24:03 -0500 Subject: [PATCH 4/5] Georeferencing: Intepret 'ortho' projection as spheroidal When loading a map in XML format, there may be references to a CRS spec with a PROJ string containing '+proj=ortho'. Prior to PROJ 7.2 these were handled as spheroidal orthographic projections. Retain that same interpretation, even if Mapper is built with PROJ 7.2, by calling 'ensureOrthoIsSpheroidal' whenever a spec string is loaded from a map. --- src/core/georeferencing.cpp | 1 + src/gdal/ogr_template.cpp | 1 + src/templates/template_image.cpp | 1 + src/templates/template_track.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/src/core/georeferencing.cpp b/src/core/georeferencing.cpp index 244fbd0fb..04cf5374d 100644 --- a/src/core/georeferencing.cpp +++ b/src/core/georeferencing.cpp @@ -626,6 +626,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)); projected_crs_spec = xml.readElementText(); + ensureOrthoIsSpheroidal(projected_crs_spec); } else if (xml.name() == literal::parameter) { diff --git a/src/gdal/ogr_template.cpp b/src/gdal/ogr_template.cpp index ba2c41143..46f7e60be 100644 --- a/src/gdal/ogr_template.cpp +++ b/src/gdal/ogr_template.cpp @@ -573,6 +573,7 @@ bool OgrTemplate::loadTypeSpecificTemplateConfiguration(QXmlStreamReader& xml) else if (xml.name() == literal::crs_spec) { track_crs_spec = xml.readElementText(); + Georeferencing::ensureOrthoIsSpheroidal(track_crs_spec); template_track_compatibility = true; } else if (xml.name() == literal::projected_crs_spec) diff --git a/src/templates/template_image.cpp b/src/templates/template_image.cpp index 50d4c59fc..91cfbc526 100644 --- a/src/templates/template_image.cpp +++ b/src/templates/template_image.cpp @@ -193,6 +193,7 @@ bool TemplateImage::loadTypeSpecificTemplateConfiguration(QXmlStreamReader& xml) { /// \todo check specification language available_georef.effective.crs_spec = xml.readElementText(); + Georeferencing::ensureOrthoIsSpheroidal(available_georef.effective.crs_spec); } else xml.skipCurrentElement(); // unsupported diff --git a/src/templates/template_track.cpp b/src/templates/template_track.cpp index ecf6df3b3..4e804e4fb 100644 --- a/src/templates/template_track.cpp +++ b/src/templates/template_track.cpp @@ -219,6 +219,7 @@ bool TemplateTrack::loadTypeSpecificTemplateConfiguration(QXmlStreamReader& xml) if (xml.name() == QLatin1String("crs_spec")) { track_crs_spec = xml.readElementText(); + Georeferencing::ensureOrthoIsSpheroidal(track_crs_spec); } else if (xml.name() == QLatin1String("projected_crs_spec")) { From 2d0140121e748cdfb004780f66b210d1934bebd5 Mon Sep 17 00:00:00 2001 From: Scott Turner Date: Fri, 7 Jan 2022 12:30:14 -0500 Subject: [PATCH 5/5] GeoreferencingDialog: Interpret 'ortho' projection as spheroidal Prior to PROJ 7.2, a spec string containing '+proj=ortho' was handled as a spheroidal orthographic projection. Ensure that Mapper handles such orthographic projections consistently by inserting '+f=0' if necessary. In particular, make '+f=0' explicit when the spec is supplied in the georeferencing dialog, by calling 'ensureOrthoIsSpheroidal'. --- src/gui/georeferencing_dialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/georeferencing_dialog.cpp b/src/gui/georeferencing_dialog.cpp index 51fd0086d..c9ff4fc54 100644 --- a/src/gui/georeferencing_dialog.cpp +++ b/src/gui/georeferencing_dialog.cpp @@ -666,6 +666,7 @@ void GeoreferencingDialog::crsEdited() Q_ASSERT(crs_template); if (spec.isEmpty()) spec = QStringLiteral(" "); // intentionally non-empty: enforce non-local state. + Georeferencing::ensureOrthoIsSpheroidal(spec); georef_copy.setProjectedCRS(crs_template->id(), spec, crs_selector->parameters()); Q_ASSERT(georef_copy.getState() != Georeferencing::Local); if (keep_geographic_radio->isChecked())