Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Backport 9.2] PROJJSON: fix import/export of integer parameter value, and deal with… #3699

Merged
merged 1 commit into from Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/proj/coordinateoperation.hpp
Expand Up @@ -49,6 +49,10 @@ class DerivedCRS;
class ProjectedCRS;
} // namespace crs

namespace io {
class JSONParser;
} // namespace io

namespace coordinates {
class CoordinateMetadata;
using CoordinateMetadataPtr = std::shared_ptr<CoordinateMetadata>;
Expand Down Expand Up @@ -184,6 +188,7 @@ class PROJ_GCC_DLL CoordinateOperation : public common::ObjectUsage,
PROJ_FRIEND(CoordinateOperationFactory);
PROJ_FRIEND(ConcatenatedOperation);
PROJ_FRIEND(io::WKTParser);
PROJ_FRIEND(io::JSONParser);
PROJ_INTERNAL void
setWeakSourceTargetCRS(std::weak_ptr<crs::CRS> sourceCRSIn,
std::weak_ptr<crs::CRS> targetCRSIn);
Expand Down Expand Up @@ -675,6 +680,10 @@ class PROJ_GCC_DLL SingleOperation : virtual public CoordinateOperation {
const io::DatabaseContextPtr &dbContext,
bool inOtherDirection) const;

PROJ_INTERNAL static GeneralParameterValueNNPtr
createOperationParameterValueFromInterpolationCRS(int methodEPSGCode,
int crsEPSGCode);

private:
PROJ_OPAQUE_PRIVATE_DATA
SingleOperation &operator=(const SingleOperation &other) = delete;
Expand Down
90 changes: 73 additions & 17 deletions src/iso19111/io.cpp
Expand Up @@ -3363,6 +3363,24 @@ UnitOfMeasure WKTParser::Private::guessUnitForParameter(

// ---------------------------------------------------------------------------

static bool
isEPSGCodeForInterpolationParameter(const OperationParameterNNPtr &parameter) {
const auto &name = parameter->nameStr();
const auto epsgCode = parameter->getEPSGCode();
return name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS ||
epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS;
}

// ---------------------------------------------------------------------------

static bool isIntegerParameter(const OperationParameterNNPtr &parameter) {
return isEPSGCodeForInterpolationParameter(parameter);
}

// ---------------------------------------------------------------------------

void WKTParser::Private::consumeParameters(
const WKTNodeNNPtr &node, bool isAbridged,
std::vector<OperationParameterNNPtr> &parameters,
Expand Down Expand Up @@ -3413,8 +3431,13 @@ void WKTParser::Private::consumeParameters(
}
}

values.push_back(
ParameterValue::create(Measure(val, unit)));
if (isIntegerParameter(parameters.back())) {
values.push_back(ParameterValue::create(
std::stoi(childNodeChildren[1]->GP()->value())));
} else {
values.push_back(
ParameterValue::create(Measure(val, unit)));
}
} catch (const std::exception &) {
throw ParsingException(concat(
"unhandled parameter value type : ", paramValue));
Expand Down Expand Up @@ -3458,6 +3481,9 @@ WKTParser::Private::buildConversion(const WKTNodeNNPtr &node,
consumeParameters(node, false, parameters, values, defaultLinearUnit,
defaultAngularUnit);

auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
dbContext_, parameters, values);

auto &convProps = buildProperties(node);
auto &methodProps = buildProperties(methodNode);
std::string convName;
Expand All @@ -3469,13 +3495,14 @@ WKTParser::Private::buildConversion(const WKTNodeNNPtr &node,

auto &invConvProps = buildProperties(node, true);
auto &invMethodProps = buildProperties(methodNode, true);
return NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
Conversion::create(invConvProps, invMethodProps, parameters, values)
->inverse()));
if (interpolationCRS)
conv->setInterpolationCRS(interpolationCRS);
return conv;
}
auto conv = Conversion::create(convProps, methodProps, parameters, values);
auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
dbContext_, parameters, values);
if (interpolationCRS)
conv->setInterpolationCRS(interpolationCRS);
return conv;
Expand All @@ -3491,15 +3518,8 @@ static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
// crs_epsg_code] into proper interpolation CRS
if (dbContext != nullptr) {
for (size_t i = 0; i < parameters.size(); ++i) {
const auto &l_name = parameters[i]->nameStr();
const auto epsgCode = parameters[i]->getEPSGCode();
if (l_name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
epsgCode ==
EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
l_name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS ||
epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) {
const int code =
static_cast<int>(values[i]->value().getSIValue());
if (isEPSGCodeForInterpolationParameter(parameters[i])) {
const int code = values[i]->integerValue();
try {
auto authFactory = AuthorityFactory::create(
NN_NO_CHECK(dbContext), Identifier::EPSG);
Expand Down Expand Up @@ -5593,6 +5613,7 @@ class JSONParser {
static std::string getString(const json &j, const char *key);
static json getObject(const json &j, const char *key);
static json getArray(const json &j, const char *key);
static int getInteger(const json &j, const char *key);
static double getNumber(const json &j, const char *key);
static UnitOfMeasure getUnit(const json &j, const char *key);
static std::string getName(const json &j);
Expand Down Expand Up @@ -5751,6 +5772,27 @@ json JSONParser::getArray(const json &j, const char *key) {

// ---------------------------------------------------------------------------

int JSONParser::getInteger(const json &j, const char *key) {
if (!j.contains(key)) {
throw ParsingException(std::string("Missing \"") + key + "\" key");
}
auto v = j[key];
if (!v.is_number()) {
throw ParsingException(std::string("The value of \"") + key +
"\" should be an integer");
}
const double dbl = v.get<double>();
if (!(dbl >= std::numeric_limits<int>::min() &&
dbl <= std::numeric_limits<int>::max() &&
static_cast<int>(dbl) == dbl)) {
throw ParsingException(std::string("The value of \"") + key +
"\" should be an integer");
}
return static_cast<int>(dbl);
}

// ---------------------------------------------------------------------------

double JSONParser::getNumber(const json &j, const char *key) {
if (!j.contains(key)) {
throw ParsingException(std::string("Missing \"") + key + "\" key");
Expand Down Expand Up @@ -6393,9 +6435,17 @@ ConversionNNPtr JSONParser::buildConversion(const json &j) {
}
parameters.emplace_back(
OperationParameter::create(buildProperties(param)));
values.emplace_back(ParameterValue::create(getMeasure(param)));
if (isIntegerParameter(parameters.back())) {
values.emplace_back(
ParameterValue::create(getInteger(param, "value")));
} else {
values.emplace_back(ParameterValue::create(getMeasure(param)));
}
}

auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
dbContext_, parameters, values);

std::string convName;
std::string methodName;
if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
Expand All @@ -6405,11 +6455,17 @@ ConversionNNPtr JSONParser::buildConversion(const json &j) {

auto invConvProps = buildProperties(j, true);
auto invMethodProps = buildProperties(methodJ, true);
return NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
Conversion::create(invConvProps, invMethodProps, parameters, values)
->inverse()));
if (interpolationCRS)
conv->setInterpolationCRS(interpolationCRS);
return conv;
}
return Conversion::create(convProps, methodProps, parameters, values);
auto conv = Conversion::create(convProps, methodProps, parameters, values);
if (interpolationCRS)
conv->setInterpolationCRS(interpolationCRS);
return conv;
}

// ---------------------------------------------------------------------------
Expand Down
86 changes: 63 additions & 23 deletions src/iso19111/operation/conversion.cpp
Expand Up @@ -3283,20 +3283,22 @@ void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {

const MethodMapping *mapping =
!isWKT2 ? getMapping(l_method.get()) : nullptr;
bool hasInterpolationCRSParameter = false;
for (const auto &genOpParamvalue : parameterValues()) {
const auto opParamvalue =
dynamic_cast<const OperationParameterValue *>(
genOpParamvalue.get());
const int paramEPSGCode =
opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0;

// EPSG has normally no Latitude of natural origin for Equidistant
// Cylindrical but PROJ can handle it, so output the parameter if
// not zero
if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
methodEPSGCode ==
EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
auto opParamvalue =
dynamic_cast<const OperationParameterValue *>(
genOpParamvalue.get());
if (opParamvalue &&
opParamvalue->parameter()->getEPSGCode() ==
EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
if (paramEPSGCode ==
EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
const auto &paramValue = opParamvalue->parameterValue();
if (paramValue->type() == ParameterValue::Type::MEASURE) {
const auto &measure = paramValue->value();
Expand All @@ -3308,27 +3310,37 @@ void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {
}
// Same for false easting / false northing for Vertical Perspective
else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) {
auto opParamvalue =
dynamic_cast<const OperationParameterValue *>(
genOpParamvalue.get());
if (opParamvalue) {
const auto paramEPSGCode =
opParamvalue->parameter()->getEPSGCode();
if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING ||
paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) {
const auto &paramValue = opParamvalue->parameterValue();
if (paramValue->type() ==
ParameterValue::Type::MEASURE) {
const auto &measure = paramValue->value();
if (measure.getSIValue() == 0) {
continue;
}
if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING ||
paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) {
const auto &paramValue = opParamvalue->parameterValue();
if (paramValue->type() == ParameterValue::Type::MEASURE) {
const auto &measure = paramValue->value();
if (measure.getSIValue() == 0) {
continue;
}
}
}
}
if (paramEPSGCode ==
EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
paramEPSGCode ==
EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) {
hasInterpolationCRSParameter = true;
}
genOpParamvalue->_exportToWKT(formatter, mapping);
}

// If we have an interpolation CRS that has a EPSG code, then
// we can export it as a PARAMETER[]
const auto l_interpolationCRS = interpolationCRS();
if (!hasInterpolationCRSParameter && l_interpolationCRS) {
const auto code = l_interpolationCRS->getEPSGCode();
if (code != 0) {
createOperationParameterValueFromInterpolationCRS(
methodEPSGCode, code)
->_exportToWKT(formatter, mapping);
}
}
}

if (isWKT2) {
Expand Down Expand Up @@ -3365,18 +3377,46 @@ void Conversion::_exportToJSON(
writer->AddObjKey("method");
formatter->setOmitTypeInImmediateChild();
formatter->setAllowIDInImmediateChild();
method()->_exportToJSON(formatter);
const auto &l_method = method();
l_method->_exportToJSON(formatter);

const auto &l_parameterValues = parameterValues();
if (!l_parameterValues.empty()) {
const auto l_interpolationCRS = interpolationCRS();
if (!l_parameterValues.empty() || l_interpolationCRS) {
writer->AddObjKey("parameters");
{
bool hasInterpolationCRSParameter = false;
auto parametersContext(writer->MakeArrayContext(false));
for (const auto &genOpParamvalue : l_parameterValues) {
const auto opParamvalue =
dynamic_cast<const OperationParameterValue *>(
genOpParamvalue.get());
const int paramEPSGCode =
opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0;
if (paramEPSGCode ==
EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
paramEPSGCode ==
EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) {
hasInterpolationCRSParameter = true;
}
formatter->setAllowIDInImmediateChild();
formatter->setOmitTypeInImmediateChild();
genOpParamvalue->_exportToJSON(formatter);
}

// If we have an interpolation CRS that has a EPSG code, then
// we can export it as a parameter
if (!hasInterpolationCRSParameter && l_interpolationCRS) {
const auto methodEPSGCode = l_method->getEPSGCode();
const auto code = l_interpolationCRS->getEPSGCode();
if (code != 0) {
formatter->setAllowIDInImmediateChild();
formatter->setOmitTypeInImmediateChild();
createOperationParameterValueFromInterpolationCRS(
methodEPSGCode, code)
->_exportToJSON(formatter);
}
}
}
}

Expand Down