aboutsummaryrefslogtreecommitdiff
path: root/test/unit/test_datum.cpp
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2018-11-14 17:40:42 +0100
committerEven Rouault <even.rouault@spatialys.com>2018-11-14 22:48:29 +0100
commitd928db15d53805d9b728b440079756081961c536 (patch)
treee862a961d26bedb34c58e4f28ef0bdeedb5f3225 /test/unit/test_datum.cpp
parent330e8bf686f9c4524075ca1ff50cbca6c9e091da (diff)
downloadPROJ-d928db15d53805d9b728b440079756081961c536.tar.gz
PROJ-d928db15d53805d9b728b440079756081961c536.zip
Implement RFC 2: Initial integration of "GDAL SRS barn" work
This work mostly consists of: - a C++ implementation of the ISO-19111:2018 / OGC Topic 2 "Referencing by coordinates" classes to represent Datums, Coordinate systems, CRSs (Coordinate Reference Systems) and Coordinate Operations. - methods to convert between this C++ modeling and WKT1, WKT2 and PROJ string representations of those objects - management and query of a SQLite3 database of CRS and Coordinate Operation definition - a C API binding part of those capabilities This is all-in-one squashed commit of the work of https://github.com/OSGeo/proj.4/pull/1040
Diffstat (limited to 'test/unit/test_datum.cpp')
-rw-r--r--test/unit/test_datum.cpp482
1 files changed, 482 insertions, 0 deletions
diff --git a/test/unit/test_datum.cpp b/test/unit/test_datum.cpp
new file mode 100644
index 00000000..18cf244a
--- /dev/null
+++ b/test/unit/test_datum.cpp
@@ -0,0 +1,482 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * 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 "gtest_include.h"
+
+#include "proj/common.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::util;
+
+namespace {
+struct UnrelatedObject : public IComparable {
+ UnrelatedObject() = default;
+
+ bool _isEquivalentTo(const IComparable *, Criterion) const override {
+ assert(false);
+ return false;
+ }
+};
+
+static nn<std::shared_ptr<UnrelatedObject>> createUnrelatedObject() {
+ return nn_make_shared<UnrelatedObject>();
+}
+} // namespace
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_from_sphere) {
+
+ auto ellipsoid = Ellipsoid::createSphere(PropertyMap(), Length(6378137));
+ EXPECT_FALSE(ellipsoid->inverseFlattening().has_value());
+ EXPECT_FALSE(ellipsoid->semiMinorAxis().has_value());
+ EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value());
+ EXPECT_TRUE(ellipsoid->isSphere());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137));
+ EXPECT_EQ(ellipsoid->celestialBody(), "Earth");
+
+ EXPECT_EQ(ellipsoid->computeSemiMinorAxis(), Length(6378137));
+ EXPECT_EQ(ellipsoid->computedInverseFlattening(), 0);
+
+ EXPECT_EQ(
+ ellipsoid->exportToPROJString(PROJStringFormatter::create().get()),
+ "+R=6378137");
+
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get()));
+ EXPECT_FALSE(ellipsoid->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_non_earth) {
+
+ auto ellipsoid =
+ Ellipsoid::createSphere(PropertyMap(), Length(1), "Unity sphere");
+ EXPECT_EQ(ellipsoid->celestialBody(), "Unity sphere");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_from_inverse_flattening) {
+
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ PropertyMap(), Length(6378137), Scale(298.257223563));
+ EXPECT_TRUE(ellipsoid->inverseFlattening().has_value());
+ EXPECT_FALSE(ellipsoid->semiMinorAxis().has_value());
+ EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value());
+ EXPECT_FALSE(ellipsoid->isSphere());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137));
+ EXPECT_EQ(*ellipsoid->inverseFlattening(), Scale(298.257223563));
+
+ EXPECT_EQ(ellipsoid->computeSemiMinorAxis().unit(),
+ ellipsoid->semiMajorAxis().unit());
+ EXPECT_NEAR(ellipsoid->computeSemiMinorAxis().value(),
+ Length(6356752.31424518).value(), 1e-9);
+ EXPECT_EQ(ellipsoid->computedInverseFlattening(), 298.257223563);
+
+ EXPECT_EQ(
+ ellipsoid->exportToPROJString(PROJStringFormatter::create().get()),
+ "+ellps=WGS84");
+
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get()));
+ EXPECT_FALSE(ellipsoid->isEquivalentTo(
+ Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137),
+ Length(6356752.31424518))
+ .get()));
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(
+ Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137),
+ Length(6356752.31424518))
+ .get(),
+ IComparable::Criterion::EQUIVALENT));
+
+ EXPECT_FALSE(Ellipsoid::WGS84->isEquivalentTo(
+ Ellipsoid::GRS1980.get(), IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_from_semi_minor_axis) {
+
+ auto ellipsoid = Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137),
+ Length(6356752.31424518));
+ EXPECT_FALSE(ellipsoid->inverseFlattening().has_value());
+ EXPECT_TRUE(ellipsoid->semiMinorAxis().has_value());
+ EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value());
+ EXPECT_FALSE(ellipsoid->isSphere());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137));
+ EXPECT_EQ(*ellipsoid->semiMinorAxis(), Length(6356752.31424518));
+
+ EXPECT_EQ(ellipsoid->computeSemiMinorAxis(), Length(6356752.31424518));
+ EXPECT_NEAR(ellipsoid->computedInverseFlattening(), 298.257223563, 1e-10);
+
+ EXPECT_EQ(
+ ellipsoid->exportToPROJString(PROJStringFormatter::create().get()),
+ "+ellps=WGS84");
+
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get()));
+ EXPECT_FALSE(ellipsoid->isEquivalentTo(
+ Ellipsoid::createFlattenedSphere(PropertyMap(), Length(6378137),
+ Scale(298.257223563))
+ .get()));
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(
+ Ellipsoid::createFlattenedSphere(PropertyMap(), Length(6378137),
+ Scale(298.257223563))
+ .get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, prime_meridian_to_PROJString) {
+
+ EXPECT_EQ(PrimeMeridian::GREENWICH->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "");
+
+ EXPECT_EQ(PrimeMeridian::PARIS->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+pm=paris");
+
+ EXPECT_EQ(PrimeMeridian::create(PropertyMap(), Angle(3.5))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "+pm=3.5");
+
+ EXPECT_EQ(
+ PrimeMeridian::create(PropertyMap(), Angle(100, UnitOfMeasure::GRAD))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "+pm=90");
+
+ EXPECT_EQ(
+ PrimeMeridian::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Origin meridian"),
+ Angle(0))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+
+ EXPECT_TRUE(PrimeMeridian::GREENWICH->isEquivalentTo(
+ PrimeMeridian::GREENWICH.get()));
+ EXPECT_FALSE(PrimeMeridian::GREENWICH->isEquivalentTo(
+ createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, datum_with_ANCHOR) {
+ auto datum = GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS_1984 with anchor"),
+ Ellipsoid::WGS84, optional<std::string>("My anchor"),
+ PrimeMeridian::GREENWICH);
+
+ auto expected = "DATUM[\"WGS_1984 with anchor\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ANCHOR[\"My anchor\"]]";
+
+ EXPECT_EQ(datum->exportToWKT(WKTFormatter::create().get()), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, dynamic_geodetic_reference_frame) {
+ auto drf = DynamicGeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), Ellipsoid::WGS84,
+ optional<std::string>("My anchor"), PrimeMeridian::GREENWICH,
+ Measure(2018.5, UnitOfMeasure::YEAR),
+ optional<std::string>("My model"));
+
+ auto expected = "DATUM[\"test\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ANCHOR[\"My anchor\"]]";
+
+ EXPECT_EQ(drf->exportToWKT(WKTFormatter::create().get()), expected);
+
+ auto expected_wtk2_2018 =
+ "DYNAMIC[\n"
+ " FRAMEEPOCH[2018.5],\n"
+ " MODEL[\"My model\"]],\n"
+ "DATUM[\"test\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ANCHOR[\"My anchor\"]]";
+ EXPECT_EQ(
+ drf->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected_wtk2_2018);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_to_PROJString) {
+
+ EXPECT_EQ(Ellipsoid::WGS84->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+ellps=WGS84");
+
+ EXPECT_EQ(Ellipsoid::GRS1980->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+ellps=GRS80");
+
+ EXPECT_EQ(
+ Ellipsoid::createFlattenedSphere(
+ PropertyMap(), Length(10, UnitOfMeasure("km", 1000)), Scale(0.5))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "+a=10000 +rf=0.5");
+
+ EXPECT_EQ(Ellipsoid::createTwoAxis(PropertyMap(),
+ Length(10, UnitOfMeasure("km", 1000)),
+ Length(5, UnitOfMeasure("km", 1000)))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "+a=10000 +b=5000");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, temporal_datum_WKT2) {
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"),
+ DateTime::create("0000-01-01"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto expected = "TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]]";
+
+ EXPECT_EQ(datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+
+ EXPECT_THROW(
+ datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+
+ EXPECT_TRUE(datum->isEquivalentTo(datum.get()));
+ EXPECT_FALSE(datum->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, temporal_datum_time_origin_non_ISO8601) {
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"),
+ DateTime::create("0001 January 1st"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto expected = "TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[\"0001 January 1st\"]]";
+
+ EXPECT_EQ(datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, temporal_datum_WKT2_2018) {
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"),
+ DateTime::create("0000-01-01"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto expected = "TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]";
+
+ EXPECT_EQ(
+ datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, dynamic_vertical_reference_frame) {
+ auto drf = DynamicVerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "test"),
+ optional<std::string>("My anchor"), optional<RealizationMethod>(),
+ Measure(2018.5, UnitOfMeasure::YEAR),
+ optional<std::string>("My model"));
+
+ auto expected = "VDATUM[\"test\",\n"
+ " ANCHOR[\"My anchor\"]]";
+
+ EXPECT_EQ(drf->exportToWKT(WKTFormatter::create().get()), expected);
+
+ auto expected_wtk2_2018 = "DYNAMIC[\n"
+ " FRAMEEPOCH[2018.5],\n"
+ " MODEL[\"My model\"]],\n"
+ "VDATUM[\"test\",\n"
+ " ANCHOR[\"My anchor\"]]";
+ EXPECT_EQ(
+ drf->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected_wtk2_2018);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, datum_ensemble) {
+ auto otherDatum = GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "other datum"),
+ Ellipsoid::WGS84, optional<std::string>(), PrimeMeridian::GREENWICH);
+ auto ensemble = DatumEnsemble::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "test"),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326, otherDatum},
+ PositionalAccuracy::create("100"));
+ EXPECT_EQ(ensemble->datums().size(), 2);
+ EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100");
+ EXPECT_THROW(ensemble->exportToWKT(WKTFormatter::create().get()),
+ FormattingException);
+ EXPECT_EQ(
+ ensemble->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ "ENSEMBLE[\"test\",\n"
+ " MEMBER[\"World Geodetic System 1984\",\n"
+ " ID[\"EPSG\",6326]],\n"
+ " MEMBER[\"other datum\"],\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ENSEMBLEACCURACY[100]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, datum_ensemble_vertical) {
+ auto ensemble = DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{
+ VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1")),
+ VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum2"))},
+ PositionalAccuracy::create("100"));
+ EXPECT_EQ(
+ ensemble->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ "ENSEMBLE[\"unnamed\",\n"
+ " MEMBER[\"vdatum1\"],\n"
+ " MEMBER[\"vdatum2\"],\n"
+ " ENSEMBLEACCURACY[100]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, datum_ensemble_exceptions) {
+ // No datum
+ EXPECT_THROW(DatumEnsemble::create(PropertyMap(), std::vector<DatumNNPtr>{},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ // Single datum
+ EXPECT_THROW(DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ auto vdatum = VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1"));
+
+ // Different datum type
+ EXPECT_THROW(
+ DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326, vdatum},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ // Different datum type
+ EXPECT_THROW(
+ DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{vdatum, GeodeticReferenceFrame::EPSG_6326},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ // Different ellipsoid
+ EXPECT_THROW(DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326,
+ GeodeticReferenceFrame::EPSG_6267},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ // Different prime meridian
+ EXPECT_THROW(DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{
+ GeodeticReferenceFrame::EPSG_6326,
+ GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "other datum"),
+ Ellipsoid::WGS84, optional<std::string>(),
+ PrimeMeridian::PARIS)},
+ PositionalAccuracy::create("100")),
+ Exception);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, edatum) {
+ auto datum = EngineeringDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Engineering datum"),
+ optional<std::string>("my anchor"));
+
+ auto expected = "EDATUM[\"Engineering datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ EXPECT_EQ(datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, pdatum) {
+ auto datum = ParametricDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Parametric datum"),
+ optional<std::string>("my anchor"));
+
+ auto expected = "PDATUM[\"Parametric datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ EXPECT_EQ(datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}