Skip to content

Commit 662c7d1

Browse files
authored
Merge pull request #4728 from rouault/CartesianCS_create_same_unit
C++ API: CartesianCS::create(): by default check that all axis have the same unit
2 parents 69f4609 + c1655ac commit 662c7d1

6 files changed

Lines changed: 175 additions & 21 deletions

File tree

include/proj/coordinatesystem.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ namespace cs {
4848

4949
// ---------------------------------------------------------------------------
5050

51+
/** \brief Exception that can be thrown when an invalid coordinate system is
52+
* attempted to be constructed.
53+
*
54+
* @since 9.9
55+
*/
56+
class PROJ_GCC_DLL InvalidCoordinateSystem : public util::Exception {
57+
public:
58+
//! @cond Doxygen_Suppress
59+
PROJ_INTERNAL explicit InvalidCoordinateSystem(const char *message);
60+
PROJ_INTERNAL explicit InvalidCoordinateSystem(const std::string &message);
61+
PROJ_DLL InvalidCoordinateSystem(const InvalidCoordinateSystem &other);
62+
PROJ_DLL ~InvalidCoordinateSystem() override;
63+
//! @endcond
64+
};
65+
66+
// ---------------------------------------------------------------------------
67+
5168
/** \brief The direction of positive increase in the coordinate value for a
5269
* coordinate system axis.
5370
*
@@ -549,12 +566,12 @@ class PROJ_GCC_DLL CartesianCS final : public CoordinateSystem {
549566
PROJ_DLL static CartesianCSNNPtr
550567
create(const util::PropertyMap &properties,
551568
const CoordinateSystemAxisNNPtr &axis1,
552-
const CoordinateSystemAxisNNPtr &axis2);
569+
const CoordinateSystemAxisNNPtr &axis2, bool enforceSameUnit = true);
553570
PROJ_DLL static CartesianCSNNPtr
554571
create(const util::PropertyMap &properties,
555572
const CoordinateSystemAxisNNPtr &axis1,
556573
const CoordinateSystemAxisNNPtr &axis2,
557-
const CoordinateSystemAxisNNPtr &axis3);
574+
const CoordinateSystemAxisNNPtr &axis3, bool enforceSameUnit = true);
558575

559576
PROJ_DLL static CartesianCSNNPtr
560577
createEastingNorthing(const common::UnitOfMeasure &unit);

scripts/reference_exported_symbols.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ osgeo::proj::cs::CartesianCS::createEastingNorthing(osgeo::proj::common::UnitOfM
209209
osgeo::proj::cs::CartesianCS::createGeocentric(osgeo::proj::common::UnitOfMeasure const&)
210210
osgeo::proj::cs::CartesianCS::createNorthingEasting(osgeo::proj::common::UnitOfMeasure const&)
211211
osgeo::proj::cs::CartesianCS::createNorthPoleEastingSouthNorthingSouth(osgeo::proj::common::UnitOfMeasure const&)
212-
osgeo::proj::cs::CartesianCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&)
213-
osgeo::proj::cs::CartesianCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&)
212+
osgeo::proj::cs::CartesianCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, bool)
213+
osgeo::proj::cs::CartesianCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, bool)
214214
osgeo::proj::cs::CartesianCS::createSouthPoleEastingNorthNorthingNorth(osgeo::proj::common::UnitOfMeasure const&)
215215
osgeo::proj::cs::CartesianCS::createWestingSouthing(osgeo::proj::common::UnitOfMeasure const&)
216216
osgeo::proj::cs::CoordinateSystemAxis::abbreviation() const
@@ -234,6 +234,8 @@ osgeo::proj::cs::EllipsoidalCS::createLongitudeLatitude(osgeo::proj::common::Uni
234234
osgeo::proj::cs::EllipsoidalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&)
235235
osgeo::proj::cs::EllipsoidalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::cs::CoordinateSystemAxis> > const&)
236236
osgeo::proj::cs::EllipsoidalCS::~EllipsoidalCS()
237+
osgeo::proj::cs::InvalidCoordinateSystem::~InvalidCoordinateSystem()
238+
osgeo::proj::cs::InvalidCoordinateSystem::InvalidCoordinateSystem(osgeo::proj::cs::InvalidCoordinateSystem const&)
237239
osgeo::proj::cs::Meridian::create(osgeo::proj::common::Angle const&)
238240
osgeo::proj::cs::Meridian::longitude() const
239241
osgeo::proj::cs::Meridian::~Meridian()

src/iso19111/coordinatesystem.cpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,27 @@ bool CoordinateSystem::_isEquivalentTo(
746746
}
747747
//! @endcond
748748

749+
//! @cond Doxygen_Suppress
750+
// ---------------------------------------------------------------------------
751+
752+
InvalidCoordinateSystem::InvalidCoordinateSystem(const char *message)
753+
: Exception(message) {}
754+
755+
// ---------------------------------------------------------------------------
756+
757+
InvalidCoordinateSystem::InvalidCoordinateSystem(const std::string &message)
758+
: Exception(message) {}
759+
760+
// ---------------------------------------------------------------------------
761+
762+
InvalidCoordinateSystem::InvalidCoordinateSystem(
763+
const InvalidCoordinateSystem &) = default;
764+
765+
// ---------------------------------------------------------------------------
766+
767+
InvalidCoordinateSystem::~InvalidCoordinateSystem() = default;
768+
//! @endcond
769+
749770
// ---------------------------------------------------------------------------
750771

751772
//! @cond Doxygen_Suppress
@@ -1101,12 +1122,20 @@ CartesianCS::CartesianCS(const CartesianCS &) = default;
11011122
* @param properties See \ref general_properties.
11021123
* @param axis1 The first axis.
11031124
* @param axis2 The second axis.
1125+
* @param enforceSameUnit Whether to check that all axis have the same unit.
1126+
* (since 9.9)
11041127
* @return a new CartesianCS.
1128+
* @throw InvalidCoordinateSystem in case of error (since 9.9)
11051129
*/
11061130
CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties,
11071131
const CoordinateSystemAxisNNPtr &axis1,
1108-
const CoordinateSystemAxisNNPtr &axis2) {
1132+
const CoordinateSystemAxisNNPtr &axis2,
1133+
bool enforceSameUnit) {
11091134
std::vector<CoordinateSystemAxisNNPtr> axis{axis1, axis2};
1135+
if (enforceSameUnit && axis1->unit() != axis2->unit()) {
1136+
throw InvalidCoordinateSystem(
1137+
"All axis of a CartesianCS must have the same unit");
1138+
}
11101139
auto cs(CartesianCS::nn_make_shared<CartesianCS>(axis));
11111140
cs->setProperties(properties);
11121141
return cs;
@@ -1120,13 +1149,22 @@ CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties,
11201149
* @param axis1 The first axis.
11211150
* @param axis2 The second axis.
11221151
* @param axis3 The third axis.
1152+
* @param enforceSameUnit Whether to check that all axis have the same unit.
1153+
* (since 9.9)
11231154
* @return a new CartesianCS.
1155+
* @throw InvalidCoordinateSystem in case of error (since 9.9)
11241156
*/
11251157
CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties,
11261158
const CoordinateSystemAxisNNPtr &axis1,
11271159
const CoordinateSystemAxisNNPtr &axis2,
1128-
const CoordinateSystemAxisNNPtr &axis3) {
1160+
const CoordinateSystemAxisNNPtr &axis3,
1161+
bool enforceSameUnit) {
11291162
std::vector<CoordinateSystemAxisNNPtr> axis{axis1, axis2, axis3};
1163+
if (enforceSameUnit &&
1164+
(axis1->unit() != axis2->unit() || axis1->unit() != axis3->unit())) {
1165+
throw InvalidCoordinateSystem(
1166+
"All axis of a CartesianCS must have the same unit");
1167+
}
11301168
auto cs(CartesianCS::nn_make_shared<CartesianCS>(axis));
11311169
cs->setProperties(properties);
11321170
return cs;

src/iso19111/crs.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,8 +1298,9 @@ std::string CRS::getOriginatingAuthName() const {
12981298
/** \brief Return a variant of this CRS "promoted" to a 3D one, if not already
12991299
* the case.
13001300
*
1301-
* The new axis will be ellipsoidal height, oriented upwards, and with metre
1302-
* units.
1301+
* The new axis will be ellipsoidal height, oriented upwards, and with the same
1302+
* unit as the existing coordinate system for a CartesianCS, or otherwise
1303+
* with metre units
13031304
*
13041305
* @param newName Name of the new CRS. If empty, nameStr() will be used.
13051306
* @param dbContext Database context to look for potentially already registered
@@ -1310,11 +1311,26 @@ std::string CRS::getOriginatingAuthName() const {
13101311
*/
13111312
CRSNNPtr CRS::promoteTo3D(const std::string &newName,
13121313
const io::DatabaseContextPtr &dbContext) const {
1314+
1315+
common::UnitOfMeasure uom = common::UnitOfMeasure::METRE;
1316+
if (auto projCRS = dynamic_cast<const ProjectedCRS *>(this)) {
1317+
const auto &axisList = projCRS->coordinateSystem()->axisList();
1318+
assert(!axisList.empty());
1319+
uom = axisList[0]->unit();
1320+
} else if (auto derivedProjCRS =
1321+
dynamic_cast<const DerivedProjectedCRS *>(this)) {
1322+
const auto &cs = derivedProjCRS->coordinateSystem();
1323+
if (dynamic_cast<const cs::CartesianCS *>(cs.get())) {
1324+
const auto &axisList = cs->axisList();
1325+
assert(!axisList.empty());
1326+
uom = axisList[0]->unit();
1327+
}
1328+
}
1329+
13131330
auto upAxis = cs::CoordinateSystemAxis::create(
13141331
util::PropertyMap().set(IdentifiedObject::NAME_KEY,
13151332
cs::AxisName::Ellipsoidal_height),
1316-
cs::AxisAbbreviation::h, cs::AxisDirection::UP,
1317-
common::UnitOfMeasure::METRE);
1333+
cs::AxisAbbreviation::h, cs::AxisDirection::UP, uom);
13181334
return promoteTo3D(newName, dbContext, upAxis);
13191335
}
13201336

@@ -1375,8 +1391,8 @@ CRSNNPtr CRS::promoteTo3D(const std::string &newName,
13751391
util::PropertyMap(), axisList[0], axisList[1],
13761392
verticalAxisIfNotAlreadyPresent);
13771393
auto baseGeog3DCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(
1378-
derivedGeogCRS->baseCRS()->promoteTo3D(
1379-
std::string(), dbContext, verticalAxisIfNotAlreadyPresent));
1394+
derivedGeogCRS->baseCRS()->promoteTo3D(std::string(),
1395+
dbContext));
13801396
return util::nn_static_pointer_cast<CRS>(
13811397
DerivedGeographicCRS::create(
13821398
createProperties(),
@@ -1393,8 +1409,8 @@ CRSNNPtr CRS::promoteTo3D(const std::string &newName,
13931409
axisList[1],
13941410
verticalAxisIfNotAlreadyPresent);
13951411
auto baseProj3DCRS = util::nn_dynamic_pointer_cast<ProjectedCRS>(
1396-
derivedProjCRS->baseCRS()->promoteTo3D(
1397-
std::string(), dbContext, verticalAxisIfNotAlreadyPresent));
1412+
derivedProjCRS->baseCRS()->promoteTo3D(std::string(),
1413+
dbContext));
13981414
return util::nn_static_pointer_cast<CRS>(
13991415
DerivedProjectedCRS::create(
14001416
createProperties(),

src/iso19111/io.cpp

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3097,10 +3097,21 @@ WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */
30973097
}
30983098
} else if (ci_equal(csType, CartesianCS::WKT2_TYPE)) {
30993099
if (axisCount == 2) {
3100-
return CartesianCS::create(csMap, axisList[0], axisList[1]);
3100+
if (axisList[0]->unit() != axisList[1]->unit()) {
3101+
emitRecoverableWarning(
3102+
"All axis of a CartesianCS must have the same unit");
3103+
}
3104+
return CartesianCS::create(csMap, axisList[0], axisList[1],
3105+
/* enforceSameUnit = */ false);
31013106
} else if (axisCount == 3) {
3107+
if (axisList[0]->unit() != axisList[1]->unit() ||
3108+
axisList[0]->unit() != axisList[2]->unit()) {
3109+
emitRecoverableWarning(
3110+
"All axis of a CartesianCS must have the same unit");
3111+
}
31023112
return CartesianCS::create(csMap, axisList[0], axisList[1],
3103-
axisList[2]);
3113+
axisList[2],
3114+
/* enforceSameUnit = */ false);
31043115
}
31053116
} else if (ci_equal(csType, AffineCS::WKT2_TYPE)) {
31063117
if (axisCount == 2) {
@@ -5943,6 +5954,7 @@ BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
59435954
class JSONParser {
59445955
DatabaseContextPtr dbContext_{};
59455956
std::string deformationModelName_{};
5957+
PJ_CONTEXT *ctx_ = nullptr;
59465958

59475959
static std::string getString(const json &j, const char *key);
59485960
static json getObject(const json &j, const char *key);
@@ -6053,6 +6065,16 @@ class JSONParser {
60536065
NN_NO_CHECK(csCast));
60546066
}
60556067

6068+
void emitRecoverableWarning(const std::string &warningMsg) {
6069+
if (ctx_) {
6070+
proj_context_log_debug(ctx_, "PROJJSON parsing: %s",
6071+
warningMsg.c_str());
6072+
}
6073+
}
6074+
6075+
JSONParser(const JSONParser &) = delete;
6076+
JSONParser &operator=(const JSONParser &) = delete;
6077+
60566078
public:
60576079
JSONParser() = default;
60586080

@@ -6061,6 +6083,11 @@ class JSONParser {
60616083
return *this;
60626084
}
60636085

6086+
JSONParser &attachContext(PJ_CONTEXT *ctx) {
6087+
ctx_ = ctx;
6088+
return *this;
6089+
}
6090+
60646091
BaseObjectNNPtr create(const json &j);
60656092
};
60666093

@@ -7113,11 +7140,22 @@ CoordinateSystemNNPtr JSONParser::buildCS(const json &j) {
71137140
}
71147141
if (subtype == CartesianCS::WKT2_TYPE) {
71157142
if (axisCount == 2) {
7116-
return CartesianCS::create(csMap, axisList[0], axisList[1]);
7143+
if (axisList[0]->unit() != axisList[1]->unit()) {
7144+
emitRecoverableWarning(
7145+
"All axis of a CartesianCS must have the same unit");
7146+
}
7147+
return CartesianCS::create(csMap, axisList[0], axisList[1],
7148+
/* enforceSameUnit = */ false);
71177149
}
71187150
if (axisCount == 3) {
7151+
if (axisList[0]->unit() != axisList[1]->unit() ||
7152+
axisList[0]->unit() != axisList[2]->unit()) {
7153+
emitRecoverableWarning(
7154+
"All axis of a CartesianCS must have the same unit");
7155+
}
71197156
return CartesianCS::create(csMap, axisList[0], axisList[1],
7120-
axisList[2]);
7157+
axisList[2],
7158+
/* enforceSameUnit = */ false);
71217159
}
71227160
throw ParsingException("Expected 2 or 3 axis");
71237161
}
@@ -7721,7 +7759,10 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text,
77217759
} catch (const std::exception &e) {
77227760
throw ParsingException(e.what());
77237761
}
7724-
return JSONParser().attachDatabaseContext(dbContext).create(j);
7762+
return JSONParser()
7763+
.attachContext(ctx)
7764+
.attachDatabaseContext(dbContext)
7765+
.create(j);
77257766
}
77267767

77277768
if (!ci_starts_with(text, "step proj=") &&
@@ -12330,9 +12371,11 @@ PROJStringParser::Private::buildProjectedCRS(int iStep,
1233012371

1233112372
auto csGeodCRS = geodCRS->coordinateSystem();
1233212373
auto cs = csGeodCRS->axisList().size() == 2
12333-
? CartesianCS::create(emptyPropertyMap, axis[0], axis[1])
12374+
? CartesianCS::create(emptyPropertyMap, axis[0], axis[1],
12375+
/* enforceSameUnit = */ false)
1233412376
: CartesianCS::create(emptyPropertyMap, axis[0], axis[1],
12335-
csGeodCRS->axisList()[2]);
12377+
csGeodCRS->axisList()[2],
12378+
/* enforceSameUnit = */ false);
1233612379
if (isTopocentricStep(step.name)) {
1233712380
cs = CartesianCS::create(
1233812381
emptyPropertyMap,

test/unit/test_crs.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,44 @@ static nn<std::shared_ptr<UnrelatedObject>> createUnrelatedObject() {
7171

7272
// ---------------------------------------------------------------------------
7373

74+
TEST(cs, CartesianCS) {
75+
auto axis0 = CoordinateSystemAxis::create(
76+
PropertyMap().set(IdentifiedObject::NAME_KEY, "Easting"), "X",
77+
AxisDirection::EAST, UnitOfMeasure::METRE);
78+
79+
auto axis1 = CoordinateSystemAxis::create(
80+
PropertyMap().set(IdentifiedObject::NAME_KEY, "Northing"), "Y",
81+
AxisDirection::NORTH, UnitOfMeasure::METRE);
82+
83+
auto axis2 = CoordinateSystemAxis::create(
84+
PropertyMap().set(IdentifiedObject::NAME_KEY, "Height"), "H",
85+
AxisDirection::UP, UnitOfMeasure::METRE);
86+
87+
EXPECT_NO_THROW(CartesianCS::create(PropertyMap(), axis0, axis1));
88+
EXPECT_NO_THROW(CartesianCS::create(PropertyMap(), axis0, axis1, axis2));
89+
90+
auto axis2_foot = CoordinateSystemAxis::create(
91+
PropertyMap().set(IdentifiedObject::NAME_KEY, "Height"), "H",
92+
AxisDirection::UP, UnitOfMeasure::FOOT);
93+
94+
EXPECT_THROW(CartesianCS::create(PropertyMap(), axis0, axis2_foot),
95+
InvalidCoordinateSystem);
96+
EXPECT_THROW(CartesianCS::create(PropertyMap(), axis0, axis2_foot, true),
97+
InvalidCoordinateSystem);
98+
EXPECT_NO_THROW(
99+
CartesianCS::create(PropertyMap(), axis0, axis2_foot, false));
100+
101+
EXPECT_THROW(CartesianCS::create(PropertyMap(), axis0, axis1, axis2_foot),
102+
InvalidCoordinateSystem);
103+
EXPECT_THROW(
104+
CartesianCS::create(PropertyMap(), axis0, axis1, axis2_foot, true),
105+
InvalidCoordinateSystem);
106+
EXPECT_NO_THROW(
107+
CartesianCS::create(PropertyMap(), axis0, axis1, axis2_foot, false));
108+
}
109+
110+
// ---------------------------------------------------------------------------
111+
74112
TEST(crs, EPSG_4326_get_components) {
75113
auto crs = GeographicCRS::EPSG_4326;
76114
ASSERT_EQ(crs->identifiers().size(), 1U);

0 commit comments

Comments
 (0)