Skip to content

Commit 0527b39

Browse files
authored
Merge pull request OSGeo#2172 from PROJ-BOT/backport-2171-to-7.0
[Backport 7.0] Add limited support for non-conformant WKT1 LAS COMPD_CS[]
2 parents 171fa81 + c47934d commit 0527b39

File tree

5 files changed

+285
-30
lines changed

5 files changed

+285
-30
lines changed

include/proj/crs.hpp

+10
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ class PROJ_GCC_DLL CRS : public common::ObjectUsage,
138138

139139
PROJ_INTERNAL CRSNNPtr normalizeForVisualization() const;
140140

141+
PROJ_INTERNAL CRSNNPtr allowNonConformantWKT1Export() const;
142+
141143
//! @endcond
142144

143145
protected:
@@ -892,6 +894,14 @@ class PROJ_GCC_DLL CompoundCRS final : public CRS,
892894
const std::vector<CRSNNPtr>
893895
&components); // throw InvalidCompoundCRSException
894896

897+
//! @cond Doxygen_Suppress
898+
PROJ_INTERNAL static CRSNNPtr
899+
createLax(const util::PropertyMap &properties,
900+
const std::vector<CRSNNPtr> &components,
901+
const io::DatabaseContextPtr
902+
&dbContext); // throw InvalidCompoundCRSException
903+
//! @endcond
904+
895905
protected:
896906
// relaxed: standard say SingleCRSNNPtr
897907
PROJ_INTERNAL explicit CompoundCRS(const std::vector<CRSNNPtr> &components);

src/apps/projinfo.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,8 @@ static void outputObject(
481481
std::cout << "WKT1:GDAL string:" << std::endl;
482482
}
483483

484-
auto formatter =
485-
WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL);
484+
auto formatter = WKTFormatter::create(
485+
WKTFormatter::Convention::WKT1_GDAL, dbContext);
486486
if (outputOpt.singleLine) {
487487
formatter->setMultiLine(false);
488488
}

src/iso19111/crs.cpp

+120-22
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ struct CRS::Private {
9393
BoundCRSPtr canonicalBoundCRS_{};
9494
std::string extensionProj4_{};
9595
bool implicitCS_ = false;
96+
bool allowNonConformantWKT1Export_ = false;
9697

9798
void setImplicitCS(const util::PropertyMap &properties) {
9899
const auto pVal = properties.get("IMPLICIT_CS");
@@ -559,6 +560,18 @@ CRSNNPtr CRS::shallowClone() const { return _shallowClone(); }
559560

560561
//! @cond Doxygen_Suppress
561562

563+
CRSNNPtr CRS::allowNonConformantWKT1Export() const {
564+
auto crs = shallowClone();
565+
crs->d->allowNonConformantWKT1Export_ = true;
566+
return crs;
567+
}
568+
569+
//! @endcond
570+
571+
// ---------------------------------------------------------------------------
572+
573+
//! @cond Doxygen_Suppress
574+
562575
CRSNNPtr CRS::alterName(const std::string &newName) const {
563576
auto crs = shallowClone();
564577
auto newNameMod(newName);
@@ -1334,30 +1347,49 @@ void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {
13341347
const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
13351348
const bool isGeographic =
13361349
dynamic_cast<const GeographicCRS *>(this) != nullptr;
1337-
formatter->startNode(isWKT2
1338-
? ((formatter->use2019Keywords() && isGeographic)
1339-
? io::WKTConstants::GEOGCRS
1340-
: io::WKTConstants::GEODCRS)
1341-
: isGeocentric() ? io::WKTConstants::GEOCCS
1342-
: io::WKTConstants::GEOGCS,
1343-
!identifiers().empty());
1344-
auto l_name = nameStr();
1350+
13451351
const auto &cs = coordinateSystem();
13461352
const auto &axisList = cs->axisList();
1347-
13481353
const auto oldAxisOutputRule = formatter->outputAxis();
1354+
auto l_name = nameStr();
1355+
const auto &dbContext = formatter->databaseContext();
13491356

13501357
if (formatter->useESRIDialect()) {
13511358
if (axisList.size() != 2) {
13521359
io::FormattingException::Throw(
13531360
"Only export of Geographic 2D CRS is supported in WKT1_ESRI");
13541361
}
1362+
}
1363+
1364+
if (!isWKT2 && formatter->isStrict() && isGeographic &&
1365+
axisList.size() != 2 &&
1366+
oldAxisOutputRule != io::WKTFormatter::OutputAxisRule::NO) {
1367+
if (CRS::getPrivate()->allowNonConformantWKT1Export_) {
1368+
formatter->startNode(io::WKTConstants::COMPD_CS, false);
1369+
formatter->addQuotedString(l_name + " + " + l_name);
1370+
auto geogCRS = demoteTo2D(std::string(), dbContext);
1371+
geogCRS->_exportToWKT(formatter);
1372+
geogCRS->_exportToWKT(formatter);
1373+
formatter->endNode();
1374+
return;
1375+
}
1376+
io::FormattingException::Throw(
1377+
"WKT1 does not support Geographic 3D CRS.");
1378+
}
13551379

1380+
formatter->startNode(isWKT2
1381+
? ((formatter->use2019Keywords() && isGeographic)
1382+
? io::WKTConstants::GEOGCRS
1383+
: io::WKTConstants::GEODCRS)
1384+
: isGeocentric() ? io::WKTConstants::GEOCCS
1385+
: io::WKTConstants::GEOGCS,
1386+
!identifiers().empty());
1387+
1388+
if (formatter->useESRIDialect()) {
13561389
if (l_name == "WGS 84") {
13571390
l_name = "GCS_WGS_1984";
13581391
} else {
13591392
bool aliasFound = false;
1360-
const auto &dbContext = formatter->databaseContext();
13611393
if (dbContext) {
13621394
auto l_alias = dbContext->getAliasFromOfficialName(
13631395
l_name, "geodetic_crs", "ESRI");
@@ -1373,11 +1405,6 @@ void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {
13731405
}
13741406
}
13751407
}
1376-
} else if (!isWKT2 && formatter->isStrict() && isGeographic &&
1377-
axisList.size() != 2 &&
1378-
oldAxisOutputRule != io::WKTFormatter::OutputAxisRule::NO) {
1379-
io::FormattingException::Throw(
1380-
"WKT1 does not support Geographic 3D CRS.");
13811408
}
13821409

13831410
if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) {
@@ -3146,6 +3173,36 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
31463173
const auto &dbContext = formatter->databaseContext();
31473174

31483175
auto l_name = nameStr();
3176+
const auto &l_coordinateSystem = d->coordinateSystem();
3177+
const auto &axisList = l_coordinateSystem->axisList();
3178+
if (axisList.size() == 3 && !(isWKT2 && formatter->use2019Keywords())) {
3179+
if (!formatter->useESRIDialect() &&
3180+
CRS::getPrivate()->allowNonConformantWKT1Export_) {
3181+
formatter->startNode(io::WKTConstants::COMPD_CS, false);
3182+
formatter->addQuotedString(l_name + " + " + baseCRS()->nameStr());
3183+
auto projCRS2D = demoteTo2D(std::string(), dbContext);
3184+
if (dbContext) {
3185+
const auto res =
3186+
projCRS2D->identify(io::AuthorityFactory::create(
3187+
NN_NO_CHECK(dbContext), "EPSG"));
3188+
if (res.size() == 1) {
3189+
const auto &front = res.front();
3190+
if (front.second == 100) {
3191+
projCRS2D = front.first;
3192+
}
3193+
}
3194+
}
3195+
projCRS2D->_exportToWKT(formatter);
3196+
baseCRS()
3197+
->demoteTo2D(std::string(), dbContext)
3198+
->_exportToWKT(formatter);
3199+
formatter->endNode();
3200+
return;
3201+
}
3202+
io::FormattingException::Throw(
3203+
"Projected 3D CRS can only be exported since WKT2:2019");
3204+
}
3205+
31493206
std::string l_alias;
31503207
if (formatter->useESRIDialect() && dbContext) {
31513208
l_alias = dbContext->getAliasFromOfficialName(l_name, "projected_crs",
@@ -3197,13 +3254,6 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
31973254
}
31983255
}
31993256

3200-
const auto &l_coordinateSystem = d->coordinateSystem();
3201-
const auto &axisList = l_coordinateSystem->axisList();
3202-
if (axisList.size() == 3 && !(isWKT2 && formatter->use2019Keywords())) {
3203-
io::FormattingException::Throw(
3204-
"Projected 3D CRS can only be exported since WKT2:2019");
3205-
}
3206-
32073257
const auto exportAxis = [&l_coordinateSystem, &axisList, &formatter]() {
32083258
const auto oldAxisOutputRule = formatter->outputAxis();
32093259
if (oldAxisOutputRule ==
@@ -4073,6 +4123,54 @@ CompoundCRSNNPtr CompoundCRS::create(const util::PropertyMap &properties,
40734123

40744124
// ---------------------------------------------------------------------------
40754125

4126+
//! @cond Doxygen_Suppress
4127+
4128+
/** \brief Instantiate a CompoundCRS, a Geographic 3D CRS or a Projected CRS
4129+
* from a vector of CRS.
4130+
*
4131+
* Be a bit "lax", in allowing formulations like EPSG:4326+4326 or
4132+
* EPSG:32631+4326 to express Geographic 3D CRS / Projected3D CRS.
4133+
*
4134+
* @param properties See \ref general_properties.
4135+
* At minimum the name should be defined.
4136+
* @param components the component CRS of the CompoundCRS.
4137+
* @return new CRS.
4138+
* @throw InvalidCompoundCRSException
4139+
*/
4140+
CRSNNPtr CompoundCRS::createLax(const util::PropertyMap &properties,
4141+
const std::vector<CRSNNPtr> &components,
4142+
const io::DatabaseContextPtr &dbContext) {
4143+
4144+
if (components.size() == 2) {
4145+
auto comp0 = components[0].get();
4146+
auto comp1 = components[1].get();
4147+
auto comp0Geog = dynamic_cast<const GeographicCRS *>(comp0);
4148+
auto comp0Proj = dynamic_cast<const ProjectedCRS *>(comp0);
4149+
auto comp1Geog = dynamic_cast<const GeographicCRS *>(comp1);
4150+
if ((comp0Geog != nullptr || comp0Proj != nullptr) &&
4151+
comp1Geog != nullptr) {
4152+
const auto horizGeog =
4153+
(comp0Proj != nullptr)
4154+
? comp0Proj->baseCRS().as_nullable().get()
4155+
: comp0Geog;
4156+
if (horizGeog->_isEquivalentTo(
4157+
comp1Geog->demoteTo2D(std::string(), dbContext).get())) {
4158+
return components[0]
4159+
->promoteTo3D(std::string(), dbContext)
4160+
->allowNonConformantWKT1Export();
4161+
}
4162+
throw InvalidCompoundCRSException(
4163+
"The 'vertical' geographic CRS is not equivalent to the "
4164+
"geographic CRS of the horizontal part");
4165+
}
4166+
}
4167+
4168+
return create(properties, components);
4169+
}
4170+
//! @endcond
4171+
4172+
// ---------------------------------------------------------------------------
4173+
40764174
//! @cond Doxygen_Suppress
40774175
void CompoundCRS::_exportToWKT(io::WKTFormatter *formatter) const {
40784176
const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;

src/iso19111/io.cpp

+11-6
Original file line numberDiff line numberDiff line change
@@ -1334,7 +1334,7 @@ struct WKTParser::Private {
13341334

13351335
DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node);
13361336

1337-
CompoundCRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node);
1337+
CRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node);
13381338

13391339
BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node);
13401340

@@ -4112,16 +4112,21 @@ WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) {
41124112

41134113
// ---------------------------------------------------------------------------
41144114

4115-
CompoundCRSNNPtr
4116-
WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
4115+
CRSNNPtr WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
41174116
std::vector<CRSNNPtr> components;
41184117
for (const auto &child : node->GP()->children()) {
41194118
auto crs = buildCRS(child);
41204119
if (crs) {
41214120
components.push_back(NN_NO_CHECK(crs));
41224121
}
41234122
}
4124-
return CompoundCRS::create(buildProperties(node), components);
4123+
4124+
if (ci_equal(node->GP()->value(), WKTConstants::COMPD_CS)) {
4125+
return CompoundCRS::createLax(buildProperties(node), components,
4126+
dbContext_);
4127+
} else {
4128+
return CompoundCRS::create(buildProperties(node), components);
4129+
}
41254130
}
41264131

41274132
// ---------------------------------------------------------------------------
@@ -5805,11 +5810,11 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text,
58055810
tokensCode[0], false));
58065811
auto crs2(factory->createCoordinateReferenceSystem(
58075812
tokensCode[1], false));
5808-
return CompoundCRS::create(
5813+
return CompoundCRS::createLax(
58095814
util::PropertyMap().set(
58105815
IdentifiedObject::NAME_KEY,
58115816
crs1->nameStr() + " + " + crs2->nameStr()),
5812-
{crs1, crs2});
5817+
{crs1, crs2}, dbContext);
58135818
}
58145819
throw;
58155820
}

0 commit comments

Comments
 (0)