Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WKT ESRI: fix related to projected CRS export and add support for geographic 3D CRS with LINUNIT node #3256

Merged
merged 3 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/proj/internal/io_internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class WKTConstants {
static const std::string EXTENSION; // WKT1 only - GDAL specific
static const std::string LOCAL_CS; // WKT1 only
static const std::string LOCAL_DATUM; // WKT1 only
static const std::string LINUNIT; // WKT1 ESRI (ArcGIS Pro >= 2.7)

// WKT2 preferred
static const std::string GEODCRS;
Expand Down
3 changes: 3 additions & 0 deletions include/proj/io.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ class PROJ_GCC_DLL WKTFormatter {
setAllowEllipsoidalHeightAsVerticalCRS(bool allow) noexcept;
PROJ_DLL bool isAllowedEllipsoidalHeightAsVerticalCRS() const noexcept;

PROJ_DLL WKTFormatter &setAllowLINUNITNode(bool allow) noexcept;
PROJ_DLL bool isAllowedLINUNITNode() const noexcept;

PROJ_DLL const std::string &toString() const;

PROJ_PRIVATE :
Expand Down
2 changes: 2 additions & 0 deletions scripts/reference_exported_symbols.txt
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,10 @@ osgeo::proj::io::PROJStringParser::warningList() const
osgeo::proj::io::WKTFormatter::create(dropbox::oxygen::nn<std::unique_ptr<osgeo::proj::io::WKTFormatter, std::default_delete<osgeo::proj::io::WKTFormatter> > > const&)
osgeo::proj::io::WKTFormatter::create(osgeo::proj::io::WKTFormatter::Convention, std::shared_ptr<osgeo::proj::io::DatabaseContext>)
osgeo::proj::io::WKTFormatter::isAllowedEllipsoidalHeightAsVerticalCRS() const
osgeo::proj::io::WKTFormatter::isAllowedLINUNITNode() const
osgeo::proj::io::WKTFormatter::isStrict() const
osgeo::proj::io::WKTFormatter::setAllowEllipsoidalHeightAsVerticalCRS(bool)
osgeo::proj::io::WKTFormatter::setAllowLINUNITNode(bool)
osgeo::proj::io::WKTFormatter::setIndentationWidth(int)
osgeo::proj::io::WKTFormatter::setMultiLine(bool)
osgeo::proj::io::WKTFormatter::setOutputAxis(osgeo::proj::io::WKTFormatter::OutputAxisRule)
Expand Down
5 changes: 5 additions & 0 deletions src/iso19111/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,9 @@ const char *proj_get_id_code(const PJ *obj, int index) {
* to YES and type == PJ_WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS
* will be exported as a compound CRS whose vertical part represents an
* ellipsoidal height (for example for use with LAS 1.4 WKT1).</li>
* <li>ALLOW_LINUNIT_NODE=YES/NO. Default is YES starting with PROJ 9.1.
* Only taken into account with type == PJ_WKT1_ESRI on a Geographic 3D
* CRS.</li>
* </ul>
* @return a string, or NULL in case of error.
*/
Expand Down Expand Up @@ -1580,6 +1583,8 @@ const char *proj_as_wkt(PJ_CONTEXT *ctx, const PJ *obj, PJ_WKT_TYPE type,
"ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS="))) {
formatter->setAllowEllipsoidalHeightAsVerticalCRS(
ci_equal(value, "YES"));
} else if ((value = getOptionValue(*iter, "ALLOW_LINUNIT_NODE="))) {
formatter->setAllowLINUNITNode(ci_equal(value, "YES"));
} else {
std::string msg("Unknown option :");
msg += *iter;
Expand Down
6 changes: 3 additions & 3 deletions src/iso19111/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,10 @@ void UnitOfMeasure::_exportToWKT(
const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2;

const auto l_type = type();
if (formatter->forceUNITKeyword() && l_type != Type::PARAMETRIC) {
formatter->startNode(WKTConstants::UNIT, !codeSpace().empty());
} else if (!unitType.empty()) {
if (!unitType.empty()) {
formatter->startNode(unitType, !codeSpace().empty());
} else if (formatter->forceUNITKeyword() && l_type != Type::PARAMETRIC) {
formatter->startNode(WKTConstants::UNIT, !codeSpace().empty());
} else {
if (isWKT2 && l_type == Type::LINEAR) {
formatter->startNode(WKTConstants::LENGTHUNIT,
Expand Down
125 changes: 83 additions & 42 deletions src/iso19111/crs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1982,29 +1982,35 @@ void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {

const auto &cs = coordinateSystem();
const auto &axisList = cs->axisList();
const bool isGeographic3D = isGeographic && axisList.size() == 3;
const auto oldAxisOutputRule = formatter->outputAxis();
auto l_name = nameStr();
const auto &dbContext = formatter->databaseContext();

if (!isWKT2 && formatter->useESRIDialect() && axisList.size() == 3) {
const bool isESRIExport = !isWKT2 && formatter->useESRIDialect();
const auto &l_identifiers = identifiers();

if (isESRIExport && axisList.size() == 3) {
if (!isGeographic) {
io::FormattingException::Throw(
"Geocentric CRS not supported in WKT1_ESRI");
}
// Try to format the Geographic 3D CRS as a GEOGCS[],VERTCS[...,DATUM[]]
// if we find corresponding objects
if (dbContext) {
if (exportAsESRIWktCompoundCRSWithEllipsoidalHeight(this, this,
formatter)) {
return;
if (!formatter->isAllowedLINUNITNode()) {
// Try to format the Geographic 3D CRS as a
// GEOGCS[],VERTCS[...,DATUM[]] if we find corresponding objects
if (dbContext) {
if (exportAsESRIWktCompoundCRSWithEllipsoidalHeight(
this, this, formatter)) {
return;
}
}
io::FormattingException::Throw(
"Cannot export this Geographic 3D CRS in WKT1_ESRI");
}
io::FormattingException::Throw(
"Cannot export this Geographic 3D CRS in WKT1_ESRI");
}

if (!isWKT2 && formatter->isStrict() && isGeographic &&
axisList.size() != 2 &&
if (!isWKT2 && !isESRIExport && formatter->isStrict() && isGeographic &&
axisList.size() == 3 &&
oldAxisOutputRule != io::WKTFormatter::OutputAxisRule::NO) {

auto geogCRS2D = demoteTo2D(std::string(), dbContext);
Expand Down Expand Up @@ -2054,43 +2060,58 @@ void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {
: io::WKTConstants::GEODCRS)
: isGeocentric() ? io::WKTConstants::GEOCCS
: io::WKTConstants::GEOGCS,
!identifiers().empty());
!l_identifiers.empty());

if (formatter->useESRIDialect()) {
if (isESRIExport) {
std::string l_esri_name;
if (l_name == "WGS 84") {
l_name = "GCS_WGS_1984";
l_esri_name = isGeographic3D ? "WGS_1984_3D" : "GCS_WGS_1984";
} else {
bool aliasFound = false;
if (dbContext) {
auto l_alias = dbContext->getAliasFromOfficialName(
l_name, "geodetic_crs", "ESRI");
if (!l_alias.empty()) {
l_name = l_alias;
aliasFound = true;
const auto tableName =
isGeographic3D ? "geographic_3D_crs" : "geodetic_crs";
if (!l_identifiers.empty()) {
// Try to find the ESRI alias from the CRS identified by its
// id
const auto aliases =
dbContext->getAliases(*(l_identifiers[0]->codeSpace()),
l_identifiers[0]->code(),
std::string(), // officialName,
tableName, "ESRI");
if (aliases.size() == 1)
l_esri_name = aliases.front();
}
if (l_esri_name.empty()) {
// Then find the ESRI alias from the CRS name
l_esri_name = dbContext->getAliasFromOfficialName(
l_name, tableName, "ESRI");
}
if (l_esri_name.empty()) {
// Then try to build an ESRI CRS from the CRS name, and if
// there's one, the ESRI name is the CRS name
auto authFactory = io::AuthorityFactory::create(
NN_NO_CHECK(dbContext), "ESRI");
const bool found = authFactory
->createObjectsFromName(
l_name,
{io::AuthorityFactory::
ObjectType::GEODETIC_CRS},
false // approximateMatch
)
.size() == 1;
if (found)
l_esri_name = l_name;
}
}
if (!aliasFound && dbContext) {
auto authFactory = io::AuthorityFactory::create(
NN_NO_CHECK(dbContext), "ESRI");
aliasFound =
authFactory
->createObjectsFromName(
l_name,
{io::AuthorityFactory::ObjectType::GEODETIC_CRS},
false // approximateMatch
)
.size() == 1;
}
if (!aliasFound) {
l_name = io::WKTFormatter::morphNameToESRI(l_name);
if (!starts_with(l_name, "GCS_")) {
l_name = "GCS_" + l_name;
if (l_esri_name.empty()) {
l_esri_name = io::WKTFormatter::morphNameToESRI(l_name);
if (!starts_with(l_esri_name, "GCS_")) {
l_esri_name = "GCS_" + l_esri_name;
}
}
}
}

if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) {
l_name = l_esri_name;
} else if (!isWKT2 && isDeprecated()) {
l_name += " (deprecated)";
}
formatter->addQuotedString(l_name);
Expand All @@ -2103,6 +2124,9 @@ void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {
if (!isWKT2) {
unit._exportToWKT(formatter);
}
if (isGeographic3D && isESRIExport) {
axisList[2]->unit()._exportToWKT(formatter, io::WKTConstants::LINUNIT);
}

if (oldAxisOutputRule ==
io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE &&
Expand All @@ -2114,7 +2138,7 @@ void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {

ObjectUsage::baseExportToWKT(formatter);

if (!isWKT2 && !formatter->useESRIDialect()) {
if (!isWKT2 && !isESRIExport) {
const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
if (!extensionProj4.empty()) {
formatter->startNode(io::WKTConstants::EXTENSION, false);
Expand Down Expand Up @@ -4114,9 +4138,24 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {

std::string l_esri_name;
if (formatter->useESRIDialect() && dbContext) {
l_esri_name = dbContext->getAliasFromOfficialName(
l_name, "projected_crs", "ESRI");

if (!l_identifiers.empty()) {
// Try to find the ESRI alias from the CRS identified by its id
const auto aliases = dbContext->getAliases(
*(l_identifiers[0]->codeSpace()), l_identifiers[0]->code(),
std::string(), // officialName,
"projected_crs", "ESRI");
if (aliases.size() == 1)
l_esri_name = aliases.front();
}
if (l_esri_name.empty()) {
// Then find the ESRI alias from the CRS name
l_esri_name = dbContext->getAliasFromOfficialName(
l_name, "projected_crs", "ESRI");
}
if (l_esri_name.empty()) {
// Then try to build an ESRI CRS from the CRS name, and if there's
// one, the ESRI name is the CRS name
auto authFactory =
io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI");
const bool found =
Expand All @@ -4134,6 +4173,8 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
if (!isWKT2 && !l_identifiers.empty() &&
*(l_identifiers[0]->codeSpace()) == "ESRI") {
try {
// If the id of the objet is in the ESRI namespace, then
// try to find the full ESRI WKT from the database
const auto definition = dbContext->getTextDefinition(
"projected_crs", "ESRI", l_identifiers[0]->code());
if (starts_with(definition, "PROJCS")) {
Expand Down
54 changes: 38 additions & 16 deletions src/iso19111/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3377,7 +3377,10 @@ std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
/** \brief Gets the alias name from an official name.
*
* @param officialName Official name. Mandatory
* @param tableName Table name/category. Mandatory
* @param tableName Table name/category. Mandatory.
* "geographic_2D_crs" and "geographic_3D_crs" are also
* accepted as special names to add a constraint on the "type"
* column of the "geodetic_crs" table.
* @param source Source of the alias. Mandatory
* @return Alias name (or empty if not found).
* @throw FactoryException
Expand All @@ -3387,29 +3390,38 @@ DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
const std::string &tableName,
const std::string &source) const {
std::string sql("SELECT auth_name, code FROM \"");
sql += replaceAll(tableName, "\"", "\"\"");
const auto genuineTableName =
tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
? "geodetic_crs"
: tableName;
sql += replaceAll(genuineTableName, "\"", "\"\"");
sql += "\" WHERE name = ?";
if (tableName == "geodetic_crs") {
if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
} else if (tableName == "geographic_3D_crs") {
sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
}
sql += " ORDER BY deprecated";
auto res = d->run(sql, {officialName});
if (res.empty()) {
res = d->run(
"SELECT auth_name, code FROM alias_name WHERE table_name = ? AND "
"alt_name = ? AND source IN ('EPSG', 'PROJ')",
{tableName, officialName});
{genuineTableName, officialName});
if (res.size() != 1) {
return std::string();
}
}
const auto &row = res.front();
res = d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
"auth_name = ? AND code = ? AND source = ?",
{tableName, row[0], row[1], source});
if (res.empty()) {
return std::string();
for (const auto &row : res) {
auto res2 =
d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
"auth_name = ? AND code = ? AND source = ?",
{genuineTableName, row[0], row[1], source});
if (!res2.empty()) {
return res2.front()[0];
}
}
return res.front()[0];
return std::string();
}

// ---------------------------------------------------------------------------
Expand All @@ -3421,7 +3433,10 @@ DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
* @param authName Authority.
* @param code Code.
* @param officialName Official name.
* @param tableName Table name/category. Mandatory
* @param tableName Table name/category. Mandatory.
* "geographic_2D_crs" and "geographic_3D_crs" are also
* accepted as special names to add a constraint on the "type"
* column of the "geodetic_crs" table.
* @param source Source of the alias. May be empty.
* @return Aliases
*/
Expand All @@ -3438,19 +3453,26 @@ std::list<std::string> DatabaseContext::getAliases(

std::string resolvedAuthName(authName);
std::string resolvedCode(code);
const auto genuineTableName =
tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
? "geodetic_crs"
: tableName;
if (authName.empty() || code.empty()) {
std::string sql("SELECT auth_name, code FROM \"");
sql += replaceAll(tableName, "\"", "\"\"");
sql += replaceAll(genuineTableName, "\"", "\"\"");
sql += "\" WHERE name = ?";
if (tableName == "geodetic_crs") {
if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
} else if (tableName == "geographic_3D_crs") {
sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
}
sql += " ORDER BY deprecated";
auto resSql = d->run(sql, {officialName});
if (resSql.empty()) {
resSql = d->run("SELECT auth_name, code FROM alias_name WHERE "
"table_name = ? AND "
"alt_name = ? AND source IN ('EPSG', 'PROJ')",
{tableName, officialName});
{genuineTableName, officialName});
if (resSql.size() != 1) {
d->cacheAliasNames_.insert(key, res);
return res;
Expand All @@ -3462,7 +3484,7 @@ std::list<std::string> DatabaseContext::getAliases(
}
std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
"auth_name = ? AND code = ?");
ListOfParams params{tableName, resolvedAuthName, resolvedCode};
ListOfParams params{genuineTableName, resolvedAuthName, resolvedCode};
if (!source.empty()) {
sql += " AND source = ?";
params.emplace_back(source);
Expand Down
Loading