aboutsummaryrefslogtreecommitdiff
path: root/src/iso19111
diff options
context:
space:
mode:
authorKristian Evers <kristianevers@gmail.com>2019-01-18 07:10:42 +0100
committerGitHub <noreply@github.com>2019-01-18 07:10:42 +0100
commit8584dcd50777355b460b38418cae0db05dcf91bc (patch)
treeb7335182c91737ceda8518a638ac5a79bd2bc5b2 /src/iso19111
parent3fc48e6146e020b86a0ef87749cc645f9b4fa113 (diff)
parent25f8ad123dc5fb7de0ac2fb10b55ae9efd2723a3 (diff)
downloadPROJ-8584dcd50777355b460b38418cae0db05dcf91bc.tar.gz
PROJ-8584dcd50777355b460b38418cae0db05dcf91bc.zip
Merge pull request #1223 from rouault/unify_proj_create
Unify proj_create(), proj_create_from_user_input() and proj_create_from_proj_string() (fixes #1214)
Diffstat (limited to 'src/iso19111')
-rw-r--r--src/iso19111/c_api.cpp97
-rw-r--r--src/iso19111/crs.cpp9
-rw-r--r--src/iso19111/factory.cpp6
-rw-r--r--src/iso19111/io.cpp482
4 files changed, 346 insertions, 248 deletions
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index d13c33c7..06a3c02e 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -47,6 +47,7 @@
#include "proj/util.hpp"
#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
// PROJ include order is sensitive
// clang-format off
@@ -91,25 +92,6 @@ static void PROJ_NO_INLINE proj_log_debug(PJ_CONTEXT *ctx, const char *function,
//! @cond Doxygen_Suppress
-/** Auxiliary structure to PJ_CONTEXT storing C++ context stuff. */
-struct projCppContext {
- DatabaseContextNNPtr databaseContext;
- std::string lastUOMName_{};
-
- explicit projCppContext(PJ_CONTEXT *ctx, const char *dbPath = nullptr,
- const char *const *auxDbPaths = nullptr)
- : databaseContext(DatabaseContext::create(
- dbPath ? dbPath : std::string(), toVector(auxDbPaths), ctx)) {}
-
- static std::vector<std::string> toVector(const char *const *auxDbPaths) {
- std::vector<std::string> res;
- for (auto iter = auxDbPaths; iter && *iter; ++iter) {
- res.emplace_back(std::string(*iter));
- }
- return res;
- }
-};
-
// ---------------------------------------------------------------------------
void proj_context_delete_cpp_context(struct projCppContext *cppContext) {
@@ -150,8 +132,9 @@ static PJ *pj_obj_create(PJ_CONTEXT *ctx, const IdentifiedObjectNNPtr &objIn) {
try {
auto formatter = PROJStringFormatter::create(
PROJStringFormatter::Convention::PROJ_5, dbContext);
- auto pj = proj_create(
- ctx, coordop->exportToPROJString(formatter.get()).c_str());
+ auto projString = coordop->exportToPROJString(formatter.get());
+ auto pj = pj_create_internal(
+ ctx, projString.empty() ? "+proj=affine" : projString.c_str());
if (pj) {
pj->iso_obj = objIn;
return pj;
@@ -346,41 +329,20 @@ PJ *proj_clone(PJ_CONTEXT *ctx, const PJ *obj) {
*
* @param ctx PROJ context, or NULL for default context
* @param text String (must not be NULL)
- * @param options null-terminated list of options, or NULL. Currently
- * supported options are:
- * <ul>
- * <li>USE_PROJ4_INIT_RULES=YES/NO. Defaults to NO. When set to YES,
- * init=epsg:XXXX syntax will be allowed and will be interpreted according to
- * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
- * order and will expect/output coordinates in radians. ProjectedCRS will have
- * easting, northing axis order (except the ones with Transverse Mercator South
- * Orientated projection). In that mode, the epsg:XXXX syntax will be also
- * interprated the same way.</li>
- * </ul>
* @return Object that must be unreferenced with proj_destroy(), or NULL in
* case of error.
*/
-PJ *proj_create_from_user_input(PJ_CONTEXT *ctx, const char *text,
- const char *const *options) {
+PJ *proj_create(PJ_CONTEXT *ctx, const char *text) {
SANITIZE_CTX(ctx);
assert(text);
- (void)options;
- auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+
+ // Only connect to proj.db if needed
+ if( strstr(text, "proj=") == nullptr || strstr(text, "init=") != nullptr ) {
+ getDBcontextNoException(ctx, __FUNCTION__);
+ }
try {
- bool usePROJ4InitRules = false;
- for (auto iter = options; iter && iter[0]; ++iter) {
- const char *value;
- if ((value = getOptionValue(*iter, "USE_PROJ4_INIT_RULES="))) {
- usePROJ4InitRules = ci_equal(value, "YES");
- } else {
- std::string msg("Unknown option :");
- msg += *iter;
- proj_log_error(ctx, __FUNCTION__, msg.c_str());
- return nullptr;
- }
- }
auto identifiedObject = nn_dynamic_pointer_cast<IdentifiedObject>(
- createFromUserInput(text, dbContext, usePROJ4InitRules));
+ createFromUserInput(text, ctx));
if (identifiedObject) {
return pj_obj_create(ctx, NN_NO_CHECK(identifiedObject));
}
@@ -520,43 +482,6 @@ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt,
// ---------------------------------------------------------------------------
-/** \brief Instantiate an object from a PROJ string.
- *
- * This function calls osgeo::proj::io::PROJStringParser::createFromPROJString()
- *
- * The returned object must be unreferenced with proj_destroy() after use.
- * It should be used by at most one thread at a time.
- *
- * @param ctx PROJ context, or NULL for default context
- * @param proj_string PROJ string (must not be NULL)
- * @param options should be set to NULL for now
- * @return Object that must be unreferenced with proj_destroy(), or NULL in
- * case of error.
- */
-PJ *proj_create_from_proj_string(PJ_CONTEXT *ctx, const char *proj_string,
- const char *const *options) {
- SANITIZE_CTX(ctx);
- (void)options;
- assert(proj_string);
- try {
- PROJStringParser parser;
- auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
- if (dbContext) {
- parser.attachDatabaseContext(NN_NO_CHECK(dbContext));
- }
- auto identifiedObject = nn_dynamic_pointer_cast<IdentifiedObject>(
- parser.createFromPROJString(proj_string));
- if (identifiedObject) {
- return pj_obj_create(ctx, NN_NO_CHECK(identifiedObject));
- }
- } catch (const std::exception &e) {
- proj_log_error(ctx, __FUNCTION__, e.what());
- }
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
/** \brief Instantiate an object from a database lookup.
*
* The returned object must be unreferenced with proj_destroy() after use.
diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp
index 7bf40e0c..74b94a1f 100644
--- a/src/iso19111/crs.cpp
+++ b/src/iso19111/crs.cpp
@@ -1176,7 +1176,8 @@ void GeodeticCRS::_exportToPROJString(
{
const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
if (!extensionProj4.empty()) {
- formatter->ingestPROJString(extensionProj4);
+ formatter->ingestPROJString(
+ replaceAll(extensionProj4, " +type=crs", ""));
formatter->addNoDefs(false);
return;
}
@@ -1907,7 +1908,8 @@ void GeographicCRS::_exportToPROJString(
{
const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
if (!extensionProj4.empty()) {
- formatter->ingestPROJString(extensionProj4);
+ formatter->ingestPROJString(
+ replaceAll(extensionProj4, " +type=crs", ""));
formatter->addNoDefs(false);
return;
}
@@ -2719,7 +2721,8 @@ void ProjectedCRS::_exportToPROJString(
{
const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
if (!extensionProj4.empty()) {
- formatter->ingestPROJString(extensionProj4);
+ formatter->ingestPROJString(
+ replaceAll(extensionProj4, " +type=crs", ""));
formatter->addNoDefs(false);
return;
}
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index fb75b7b6..54fc3fd3 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -2086,7 +2086,8 @@ AuthorityFactory::createGeodeticCRS(const std::string &code,
if (!text_definition.empty()) {
DatabaseContext::Private::RecursionDetector detector(d->context());
- auto obj = createFromUserInput(text_definition, d->context());
+ auto obj = createFromUserInput(
+ pj_add_type_crs_if_needed(text_definition), d->context());
auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj);
if (geodCRS) {
return cloneWithProps(NN_NO_CHECK(geodCRS), props);
@@ -2333,7 +2334,8 @@ AuthorityFactory::createProjectedCRS(const std::string &code) const {
if (!text_definition.empty()) {
DatabaseContext::Private::RecursionDetector detector(d->context());
- auto obj = createFromUserInput(text_definition, d->context());
+ auto obj = createFromUserInput(
+ pj_add_type_crs_if_needed(text_definition), d->context());
auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get());
if (projCRS) {
const auto &conv = projCRS->derivingConversionRef();
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp
index 52c484b9..bac42d5b 100644
--- a/src/iso19111/io.cpp
+++ b/src/iso19111/io.cpp
@@ -3344,6 +3344,9 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
std::string projString = stripQuotes(extensionChildren[1]);
if (starts_with(projString, "+proj=")) {
+ if (projString.find(" +type=crs") == std::string::npos) {
+ projString += " +type=crs";
+ }
try {
auto projObj =
PROJStringParser().createFromPROJString(projString);
@@ -4281,58 +4284,44 @@ BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
throw ParsingException(concat("unhandled keyword: ", name));
}
-//! @endcond
// ---------------------------------------------------------------------------
-/** \brief Instantiate a sub-class of BaseObject from a user specified text.
- *
- * The text can be a:
- * <ul>
- * <li>WKT string</li>
- * <li>PROJ string</li>
- * <li>database code, prefixed by its authoriy. e.g. "EPSG:4326"</li>
- * <li>URN. e.g. "urn:ogc:def:crs:EPSG::4326",
- * "urn:ogc:def:coordinateOperation:EPSG::1671"</li>
- * <li>an objet name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
- * uniqueness is not guaranteed, the function may apply heuristics to
- * determine the appropriate best match.</li>
- * </ul>
- *
- * @param text One of the above mentioned text format
- * @param dbContext Database context, or nullptr (in which case database
- * lookups will not work)
- * @param usePROJ4InitRules When set to true,
- * init=epsg:XXXX syntax will be allowed and will be interpreted according to
- * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
- * order and will expect/output coordinates in radians. ProjectedCRS will have
- * easting, northing axis order (except the ones with Transverse Mercator South
- * Orientated projection). In that mode, the epsg:XXXX syntax will be also
- * interprated the same way.
- * @throw ParsingException
- */
-BaseObjectNNPtr createFromUserInput(const std::string &text,
- const DatabaseContextPtr &dbContext,
- bool usePROJ4InitRules) {
+static BaseObjectNNPtr createFromUserInput(const std::string &text,
+ const DatabaseContextPtr &dbContext,
+ bool usePROJ4InitRules,
+ PJ_CONTEXT *ctx) {
- for (const auto &wktConstants : WKTConstants::constants()) {
- if (ci_starts_with(text, wktConstants)) {
- return WKTParser()
- .attachDatabaseContext(dbContext)
- .setStrict(false)
- .createFromWKT(text);
+ if (!ci_starts_with(text, "step proj=") &&
+ !ci_starts_with(text, "step +proj=")) {
+ for (const auto &wktConstants : WKTConstants::constants()) {
+ if (ci_starts_with(text, wktConstants)) {
+ return WKTParser()
+ .attachDatabaseContext(dbContext)
+ .setStrict(false)
+ .createFromWKT(text);
+ }
}
}
+
const char *textWithoutPlusPrefix = text.c_str();
if (textWithoutPlusPrefix[0] == '+')
textWithoutPlusPrefix++;
if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 ||
+ text.find(" +proj=") != std::string::npos ||
+ text.find(" proj=") != std::string::npos ||
strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 ||
+ text.find(" +init=") != std::string::npos ||
+ text.find(" init=") != std::string::npos ||
strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) {
return PROJStringParser()
.attachDatabaseContext(dbContext)
- .setUsePROJ4InitRules(usePROJ4InitRules)
+ .attachContext(ctx)
+ .setUsePROJ4InitRules(ctx != nullptr
+ ? (proj_context_get_use_proj4_init_rules(
+ ctx, false) == TRUE)
+ : usePROJ4InitRules)
.createFromPROJString(text);
}
@@ -4449,6 +4438,69 @@ BaseObjectNNPtr createFromUserInput(const std::string &text,
throw ParsingException("unrecognized format / unknown name");
}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a sub-class of BaseObject from a user specified text.
+ *
+ * The text can be a:
+ * <ul>
+ * <li>WKT string</li>
+ * <li>PROJ string</li>
+ * <li>database code, prefixed by its authoriy. e.g. "EPSG:4326"</li>
+ * <li>URN. e.g. "urn:ogc:def:crs:EPSG::4326",
+ * "urn:ogc:def:coordinateOperation:EPSG::1671"</li>
+ * <li>an objet name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
+ * uniqueness is not guaranteed, the function may apply heuristics to
+ * determine the appropriate best match.</li>
+ * </ul>
+ *
+ * @param text One of the above mentioned text format
+ * @param dbContext Database context, or nullptr (in which case database
+ * lookups will not work)
+ * @param usePROJ4InitRules When set to true,
+ * init=epsg:XXXX syntax will be allowed and will be interpreted according to
+ * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
+ * order and will expect/output coordinates in radians. ProjectedCRS will have
+ * easting, northing axis order (except the ones with Transverse Mercator South
+ * Orientated projection). In that mode, the epsg:XXXX syntax will be also
+ * interprated the same way.
+ * @throw ParsingException
+ */
+BaseObjectNNPtr createFromUserInput(const std::string &text,
+ const DatabaseContextPtr &dbContext,
+ bool usePROJ4InitRules) {
+ return createFromUserInput(text, dbContext, usePROJ4InitRules, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a sub-class of BaseObject from a user specified text.
+ *
+ * The text can be a:
+ * <ul>
+ * <li>WKT string</li>
+ * <li>PROJ string</li>
+ * <li>database code, prefixed by its authoriy. e.g. "EPSG:4326"</li>
+ * <li>URN. e.g. "urn:ogc:def:crs:EPSG::4326",
+ * "urn:ogc:def:coordinateOperation:EPSG::1671"</li>
+ * <li>an objet name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
+ * uniqueness is not guaranteed, the function may apply heuristics to
+ * determine the appropriate best match.</li>
+ * </ul>
+ *
+ * @param text One of the above mentioned text format
+ * @param ctx PROJ context
+ * @throw ParsingException
+ */
+BaseObjectNNPtr createFromUserInput(const std::string &text, PJ_CONTEXT *ctx) {
+ return createFromUserInput(
+ text, ctx != nullptr && ctx->cpp_context
+ ? ctx->cpp_context->databaseContext.as_nullable()
+ : nullptr,
+ false, ctx);
+}
// ---------------------------------------------------------------------------
@@ -4619,6 +4671,9 @@ std::string IPROJStringExportable::exportToPROJString(
}
}
if (bIsCRS) {
+ if (!formatter->hasParam("type")) {
+ formatter->addParam("type", "crs");
+ }
formatter->setCRSExport(false);
}
return formatter->toString();
@@ -5217,17 +5272,68 @@ void PROJStringFormatter::Private::appendToResult(const char *str) {
static void
PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
std::vector<Step::KeyValue> &globalParamValues,
- std::string &title, std::string &vunits,
- std::string &vto_meter) {
+ std::string &title) {
std::string word;
std::istringstream iss(projString, std::istringstream::in);
- bool inverted = false;
- bool prevWasStep = false;
- bool inProj = false;
- bool inPipeline = false;
bool prevWasTitle = false;
- bool prevWasInit = false;
+ if (projString.find("proj=pipeline") == std::string::npos) {
+ const bool hasProj = projString.find("proj=") == 0 ||
+ projString.find("+proj=") == 0 ||
+ projString.find(" proj=") != std::string::npos ||
+ projString.find(" +proj=") != std::string::npos;
+ const bool hasInit = projString.find("init=") == 0 ||
+ projString.find("+init=") == 0 ||
+ projString.find(" init=") != std::string::npos ||
+ projString.find(" +init=") != std::string::npos;
+ if (hasProj || hasInit) {
+ steps.push_back(Step());
+ }
+
+ while (iss >> word) {
+ if (word[0] == '+') {
+ word = word.substr(1);
+ } else if (prevWasTitle && word.find('=') == std::string::npos) {
+ title += " ";
+ title += word;
+ continue;
+ }
+
+ prevWasTitle = false;
+ if (starts_with(word, "proj=") && !hasInit) {
+ assert(hasProj);
+ auto stepName = word.substr(strlen("proj="));
+ steps.back().name = stepName;
+ } else if (starts_with(word, "init=")) {
+ assert(hasInit);
+ auto initName = word.substr(strlen("init="));
+ steps.back().name = initName;
+ steps.back().isInit = true;
+ } else if (word == "inv") {
+ if (!steps.empty()) {
+ steps.back().inverted = true;
+ }
+ } else if (starts_with(word, "title=")) {
+ title = word.substr(strlen("title="));
+ prevWasTitle = true;
+ } else if (word != "step") {
+ const auto pos = word.find('=');
+ auto key = word.substr(0, pos);
+ auto pair = (pos != std::string::npos)
+ ? Step::KeyValue(key, word.substr(pos + 1))
+ : Step::KeyValue(key);
+ if (steps.empty()) {
+ globalParamValues.push_back(pair);
+ } else {
+ steps.back().paramValues.push_back(pair);
+ }
+ }
+ }
+ return;
+ }
+
+ bool inPipeline = false;
+ bool invGlobal = false;
while (iss >> word) {
if (word[0] == '+') {
word = word.substr(1);
@@ -5242,52 +5348,31 @@ PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
if (inPipeline) {
throw ParsingException("nested pipeline not supported");
}
- inverted = false;
- prevWasStep = false;
- inProj = true;
inPipeline = true;
} else if (word == "step") {
if (!inPipeline) {
throw ParsingException("+step found outside pipeline");
}
- inverted = false;
- prevWasStep = true;
- prevWasInit = false;
+ steps.push_back(Step());
} else if (word == "inv") {
- if (prevWasStep) {
- inverted = true;
+ if (steps.empty()) {
+ invGlobal = true;
} else {
- if (steps.empty()) {
- throw ParsingException("+inv found at unexpected place");
- }
steps.back().inverted = true;
}
- prevWasStep = false;
- } else if (starts_with(word, "proj=")) {
+ } else if (inPipeline && !steps.empty() && starts_with(word, "proj=") &&
+ steps.back().name.empty()) {
auto stepName = word.substr(strlen("proj="));
- if (prevWasInit) {
- steps.back() = Step();
- prevWasInit = false;
- } else {
- steps.push_back(Step());
- }
steps.back().name = stepName;
- steps.back().inverted = inverted;
- prevWasStep = false;
- inProj = true;
- } else if (starts_with(word, "init=")) {
- if (prevWasInit) {
- throw ParsingException("+init= found at unexpected place");
- }
+ } else if (inPipeline && !steps.empty() && starts_with(word, "init=") &&
+ steps.back().name.empty()) {
auto initName = word.substr(strlen("init="));
- steps.push_back(Step());
steps.back().name = initName;
steps.back().isInit = true;
- steps.back().inverted = inverted;
- prevWasStep = false;
- prevWasInit = true;
- inProj = true;
- } else if (inProj) {
+ } else if (!inPipeline && starts_with(word, "title=")) {
+ title = word.substr(strlen("title="));
+ prevWasTitle = true;
+ } else {
const auto pos = word.find('=');
auto key = word.substr(0, pos);
auto pair = (pos != std::string::npos)
@@ -5298,18 +5383,14 @@ PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
} else {
steps.back().paramValues.push_back(pair);
}
- prevWasStep = false;
- } else if (starts_with(word, "vunits=")) {
- vunits = word.substr(strlen("vunits="));
- } else if (starts_with(word, "vto_meter=")) {
- vto_meter = word.substr(strlen("vto_meter="));
- } else if (starts_with(word, "title=")) {
- title = word.substr(strlen("title="));
- prevWasTitle = true;
- } else {
- throw ParsingException("Unexpected token: " + word);
}
}
+ if (invGlobal) {
+ for (auto &step : steps) {
+ step.inverted = !step.inverted;
+ }
+ std::reverse(steps.begin(), steps.end());
+ }
}
// ---------------------------------------------------------------------------
@@ -5319,10 +5400,7 @@ void PROJStringFormatter::ingestPROJString(
{
std::vector<Step> steps;
std::string title;
- std::string vunits;
- std::string vto_meter;
- PROJStringSyntaxParser(str, steps, d->globalParamValues_, title, vunits,
- vto_meter);
+ PROJStringSyntaxParser(str, steps, d->globalParamValues_, title);
d->steps_.insert(d->steps_.end(), steps.begin(), steps.end());
}
@@ -5596,6 +5674,7 @@ const DatabaseContextPtr &PROJStringFormatter::databaseContext() const {
struct PROJStringParser::Private {
DatabaseContextPtr dbContext_{};
+ PJ_CONTEXT *ctx_{};
bool usePROJ4InitRules_ = false;
std::vector<std::string> warningList_{};
@@ -5623,6 +5702,17 @@ struct PROJStringParser::Private {
template <class T>
// cppcheck-suppress functionStatic
+ const std::string &getGlobalParamValue(const T key) {
+ for (const auto &pair : globalParamValues_) {
+ if (ci_equal(pair.key, key)) {
+ return pair.value;
+ }
+ }
+ return emptyString;
+ }
+
+ template <class T>
+ // cppcheck-suppress functionStatic
const std::string &getParamValue(const Step &step, const T key) {
for (const auto &pair : globalParamValues_) {
if (ci_equal(pair.key, key)) {
@@ -5704,6 +5794,15 @@ PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+PROJStringParser &PROJStringParser::attachContext(PJ_CONTEXT *ctx) {
+ d->ctx_ = ctx;
+ return *this;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
/** \brief Set how init=epsg:XXXX syntax should be interpreted.
*
* @param enable When set to true,
@@ -6902,7 +7001,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
param.key == "towgs84" || param.key == "nadgrids" ||
param.key == "geoidgrids" || param.key == "units" ||
param.key == "to_meter" || param.key == "vunits" ||
- param.key == "vto_meter") {
+ param.key == "vto_meter" || param.key == "type") {
continue;
}
if (param.value.empty()) {
@@ -7252,22 +7351,24 @@ static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) {
// ---------------------------------------------------------------------------
/** \brief Instantiate a sub-class of BaseObject from a PROJ string.
+ *
+ * The projString must contain +type=crs for the object to be detected as a
+ * CRS instead of a CoordinateOperation.
+ *
* @throw ParsingException
*/
BaseObjectNNPtr
PROJStringParser::createFromPROJString(const std::string &projString) {
- std::string vunits;
- std::string vto_meter;
-
d->steps_.clear();
d->title_.clear();
d->globalParamValues_.clear();
d->projString_ = projString;
PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_,
- d->title_, vunits, vto_meter);
+ d->title_);
if (d->steps_.empty()) {
-
+ const auto &vunits = d->getGlobalParamValue("vunits");
+ const auto &vto_meter = d->getGlobalParamValue("vto_meter");
if (!vunits.empty() || !vto_meter.empty()) {
Step fakeStep;
if (!vunits.empty()) {
@@ -7288,9 +7389,10 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
}
}
- if ((d->steps_.size() == 1 ||
+ if (((d->steps_.size() == 1 &&
+ d->getParamValue(d->steps_[0], "type") == "crs") ||
(d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) &&
- isGeocentricStep(d->steps_[0].name)) {
+ !d->steps_[0].inverted && isGeocentricStep(d->steps_[0].name)) {
return d->buildBoundOrCompoundCRSIfNeeded(
0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
d->steps_[1].name == "unitconvert")
@@ -7300,7 +7402,7 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
// +init=xxxx:yyyy syntax
if (d->steps_.size() == 1 && d->steps_[0].isInit &&
- d->steps_[0].paramValues.size() == 0) {
+ !d->steps_[0].inverted) {
// Those used to come from a text init file
// We only support them in compatibility mode
@@ -7331,9 +7433,14 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
if (!file_found) {
auto obj = createFromUserInput(stepName, d->dbContext_, true);
auto crs = dynamic_cast<CRS *>(obj.get());
- if (crs) {
+ if (crs &&
+ (d->steps_[0].paramValues.empty() ||
+ (d->steps_[0].paramValues.size() == 1 &&
+ d->getParamValue(d->steps_[0], "type") == "crs"))) {
PropertyMap properties;
- properties.set(IdentifiedObject::NAME_KEY, crs->nameStr());
+ properties.set(IdentifiedObject::NAME_KEY,
+ d->title_.empty() ? crs->nameStr()
+ : d->title_);
const auto &extent = getExtent(crs);
if (extent) {
properties.set(
@@ -7342,12 +7449,12 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
}
auto geogCRS = dynamic_cast<GeographicCRS *>(crs);
if (geogCRS) {
- // Override with longitude latitude in radian
+ // Override with longitude latitude in degrees
return GeographicCRS::create(
properties, geogCRS->datum(),
geogCRS->datumEnsemble(),
EllipsoidalCS::createLongitudeLatitude(
- UnitOfMeasure::RADIAN));
+ UnitOfMeasure::DEGREE));
}
auto projCRS = dynamic_cast<ProjectedCRS *>(crs);
if (projCRS) {
@@ -7363,8 +7470,31 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
->unit()));
}
}
+ return obj;
+ }
+ auto projStringExportable =
+ dynamic_cast<IPROJStringExportable *>(crs);
+ if (projStringExportable) {
+ std::string expanded;
+ if (!d->title_.empty()) {
+ expanded = "title=";
+ expanded += d->title_;
+ }
+ for (const auto &pair : d->steps_[0].paramValues) {
+ if (!expanded.empty())
+ expanded += ' ';
+ expanded += '+';
+ expanded += pair.key;
+ if (!pair.value.empty()) {
+ expanded += '=';
+ expanded += pair.value;
+ }
+ }
+ expanded += ' ';
+ expanded += projStringExportable->exportToPROJString(
+ PROJStringFormatter::create().get());
+ return createFromPROJString(expanded);
}
- return obj;
}
}
@@ -7372,18 +7502,23 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
if (!init) {
throw ParsingException("out of memory");
}
- PJ_CONTEXT *ctx = proj_context_create();
+ PJ_CONTEXT *ctx = d->ctx_ ? d->ctx_ : proj_context_create();
if (!ctx) {
pj_dealloc(init);
throw ParsingException("out of memory");
}
paralist *list = pj_expand_init(ctx, init);
- proj_context_destroy(ctx);
+ if (ctx != d->ctx_) {
+ proj_context_destroy(ctx);
+ }
if (!list) {
pj_dealloc(init);
throw ParsingException("cannot expand " + projString);
}
std::string expanded;
+ if (!d->title_.empty()) {
+ expanded = "title=" + d->title_;
+ }
bool first = true;
bool has_init_term = false;
for (auto t = list; t;) {
@@ -7403,6 +7538,14 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
pj_dealloc(t);
t = n;
}
+ for (const auto &pair : d->steps_[0].paramValues) {
+ expanded += " +";
+ expanded += pair.key;
+ if (!pair.value.empty()) {
+ expanded += '=';
+ expanded += pair.value;
+ }
+ }
if (!has_init_term) {
return createFromPROJString(expanded);
@@ -7420,7 +7563,7 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
int iFirstCart = -1;
int iSecondCart = -1;
int iMolodensky = -1;
- bool unexpectedStructure = false;
+ bool unexpectedStructure = d->steps_.empty();
for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) {
const auto &stepName = d->steps_[i].name;
if (isGeographicStep(stepName)) {
@@ -7483,9 +7626,82 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
}
}
+ if (iHelmert < 0 && iMolodensky < 0 && !d->steps_.empty()) {
+ // CRS candidate
+ if ((d->steps_.size() == 1 &&
+ d->getParamValue(d->steps_[0], "type") != "crs") ||
+ (d->steps_.size() > 1 && d->getGlobalParamValue("type") != "crs")) {
+ unexpectedStructure = true;
+ }
+ } else if (iHelmert >= 0 &&
+ (d->hasParamValue(d->steps_[iHelmert], "theta") ||
+ d->hasParamValue(d->steps_[iHelmert], "exact") ||
+ d->hasParamValue(d->steps_[iHelmert], "transpose") ||
+ d->hasParamValue(d->steps_[iHelmert], "towgs84"))) {
+ unexpectedStructure = true;
+ }
+
+ if (unexpectedStructure || iHelmert >= 0 || iMolodensky >= 0) {
+ struct Logger {
+ std::string msg{};
+
+ // cppcheck-suppress functionStatic
+ void setMessage(const char *msgIn) noexcept {
+ try {
+ msg = msgIn;
+ } catch (const std::exception &) {
+ }
+ }
+
+ static void log(void *user_data, int level, const char *msg) {
+ if (level == PJ_LOG_ERROR) {
+ static_cast<Logger *>(user_data)->setMessage(msg);
+ }
+ }
+ };
+
+ // If the structure is not recognized, then try to instantiate the
+ // pipeline, and if successful, wrap it in a PROJBasedOperation
+ Logger logger;
+ bool valid;
+
+ auto pj_context = d->ctx_ ? d->ctx_ : proj_context_create();
+ if (!pj_context) {
+ throw ParsingException("out of memory");
+ }
+ if (pj_context != d->ctx_) {
+ proj_log_func(pj_context, &logger, Logger::log);
+ proj_context_use_proj4_init_rules(pj_context,
+ d->usePROJ4InitRules_);
+ }
+ auto pj = pj_create_internal(pj_context, projString.c_str());
+ valid = pj != nullptr;
+ proj_destroy(pj);
+
+ if (!valid) {
+ std::string prefix("Error " +
+ toString(proj_context_errno(pj_context)) + ": ");
+ if (logger.msg.empty()) {
+ logger.msg =
+ prefix + proj_errno_string(proj_context_errno(pj_context));
+ } else {
+ logger.msg = prefix + logger.msg;
+ }
+ }
+
+ if (pj_context != d->ctx_) {
+ proj_context_destroy(pj_context);
+ }
+
+ if (!valid) {
+ throw ParsingException(logger.msg);
+ }
+ }
+
if (!unexpectedStructure) {
- if (iFirstGeogStep == 0 && iSecondGeogStep < 0 && iProjStep < 0 &&
- iHelmert < 0 && iFirstCart < 0 && iMolodensky < 0 &&
+ if (iFirstGeogStep == 0 && !d->steps_[iFirstGeogStep].inverted &&
+ iSecondGeogStep < 0 && iProjStep < 0 && iHelmert < 0 &&
+ iFirstCart < 0 && iMolodensky < 0 &&
(iFirstUnitConvert < 0 || iSecondUnitConvert < 0) &&
(iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) {
const bool ignoreVUnits = false;
@@ -7515,9 +7731,6 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap
: iFirstAxisSwap));
}
- if (d->steps_.size() == 1 && iHelmert == 0) {
- return d->buildHelmertTransformation(iHelmert);
- }
if (iProjStep < 0 && iHelmert > 0 && iMolodensky < 0 &&
(iFirstGeogStep < 0 || iFirstGeogStep == iFirstCart - 1 ||
@@ -7541,10 +7754,6 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
iSecondAxisSwap, iSecondUnitConvert);
}
- if (d->steps_.size() == 1 && iMolodensky == 0) {
- return d->buildMolodenskyTransformation(iMolodensky);
- }
-
if (iProjStep < 0 && iHelmert < 0 && iMolodensky > 0 &&
(iFirstGeogStep < 0 || iFirstGeogStep == iMolodensky - 1 ||
(iFirstGeogStep == iMolodensky + 1 && iSecondGeogStep < 0)) &&
@@ -7565,47 +7774,6 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
}
}
- struct Logger {
- std::string msg{};
-
- // cppcheck-suppress functionStatic
- void setMessage(const char *msgIn) noexcept {
- try {
- msg = msgIn;
- } catch (const std::exception &) {
- }
- }
-
- static void log(void *user_data, int level, const char *msg) {
- if (level == PJ_LOG_ERROR) {
- static_cast<Logger *>(user_data)->setMessage(msg);
- }
- }
- };
-
- // If the structure is not recognized, then try to instantiate the
- // pipeline, and if successful, wrap it in a PROJBasedOperation
- Logger logger;
- auto pj_context = proj_context_create();
- if (!pj_context) {
- throw ParsingException("out of memory");
- }
- proj_log_func(pj_context, &logger, Logger::log);
- proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_);
- auto pj = proj_create(pj_context, projString.c_str());
- bool valid = pj != nullptr;
- proj_destroy(pj);
-
- if (!valid && logger.msg.empty()) {
- logger.setMessage(proj_errno_string(proj_context_errno(pj_context)));
- }
-
- proj_context_destroy(pj_context);
-
- if (!valid) {
- throw ParsingException(logger.msg);
- }
-
auto props = PropertyMap();
if (!d->title_.empty()) {
props.set(IdentifiedObject::NAME_KEY, d->title_);