diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 556f068c04..cd0e561338 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -4487,10 +4487,80 @@ WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) { ThrowMissing(WKTConstants::CS_); } - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - auto cartesianCS = nn_dynamic_pointer_cast(cs); const std::string projCRSName = stripQuotes(nodeP->children()[0]); + + auto cs = [this, &projCRSName, &nodeP, &props, &csNode, &node, &nodeValue, + &conversionNode]() -> CoordinateSystemNNPtr { + if (isNull(csNode) && ci_equal(nodeValue, WKTConstants::BASEPROJCRS) && + !isNull(conversionNode)) { + // A BASEPROJCRS (as of WKT2 18-010r11) normally lacks an explicit + // CS[] which cause issues to properly instanciate it. So we first + // start by trying to identify the BASEPROJCRS by its id or name. + // And fallback to exploring the conversion parameters to infer the + // CS AXIS unit from the linear parameter unit... Not fully bullet + // proof. + if (dbContext_) { + // Get official name from database if ID is present + auto &idNode = nodeP->lookForChild(WKTConstants::ID); + if (!isNull(idNode)) { + try { + auto id = buildId(idNode, false, false); + auto authFactory = AuthorityFactory::create( + NN_NO_CHECK(dbContext_), *id->codeSpace()); + auto projCRS = + authFactory->createProjectedCRS(id->code()); + return projCRS->coordinateSystem(); + } catch (const std::exception &) { + } + } + + auto authFactory = AuthorityFactory::create( + NN_NO_CHECK(dbContext_), std::string()); + auto res = authFactory->createObjectsFromName( + projCRSName, {AuthorityFactory::ObjectType::PROJECTED_CRS}, + false, 2); + if (res.size() == 1) { + auto projCRS = + dynamic_cast(res.front().get()); + if (projCRS) { + return projCRS->coordinateSystem(); + } + } + } + + auto conv = buildConversion(conversionNode, UnitOfMeasure::METRE, + UnitOfMeasure::DEGREE); + UnitOfMeasure linearUOM = UnitOfMeasure::NONE; + for (const auto &genOpParamvalue : conv->parameterValues()) { + auto opParamvalue = + dynamic_cast( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meterValue = opParamvalue->parameterValue(); + if (parameterValue->type() == + operation::ParameterValue::Type::MEASURE) { + const auto &measure = parameterValue->value(); + const auto &unit = measure.unit(); + if (unit.type() == UnitOfMeasure::Type::LINEAR) { + if (linearUOM == UnitOfMeasure::NONE) { + linearUOM = unit; + } else if (linearUOM != unit) { + linearUOM = UnitOfMeasure::NONE; + break; + } + } + } + } + } + if (linearUOM != UnitOfMeasure::NONE) { + return CartesianCS::createEastingNorthing(linearUOM); + } + } + return buildCS(csNode, node, UnitOfMeasure::NONE); + }(); + auto cartesianCS = nn_dynamic_pointer_cast(cs); + if (esriStyle_ && dbContext_) { if (cartesianCS) { std::string outTableName; diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp index 7e4d101877..0aec43d3b9 100644 --- a/test/unit/test_io.cpp +++ b/test/unit/test_io.cpp @@ -5643,6 +5643,182 @@ TEST(wkt_parse, DerivedProjectedCRS) { // --------------------------------------------------------------------------- +TEST(wkt_parse, DerivedProjectedCRS_base_crs_cs_non_metre_from_conversion) { + auto wkt = + "DERIVEDPROJCRS[\"Ground for NAD83(2011) / Idaho West (ftUS)\",\n" + " BASEPROJCRS[\"foo\",\n" + " BASEGEOGCRS[\"NAD83(2011)\",\n" + " DATUM[\"NAD83 (National Spatial Reference System " + "2011)\",\n" + " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" + " LENGTHUNIT[\"metre\",1]]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + " CONVERSION[\"SPCS83 Idaho West zone (US Survey feet)\",\n" + " METHOD[\"Transverse Mercator\",\n" + " ID[\"EPSG\",9807]],\n" + " PARAMETER[\"Latitude of natural " + "origin\",41.6666666666667,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + " ID[\"EPSG\",8801]],\n" + " PARAMETER[\"Longitude of natural origin\",-115.75,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + " ID[\"EPSG\",8802]],\n" + " PARAMETER[\"Scale factor at natural " + "origin\",0.999933333,\n" + " SCALEUNIT[\"unity\",1],\n" + " ID[\"EPSG\",8805]],\n" + " PARAMETER[\"False easting\",2624666.667,\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" + " ID[\"EPSG\",8806]],\n" + " PARAMETER[\"False northing\",0,\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" + " ID[\"EPSG\",8807]]]],\n" + " DERIVINGCONVERSION[\"Grid to ground\",\n" + " METHOD[\"Similarity transformation\",\n" + " ID[\"EPSG\",9621]],\n" + " PARAMETER[\"Ordinate 1 of evaluation point in target " + "CRS\",1000,\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" + " ID[\"EPSG\",8621]],\n" + " PARAMETER[\"Ordinate 2 of evaluation point in target " + "CRS\",0,\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" + " ID[\"EPSG\",8622]],\n" + " PARAMETER[\"Scale factor for source CRS axes\",1,\n" + " SCALEUNIT[\"unity\",1],\n" + " ID[\"EPSG\",1061]],\n" + " PARAMETER[\"Rotation angle of source CRS axes\",0,\n" + " ANGLEUNIT[\"degree\",0],\n" + " ID[\"EPSG\",8614]]],\n" + " CS[Cartesian,2],\n" + " AXIS[\"easting (X)\",east,\n" + " ORDER[1],\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" + " AXIS[\"northing (Y)\",north,\n" + " ORDER[2],\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219]]]"; + + auto obj = WKTParser().createFromWKT(wkt); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + const auto &axisList = crs->baseCRS()->coordinateSystem()->axisList(); + ASSERT_EQ(axisList.size(), 2U); + EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::US_FOOT); +} + +// --------------------------------------------------------------------------- + +TEST( + wkt_parse, + DerivedProjectedCRS_base_crs_cs_non_metre_from_conversion_context_from_baseprojcrs_name) { + auto wkt = + "DERIVEDPROJCRS[\"Ground for NAD83(2011) / Idaho West (ftUS)\",\n" + " BASEPROJCRS[\"NAD83(2011) / Idaho West (ftUS)\",\n" + " BASEGEOGCRS[\"NAD83(2011)\",\n" + " DATUM[\"NAD83 (National Spatial Reference System " + "2011)\",\n" + " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" + " LENGTHUNIT[\"metre\",1]]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + " CONVERSION[\"incomplete conversion\",\n" + " METHOD[\"Transverse Mercator\",\n" + " ID[\"EPSG\",9807]]]],\n" + " DERIVINGCONVERSION[\"Grid to ground\",\n" + " METHOD[\"Similarity transformation\",\n" + " ID[\"EPSG\",9621]],\n" + " PARAMETER[\"Ordinate 1 of evaluation point in target " + "CRS\",1000,\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" + " ID[\"EPSG\",8621]],\n" + " PARAMETER[\"Ordinate 2 of evaluation point in target " + "CRS\",0,\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" + " ID[\"EPSG\",8622]],\n" + " PARAMETER[\"Scale factor for source CRS axes\",1,\n" + " SCALEUNIT[\"unity\",1],\n" + " ID[\"EPSG\",1061]],\n" + " PARAMETER[\"Rotation angle of source CRS axes\",0,\n" + " ANGLEUNIT[\"degree\",0],\n" + " ID[\"EPSG\",8614]]],\n" + " CS[Cartesian,2],\n" + " AXIS[\"easting (X)\",east,\n" + " ORDER[1],\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" + " AXIS[\"northing (Y)\",north,\n" + " ORDER[2],\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219]]]"; + + auto obj = WKTParser() + .attachDatabaseContext(DatabaseContext::create()) + .createFromWKT(wkt); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + const auto &axisList = crs->baseCRS()->coordinateSystem()->axisList(); + ASSERT_EQ(axisList.size(), 2U); + EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::US_FOOT); +} + +// --------------------------------------------------------------------------- + +TEST( + wkt_parse, + DerivedProjectedCRS_base_crs_cs_non_metre_from_conversion_context_from_baseprojcrs_id) { + auto wkt = + "DERIVEDPROJCRS[\"Ground for NAD83(2011) / Idaho West (ftUS)\",\n" + " BASEPROJCRS[\"foo\",\n" + " BASEGEOGCRS[\"NAD83(2011)\",\n" + " DATUM[\"NAD83 (National Spatial Reference System " + "2011)\",\n" + " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" + " LENGTHUNIT[\"metre\",1]]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + " CONVERSION[\"incomplete conversion\",\n" + " METHOD[\"Transverse Mercator\",\n" + " ID[\"EPSG\",9807]]],\n" + " ID[\"EPSG\",6453]],\n" + " DERIVINGCONVERSION[\"Grid to ground\",\n" + " METHOD[\"Similarity transformation\",\n" + " ID[\"EPSG\",9621]],\n" + " PARAMETER[\"Ordinate 1 of evaluation point in target " + "CRS\",1000,\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" + " ID[\"EPSG\",8621]],\n" + " PARAMETER[\"Ordinate 2 of evaluation point in target " + "CRS\",0,\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" + " ID[\"EPSG\",8622]],\n" + " PARAMETER[\"Scale factor for source CRS axes\",1,\n" + " SCALEUNIT[\"unity\",1],\n" + " ID[\"EPSG\",1061]],\n" + " PARAMETER[\"Rotation angle of source CRS axes\",0,\n" + " ANGLEUNIT[\"degree\",0],\n" + " ID[\"EPSG\",8614]]],\n" + " CS[Cartesian,2],\n" + " AXIS[\"easting (X)\",east,\n" + " ORDER[1],\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" + " AXIS[\"northing (Y)\",north,\n" + " ORDER[2],\n" + " LENGTHUNIT[\"US survey foot\",0.304800609601219]]]"; + + auto obj = WKTParser() + .attachDatabaseContext(DatabaseContext::create()) + .createFromWKT(wkt); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + const auto &axisList = crs->baseCRS()->coordinateSystem()->axisList(); + ASSERT_EQ(axisList.size(), 2U); + EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::US_FOOT); +} + +// --------------------------------------------------------------------------- + TEST(wkt_parse, DerivedProjectedCRS_ordinal) { auto wkt = "DERIVEDPROJCRS[\"derived projectedCRS\",\n" " BASEPROJCRS[\"BASEPROJCRS\",\n"