diff options
| -rw-r--r-- | include/proj/io.hpp | 3 | ||||
| -rw-r--r-- | src/4D_api.cpp | 7 | ||||
| -rw-r--r-- | src/iso19111/common.cpp | 3 | ||||
| -rw-r--r-- | src/iso19111/coordinateoperation.cpp | 1 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 7 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 420 | ||||
| -rw-r--r-- | test/unit/test_factory.cpp | 6 | ||||
| -rw-r--r-- | test/unit/test_io.cpp | 44 | ||||
| -rw-r--r-- | test/unit/test_operation.cpp | 29 |
9 files changed, 349 insertions, 171 deletions
diff --git a/include/proj/io.hpp b/include/proj/io.hpp index 825e8e44..4120d707 100644 --- a/include/proj/io.hpp +++ b/include/proj/io.hpp @@ -422,6 +422,9 @@ class PROJ_GCC_DLL PROJStringFormatter { PROJ_INTERNAL void setOmitZUnitConversion(bool omit); PROJ_INTERNAL bool omitZUnitConversion() const; + PROJ_INTERNAL void setDropEarlyBindingsTerms(bool drop); + PROJ_INTERNAL bool getDropEarlyBindingsTerms() const; + PROJ_INTERNAL const DatabaseContextPtr &databaseContext() const; PROJ_INTERNAL Convention convention() const; diff --git a/src/4D_api.cpp b/src/4D_api.cpp index 67460f3a..97aadb12 100644 --- a/src/4D_api.cpp +++ b/src/4D_api.cpp @@ -494,6 +494,9 @@ Returns 1 on success, 0 on failure /* Swap axes? */ p = pj_param_exists (P->params, "axis"); + const bool disable_grid_presence_check = pj_param_exists ( + P->params, "disable_grid_presence_check") != nullptr; + /* Don't axisswap if data are already in "enu" order */ if (p && (0!=strcmp ("enu", p->param))) { char *def = static_cast<char*>(malloc (100+strlen(P->axis))); @@ -509,7 +512,7 @@ Returns 1 on success, 0 on failure /* Geoid grid(s) given? */ p = pj_param_exists (P->params, "geoidgrids"); - if (p && strlen (p->param) > strlen ("geoidgrids=")) { + if (!disable_grid_presence_check && p && strlen (p->param) > strlen ("geoidgrids=")) { char *gridnames = p->param + strlen ("geoidgrids="); char *def = static_cast<char*>(malloc (100+2*strlen(gridnames))); if (nullptr==def) @@ -525,7 +528,7 @@ Returns 1 on success, 0 on failure /* Datum shift grid(s) given? */ p = pj_param_exists (P->params, "nadgrids"); - if (p && strlen (p->param) > strlen ("nadgrids=")) { + if (!disable_grid_presence_check && p && strlen (p->param) > strlen ("nadgrids=")) { char *gridnames = p->param + strlen ("nadgrids="); char *def = static_cast<char*>(malloc (100+2*strlen(gridnames))); if (nullptr==def) diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp index ca9d3b3e..57654d84 100644 --- a/src/iso19111/common.cpp +++ b/src/iso19111/common.cpp @@ -1122,8 +1122,7 @@ struct DataEpoch::Private { // --------------------------------------------------------------------------- -DataEpoch::DataEpoch() - : d(internal::make_unique<Private>(Measure())) {} +DataEpoch::DataEpoch() : d(internal::make_unique<Private>(Measure())) {} // --------------------------------------------------------------------------- diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp index 723fddec..6f9b6283 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -11100,6 +11100,7 @@ CoordinateOperationFactory::Private::createOperations( } auto projFormatter = io::PROJStringFormatter::create(); projFormatter->setCRSExport(true); + projFormatter->setDropEarlyBindingsTerms(true); projFormatter->startInversion(); sourceProjExportable->_exportToPROJString(projFormatter.get()); auto geogSrc = diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 1f03e262..ebbed7a1 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1209,7 +1209,7 @@ void GeodeticCRS::addDatumInfoToPROJString( const auto &nadgrids = formatter->getHDatumExtension(); const auto &l_datum = datum(); if (formatter->getCRSExport() && l_datum && TOWGS84Params.empty() && - nadgrids.empty()) { + nadgrids.empty() && !formatter->getDropEarlyBindingsTerms()) { if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6326.get(), util::IComparable::Criterion::EQUIVALENT)) { @@ -3766,6 +3766,11 @@ void BoundCRS::_exportToPROJString( "baseCRS of BoundCRS cannot be exported as a PROJ string"); } + if (formatter->getDropEarlyBindingsTerms()) { + crs_exportable->_exportToPROJString(formatter); + return; + } + auto vdatumProj4GridName = getVDatumPROJ4GRIDS(); if (!vdatumProj4GridName.empty()) { formatter->setVDatumExtension(vdatumProj4GridName); diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 43f4720a..5dc20f51 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -4692,6 +4692,7 @@ struct Step { struct KeyValue { std::string key{}; std::string value{}; + bool usedByParser = false; // only for PROJStringParser used explicit KeyValue(const std::string &keyIn) : key(keyIn) {} @@ -4745,6 +4746,7 @@ struct PROJStringFormatter::Private { bool addNoDefs_ = true; bool coordOperationOptimizations_ = false; bool crsExport_ = false; + bool dropEarlyBindingsTerms_ = false; std::string result_{}; @@ -5264,7 +5266,7 @@ void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) { void PROJStringFormatter::Private::appendToResult(const char *str) { if (!result_.empty()) { - result_ += " "; + result_ += ' '; } result_ += str; } @@ -5274,38 +5276,40 @@ 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 &title, bool dropEarlyBindingsTerms) { const char *c_str = projString.c_str(); std::vector<std::string> tokens; - size_t i = 0; - while (true) { - for (; isspace(c_str[i]); i++) { - } - std::string token; - bool in_string = false; - for (; c_str[i]; i++) { - if (in_string) { - if (c_str[i] == '"' && c_str[i + 1] == '"') { + { + size_t i = 0; + while (true) { + for (; isspace(c_str[i]); i++) { + } + std::string token; + bool in_string = false; + for (; c_str[i]; i++) { + if (in_string) { + if (c_str[i] == '"' && c_str[i + 1] == '"') { + i++; + } else if (c_str[i] == '"') { + in_string = false; + continue; + } + } else if (c_str[i] == '=' && c_str[i + 1] == '"') { + in_string = true; + token += c_str[i]; i++; - } else if (c_str[i] == '"') { - in_string = false; continue; + } else if (isspace(c_str[i])) { + break; } - } else if (c_str[i] == '=' && c_str[i + 1] == '"') { - in_string = true; token += c_str[i]; - i++; - continue; - } else if (isspace(c_str[i])) { + } + if (token.empty()) { break; } - token += c_str[i]; + tokens.emplace_back(token); } - if (token.empty()) { - break; - } - tokens.emplace_back(token); } bool prevWasTitle = false; @@ -5352,13 +5356,27 @@ PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps, } 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); + if (!(dropEarlyBindingsTerms && + (key == "towgs84" || key == "nadgrids" || + key == "geoidgrids"))) { + auto pair = (pos != std::string::npos) + ? Step::KeyValue(key, word.substr(pos + 1)) + : Step::KeyValue(key); + if (dropEarlyBindingsTerms && key == "datum") { + const auto datums = pj_get_datums_ref(); + for (int i = 0; datums[i].id != nullptr; i++) { + if (pair.value == datums[i].id) { + pair.key = "ellps"; + pair.value = datums[i].ellipse_id; + break; + } + } + } + if (steps.empty()) { + globalParamValues.push_back(pair); + } else { + steps.back().paramValues.push_back(pair); + } } } } @@ -5433,7 +5451,8 @@ void PROJStringFormatter::ingestPROJString( { std::vector<Step> steps; std::string title; - PROJStringSyntaxParser(str, steps, d->globalParamValues_, title); + PROJStringSyntaxParser(str, steps, d->globalParamValues_, title, + d->dropEarlyBindingsTerms_); d->steps_.insert(d->steps_.end(), steps.begin(), steps.end()); } @@ -5695,6 +5714,18 @@ bool PROJStringFormatter::omitZUnitConversion() const { // --------------------------------------------------------------------------- +void PROJStringFormatter::setDropEarlyBindingsTerms(bool drop) { + d->dropEarlyBindingsTerms_ = drop; +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::getDropEarlyBindingsTerms() const { + return d->dropEarlyBindingsTerms_; +} + +// --------------------------------------------------------------------------- + const DatabaseContextPtr &PROJStringFormatter::databaseContext() const { return d->dbContext_; } @@ -5717,16 +5748,20 @@ struct PROJStringParser::Private { std::vector<Step::KeyValue> globalParamValues_{}; std::string title_{}; + bool ignoreNadgrids_ = false; + template <class T> // cppcheck-suppress functionStatic - bool hasParamValue(const Step &step, const T key) { - for (const auto &pair : globalParamValues_) { + bool hasParamValue(Step &step, const T key) { + for (auto &pair : globalParamValues_) { if (ci_equal(pair.key, key)) { + pair.usedByParser = true; return true; } } - for (const auto &pair : step.paramValues) { + for (auto &pair : step.paramValues) { if (ci_equal(pair.key, key)) { + pair.usedByParser = true; return true; } } @@ -5735,9 +5770,10 @@ struct PROJStringParser::Private { template <class T> // cppcheck-suppress functionStatic - const std::string &getGlobalParamValue(const T key) { - for (const auto &pair : globalParamValues_) { + const std::string &getGlobalParamValue(T key) { + for (auto &pair : globalParamValues_) { if (ci_equal(pair.key, key)) { + pair.usedByParser = true; return pair.value; } } @@ -5746,24 +5782,26 @@ struct PROJStringParser::Private { template <class T> // cppcheck-suppress functionStatic - const std::string &getParamValue(const Step &step, const T key) { - for (const auto &pair : globalParamValues_) { + const std::string &getParamValue(Step &step, const T key) { + for (auto &pair : globalParamValues_) { if (ci_equal(pair.key, key)) { + pair.usedByParser = true; return pair.value; } } - for (const auto &pair : step.paramValues) { + for (auto &pair : step.paramValues) { if (ci_equal(pair.key, key)) { + pair.usedByParser = true; return pair.value; } } return emptyString; } - // cppcheck-suppress functionStatic - const std::string &getParamValueK(const Step &step) { - for (const auto &pair : step.paramValues) { + static const std::string &getParamValueK(Step &step) { + for (auto &pair : step.paramValues) { if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) { + pair.usedByParser = true; return pair.value; } } @@ -5771,10 +5809,22 @@ struct PROJStringParser::Private { } // cppcheck-suppress functionStatic + bool hasUnusedParameters(const Step &step) const { + if (steps_.size() == 1) { + for (const auto &pair : step.paramValues) { + if (pair.key != "no_defs" && !pair.usedByParser) { + return true; + } + } + } + return false; + } + + // cppcheck-suppress functionStatic std::string guessBodyName(double a); - PrimeMeridianNNPtr buildPrimeMeridian(const Step &step); - GeodeticReferenceFrameNNPtr buildDatum(const Step &step, + PrimeMeridianNNPtr buildPrimeMeridian(Step &step); + GeodeticReferenceFrameNNPtr buildDatum(Step &step, const std::string &title); GeographicCRSNNPtr buildGeographicCRS(int iStep, int iUnitConvert, int iAxisSwap, bool ignoreVUnits, @@ -5783,7 +5833,7 @@ struct PROJStringParser::Private { CRSNNPtr buildProjectedCRS(int iStep, GeographicCRSNNPtr geogCRS, int iUnitConvert, int iAxisSwap); CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs); - UnitOfMeasure buildUnit(const Step &step, const std::string &unitsParamName, + UnitOfMeasure buildUnit(Step &step, const std::string &unitsParamName, const std::string &toMeterParamName); CoordinateOperationNNPtr buildHelmertTransformation( int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1, @@ -5797,7 +5847,7 @@ struct PROJStringParser::Private { enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE }; std::vector<CoordinateSystemAxisNNPtr> - processAxisSwap(const Step &step, const UnitOfMeasure &unit, int iAxisSwap, + processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap, AxisType axisType, bool ignorePROJAxis); EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert, @@ -5940,7 +5990,7 @@ static UnitOfMeasure _buildUnit(double to_meter_value) { // --------------------------------------------------------------------------- UnitOfMeasure -PROJStringParser::Private::buildUnit(const Step &step, +PROJStringParser::Private::buildUnit(Step &step, const std::string &unitsParamName, const std::string &toMeterParamName) { UnitOfMeasure unit = UnitOfMeasure::METRE; @@ -6049,8 +6099,7 @@ static PropertyMap createMapWithUnknownName() { // --------------------------------------------------------------------------- -PrimeMeridianNNPtr -PROJStringParser::Private::buildPrimeMeridian(const Step &step) { +PrimeMeridianNNPtr PROJStringParser::Private::buildPrimeMeridian(Step &step) { PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH; const auto &pmStr = getParamValue(step, "pm"); @@ -6098,8 +6147,7 @@ std::string PROJStringParser::Private::guessBodyName(double a) { // --------------------------------------------------------------------------- GeodeticReferenceFrameNNPtr -PROJStringParser::Private::buildDatum(const Step &step, - const std::string &title) { +PROJStringParser::Private::buildDatum(Step &step, const std::string &title) { const auto &ellpsStr = getParamValue(step, "ellps"); const auto &datumStr = getParamValue(step, "datum"); @@ -6416,7 +6464,7 @@ createAxis(const std::string &name, const std::string &abbreviation, } std::vector<CoordinateSystemAxisNNPtr> -PROJStringParser::Private::processAxisSwap(const Step &step, +PROJStringParser::Private::processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap, AxisType axisType, bool ignorePROJAxis) { @@ -6493,7 +6541,7 @@ PROJStringParser::Private::processAxisSwap(const Step &step, throw ParsingException("Unhandled axis=" + axisStr); } } else if (iAxisSwap >= 0) { - const auto &stepAxisSwap = steps_[iAxisSwap]; + auto &stepAxisSwap = steps_[iAxisSwap]; const auto &orderStr = getParamValue(stepAxisSwap, "order"); auto orderTab = split(orderStr, ','); if (orderTab.size() != 2) { @@ -6526,13 +6574,13 @@ EllipsoidalCSNNPtr PROJStringParser::Private::buildEllipsoidalCS(int iStep, int iUnitConvert, int iAxisSwap, bool ignoreVUnits, bool ignorePROJAxis) { - const auto &step = steps_[iStep]; + auto &step = steps_[iStep]; assert(iUnitConvert < 0 || ci_equal(steps_[iUnitConvert].name, "unitconvert")); UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE; if (iUnitConvert >= 0) { - const auto &stepUnitConvert = steps_[iUnitConvert]; + auto &stepUnitConvert = steps_[iUnitConvert]; const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); if (stepUnitConvert.inverted) { @@ -6588,32 +6636,36 @@ GeographicCRSNNPtr PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert, int iAxisSwap, bool ignoreVUnits, bool ignorePROJAxis) { - const auto &step = steps_[iStep]; + auto &step = steps_[iStep]; const bool l_isGeographicStep = isGeographicStep(step.name); const auto &title = l_isGeographicStep ? title_ : emptyString; + // units=m is often found in the wild. + // No need to create a extension string for this + hasParamValue(step, "units"); + auto datum = buildDatum(step, title); auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" : title); + auto cs = buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignoreVUnits, + ignorePROJAxis); + if (l_isGeographicStep && - (hasParamValue(step, "wktext") || - hasParamValue(step, "lon_wrap") | hasParamValue(step, "geoc") || + (hasUnusedParameters(step) || getNumericValue(getParamValue(step, "lon_0")) != 0.0)) { props.set("EXTENSION_PROJ4", projString_); } - return GeographicCRS::create( - props, datum, buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, - ignoreVUnits, ignorePROJAxis)); + return GeographicCRS::create(props, datum, cs); } // --------------------------------------------------------------------------- GeodeticCRSNNPtr PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { - const auto &step = steps_[iStep]; + auto &step = steps_[iStep]; assert(isGeocentricStep(step.name)); assert(iUnitConvert < 0 || @@ -6625,7 +6677,7 @@ PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { UnitOfMeasure unit = UnitOfMeasure::METRE; if (iUnitConvert >= 0) { - const auto &stepUnitConvert = steps_[iUnitConvert]; + auto &stepUnitConvert = steps_[iUnitConvert]; const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); const std::string *z_in = &getParamValue(stepUnitConvert, "z_in"); @@ -6659,11 +6711,12 @@ PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" : title); - if (hasParamValue(step, "wktext")) { + auto cs = CartesianCS::createGeocentric(unit); + + if (hasUnusedParameters(step)) { props.set("EXTENSION_PROJ4", projString_); } - auto cs = CartesianCS::createGeocentric(unit); return GeodeticCRS::create(props, datum, cs); } @@ -6672,9 +6725,13 @@ PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { CRSNNPtr PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs) { - const auto &step = steps_[iStep]; + auto &step = steps_[iStep]; + const auto &nadgrids = getParamValue(step, "nadgrids"); const auto &towgs84 = getParamValue(step, "towgs84"); - if (!towgs84.empty()) { + // nadgrids has the priority over towgs84 + if (!ignoreNadgrids_ && !nadgrids.empty()) { + crs = BoundCRS::createFromNadgrids(crs, nadgrids); + } else if (!towgs84.empty()) { std::vector<double> towgs84Values; const auto tokens = split(towgs84, ','); for (const auto &str : tokens) { @@ -6687,11 +6744,6 @@ PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep, crs = BoundCRS::createFromTOWGS84(crs, towgs84Values); } - const auto &nadgrids = getParamValue(step, "nadgrids"); - if (!nadgrids.empty()) { - crs = BoundCRS::createFromNadgrids(crs, nadgrids); - } - const auto &geoidgrids = getParamValue(step, "geoidgrids"); if (!geoidgrids.empty()) { auto vdatum = @@ -6830,7 +6882,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR); for (size_t i = 0; i < step.paramValues.size(); ++i) { if (ci_equal(step.paramValues[i].key, "nadgrids")) { - step.paramValues.erase(step.paramValues.begin() + i); + ignoreNadgrids_ = true; break; } } @@ -6892,7 +6944,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( UnitOfMeasure unit = buildUnit(step, "units", "to_meter"); if (iUnitConvert >= 0) { - const auto &stepUnitConvert = steps_[iUnitConvert]; + auto &stepUnitConvert = steps_[iUnitConvert]; const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); if (stepUnitConvert.inverted) { @@ -7085,8 +7137,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" : title); - - if (hasParamValue(step, "wktext")) { + if (hasUnusedParameters(step)) { props.set("EXTENSION_PROJ4", projString_); } @@ -7105,7 +7156,6 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( crs = CompoundCRS::create(mapWithUnknownName, std::vector<CRSNNPtr>{crs, vcrs}); } - return crs; } @@ -7397,7 +7447,7 @@ PROJStringParser::createFromPROJString(const std::string &projString) { d->globalParamValues_.clear(); d->projString_ = projString; PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_, - d->title_); + d->title_, false); if (d->steps_.empty()) { const auto &vunits = d->getGlobalParamValue("vunits"); @@ -7422,16 +7472,11 @@ PROJStringParser::createFromPROJString(const std::string &projString) { } } - if (((d->steps_.size() == 1 && + const bool isGeocentricCRS = + ((d->steps_.size() == 1 && d->getParamValue(d->steps_[0], "type") == "crs") || (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) && - !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") - ? 1 - : -1)); - } + !d->steps_[0].inverted && isGeocentricStep(d->steps_[0].name); // +init=xxxx:yyyy syntax if (d->steps_.size() == 1 && d->steps_[0].isInit && @@ -7674,60 +7719,133 @@ PROJStringParser::createFromPROJString(const std::string &projString) { unexpectedStructure = true; } - if (unexpectedStructure || iHelmert >= 0 || iMolodensky >= 0) { - struct Logger { - std::string msg{}; + struct Logger { + std::string msg{}; - // cppcheck-suppress functionStatic - void setMessage(const char *msgIn) noexcept { - try { - msg = msgIn; - } catch (const std::exception &) { - } + // 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); - } + 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; + // 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.find("type=crs") != std::string::npos + ? projString + " +disable_grid_presence_check" + : projString) + .c_str()); + valid = pj != nullptr; + + // Remove parameters not understood by PROJ. + if (valid && d->steps_.size() == 1) { + std::vector<Step::KeyValue> newParamValues{}; + std::set<std::string> foundKeys; + auto &step = d->steps_[0]; + + for (auto &kv : step.paramValues) { + bool recognizedByPROJ = false; + if (foundKeys.find(kv.key) != foundKeys.end()) { + continue; + } + foundKeys.insert(kv.key); + if (step.name == "krovak" && kv.key == "alpha") { + // We recognize it in our CRS parsing code + recognizedByPROJ = true; + } else { + for (auto cur = pj->params; cur; cur = cur->next) { + const char *equal = strchr(cur->param, '='); + if (equal && + static_cast<size_t>(equal - cur->param) == + kv.key.size()) { + if (memcmp(cur->param, kv.key.c_str(), kv.key.size()) == + 0) { + recognizedByPROJ = (cur->used == 1); + break; + } + } else if (strcmp(cur->param, kv.key.c_str()) == 0) { + recognizedByPROJ = (cur->used == 1); + break; + } + } + } + if (recognizedByPROJ) { + newParamValues.emplace_back(kv); + } + } + step.paramValues = newParamValues; - auto pj_context = d->ctx_ ? d->ctx_ : proj_context_create(); - if (!pj_context) { - throw ParsingException("out of memory"); + d->projString_.clear(); + if (!step.name.empty()) { + d->projString_ += step.isInit ? "+init=" : "+proj="; + d->projString_ += step.name; } - 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; + for (const auto ¶mValue : step.paramValues) { + if (!d->projString_.empty()) { + d->projString_ += ' '; + } + d->projString_ += '+'; + d->projString_ += paramValue.key; + if (!paramValue.value.empty()) { + d->projString_ += '='; + d->projString_ += + pj_double_quote_string_param_if_needed(paramValue.value); } } + } + + proj_destroy(pj); - if (pj_context != d->ctx_) { - proj_context_destroy(pj_context); + 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 (!valid) { - throw ParsingException(logger.msg); + if (isGeocentricCRS) { + // First run is dry run to mark all recognized/unrecognized tokens + for (int iter = 0; iter < 2; iter++) { + auto obj = d->buildBoundOrCompoundCRSIfNeeded( + 0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 && + d->steps_[1].name == "unitconvert") + ? 1 + : -1)); + if (iter == 1) { + return nn_static_pointer_cast<BaseObject>(obj); + } } } @@ -7738,9 +7856,16 @@ PROJStringParser::createFromPROJString(const std::string &projString) { (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) && (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) { const bool ignoreVUnits = false; - return d->buildBoundOrCompoundCRSIfNeeded( - 0, d->buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, - iFirstAxisSwap, ignoreVUnits, false)); + // First run is dry run to mark all recognized/unrecognized tokens + for (int iter = 0; iter < 2; iter++) { + auto obj = d->buildBoundOrCompoundCRSIfNeeded( + 0, + d->buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, + iFirstAxisSwap, ignoreVUnits, false)); + if (iter == 1) { + return nn_static_pointer_cast<BaseObject>(obj); + } + } } if (iProjStep >= 0 && !d->steps_[iProjStep].inverted && (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) && @@ -7749,20 +7874,27 @@ PROJStringParser::createFromPROJString(const std::string &projString) { if (iFirstGeogStep < 0) iFirstGeogStep = iProjStep; const bool ignoreVUnits = true; - return d->buildBoundOrCompoundCRSIfNeeded( - iProjStep, - d->buildProjectedCRS( + // First run is dry run to mark all recognized/unrecognized tokens + for (int iter = 0; iter < 2; iter++) { + auto obj = d->buildBoundOrCompoundCRSIfNeeded( iProjStep, - d->buildGeographicCRS( - iFirstGeogStep, - iFirstUnitConvert < iFirstGeogStep ? iFirstUnitConvert - : -1, - iFirstAxisSwap < iFirstGeogStep ? iFirstAxisSwap : -1, - ignoreVUnits, true), - iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert - : iFirstUnitConvert, - iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap - : iFirstAxisSwap)); + d->buildProjectedCRS( + iProjStep, + d->buildGeographicCRS( + iFirstGeogStep, iFirstUnitConvert < iFirstGeogStep + ? iFirstUnitConvert + : -1, + iFirstAxisSwap < iFirstGeogStep ? iFirstAxisSwap + : -1, + ignoreVUnits, true), + iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert + : iFirstUnitConvert, + iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap + : iFirstAxisSwap)); + if (iter == 1) { + return nn_static_pointer_cast<BaseObject>(obj); + } + } } if (iProjStep < 0 && iHelmert > 0 && iMolodensky < 0 && diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp index ea469c8a..81f745f5 100644 --- a/test/unit/test_factory.cpp +++ b/test/unit/test_factory.cpp @@ -2324,8 +2324,7 @@ TEST_F(FactoryWithTmpDatabase, custom_projected_crs) { EXPECT_EQ(crs->identifiers().size(), 1); EXPECT_EQ(crs->derivingConversion()->targetCRS().get(), crs.get()); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), - "+proj=mbt_s +unused_flag +datum=WGS84 +units=m +no_defs " - "+type=crs"); + "+proj=mbt_s +datum=WGS84 +units=m +no_defs +type=crs"); EXPECT_TRUE(crs->canonicalBoundCRS() == nullptr); } { @@ -2334,8 +2333,7 @@ TEST_F(FactoryWithTmpDatabase, custom_projected_crs) { EXPECT_EQ(crs->identifiers().size(), 1); EXPECT_EQ(crs->derivingConversion()->targetCRS().get(), crs.get()); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), - "+proj=mbt_s +unused_flag +datum=WGS84 +units=m +no_defs " - "+type=crs"); + "+proj=mbt_s +datum=WGS84 +units=m +no_defs +type=crs"); EXPECT_TRUE(crs->canonicalBoundCRS() != nullptr); } diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp index b10a0ab0..a4f60416 100644 --- a/test/unit/test_io.cpp +++ b/test/unit/test_io.cpp @@ -7024,6 +7024,17 @@ TEST(io, projparse_longlat_nadgrids) { // --------------------------------------------------------------------------- +TEST(io, projparse_longlat_nadgrids_towgs84_ignored) { + auto obj = PROJStringParser().createFromPROJString( + "+proj=longlat +ellps=GRS80 +towgs84=1,2,3 +nadgrids=foo.gsb " + "+type=crs"); + auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_TRUE(dynamic_cast<GeographicCRS *>(crs->baseCRS().get()) != nullptr); +} + +// --------------------------------------------------------------------------- + TEST(io, projparse_longlat_geoidgrids) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +type=crs"); @@ -7461,7 +7472,7 @@ TEST(io, projparse_cea_ellipsoidal) { TEST(io, projparse_geos_sweep_x) { auto obj = PROJStringParser().createFromPROJString( - "+proj=geos +sweep=x +type=crs"); + "+proj=geos +sweep=x +h=1 +type=crs"); auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); @@ -7477,7 +7488,8 @@ TEST(io, projparse_geos_sweep_x) { // --------------------------------------------------------------------------- TEST(io, projparse_geos_sweep_y) { - auto obj = PROJStringParser().createFromPROJString("+proj=geos +type=crs"); + auto obj = + PROJStringParser().createFromPROJString("+proj=geos +h=1 +type=crs"); auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); @@ -8089,7 +8101,7 @@ TEST(io, projparse_projected_vunits) { TEST(io, projparse_projected_unknown) { auto obj = PROJStringParser().createFromPROJString( "+proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 +x_0=10 +y_0=0 " - "+type=crs"); + "+datum=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); ASSERT_TRUE(crs != nullptr); @@ -8100,8 +8112,8 @@ TEST(io, projparse_projected_unknown) { crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( - wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s " - "unused_flag\"],PARAMETER[\"lat_0\",45,ANGLEUNIT[" + wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s\"]," + "PARAMETER[\"lat_0\",45,ANGLEUNIT[" "\"degree\",0.0174532925199433]],PARAMETER[\"lon_0\"," "0,ANGLEUNIT[\"degree\",0.0174532925199433]]," "PARAMETER[\"k\",1,SCALEUNIT[\"unity\",1]],PARAMETER[" @@ -8119,8 +8131,8 @@ TEST(io, projparse_projected_unknown) { "\"9122\"]]]," "PROJECTION[\"custom_proj4\"],UNIT[\"metre\",1,AUTHORITY[\"EPSG\"," "\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],EXTENSION[" - "\"PROJ4\",\"+proj=mbt_s +datum=WGS84 +unused_flag +lat_0=45 " - "+lon_0=0 +k=1 +x_0=10 +y_0=0 +wktext +no_defs\"]]"; + "\"PROJ4\",\"+proj=mbt_s +lat_0=45 " + "+lon_0=0 +k=1 +x_0=10 +y_0=0 +datum=WGS84\"]]"; { WKTFormatterNNPtr f( @@ -8133,8 +8145,8 @@ TEST(io, projparse_projected_unknown) { } EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), - "+proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 +x_0=10 " - "+y_0=0 +datum=WGS84 +units=m +no_defs +type=crs"); + "+proj=mbt_s +lat_0=45 +lon_0=0 +k=1 +x_0=10 " + "+y_0=0 +datum=WGS84 +type=crs"); { auto obj2 = WKTParser().createFromWKT(expected_wkt1); @@ -8147,8 +8159,8 @@ TEST(io, projparse_projected_unknown) { crs2->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( - wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s " - "unused_flag\"],PARAMETER[\"lat_0\",45,ANGLEUNIT[" + wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s\"]," + "PARAMETER[\"lat_0\",45,ANGLEUNIT[" "\"degree\",0.0174532925199433]],PARAMETER[\"lon_0\"," "0,ANGLEUNIT[\"degree\",0.0174532925199433]]," "PARAMETER[\"k\",1,SCALEUNIT[\"unity\",1]],PARAMETER[" @@ -8263,7 +8275,7 @@ TEST(io, projparse_longlat_wktext) { crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), - input); + "+proj=longlat +datum=WGS84 +no_defs +type=crs"); } // --------------------------------------------------------------------------- @@ -8277,7 +8289,7 @@ TEST(io, projparse_geocent_wktext) { crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), - input); + "+proj=geocent +datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- @@ -8291,7 +8303,8 @@ TEST(io, projparse_projected_wktext) { crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), - input); + "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs " + "+type=crs"); } // --------------------------------------------------------------------------- @@ -8301,8 +8314,7 @@ TEST(io, projparse_ob_tran_longlat) { "+type=crs +proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=ob_tran " "+o_proj=longlat +o_lat_p=52 +o_lon_p=-30 +lon_0=-25 +ellps=WGS84 " - "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step " - "+proj=axisswap +order=2,1"); + "+step +proj=axisswap +order=2,1"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj); ASSERT_TRUE(crs != nullptr); diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp index d134f086..6c1ecf1c 100644 --- a/test/unit/test_operation.cpp +++ b/test/unit/test_operation.cpp @@ -6303,7 +6303,7 @@ TEST(operation, createOperation_on_crs_with_canonical_bound_crs) { TEST(operation, createOperation_fallback_to_proj4_strings) { auto objDest = PROJStringParser().createFromPROJString( - "+proj=longlat +geoc +ellps=WGS84 +type=crs"); + "+proj=longlat +geoc +datum=WGS84 +type=crs"); auto dest = nn_dynamic_pointer_cast<GeographicCRS>(objDest); ASSERT_TRUE(dest != nullptr); @@ -6313,13 +6313,38 @@ TEST(operation, createOperation_fallback_to_proj4_strings) { EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " - "+step +inv +proj=longlat +datum=WGS84 " "+step +proj=longlat +geoc +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- +TEST(operation, createOperation_on_crs_with_bound_crs_and_wktext) { + auto objSrc = PROJStringParser().createFromPROJString( + "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 " + "+units=m +no_defs +nadgrids=@GDA94_GDA2020_conformal.gsb +ignored1 " + "+ignored2=val +wktext +type=crs"); + auto src = nn_dynamic_pointer_cast<CRS>(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDst = PROJStringParser().createFromPROJString( + "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 " + "+units=m +no_defs +type=crs"); + auto dst = nn_dynamic_pointer_cast<CRS>(objDst); + ASSERT_TRUE(dst != nullptr); + + auto op = CoordinateOperationFactory::create()->createOperation( + NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); + ASSERT_TRUE(op != nullptr); + EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=pipeline +step +inv +proj=utm +zone=55 +south " + "+ellps=GRS80 +step +proj=hgridshift " + "+grids=@GDA94_GDA2020_conformal.gsb +step +proj=utm +zone=55 " + "+south +ellps=GRS80"); +} + +// --------------------------------------------------------------------------- + TEST(operation, mercator_variant_A_to_variant_B) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, |
