aboutsummaryrefslogtreecommitdiff
path: root/src/iso19111/c_api.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/iso19111/c_api.cpp')
-rw-r--r--src/iso19111/c_api.cpp275
1 files changed, 187 insertions, 88 deletions
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index b3f200fe..5d2216e8 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -30,10 +30,12 @@
#define FROM_PROJ_CPP
#endif
+#include <algorithm>
#include <cassert>
#include <cstdarg>
#include <cstring>
#include <map>
+#include <memory>
#include <new>
#include <utility>
#include <vector>
@@ -77,6 +79,11 @@ static void PROJ_NO_INLINE proj_log_error(PJ_CONTEXT *ctx, const char *function,
msg += ": ";
msg += text;
ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, msg.c_str());
+ auto previous_errno = pj_ctx_get_errno(ctx);
+ if (previous_errno == 0) {
+ // only set errno if it wasn't set deeper down the call stack
+ pj_ctx_set_errno(ctx, PJD_ERR_GENERIC_ERROR);
+ }
}
// ---------------------------------------------------------------------------
@@ -134,8 +141,7 @@ static PJ *pj_obj_create(PJ_CONTEXT *ctx, const IdentifiedObjectNNPtr &objIn) {
auto formatter = PROJStringFormatter::create(
PROJStringFormatter::Convention::PROJ_5, dbContext);
auto projString = coordop->exportToPROJString(formatter.get());
- auto pj = pj_create_internal(
- ctx, projString.empty() ? "+proj=affine" : projString.c_str());
+ auto pj = pj_create_internal(ctx, projString.c_str());
if (pj) {
pj->iso_obj = objIn;
return pj;
@@ -147,6 +153,7 @@ static PJ *pj_obj_create(PJ_CONTEXT *ctx, const IdentifiedObjectNNPtr &objIn) {
}
auto pj = pj_new();
if (pj) {
+ pj->ctx = ctx;
pj->descr = "ISO-19111 object";
pj->iso_obj = objIn;
}
@@ -975,29 +982,18 @@ int proj_is_equivalent_to(const PJ *obj, const PJ *other,
return false;
}
- // Make sure that the C and C++ enumerations match
- static_assert(static_cast<int>(PJ_COMP_STRICT) ==
- static_cast<int>(IComparable::Criterion::STRICT),
- "");
- static_assert(static_cast<int>(PJ_COMP_EQUIVALENT) ==
- static_cast<int>(IComparable::Criterion::EQUIVALENT),
- "");
- static_assert(
- static_cast<int>(PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) ==
- static_cast<int>(
- IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS),
- "");
+ const auto cppCriterion = ([](PJ_COMPARISON_CRITERION l_criterion) {
+ switch (l_criterion) {
+ case PJ_COMP_STRICT:
+ return IComparable::Criterion::STRICT;
+ case PJ_COMP_EQUIVALENT:
+ return IComparable::Criterion::EQUIVALENT;
+ case PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS:
+ break;
+ }
+ return IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS;
+ })(criterion);
- // Make sure we enumerate all values. If adding a new value, as we
- // don't have a default clause, the compiler will warn.
- switch (criterion) {
- case PJ_COMP_STRICT:
- case PJ_COMP_EQUIVALENT:
- case PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS:
- break;
- }
- const IComparable::Criterion cppCriterion =
- static_cast<IComparable::Criterion>(criterion);
return obj->iso_obj->isEquivalentTo(other->iso_obj.get(), cppCriterion);
}
@@ -1121,40 +1117,24 @@ const char *proj_as_wkt(PJ_CONTEXT *ctx, const PJ *obj, PJ_WKT_TYPE type,
return nullptr;
}
- // Make sure that the C and C++ enumerations match
- static_assert(static_cast<int>(PJ_WKT2_2015) ==
- static_cast<int>(WKTFormatter::Convention::WKT2_2015),
- "");
- static_assert(
- static_cast<int>(PJ_WKT2_2015_SIMPLIFIED) ==
- static_cast<int>(WKTFormatter::Convention::WKT2_2015_SIMPLIFIED),
- "");
- static_assert(static_cast<int>(PJ_WKT2_2018) ==
- static_cast<int>(WKTFormatter::Convention::WKT2_2018),
- "");
- static_assert(
- static_cast<int>(PJ_WKT2_2018_SIMPLIFIED) ==
- static_cast<int>(WKTFormatter::Convention::WKT2_2018_SIMPLIFIED),
- "");
- static_assert(static_cast<int>(PJ_WKT1_GDAL) ==
- static_cast<int>(WKTFormatter::Convention::WKT1_GDAL),
- "");
- static_assert(static_cast<int>(PJ_WKT1_ESRI) ==
- static_cast<int>(WKTFormatter::Convention::WKT1_ESRI),
- "");
- // Make sure we enumerate all values. If adding a new value, as we
- // don't have a default clause, the compiler will warn.
- switch (type) {
- case PJ_WKT2_2015:
- case PJ_WKT2_2015_SIMPLIFIED:
- case PJ_WKT2_2018:
- case PJ_WKT2_2018_SIMPLIFIED:
- case PJ_WKT1_GDAL:
- case PJ_WKT1_ESRI:
- break;
- }
- const WKTFormatter::Convention convention =
- static_cast<WKTFormatter::Convention>(type);
+ const auto convention = ([](PJ_WKT_TYPE l_type) {
+ switch (l_type) {
+ case PJ_WKT2_2015:
+ return WKTFormatter::Convention::WKT2_2015;
+ case PJ_WKT2_2015_SIMPLIFIED:
+ return WKTFormatter::Convention::WKT2_2015_SIMPLIFIED;
+ case PJ_WKT2_2018:
+ return WKTFormatter::Convention::WKT2_2018;
+ case PJ_WKT2_2018_SIMPLIFIED:
+ return WKTFormatter::Convention::WKT2_2018_SIMPLIFIED;
+ case PJ_WKT1_GDAL:
+ return WKTFormatter::Convention::WKT1_GDAL;
+ case PJ_WKT1_ESRI:
+ break;
+ }
+ return WKTFormatter::Convention::WKT1_ESRI;
+ })(type);
+
try {
auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
auto formatter = WKTFormatter::create(convention, dbContext);
@@ -1365,7 +1345,7 @@ static const GeodeticCRS *extractGeodeticCRS(PJ_CONTEXT *ctx, const PJ *crs,
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param crs Objet of type CRS (must not be NULL)
+ * @param crs Object of type CRS (must not be NULL)
* @return Object that must be unreferenced with proj_destroy(), or NULL
* in case of error.
*/
@@ -1389,7 +1369,7 @@ PJ *proj_crs_get_geodetic_crs(PJ_CONTEXT *ctx, const PJ *crs) {
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param crs Objet of type CRS (must not be NULL)
+ * @param crs Object of type CRS (must not be NULL)
* @param index Index of the CRS component (typically 0 = horizontal, 1 =
* vertical)
* @return Object that must be unreferenced with proj_destroy(), or NULL
@@ -1471,7 +1451,7 @@ PJ *proj_crs_create_bound_crs(PJ_CONTEXT *ctx, const PJ *base_crs,
* osgeo::proj::crs::CRS::createBoundCRSToWGS84IfPossible()
*
* @param ctx PROJ context, or NULL for default context
- * @param crs Objet of type CRS (must not be NULL)
+ * @param crs Object of type CRS (must not be NULL)
* @param options null-terminated list of options, or NULL. Currently
* supported options are:
* <ul>
@@ -1530,7 +1510,7 @@ PJ *proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, const PJ *crs,
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param obj Objet of type CRS or GeodeticReferenceFrame (must not be NULL)
+ * @param obj Object of type CRS or GeodeticReferenceFrame (must not be NULL)
* @return Object that must be unreferenced with proj_destroy(), or NULL
* in case of error.
*/
@@ -1562,7 +1542,7 @@ PJ *proj_get_ellipsoid(PJ_CONTEXT *ctx, const PJ *obj) {
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param crs Objet of type CRS (must not be NULL)
+ * @param crs Object of type CRS (must not be NULL)
* @return Object that must be unreferenced with proj_destroy(), or NULL
* in case of error.
*/
@@ -1645,7 +1625,7 @@ int proj_ellipsoid_get_parameters(PJ_CONTEXT *ctx, const PJ *ellipsoid,
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param obj Objet of type CRS or GeodeticReferenceFrame (must not be NULL)
+ * @param obj Object of type CRS or GeodeticReferenceFrame (must not be NULL)
* @return Object that must be unreferenced with proj_destroy(), or NULL
* in case of error.
*/
@@ -1720,7 +1700,7 @@ int proj_prime_meridian_get_parameters(PJ_CONTEXT *ctx,
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param obj Objet of type BoundCRS or CoordinateOperation (must not be NULL)
+ * @param obj Object of type BoundCRS or CoordinateOperation (must not be NULL)
* @return Object that must be unreferenced with proj_destroy(), or NULL
* in case of error, or missing source CRS.
*/
@@ -1763,7 +1743,7 @@ PJ *proj_get_source_crs(PJ_CONTEXT *ctx, const PJ *obj) {
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param obj Objet of type BoundCRS or CoordinateOperation (must not be NULL)
+ * @param obj Object of type BoundCRS or CoordinateOperation (must not be NULL)
* @return Object that must be unreferenced with proj_destroy(), or NULL
* in case of error, or missing target CRS.
*/
@@ -1797,7 +1777,7 @@ PJ *proj_get_target_crs(PJ_CONTEXT *ctx, const PJ *obj) {
/** \brief Identify the CRS with reference CRSs.
*
* The candidate CRSs are either hard-coded, or looked in the database when
- * authorityFactory is not null.
+ * it is available.
*
* The method returns a list of matching reference CRS, and the percentage
* (0-100) of confidence in the match. The list is sorted by decreasing
@@ -1950,7 +1930,7 @@ void proj_string_list_destroy(PROJ_STRING_LIST list) {
// ---------------------------------------------------------------------------
-/** \brief Instanciate a default set of parameters to be used by
+/** \brief Instantiate a default set of parameters to be used by
* proj_get_crs_list().
*
* @return a new object to free with proj_get_crs_list_parameters_destroy() */
@@ -1987,7 +1967,7 @@ void proj_get_crs_list_parameters_destroy(PROJ_CRS_LIST_PARAMETERS *params) {
* entry is NULL. This array should be freed with proj_crs_info_list_destroy()
*
* When no filter parameters are set, this is functionnaly equivalent to
- * proj_get_crs_info_list_from_database(), instanciating a PJ* object for each
+ * proj_get_crs_info_list_from_database(), instantiating a PJ* object for each
* of the proj_create_from_database() and retrieving information with the
* various getters. However this function will be much faster.
*
@@ -2167,7 +2147,7 @@ void proj_crs_info_list_destroy(PROJ_CRS_INFO **list) {
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param crs Objet of type DerivedCRS or BoundCRSs (must not be NULL)
+ * @param crs Object of type DerivedCRS or BoundCRSs (must not be NULL)
* @return Object of type SingleOperation that must be unreferenced with
* proj_destroy(), or NULL in case of error.
*/
@@ -2198,7 +2178,7 @@ PJ *proj_crs_get_coordoperation(PJ_CONTEXT *ctx, const PJ *crs) {
/** \brief Return information on the operation method of the SingleOperation.
*
* @param ctx PROJ context, or NULL for default context
- * @param coordoperation Objet of type SingleOperation (typically a Conversion
+ * @param coordoperation Object of type SingleOperation (typically a Conversion
* or Transformation) (must not be NULL)
* @param out_method_name Pointer to a string value to store the method
* (projection) name. or NULL
@@ -5729,12 +5709,12 @@ PJ *proj_create_conversion_equal_earth(PJ_CONTEXT *ctx, double center_long,
* available.
*
* @param ctx PROJ context, or NULL for default context
- * @param coordoperation Objet of type CoordinateOperation or derived classes
+ * @param coordoperation Object of type CoordinateOperation or derived classes
* (must not be NULL)
* @return TRUE or FALSE.
*/
-int proj_coordoperation_is_instanciable(PJ_CONTEXT *ctx,
+int proj_coordoperation_is_instantiable(PJ_CONTEXT *ctx,
const PJ *coordoperation) {
assert(coordoperation);
auto op = dynamic_cast<const CoordinateOperation *>(
@@ -5746,7 +5726,7 @@ int proj_coordoperation_is_instanciable(PJ_CONTEXT *ctx,
}
auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
try {
- return op->isPROJInstanciable(dbContext) ? 1 : 0;
+ return op->isPROJInstantiable(dbContext) ? 1 : 0;
} catch (const std::exception &) {
return 0;
}
@@ -5754,10 +5734,40 @@ int proj_coordoperation_is_instanciable(PJ_CONTEXT *ctx,
// ---------------------------------------------------------------------------
+/** \brief Return whether a coordinate operation has a "ballpark"
+ * transformation,
+ * that is a very approximate one, due to lack of more accurate transformations.
+ *
+ * Typically a null geographic offset between two horizontal datum, or a
+ * null vertical offset (or limited to unit changes) between two vertical
+ * datum. Errors of several tens to one hundred meters might be expected,
+ * compared to more accurate transformations.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param coordoperation Object of type CoordinateOperation or derived classes
+ * (must not be NULL)
+ * @return TRUE or FALSE.
+ */
+
+int proj_coordoperation_has_ballpark_transformation(PJ_CONTEXT *ctx,
+ const PJ *coordoperation) {
+ assert(coordoperation);
+ auto op = dynamic_cast<const CoordinateOperation *>(
+ coordoperation->iso_obj.get());
+ if (!op) {
+ proj_log_error(ctx, __FUNCTION__,
+ "Object is not a CoordinateOperation");
+ return 0;
+ }
+ return op->hasBallparkTransformation();
+}
+
+// ---------------------------------------------------------------------------
+
/** \brief Return the number of parameters of a SingleOperation
*
* @param ctx PROJ context, or NULL for default context
- * @param coordoperation Objet of type SingleOperation or derived classes
+ * @param coordoperation Object of type SingleOperation or derived classes
* (must not be NULL)
*/
@@ -5779,7 +5789,7 @@ int proj_coordoperation_get_param_count(PJ_CONTEXT *ctx,
/** \brief Return the index of a parameter of a SingleOperation
*
* @param ctx PROJ context, or NULL for default context
- * @param coordoperation Objet of type SingleOperation or derived classes
+ * @param coordoperation Object of type SingleOperation or derived classes
* (must not be NULL)
* @param name Parameter name. Must not be NULL
* @return index (>=0), or -1 in case of error.
@@ -5812,7 +5822,7 @@ int proj_coordoperation_get_param_index(PJ_CONTEXT *ctx,
/** \brief Return a parameter of a SingleOperation
*
* @param ctx PROJ context, or NULL for default context
- * @param coordoperation Objet of type SingleOperation or derived classes
+ * @param coordoperation Object of type SingleOperation or derived classes
* (must not be NULL)
* @param index Parameter index.
* @param out_name Pointer to a string value to store the parameter name. or
@@ -5952,10 +5962,14 @@ int proj_coordoperation_get_param(
* values.
*
* @param ctx PROJ context, or NULL for default context
- * @param coordoperation Objet of type Transformation, that can be represented
+ * @param coordoperation Object of type Transformation, that can be represented
* as a WKT1 TOWGS84 node (must not be NULL)
* @param out_values Pointer to an array of value_count double values.
- * @param value_count Size of out_values array.
+ * @param value_count Size of out_values array. The suggested size is 7 to get
+ * translation, rotation and scale difference parameters. Rotation and scale
+ * difference terms might be zero if the transformation only includes
+ * translation
+ * parameters. In that case, value_count could be set to 3.
* @param emit_error_if_incompatible Boolean to inicate if an error must be
* logged if coordoperation is not compatible with a WKT1 TOWGS84
* representation.
@@ -5997,7 +6011,7 @@ int proj_coordoperation_get_towgs84_values(PJ_CONTEXT *ctx,
/** \brief Return the number of grids used by a CoordinateOperation
*
* @param ctx PROJ context, or NULL for default context
- * @param coordoperation Objet of type CoordinateOperation or derived classes
+ * @param coordoperation Object of type CoordinateOperation or derived classes
* (must not be NULL)
*/
@@ -6033,7 +6047,7 @@ int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx,
/** \brief Return a parameter of a SingleOperation
*
* @param ctx PROJ context, or NULL for default context
- * @param coordoperation Objet of type SingleOperation or derived classes
+ * @param coordoperation Object of type SingleOperation or derived classes
* (must not be NULL)
* @param index Parameter index.
* @param out_short_name Pointer to a string value to store the grid short name.
@@ -6508,7 +6522,7 @@ proj_create_operations(PJ_CONTEXT *ctx, const PJ *source_crs,
/** \brief Return the number of objects in the result set
*
- * @param result Objet of type PJ_OBJ_LIST (must not be NULL)
+ * @param result Object of type PJ_OBJ_LIST (must not be NULL)
*/
int proj_list_get_count(const PJ_OBJ_LIST *result) {
assert(result);
@@ -6524,7 +6538,7 @@ int proj_list_get_count(const PJ_OBJ_LIST *result) {
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param result Objet of type PJ_OBJ_LIST (must not be NULL)
+ * @param result Object of type PJ_OBJ_LIST (must not be NULL)
* @param index Index
* @return a new object that must be unreferenced with proj_destroy(),
* or nullptr in case of error.
@@ -6590,7 +6604,7 @@ double proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx,
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param crs Objet of type SingleCRS (must not be NULL)
+ * @param crs Object of type SingleCRS (must not be NULL)
* @return Object that must be unreferenced with proj_destroy(), or NULL
* in case of error (or if there is no datum)
*/
@@ -6618,7 +6632,7 @@ PJ *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs) {
* It should be used by at most one thread at a time.
*
* @param ctx PROJ context, or NULL for default context
- * @param crs Objet of type SingleCRS (must not be NULL)
+ * @param crs Object of type SingleCRS (must not be NULL)
* @return Object that must be unreferenced with proj_destroy(), or NULL
* in case of error.
*/
@@ -6638,7 +6652,7 @@ PJ *proj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ *crs) {
/** \brief Returns the type of the coordinate system.
*
* @param ctx PROJ context, or NULL for default context
- * @param cs Objet of type CoordinateSystem (must not be NULL)
+ * @param cs Object of type CoordinateSystem (must not be NULL)
* @return type, or PJ_CS_TYPE_UNKNOWN in case of error.
*/
PJ_COORDINATE_SYSTEM_TYPE proj_cs_get_type(PJ_CONTEXT *ctx, const PJ *cs) {
@@ -6684,7 +6698,7 @@ PJ_COORDINATE_SYSTEM_TYPE proj_cs_get_type(PJ_CONTEXT *ctx, const PJ *cs) {
/** \brief Returns the number of axis of the coordinate system.
*
* @param ctx PROJ context, or NULL for default context
- * @param cs Objet of type CoordinateSystem (must not be NULL)
+ * @param cs Object of type CoordinateSystem (must not be NULL)
* @return number of axis, or -1 in case of error.
*/
int proj_cs_get_axis_count(PJ_CONTEXT *ctx, const PJ *cs) {
@@ -6703,7 +6717,7 @@ int proj_cs_get_axis_count(PJ_CONTEXT *ctx, const PJ *cs) {
/** \brief Returns information on an axis
*
* @param ctx PROJ context, or NULL for default context
- * @param cs Objet of type CoordinateSystem (must not be NULL)
+ * @param cs Object of type CoordinateSystem (must not be NULL)
* @param index Index of the coordinate system (between 0 and
* proj_cs_get_axis_count() - 1)
* @param out_name Pointer to a string value to store the axis name. or NULL
@@ -6764,3 +6778,88 @@ int proj_cs_get_axis_info(PJ_CONTEXT *ctx, const PJ *cs, int index,
}
return true;
}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns a PJ* object whose axis order is the one expected for
+ * visualization purposes.
+ *
+ * The input object must be a coordinate operation, that has been created with
+ * proj_create_crs_to_crs().
+ * If the axis order of its source or target CRS is northing,easting, then an
+ * axis swap operation will be inserted.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object of type CoordinateOperation,
+ * created with proj_create_crs_to_crs() (must not be NULL)
+ * @return a new PJ* object to free with proj_destroy() in case of success, or
+ * nullptr in case of error
+ */
+PJ *proj_normalize_for_visualization(PJ_CONTEXT *ctx, const PJ *obj) {
+
+ SANITIZE_CTX(ctx);
+ if (!obj->alternativeCoordinateOperations.empty()) {
+ try {
+ auto pjNew = std::unique_ptr<PJ>(pj_new());
+ if (!pjNew)
+ return nullptr;
+ pjNew->ctx = ctx;
+ for (const auto &alt : obj->alternativeCoordinateOperations) {
+ auto co = dynamic_cast<const CoordinateOperation *>(
+ alt.pj->iso_obj.get());
+ if (co) {
+ double minxSrc = alt.minxSrc;
+ double minySrc = alt.minySrc;
+ double maxxSrc = alt.maxxSrc;
+ double maxySrc = alt.maxySrc;
+ double minxDst = alt.minxDst;
+ double minyDst = alt.minyDst;
+ double maxxDst = alt.maxxDst;
+ double maxyDst = alt.maxyDst;
+
+ auto l_sourceCRS = co->sourceCRS();
+ auto l_targetCRS = co->targetCRS();
+ if (l_sourceCRS && l_targetCRS) {
+ const bool swapSource =
+ l_sourceCRS
+ ->mustAxisOrderBeSwitchedForVisualization();
+ if (swapSource) {
+ std::swap(minxSrc, minySrc);
+ std::swap(maxxSrc, maxySrc);
+ }
+ const bool swapTarget =
+ l_targetCRS
+ ->mustAxisOrderBeSwitchedForVisualization();
+ if (swapTarget) {
+ std::swap(minxDst, minyDst);
+ std::swap(maxxDst, maxyDst);
+ }
+ }
+ pjNew->alternativeCoordinateOperations.emplace_back(
+ minxSrc, minySrc, maxxSrc, maxySrc, minxDst, minyDst,
+ maxxDst, maxyDst,
+ pj_obj_create(ctx, co->normalizeForVisualization()),
+ co->nameStr());
+ }
+ }
+ return pjNew.release();
+ } catch (const std::exception &e) {
+ proj_log_debug(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+ }
+
+ auto co = dynamic_cast<const CoordinateOperation *>(obj->iso_obj.get());
+ if (!co) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation "
+ "created with "
+ "proj_create_crs_to_crs");
+ return nullptr;
+ }
+ try {
+ return pj_obj_create(ctx, co->normalizeForVisualization());
+ } catch (const std::exception &e) {
+ proj_log_debug(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}