diff options
Diffstat (limited to 'src/iso19111/io.cpp')
| -rw-r--r-- | src/iso19111/io.cpp | 407 |
1 files changed, 278 insertions, 129 deletions
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index e14239b0..7329758a 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -1,7 +1,7 @@ /****************************************************************************** * * Project: PROJ - * Purpose: ISO19111:2018 implementation + * Purpose: ISO19111:2019 implementation * Author: Even Rouault <even dot rouault at spatialys dot com> * ****************************************************************************** @@ -141,6 +141,7 @@ struct WKTFormatter::Private { std::vector<bool> stackHasChild_{}; std::vector<bool> stackHasId_{false}; std::vector<bool> stackEmptyKeyword_{}; + std::vector<bool> stackDisableUsage_{}; std::vector<bool> outputUnitStack_{true}; std::vector<bool> outputIdStack_{true}; std::vector<UnitOfMeasureNNPtr> axisLinearUnitStack_{ @@ -272,6 +273,11 @@ const std::string &WKTFormatter::toString() const { if (d->outputUnitStack_.size() != 1) throw FormattingException( "Unbalanced pushOutputUnit() / popOutputUnit()"); + if (d->stackHasId_.size() != 1) + throw FormattingException("Unbalanced pushHasId() / popHasId()"); + if (!d->stackDisableUsage_.empty()) + throw FormattingException( + "Unbalanced pushDisableUsage() / popDisableUsage()"); return d->result_; } @@ -556,6 +562,28 @@ bool WKTFormatter::outputId() const { // --------------------------------------------------------------------------- +void WKTFormatter::pushHasId(bool hasId) { d->stackHasId_.push_back(hasId); } + +// --------------------------------------------------------------------------- + +void WKTFormatter::popHasId() { d->stackHasId_.pop_back(); } + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushDisableUsage() { d->stackDisableUsage_.push_back(true); } + +// --------------------------------------------------------------------------- + +void WKTFormatter::popDisableUsage() { d->stackDisableUsage_.pop_back(); } + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputUsage() const { + return outputId() && d->stackDisableUsage_.empty(); +} + +// --------------------------------------------------------------------------- + void WKTFormatter::pushAxisLinearUnit(const UnitOfMeasureNNPtr &unit) { d->axisLinearUnitStack_.push_back(unit); } @@ -634,6 +662,18 @@ bool WKTFormatter::primeMeridianInDegree() const { // --------------------------------------------------------------------------- +bool WKTFormatter::idOnTopLevelOnly() const { + return d->params_.idOnTopLevelOnly_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::topLevelHasId() const { + return d->stackHasId_.size() >= 2 && d->stackHasId_[1]; +} + +// --------------------------------------------------------------------------- + WKTFormatter::Version WKTFormatter::version() const { return d->params_.version_; } @@ -717,13 +757,22 @@ const std::string &WKTFormatter::getHDatumExtension() const { // --------------------------------------------------------------------------- std::string WKTFormatter::morphNameToESRI(const std::string &name) { + + for (const auto *suffix : {"(m)", "(ftUS)", "(E-N)", "(N-E)"}) { + if (ends_with(name, suffix)) { + return morphNameToESRI( + name.substr(0, name.size() - strlen(suffix))) + + suffix; + } + } + std::string ret; bool insertUnderscore = false; // Replace any special character by underscore, except at the beginning // and of the name where those characters are removed. for (char ch : name) { - if (ch == '+' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || - (ch >= 'A' && ch <= 'Z')) { + if (ch == '+' || ch == '-' || (ch >= '0' && ch <= '9') || + (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { if (insertUnderscore && !ret.empty()) { ret += '_'; } @@ -973,7 +1022,7 @@ const std::vector<WKTNodeNNPtr> &WKTNode::children() const { //! @cond Doxygen_Suppress static size_t skipSpace(const std::string &str, size_t start) { size_t i = start; - while (i < str.size() && ::isspace(str[i])) { + while (i < str.size() && ::isspace(static_cast<unsigned char>(str[i]))) { ++i; } return i; @@ -1002,8 +1051,9 @@ WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart, bool inString = false; for (; i < wkt.size() && - (inString || (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && - wkt[i] != ']' && wkt[i] != ')' && !::isspace(wkt[i]))); + (inString || + (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && wkt[i] != ']' && + wkt[i] != ')' && !::isspace(static_cast<unsigned char>(wkt[i])))); ++i) { if (wkt[i] == '"') { if (!inString) { @@ -1185,7 +1235,7 @@ struct WKTParser::Private { buildPrimeMeridian(const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit); - optional<std::string> getAnchor(const WKTNodeNNPtr &node); + static optional<std::string> getAnchor(const WKTNodeNNPtr &node); static void parseDynamic(const WKTNodeNNPtr &dynamicNode, double &frameReferenceEpoch, @@ -1301,7 +1351,7 @@ struct WKTParser::Private { CRSPtr buildCRS(const WKTNodeNNPtr &node); - CoordinateOperationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node); + TransformationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node); ConcatenatedOperationNNPtr buildConcatenatedOperation(const WKTNodeNNPtr &node); @@ -1580,6 +1630,17 @@ PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node, } } + auto &versionNode = nodeP->lookForChild(WKTConstants::VERSION); + if (!isNull(versionNode)) { + const auto &versionChildren = versionNode->GP()->children(); + if (versionChildren.size() == 1) { + properties->set(CoordinateOperation::OPERATION_VERSION_KEY, + stripQuotes(versionChildren[0])); + } else { + ThrowNotRequiredNumberOfChildren(versionNode->GP()->value()); + } + } + return *properties; } @@ -2556,13 +2617,13 @@ WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) { auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); - auto &csNode = nodeP->lookForChild(WKTConstants::CS); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); const auto &nodeName = nodeP->value(); if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) && !ci_equal(nodeName, WKTConstants::GEOCCS) && !ci_equal(nodeName, WKTConstants::BASEGEODCRS) && !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto &primeMeridianNode = @@ -2712,9 +2773,9 @@ CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) { auto derivingConversion = buildConversion( derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); - auto &csNode = nodeP->lookForChild(WKTConstants::CS); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); @@ -2888,7 +2949,7 @@ WKTParser::Private::buildConversion(const WKTNodeNNPtr &node, // --------------------------------------------------------------------------- -CoordinateOperationNNPtr +TransformationNNPtr WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD); @@ -2939,11 +3000,10 @@ WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) { stripQuotes(accuracyNode->GP()->children()[0]))); } - return util::nn_static_pointer_cast<CoordinateOperation>( - Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS), - NN_NO_CHECK(targetCRS), interpolationCRS, - buildProperties(methodNode), parameters, values, - accuracies)); + return Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS), + NN_NO_CHECK(targetCRS), interpolationCRS, + buildProperties(methodNode), parameters, + values, accuracies); } // --------------------------------------------------------------------------- @@ -3116,7 +3176,6 @@ ConversionNNPtr WKTParser::Private::buildProjectionFromESRI( } const auto *wkt2_mapping = getMapping(esriMapping->wkt2_name); - assert(wkt2_mapping); if (ci_equal(esriProjectionName, "Stereographic")) { try { if (std::fabs(io::asDouble( @@ -3127,6 +3186,7 @@ ConversionNNPtr WKTParser::Private::buildProjectionFromESRI( } catch (const std::exception &) { } } + assert(wkt2_mapping); PropertyMap propertiesMethod; propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name); @@ -3205,7 +3265,10 @@ ConversionNNPtr WKTParser::Private::buildProjectionFromESRI( } return Conversion::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + PropertyMap().set(IdentifiedObject::NAME_KEY, + esriProjectionName == "Gauss_Kruger" + ? "unnnamed (Gauss Kruger)" + : "unnamed"), propertiesMethod, parameters, values) ->identify(); } @@ -3539,11 +3602,11 @@ WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { ? buildConversion(conversionNode, linearUnit, angularUnit) : buildProjection(node, projectionNode, linearUnit, angularUnit); - auto &csNode = nodeP->lookForChild(WKTConstants::CS); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); const auto &nodeValue = nodeP->value(); if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) && !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs); @@ -3726,11 +3789,11 @@ CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) { ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable() : nullptr; - auto &csNode = nodeP->lookForChild(WKTConstants::CS); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); const auto &nodeValue = nodeP->value(); if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) && !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs); @@ -3755,7 +3818,7 @@ CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) { Transformation::createGravityRelatedHeightToGeographic3D( PropertyMap().set(IdentifiedObject::NAME_KEY, transformationName), - crs, GeographicCRS::EPSG_4979, + crs, GeographicCRS::EPSG_4979, nullptr, stripQuotes(extensionChildren[1]), std::vector<PositionalAccuracyNNPtr>()); return nn_static_pointer_cast<CRS>(BoundCRS::create( @@ -3787,9 +3850,9 @@ WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) { auto derivingConversion = buildConversion( derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); - auto &csNode = nodeP->lookForChild(WKTConstants::CS); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); @@ -3892,10 +3955,10 @@ BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) { TemporalCSNNPtr WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) { - auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS); + auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); auto temporalCS = nn_dynamic_pointer_cast<TemporalCS>(cs); @@ -3953,9 +4016,9 @@ WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) { throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node"); } - auto &csNode = nodeP->lookForChild(WKTConstants::CS); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); @@ -3998,9 +4061,9 @@ WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) { auto derivingConversion = buildConversion( derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); - auto &csNode = nodeP->lookForChild(WKTConstants::CS); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); @@ -4013,10 +4076,10 @@ WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) { ParametricCSNNPtr WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) { - auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS); + auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); auto parametricCS = nn_dynamic_pointer_cast<ParametricCS>(cs); @@ -4086,9 +4149,9 @@ WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) { auto conversion = buildConversion(conversionNode, linearUnit, angularUnit); - auto &csNode = nodeP->lookForChild(WKTConstants::CS); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) { - ThrowMissing(WKTConstants::CS); + ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS, @@ -4255,16 +4318,31 @@ BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) { } if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) { - return util::nn_static_pointer_cast<BaseObject>( - buildCoordinateOperation(node)); + auto transf = buildCoordinateOperation(node); + + const char *prefixes[] = { + "PROJ-based operation method: ", + "PROJ-based operation method (approximate): "}; + for (const char *prefix : prefixes) { + if (starts_with(transf->method()->nameStr(), prefix)) { + auto projString = + transf->method()->nameStr().substr(strlen(prefix)); + return util::nn_static_pointer_cast<BaseObject>( + PROJBasedOperation::create( + PropertyMap(), projString, transf->sourceCRS(), + transf->targetCRS(), + transf->coordinateOperationAccuracies())); + } + } + + return util::nn_static_pointer_cast<BaseObject>(transf); } if (ci_equal(name, WKTConstants::CONVERSION)) { auto conv = buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE); - if (conv->nameStr() == "PROJ-based coordinate operation" && - starts_with(conv->method()->nameStr(), + if (starts_with(conv->method()->nameStr(), "PROJ-based operation method: ")) { auto projString = conv->method()->nameStr().substr( strlen("PROJ-based operation method: ")); @@ -4345,15 +4423,97 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, const auto authorities = dbContextNNPtr->getAuthorities(); for (const auto &authCandidate : authorities) { if (ci_equal(authCandidate, authName)) { - return AuthorityFactory::create(dbContextNNPtr, - authCandidate) - ->createCoordinateReferenceSystem(code); + factory = + AuthorityFactory::create(dbContextNNPtr, authCandidate); + try { + return factory->createCoordinateReferenceSystem(code); + } catch (...) { + // EPSG:4326+3855 + auto tokensCode = split(code, '+'); + if (tokensCode.size() == 2) { + auto crs1(factory->createCoordinateReferenceSystem( + tokensCode[0], false)); + auto crs2(factory->createCoordinateReferenceSystem( + tokensCode[1], false)); + return CompoundCRS::create( + util::PropertyMap().set( + IdentifiedObject::NAME_KEY, + crs1->nameStr() + " + " + crs2->nameStr()), + {crs1, crs2}); + } + throw; + } } } throw; } } + // OGC 07-092r2: para 7.5.2 + // URN combined references for compound coordinate reference systems + if (starts_with(text, "urn:ogc:def:crs,")) { + if (!dbContext) { + throw ParsingException("no database context specified"); + } + auto tokensComma = split(text, ','); + std::vector<CRSNNPtr> components; + std::string name; + for (size_t i = 1; i < tokensComma.size(); i++) { + tokens = split(tokensComma[i], ':'); + if (tokens.size() != 4) { + throw ParsingException( + concat("invalid crs component: ", tokensComma[i])); + } + const auto &type = tokens[0]; + auto factory = + AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]); + const auto &code = tokens[3]; + if (type == "crs") { + auto crs(factory->createCoordinateReferenceSystem(code, false)); + components.emplace_back(crs); + if (!name.empty()) { + name += " + "; + } + name += crs->nameStr(); + } else { + throw ParsingException( + concat("unexpected object type: ", type)); + } + } + return CompoundCRS::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, name), + components); + } + + // OGC 07-092r2: para 7.5.3 + // 7.5.3 URN combined references for concatenated operations + if (starts_with(text, "urn:ogc:def:coordinateOperation,")) { + if (!dbContext) { + throw ParsingException("no database context specified"); + } + auto tokensComma = split(text, ','); + std::vector<CoordinateOperationNNPtr> components; + for (size_t i = 1; i < tokensComma.size(); i++) { + tokens = split(tokensComma[i], ':'); + if (tokens.size() != 4) { + throw ParsingException(concat( + "invalid coordinateOperation component: ", tokensComma[i])); + } + const auto &type = tokens[0]; + auto factory = + AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]); + const auto &code = tokens[3]; + if (type == "coordinateOperation") { + auto op(factory->createCoordinateOperation(code, false)); + components.emplace_back(op); + } else { + throw ParsingException( + concat("unexpected object type: ", type)); + } + } + return ConcatenatedOperation::createComputeMetadata(components, true); + } + // urn:ogc:def:crs:EPSG::4326 if (tokens.size() == 7) { if (!dbContext) { @@ -4454,9 +4614,18 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, * <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 + * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326", + * "urn:ogc:def:coordinateOperation:EPSG::1671", + * "urn:ogc:def:ellipsoid:EPSG::7001" + * or "urn:ogc:def:datum:EPSG::6326"</li> + * <li> OGC URN combining references for compound coordinate reference systems + * e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717" + * We also accept a custom abbreviated syntax EPSG:2393+5717 + * </li> + * <li> OGC URN combining references for concatenated operations + * e.g. + * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li> + * <li>an Object 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> @@ -4488,9 +4657,18 @@ BaseObjectNNPtr createFromUserInput(const std::string &text, * <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 + * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326", + * "urn:ogc:def:coordinateOperation:EPSG::1671", + * "urn:ogc:def:ellipsoid:EPSG::7001" + * or "urn:ogc:def:datum:EPSG::6326"</li> + * <li> OGC URN combining references for compound coordinate reference systems + * e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717" + * We also accept a custom abbreviated syntax EPSG:2393+5717 + * </li> + * <li> OGC URN combining references for concatenated operations + * e.g. + * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li> + * <li>an Object 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> @@ -4744,7 +4922,7 @@ struct PROJStringFormatter::Private { }; std::vector<InversionStackElt> inversionStack_{InversionStackElt()}; bool omitProjLongLatIfPossible_ = false; - bool omitZUnitConversion_ = false; + std::vector<bool> omitZUnitConversion_{false}; DatabaseContextPtr dbContext_{}; bool useApproxTMerc_ = false; bool addNoDefs_ = true; @@ -5048,7 +5226,7 @@ const std::string &PROJStringFormatter::toString() const { first.paramValues[1].keyEquals("z_in") && first.paramValues[2].keyEquals("xy_out") && first.paramValues[3].keyEquals("z_out") && - second.paramValues[0].keyEquals("xy_in=") && + second.paramValues[0].keyEquals("xy_in") && second.paramValues[1].keyEquals("xy_out") && first.paramValues[0].value == second.paramValues[1].value && first.paramValues[2].value == second.paramValues[0].value) { @@ -5073,6 +5251,22 @@ const std::string &PROJStringFormatter::toString() const { break; } + // unitconvert (1), axisswap order=2,1, unitconvert(2) ==> + // axisswap order=2,1, unitconvert (1), unitconvert(2) which + // will get further optimized by previous case + if (i + 1 < d->steps_.size() && prevStep.name == "unitconvert" && + curStep.name == "axisswap" && curStepParamCount == 1 && + curStep.paramValues[0].equals("order", "2,1")) { + auto iterNext = iterCur; + ++iterNext; + auto &nextStep = *iterNext; + if (nextStep.name == "unitconvert") { + std::swap(*iterPrev, *iterCur); + changeDone = true; + break; + } + } + // axisswap order=2,1 followed by itself is a no-op if (curStep.name == "axisswap" && prevStep.name == "axisswap" && curStepParamCount == 1 && prevStepParamCount == 1 && @@ -5102,71 +5296,6 @@ const std::string &PROJStringFormatter::toString() const { } } - // axisswap order=2,1, pop/push v_3, axisswap order=2,1 -> can - // suppress axisswap - if (i + 1 < d->steps_.size() && prevStep.name == "axisswap" && - (curStep.name == "push" || curStep.name == "pop") && - prevStepParamCount == 1 && - prevStep.paramValues[0].equals("order", "2,1") && - curStepParamCount == 1 && curStep.paramValues[0].key == "v_3") { - auto iterNext = iterCur; - ++iterNext; - auto &nextStep = *iterNext; - if (nextStep.name == "axisswap" && - nextStep.paramValues.size() == 1 && - nextStep.paramValues[0].equals("order", "2,1")) { - d->steps_.erase(iterPrev); - d->steps_.erase(iterNext); - changeDone = true; - break; - } - } - - // push v_3, axisswap order=2,1, pop v_3 -> can suppress push/pop - if (i + 1 < d->steps_.size() && prevStep.name == "push" && - prevStepParamCount == 1 && - prevStep.paramValues[0].key == "v_3" && - curStep.name == "axisswap" && curStepParamCount == 1 && - curStep.paramValues[0].equals("order", "2,1")) { - auto iterNext = iterCur; - ++iterNext; - auto &nextStep = *iterNext; - if (nextStep.name == "pop" && - nextStep.paramValues.size() == 1 && - nextStep.paramValues[0].key == "v_3") { - d->steps_.erase(iterPrev); - d->steps_.erase(iterNext); - changeDone = true; - break; - } - } - - // unitconvert xy_in=A xy_out=B, pop/push v_3, unitconvert xy_in=B - // xy_out=A -> can suppress unitconvert - if (i + 1 < d->steps_.size() && prevStep.name == "unitconvert" && - (curStep.name == "push" || curStep.name == "pop") && - prevStepParamCount == 2 && - prevStep.paramValues[0].key == "xy_in" && - prevStep.paramValues[1].key == "xy_out" && - curStepParamCount == 1 && curStep.paramValues[0].key == "v_3") { - auto iterNext = iterCur; - ++iterNext; - auto &nextStep = *iterNext; - if (nextStep.name == "unitconvert" && - nextStep.paramValues.size() == 2 && - nextStep.paramValues[0].key == "xy_in" && - nextStep.paramValues[1].key == "xy_out" && - nextStep.paramValues[0].value == - prevStep.paramValues[1].value && - nextStep.paramValues[1].value == - prevStep.paramValues[0].value) { - d->steps_.erase(iterPrev); - d->steps_.erase(iterNext); - changeDone = true; - break; - } - } - // for practical purposes WGS84 and GRS80 ellipsoids are // equivalents (cartesian transform between both lead to differences // of the order of 1e-14 deg..). @@ -5335,6 +5464,11 @@ const std::string &PROJStringFormatter::toString() const { } } } + + if (d->result_.empty()) { + d->appendToResult("+proj=noop"); + } + return d->result_; } @@ -5382,7 +5516,7 @@ PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps, { size_t i = 0; while (true) { - for (; isspace(c_str[i]); i++) { + for (; isspace(static_cast<unsigned char>(c_str[i])); i++) { } std::string token; bool in_string = false; @@ -5399,7 +5533,7 @@ PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps, token += c_str[i]; i++; continue; - } else if (isspace(c_str[i])) { + } else if (isspace(static_cast<unsigned char>(c_str[i]))) { break; } token += c_str[i]; @@ -5805,15 +5939,21 @@ bool PROJStringFormatter::omitProjLongLatIfPossible() const { // --------------------------------------------------------------------------- -void PROJStringFormatter::setOmitZUnitConversion(bool omit) { - assert(d->omitZUnitConversion_ ^ omit); - d->omitZUnitConversion_ = omit; +void PROJStringFormatter::pushOmitZUnitConversion() { + d->omitZUnitConversion_.push_back(true); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::popOmitZUnitConversion() { + assert(d->omitZUnitConversion_.size() > 1); + d->omitZUnitConversion_.pop_back(); } // --------------------------------------------------------------------------- bool PROJStringFormatter::omitZUnitConversion() const { - return d->omitZUnitConversion_; + return d->omitZUnitConversion_.back(); } // --------------------------------------------------------------------------- @@ -6079,6 +6219,9 @@ static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) { static UnitOfMeasure _buildUnit(double to_meter_value) { // TODO: look-up in EPSG catalog + if (to_meter_value == 0) { + throw ParsingException("invalid unit value"); + } return UnitOfMeasure("unknown", to_meter_value, UnitOfMeasure::Type::LINEAR); } @@ -6593,9 +6736,9 @@ PROJStringParser::Private::processAxisSwap(Step &step, ? AxisDirection::NORTH : (axisType == AxisType::NORTH_POLE) ? AxisDirection::SOUTH - : (axisType == AxisType::SOUTH_POLE) - ? AxisDirection::NORTH - : AxisDirection::NORTH; + /*: (axisType == AxisType::SOUTH_POLE) + ? AxisDirection::NORTH*/ + : AxisDirection::NORTH; CoordinateSystemAxisNNPtr north = createAxis( northName, northAbbev, northDir, unit, (!isGeographic && axisType == AxisType::NORTH_POLE) @@ -6727,6 +6870,9 @@ static double getNumericValue(const std::string ¶mValue, } // --------------------------------------------------------------------------- +namespace { +template <class T> inline void ignoreRetVal(T) {} +} GeographicCRSNNPtr PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert, @@ -6739,7 +6885,7 @@ PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert, // units=m is often found in the wild. // No need to create a extension string for this - hasParamValue(step, "units"); + ignoreRetVal(hasParamValue(step, "units")); auto datum = buildDatum(step, title); @@ -6753,6 +6899,7 @@ PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert, getNumericValue(getParamValue(step, "lon_0")) != 0.0)) { props.set("EXTENSION_PROJ4", projString_); } + props.set("IMPLICIT_CS", true); return GeographicCRS::create(props, datum, cs); } @@ -6771,7 +6918,7 @@ PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { auto datum = buildDatum(step, title); - UnitOfMeasure unit = UnitOfMeasure::METRE; + UnitOfMeasure unit = buildUnit(step, "units", ""); if (iUnitConvert >= 0) { auto &stepUnitConvert = steps_[iUnitConvert]; const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); @@ -6855,7 +7002,7 @@ PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep, Transformation::createGravityRelatedHeightToGeographic3D( PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown to WGS84 ellipsoidal height"), - crs, GeographicCRS::EPSG_4979, geoidgrids, + crs, GeographicCRS::EPSG_4979, nullptr, geoidgrids, std::vector<PositionalAccuracyNNPtr>()); auto boundvcrs = BoundCRS::create(vcrs, GeographicCRS::EPSG_4979, transformation); @@ -7243,6 +7390,8 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( props.set("EXTENSION_PROJ4", projString_); } + props.set("IMPLICIT_CS", true); + CRSNNPtr crs = ProjectedCRS::create(props, geogCRS, NN_NO_CHECK(conv), cs); if (!hasParamValue(step, "geoidgrids") && |
