aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2018-11-19 11:44:03 +0100
committerEven Rouault <even.rouault@spatialys.com>2018-11-19 12:39:13 +0100
commitcf54b0b10cf015d15de39f4ab6542f0925d15478 (patch)
tree84365df91b1ba6e20dbbfaf617336e87a667bd1a
parent229bc49f7ad1bd024fc503f68d6705f3b0adcdf2 (diff)
downloadPROJ-cf54b0b10cf015d15de39f4ab6542f0925d15478.tar.gz
PROJ-cf54b0b10cf015d15de39f4ab6542f0925d15478.zip
Assorted set of fixes for PROJString to ISO19111 model:
- createFromPROJString(): take into account axisswap step for Krovak and Transverse Mercator (South Orientated) - Geocentric export to PROJ4: use datum when possible, and add explicit units=m - ESRI WKT parser: make it case insensitive to parameter and projection names, and more tolerant about possible parameter name aliases - import from WKT1 for Polar_Stereographic: don't be case sensitive - importFromPROJString: allow pm to override datum - Equidistant cylindrical: add support for non-standard latitude of natural origin, used in a GDAL test case - tmerc export to PROJString: use 'k' instead of 'k_0' - pj_ellps: use official value from EPSG for reverse flattening of Airy ellipsoid - GDAL compatibility: add support for importing odd formulations of Mercator as WKT1, but rejecting them when exporting to PROJ - Add export of 'Geostationary Satellite (Sweep X)' to WKT1_GDAL via EXTENSION.PROJ4 node - importFromPROJString: add support for +f - WKT1 / PROJ4: add support for EXTENSION.PROJ4 nodes and +wktext - exportToWKT: change way we deal with AXIS by default for WKT1_GDAL - Improve etmerc handling - Fix WKT import of peg_point_heading for Spherical_Cross_Track_Height - International Map of the World Polyconic: change parameter mapping - exportToPROJ: add alpha parameter - Hotine_Oblique_Mercator_Two_Point_Natural_Origin: GDAL_WKT1 related fix - GDAL compatibility improvements in import from PROJ4 / WKT1 for polar stereographic - Add support for +towgs84 when importing a +proj=geocent - import from WKT1: add support for an odd Mercator_1SP formulation handled by GDAL - export to proj4 strings: add +units=m to projected CRS for better GDAL compatibility - export to proj4 strings: add +no_defs to CRS for better GDAL compatibility
-rw-r--r--include/proj/coordinateoperation.hpp3
-rw-r--r--include/proj/crs.hpp2
-rw-r--r--include/proj/internal/coordinateoperation_constants.hpp37
-rw-r--r--include/proj/internal/coordinateoperation_internal.hpp2
-rw-r--r--include/proj/internal/internal.hpp2
-rw-r--r--include/proj/io.hpp23
-rw-r--r--src/c_api.cpp17
-rw-r--r--src/coordinateoperation.cpp185
-rw-r--r--src/coordinatesystem.cpp2
-rw-r--r--src/crs.cpp125
-rw-r--r--src/internal.cpp10
-rw-r--r--src/io.cpp394
-rw-r--r--src/pj_ellps.c2
-rw-r--r--test/unit/test_c_api.cpp43
-rw-r--r--test/unit/test_crs.cpp186
-rw-r--r--test/unit/test_io.cpp581
-rw-r--r--test/unit/test_operation.cpp182
17 files changed, 1471 insertions, 325 deletions
diff --git a/include/proj/coordinateoperation.hpp b/include/proj/coordinateoperation.hpp
index 5e3ecf0b..293ba480 100644
--- a/include/proj/coordinateoperation.hpp
+++ b/include/proj/coordinateoperation.hpp
@@ -497,6 +497,7 @@ class PROJ_GCC_DLL OperationMethod : public common::IdentifiedObject {
PROJ_INTERNAL OperationMethod();
PROJ_INTERNAL OperationMethod(const OperationMethod &other);
INLINED_MAKE_SHARED
+ friend class Conversion;
private:
PROJ_OPAQUE_PRIVATE_DATA
@@ -1296,7 +1297,7 @@ class PROJ_GCC_DLL Conversion : public SingleOperation {
INLINED_MAKE_SHARED
PROJ_FRIEND(crs::ProjectedCRS);
- PROJ_INTERNAL void addWKTExtensionNode(io::WKTFormatter *formatter) const;
+ PROJ_INTERNAL bool addWKTExtensionNode(io::WKTFormatter *formatter) const;
private:
PROJ_OPAQUE_PRIVATE_DATA
diff --git a/include/proj/crs.hpp b/include/proj/crs.hpp
index 252c90e3..82b2bd49 100644
--- a/include/proj/crs.hpp
+++ b/include/proj/crs.hpp
@@ -472,7 +472,7 @@ class PROJ_GCC_DLL DerivedCRS : virtual public SingleCRS {
PROJ_PRIVATE :
//! @cond Doxygen_Suppress
- const operation::ConversionNNPtr &
+ PROJ_INTERNAL const operation::ConversionNNPtr &
derivingConversionRef() PROJ_CONST_DECL;
//! @endcond
diff --git a/include/proj/internal/coordinateoperation_constants.hpp b/include/proj/internal/coordinateoperation_constants.hpp
index e999b3a6..d5495193 100644
--- a/include/proj/internal/coordinateoperation_constants.hpp
+++ b/include/proj/internal/coordinateoperation_constants.hpp
@@ -88,6 +88,11 @@ static const ParamMapping paramScaleFactor = {
EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
common::UnitOfMeasure::Type::SCALE, k_0};
+static const ParamMapping paramScaleFactorK = {
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
+ common::UnitOfMeasure::Type::SCALE, k};
+
static const ParamMapping paramFalseEasting = {
EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING,
WKT1_FALSE_EASTING, common::UnitOfMeasure::Type::LINEAR, x_0};
@@ -130,6 +135,10 @@ static const ParamMapping *const paramsNatOriginScale[] = {
&paramLatitudeNatOrigin, &paramLongitudeNatOrigin, &paramScaleFactor,
&paramFalseEasting, &paramFalseNorthing, nullptr};
+static const ParamMapping *const paramsNatOriginScaleK[] = {
+ &paramLatitudeNatOrigin, &paramLongitudeNatOrigin, &paramScaleFactorK,
+ &paramFalseEasting, &paramFalseNorthing, nullptr};
+
static const ParamMapping paramLatFirstPoint = {
"Latitude of 1st point", 0, "Latitude_Of_1st_Point",
common::UnitOfMeasure::Type::ANGULAR, lat_1};
@@ -243,9 +252,11 @@ static const ParamMapping *const paramsEQDC[] = {&paramLatNatLatCenter,
static const ParamMapping *const paramsLonNatOrigin[] = {
&paramLongitudeNatOrigin, &paramFalseEasting, &paramFalseNorthing, nullptr};
-static const ParamMapping *const paramsEqc[] = // same as paramsCEA
- {&paramLat1stParallelLatTs, &paramLongitudeNatOrigin, &paramFalseEasting,
- &paramFalseNorthing, nullptr};
+static const ParamMapping *const paramsEqc[] = {
+ &paramLat1stParallelLatTs,
+ &paramLatitudeNatOrigin, // extension of EPSG, but used by GDAL / PROJ
+ &paramLongitudeNatOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
static const ParamMapping paramSatelliteHeight = {
"Satellite Height", 0, "satellite_height",
@@ -331,7 +342,7 @@ static const ParamMapping paramLonPoint2 = {
common::UnitOfMeasure::Type::ANGULAR, lon_2};
static const ParamMapping *const paramsHomTwoPoint[] = {
- &paramLatCentreLatOrigin,
+ &paramLatCentreLatCenter,
&paramLatPoint1,
&paramLonPoint1,
&paramLatPoint2,
@@ -342,9 +353,8 @@ static const ParamMapping *const paramsHomTwoPoint[] = {
nullptr};
static const ParamMapping *const paramsIMWP[] = {
- &paramLongitudeNatOrigin, &paramLatitude1stStdParallel,
- &paramLatitude2ndStdParallel, &paramFalseEasting,
- &paramFalseNorthing, nullptr};
+ &paramLongitudeNatOrigin, &paramLatFirstPoint, &paramLatSecondPoint,
+ &paramFalseEasting, &paramFalseNorthing, nullptr};
static const ParamMapping paramLonCentreLonCenter = {
EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN,
@@ -355,7 +365,7 @@ static const ParamMapping paramColatitudeConeAxis = {
EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS,
EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, WKT1_AZIMUTH,
common::UnitOfMeasure::Type::ANGULAR,
- nullptr}; /* ignored by PROJ currently */
+ "alpha"}; /* ignored by PROJ currently */
static const ParamMapping paramLatitudePseudoStdParallel = {
EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
@@ -393,11 +403,6 @@ static const ParamMapping paramLatMerc1SP = {
common::UnitOfMeasure::Type::ANGULAR,
nullptr}; // always set to zero, not to be exported in PROJ strings
-static const ParamMapping paramScaleFactorK = {
- EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
- EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
- common::UnitOfMeasure::Type::SCALE, k};
-
static const ParamMapping *const paramsMerc1SP[] = {
&paramLatMerc1SP, &paramLongitudeNatOrigin, &paramScaleFactorK,
&paramFalseEasting, &paramFalseNorthing, nullptr};
@@ -492,12 +497,12 @@ static const ParamMapping *const paramsLabordeObliqueMercator[] = {
static const MethodMapping methodMappings[] = {
{EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
- "Transverse_Mercator", "tmerc", nullptr, paramsNatOriginScale},
+ "Transverse_Mercator", "tmerc", nullptr, paramsNatOriginScaleK},
{EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED,
EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED,
"Transverse_Mercator_South_Orientated", "tmerc", "axis=wsu",
- paramsNatOriginScale},
+ paramsNatOriginScaleK},
{PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, "Two_Point_Equidistant",
"tpeqd", nullptr, paramsTPEQD},
@@ -605,7 +610,7 @@ static const MethodMapping methodMappings[] = {
{PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0,
"Interrupted_Goode_Homolosine", "igh", nullptr, paramsLonNatOrigin},
- // No WKT1 representation fr sweep=x
+ // No proper WKT1 representation fr sweep=x
{PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, 0, nullptr, "geos",
"sweep=x", paramsGeos},
diff --git a/include/proj/internal/coordinateoperation_internal.hpp b/include/proj/internal/coordinateoperation_internal.hpp
index 83e1e987..bafc3a3b 100644
--- a/include/proj/internal/coordinateoperation_internal.hpp
+++ b/include/proj/internal/coordinateoperation_internal.hpp
@@ -70,6 +70,8 @@ const ParamMapping *getMapping(const MethodMapping *mapping,
const OperationParameterValue *param);
const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping,
const std::string &wkt1_name);
+bool areEquivalentParameters(const std::string &a, const std::string &b);
+
// ---------------------------------------------------------------------------
struct ESRIParamMapping {
diff --git a/include/proj/internal/internal.hpp b/include/proj/internal/internal.hpp
index cbf6e259..85dd5ac3 100644
--- a/include/proj/internal/internal.hpp
+++ b/include/proj/internal/internal.hpp
@@ -125,6 +125,8 @@ inline bool starts_with(const std::string &str, const char *prefix) noexcept {
return std::memcmp(str.c_str(), prefix, prefixSize) == 0;
}
+bool ci_less(const std::string &a, const std::string &b) noexcept;
+
bool ci_starts_with(const char *str, const char *prefix) noexcept;
bool ci_starts_with(const std::string &str, const std::string &prefix) noexcept;
diff --git a/include/proj/io.hpp b/include/proj/io.hpp
index 689fede1..c649fa9f 100644
--- a/include/proj/io.hpp
+++ b/include/proj/io.hpp
@@ -206,7 +206,19 @@ class PROJ_GCC_DLL WKTFormatter {
PROJ_DLL WKTFormatter &setMultiLine(bool multiLine) noexcept;
PROJ_DLL WKTFormatter &setIndentationWidth(int width) noexcept;
- PROJ_DLL WKTFormatter &setOutputAxis(bool outputAxis) noexcept;
+
+ /** Rule for output AXIS nodes */
+ enum class OutputAxisRule {
+ /** Always include AXIS nodes */
+ YES,
+ /** Never include AXIS nodes */
+ NO,
+ /** Includes them only on PROJCS node if it uses Easting/Northing
+ *ordering. Typically used for WKT1_GDAL */
+ WKT1_GDAL_EPSG_STYLE,
+ };
+
+ PROJ_DLL WKTFormatter &setOutputAxis(OutputAxisRule outputAxis) noexcept;
PROJ_DLL WKTFormatter &setStrict(bool strict) noexcept;
PROJ_DLL bool isStrict() const noexcept;
@@ -273,7 +285,7 @@ class PROJ_GCC_DLL WKTFormatter {
PROJ_INTERNAL bool isInverted() const;
#endif
- PROJ_INTERNAL bool outputAxis() const;
+ PROJ_INTERNAL OutputAxisRule outputAxis() const;
PROJ_INTERNAL bool outputAxisOrder() const;
PROJ_INTERNAL bool primeMeridianOmittedIfGreenwich() const;
PROJ_INTERNAL bool ellipsoidUnitOmittedIfMetre() const;
@@ -352,7 +364,7 @@ class PROJ_GCC_DLL PROJStringFormatter {
startInversion();
PROJ_DLL void stopInversion();
PROJ_INTERNAL bool isInverted() const;
- PROJ_INTERNAL bool getUseETMercForTMerc() const;
+ PROJ_INTERNAL bool getUseETMercForTMerc(bool &settingSetOut) const;
PROJ_DLL void
ingestPROJString(const std::string &str); // throw ParsingException
@@ -373,6 +385,11 @@ class PROJ_GCC_DLL PROJStringFormatter {
PROJ_DLL void addParam(const char *paramName,
const std::vector<double> &vals);
+ PROJ_INTERNAL bool hasParam(const char *paramName) const;
+
+ PROJ_INTERNAL void addNoDefs(bool b);
+ PROJ_INTERNAL bool getAddNoDefs() const;
+
PROJ_INTERNAL std::set<std::string> getUsedGridNames() const;
PROJ_INTERNAL void setTOWGS84Parameters(const std::vector<double> &params);
diff --git a/src/c_api.cpp b/src/c_api.cpp
index 1720a7a1..fbba0f51 100644
--- a/src/c_api.cpp
+++ b/src/c_api.cpp
@@ -839,7 +839,10 @@ static const char *getOptionValue(const char *option,
* <li>MULTILINE=YES/NO. Defaults to YES, except for WKT1_ESRI</li>
* <li>INDENTATION_WIDTH=number. Defauls to 4 (when multiline output is
* on).</li>
- * <li>OUTPUT_AXIS=YES/NO. Defaults to YES, except for WKT1_ESRI.</li>
+ * <li>OUTPUT_AXIS=AUTO/YES/NO. In AUTO mode, axis will be output for WKT2
+ * variants, for WKT1_GDAL for ProjectedCRS with easting/northing ordering
+ * (otherwise stripped), but not for WKT1_ESRI. Setting to YES will output
+ * them unconditionaly, and to NO will omit them unconditionaly.</li>
* </ul>
* @return a string, or NULL in case of error.
*/
@@ -890,7 +893,12 @@ const char *proj_obj_as_wkt(PJ_OBJ *obj, PJ_WKT_TYPE type,
} else if ((value = getOptionValue(*iter, "INDENTATION_WIDTH="))) {
formatter->setIndentationWidth(std::atoi(value));
} else if ((value = getOptionValue(*iter, "OUTPUT_AXIS="))) {
- formatter->setOutputAxis(ci_equal(value, "YES"));
+ if (!ci_equal(value, "AUTO")) {
+ formatter->setOutputAxis(
+ ci_equal(value, "YES")
+ ? WKTFormatter::OutputAxisRule::YES
+ : WKTFormatter::OutputAxisRule::NO);
+ }
} else {
std::string msg("Unknown option :");
msg += *iter;
@@ -925,7 +933,8 @@ const char *proj_obj_as_wkt(PJ_OBJ *obj, PJ_WKT_TYPE type,
* @param options NULL-terminated list of strings with "KEY=VALUE" format. or
* NULL.
* The currently recognized option is USE_ETMERC=YES to use
- * +proj=etmerc instead of +proj=tmerc
+ * +proj=etmerc instead of +proj=tmerc (or USE_ETMERC=NO to disable implicit
+ * use of etmerc by utm conversions)
* @return a string, or NULL in case of error.
*/
const char *proj_obj_as_proj_string(PJ_OBJ *obj, PJ_PROJ_STRING_TYPE type,
@@ -960,6 +969,8 @@ const char *proj_obj_as_proj_string(PJ_OBJ *obj, PJ_PROJ_STRING_TYPE type,
if (options != nullptr && options[0] != nullptr) {
if (ci_equal(options[0], "USE_ETMERC=YES")) {
formatter->setUseETMercForTMerc(true);
+ } else if (ci_equal(options[0], "USE_ETMERC=NO")) {
+ formatter->setUseETMercForTMerc(false);
}
}
obj->lastPROJString = exportable->exportToPROJString(formatter.get());
diff --git a/src/coordinateoperation.cpp b/src/coordinateoperation.cpp
index 33e6f422..42cc806e 100644
--- a/src/coordinateoperation.cpp
+++ b/src/coordinateoperation.cpp
@@ -143,7 +143,7 @@ static std::set<std::string> buildSetEquivalentParameters() {
std::set<std::string> set;
- const char *const listOfEquivalentParameterNames[][5] = {
+ const char *const listOfEquivalentParameterNames[][7] = {
{"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr},
{"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr},
{"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr},
@@ -157,11 +157,13 @@ static std::set<std::string> buildSetEquivalentParameters() {
EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr},
- {EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER,
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, nullptr},
- {EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER,
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr},
@@ -179,8 +181,7 @@ static std::set<std::string> buildSetEquivalentParameters() {
return set;
}
-static bool areEquivalentParameters(const std::string &a,
- const std::string &b) {
+bool areEquivalentParameters(const std::string &a, const std::string &b) {
static const std::set<std::string> setEquivalentParameters =
buildSetEquivalentParameters();
@@ -690,6 +691,7 @@ struct OperationMethod::Private {
util::optional<std::string> formula_{};
util::optional<metadata::Citation> formulaCitation_{};
std::vector<GeneralOperationParameterNNPtr> parameters_{};
+ std::string projMethodOverride_{};
};
//! @endcond
@@ -767,6 +769,7 @@ OperationMethodNNPtr OperationMethod::create(
method->assignSelf(method);
method->setProperties(properties);
method->d->parameters_ = parameters;
+ properties.getStringValue("proj_method", method->d->projMethodOverride_);
return method;
}
@@ -823,12 +826,17 @@ void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const {
if (mapping == nullptr) {
l_name = replaceAll(l_name, " ", "_");
} else {
- if (mapping->wkt1_name == nullptr) {
- throw io::FormattingException(
- std::string("Unsupported conversion method: ") +
- mapping->wkt2_name);
+ if (l_name ==
+ PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
+ l_name = "Geostationary_Satellite";
+ } else {
+ if (mapping->wkt1_name == nullptr) {
+ throw io::FormattingException(
+ std::string("Unsupported conversion method: ") +
+ mapping->wkt2_name);
+ }
+ l_name = mapping->wkt1_name;
}
- l_name = mapping->wkt1_name;
}
}
formatter->addQuotedString(l_name);
@@ -2870,7 +2878,7 @@ ConversionNNPtr Conversion::createEquidistantCylindrical(
const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
const common::Length &falseNorthing) {
return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL,
- createParams(latitudeFirstParallel, longitudeNatOrigin,
+ createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
falseEasting, falseNorthing));
}
@@ -2905,7 +2913,7 @@ ConversionNNPtr Conversion::createEquidistantCylindricalSpherical(
const common::Length &falseNorthing) {
return create(properties,
EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL,
- createParams(latitudeFirstParallel, longitudeNatOrigin,
+ createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
falseEasting, falseNorthing));
}
@@ -4781,8 +4789,30 @@ void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {
const MethodMapping *mapping =
!isWKT2 ? getMapping(l_method.get()) : nullptr;
- for (const auto &paramValue : parameterValues()) {
- paramValue->_exportToWKT(formatter, mapping);
+ for (const auto &genOpParamvalue : parameterValues()) {
+
+ // 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) {
+ const auto &paramValue = opParamvalue->parameterValue();
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = paramValue->value();
+ if (measure.getSIValue() == 0) {
+ continue;
+ }
+ }
+ }
+ }
+ genOpParamvalue->_exportToWKT(formatter, mapping);
}
}
@@ -4899,14 +4929,29 @@ createPROJExtensionFromCustomProj(const Conversion *conv,
// ---------------------------------------------------------------------------
-void Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
+bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
if (!isWKT2) {
- const auto &methodName = method()->nameStr();
- const int methodEPSGCode = method()->getEPSGCode();
- if (methodEPSGCode ==
- EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR ||
- nameStr() == "Popular Visualisation Mercator") {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ int zone = 0;
+ bool north = true;
+ if (l_method->getPrivate()->projMethodOverride_ == "etmerc" &&
+ !isUTM(zone, north)) {
+ auto projFormatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_4);
+ projFormatter->setUseETMercForTMerc(true);
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ _exportToPROJString(projFormatter.get());
+ projFormatter->addParam("no_defs");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ return true;
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR ||
+ nameStr() == "Popular Visualisation Mercator") {
auto projFormatter = io::PROJStringFormatter::create(
io::PROJStringFormatter::Convention::PROJ_4);
@@ -4915,6 +4960,7 @@ void Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
formatter->addQuotedString("PROJ4");
formatter->addQuotedString(projFormatter->toString());
formatter->endNode();
+ return true;
}
} else if (starts_with(methodName, "PROJ ")) {
auto projFormatter = io::PROJStringFormatter::create(
@@ -4925,9 +4971,22 @@ void Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
formatter->addQuotedString("PROJ4");
formatter->addQuotedString(projFormatter->toString());
formatter->endNode();
+ return true;
}
+ } else if (methodName ==
+ PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
+ auto projFormatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_4);
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ _exportToPROJString(projFormatter.get());
+ projFormatter->addParam("no_defs");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ return true;
}
}
+ return false;
}
// ---------------------------------------------------------------------------
@@ -4981,15 +5040,16 @@ void Conversion::_exportToPROJString(
// Check for UTM
int zone = 0;
bool north = true;
- if (isUTM(zone, north)) {
+ bool etMercSettingSet = false;
+ useETMerc = formatter->getUseETMercForTMerc(etMercSettingSet) ||
+ l_method->getPrivate()->projMethodOverride_ == "etmerc";
+ if (isUTM(zone, north) && !(etMercSettingSet && !useETMerc)) {
bConversionDone = true;
formatter->addStep("utm");
formatter->addParam("zone", zone);
if (!north) {
formatter->addParam("south");
}
- } else {
- useETMerc = formatter->getUseETMercForTMerc();
}
} else if (methodEPSGCode ==
EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
@@ -5077,6 +5137,21 @@ void Conversion::_exportToPROJString(
std::string("Unsupported value for ") +
EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
}
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) {
+ const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0);
+ if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN &&
+ std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) {
+ throw io::FormattingException(
+ "Unexpected presence of scale factor in Mercator (variant B)");
+ }
+ double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
// PROJ.4 specific hack for webmercator
} else if (formatter->convention() ==
io::PROJStringFormatter::Convention::PROJ_4 &&
@@ -5265,12 +5340,15 @@ bool Conversion::isUTM(int &zone, bool &north) const {
const auto &measure = l_parameterValue->value();
if (epsg_code ==
EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN &&
- measure.value() == UTM_LATITUDE_OF_NATURAL_ORIGIN) {
+ std::fabs(measure.value() -
+ UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) {
bLatitudeNatOriginUTM = true;
} else if (
epsg_code ==
EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN &&
- measure.unit() == common::UnitOfMeasure::DEGREE) {
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::DEGREE,
+ util::IComparable::Criterion::EQUIVALENT)) {
double dfZone = (measure.value() + 183.0) / 6.0;
if (dfZone > 0.9 && dfZone < 60.1 &&
std::abs(dfZone - std::round(dfZone)) < 1e-10) {
@@ -5279,20 +5357,29 @@ bool Conversion::isUTM(int &zone, bool &north) const {
} else if (
epsg_code ==
EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN &&
- measure.value() == UTM_SCALE_FACTOR) {
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::SCALE_UNITY,
+ util::IComparable::Criterion::EQUIVALENT) &&
+ std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) {
bScaleFactorUTM = true;
} else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING &&
measure.value() == UTM_FALSE_EASTING &&
- measure.unit() == common::UnitOfMeasure::METRE) {
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
bFalseEastingUTM = true;
} else if (epsg_code ==
EPSG_CODE_PARAMETER_FALSE_NORTHING &&
- measure.unit() == common::UnitOfMeasure::METRE) {
- if (measure.value() == UTM_NORTH_FALSE_NORTHING) {
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ if (std::fabs(measure.value() -
+ UTM_NORTH_FALSE_NORTHING) < 1e-10) {
bFalseNorthingUTM = true;
north = true;
- } else if (measure.value() ==
- UTM_SOUTH_FALSE_NORTHING) {
+ } else if (std::fabs(measure.value() -
+ UTM_SOUTH_FALSE_NORTHING) <
+ 1e-10) {
bFalseNorthingUTM = true;
north = false;
}
@@ -6197,32 +6284,36 @@ TransformationNNPtr Transformation::createTOWGS84(
"Invalid number of elements in TOWGS84Parameters");
}
- crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeographicCRS();
+ crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeodeticCRS();
if (!transformSourceCRS) {
throw InvalidOperation(
- "Cannot find GeographicCRS in sourceCRS of TOWGS84 transformation");
+ "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation");
}
+ util::PropertyMap properties;
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ concat("Transformation from ", transformSourceCRS->nameStr(),
+ " to WGS84"));
+
+ auto targetCRS =
+ dynamic_cast<const crs::GeographicCRS *>(transformSourceCRS.get())
+ ? util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeographicCRS::EPSG_4326)
+ : util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeodeticCRS::EPSG_4978);
+
if (TOWGS84Parameters.size() == 3) {
return createGeocentricTranslations(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
- concat("Transformation from ",
- transformSourceCRS->nameStr(),
- " to WGS84")),
- NN_NO_CHECK(transformSourceCRS), crs::GeographicCRS::EPSG_4326,
+ properties, NN_NO_CHECK(transformSourceCRS), targetCRS,
TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2],
{});
}
- return createPositionVector(
- util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
- concat("Transformation from ",
- transformSourceCRS->nameStr(),
- " to WGS84")),
- NN_NO_CHECK(transformSourceCRS), crs::GeographicCRS::EPSG_4326,
- TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2],
- TOWGS84Parameters[3], TOWGS84Parameters[4], TOWGS84Parameters[5],
- TOWGS84Parameters[6], {});
+ return createPositionVector(properties, NN_NO_CHECK(transformSourceCRS),
+ targetCRS, TOWGS84Parameters[0],
+ TOWGS84Parameters[1], TOWGS84Parameters[2],
+ TOWGS84Parameters[3], TOWGS84Parameters[4],
+ TOWGS84Parameters[5], TOWGS84Parameters[6], {});
}
// ---------------------------------------------------------------------------
diff --git a/src/coordinatesystem.cpp b/src/coordinatesystem.cpp
index 1c84cf2c..f8d2d2b3 100644
--- a/src/coordinatesystem.cpp
+++ b/src/coordinatesystem.cpp
@@ -460,7 +460,7 @@ CoordinateSystem::axisList() PROJ_CONST_DEFN {
void CoordinateSystem::_exportToWKT(
io::WKTFormatter *formatter) const // throw(FormattingException)
{
- if (!formatter->outputAxis()) {
+ if (formatter->outputAxis() != io::WKTFormatter::OutputAxisRule::YES) {
return;
}
const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
diff --git a/src/crs.cpp b/src/crs.cpp
index dab704b4..a204a037 100644
--- a/src/crs.cpp
+++ b/src/crs.cpp
@@ -87,6 +87,7 @@ namespace crs {
//! @cond Doxygen_Suppress
struct CRS::Private {
BoundCRSPtr canonicalBoundCRS_{};
+ std::string extensionProj4_{};
};
//! @endcond
@@ -745,6 +746,8 @@ GeodeticCRS::create(const util::PropertyMap &properties,
GeodeticCRS::nn_make_shared<GeodeticCRS>(datum, datumEnsemble, cs));
crs->assignSelf(crs);
crs->setProperties(properties);
+ properties.getStringValue("EXTENSION_PROJ4",
+ crs->CRS::getPrivate()->extensionProj4_);
return crs;
}
@@ -789,6 +792,8 @@ GeodeticCRS::create(const util::PropertyMap &properties,
GeodeticCRS::nn_make_shared<GeodeticCRS>(datum, datumEnsemble, cs));
crs->assignSelf(crs);
crs->setProperties(properties);
+ properties.getStringValue("EXTENSION_PROJ4",
+ crs->CRS::getPrivate()->extensionProj4_);
return crs;
}
@@ -850,6 +855,17 @@ void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {
}
cs->_exportToWKT(formatter);
ObjectUsage::baseExportToWKT(formatter);
+
+ if (!isWKT2 && !formatter->useESRIDialect()) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ formatter->addQuotedString(extensionProj4);
+ formatter->endNode();
+ }
+ }
+
formatter->endNode();
}
//! @endcond
@@ -862,7 +878,8 @@ void GeodeticCRS::addGeocentricUnitConversionIntoPROJString(
const auto &axisList = coordinateSystem()->axisList();
const auto &unit = axisList[0]->unit();
- if (unit != common::UnitOfMeasure::METRE) {
+ if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
if (formatter->convention() ==
io::PROJStringFormatter::Convention::PROJ_4) {
io::FormattingException::Throw("GeodeticCRS::exportToPROJString(): "
@@ -885,6 +902,9 @@ void GeodeticCRS::addGeocentricUnitConversionIntoPROJString(
const auto &toSI = unit.conversionToSI();
formatter->addParam("xy_out", toSI);
formatter->addParam("z_out", toSI);
+ } else if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ formatter->addParam("units", "m");
}
}
//! @endcond
@@ -895,6 +915,16 @@ void GeodeticCRS::addGeocentricUnitConversionIntoPROJString(
void GeodeticCRS::_exportToPROJString(
io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
{
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->ingestPROJString(extensionProj4);
+ formatter->addNoDefs(false);
+ return;
+ }
+ }
+
if (!isGeocentric()) {
io::FormattingException::Throw(
"GeodeticCRS::exportToPROJString() only "
@@ -907,14 +937,7 @@ void GeodeticCRS::_exportToPROJString(
} else {
formatter->addStep("cart");
}
- ellipsoid()->_exportToPROJString(formatter);
- if (formatter->convention() ==
- io::PROJStringFormatter::Convention::PROJ_4) {
- const auto &TOWGS84Params = formatter->getTOWGS84Parameters();
- if (TOWGS84Params.size() == 7) {
- formatter->addParam("towgs84", TOWGS84Params);
- }
- }
+ addDatumInfoToPROJString(formatter);
addGeocentricUnitConversionIntoPROJString(formatter);
}
//! @endcond
@@ -1421,6 +1444,8 @@ GeographicCRS::create(const util::PropertyMap &properties,
GeographicCRS::nn_make_shared<GeographicCRS>(datum, datumEnsemble, cs));
crs->assignSelf(crs);
crs->setProperties(properties);
+ properties.getStringValue("EXTENSION_PROJ4",
+ crs->CRS::getPrivate()->extensionProj4_);
return crs;
}
@@ -1626,6 +1651,16 @@ void GeographicCRS::addAngularUnitConvertAndAxisSwap(
void GeographicCRS::_exportToPROJString(
io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
{
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->ingestPROJString(extensionProj4);
+ formatter->addNoDefs(false);
+ return;
+ }
+ }
+
if (!formatter->omitProjLongLatIfPossible() ||
primeMeridian()->longitude().getSIValue() != 0.0 ||
!formatter->getTOWGS84Parameters().empty() ||
@@ -1763,7 +1798,15 @@ void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const {
if (!isWKT2) {
axisList[0]->unit()._exportToWKT(formatter);
}
+
+ const auto oldAxisOutputRule = formatter->outputAxis();
+ if (oldAxisOutputRule ==
+ io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) {
+ formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
+ }
cs->_exportToWKT(formatter);
+ formatter->setOutputAxis(oldAxisOutputRule);
+
ObjectUsage::baseExportToWKT(formatter);
formatter->endNode();
}
@@ -2231,6 +2274,22 @@ const cs::CartesianCSNNPtr &ProjectedCRS::coordinateSystem() PROJ_CONST_DEFN {
void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ const auto &l_coordinateSystem = d->coordinateSystem();
+ const auto &axisList = l_coordinateSystem->axisList();
+
+ const auto exportAxis = [&l_coordinateSystem, &axisList, &formatter]() {
+ const auto oldAxisOutputRule = formatter->outputAxis();
+ if (oldAxisOutputRule ==
+ io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) {
+ if (&axisList[0]->direction() == &cs::AxisDirection::EAST &&
+ &axisList[1]->direction() == &cs::AxisDirection::NORTH) {
+ formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
+ }
+ }
+ l_coordinateSystem->_exportToWKT(formatter);
+ formatter->setOutputAxis(oldAxisOutputRule);
+ };
+
if (!isWKT2 && !formatter->useESRIDialect() &&
starts_with(nameStr(), "Popular Visualisation CRS / Mercator")) {
formatter->startNode(io::WKTConstants::PROJCS, !identifiers().empty());
@@ -2263,9 +2322,8 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
formatter->add(0.0);
formatter->endNode();
- const auto &axisList = d->coordinateSystem()->axisList();
axisList[0]->unit()._exportToWKT(formatter);
- d->coordinateSystem()->_exportToWKT(formatter);
+ exportAxis();
derivingConversionRef()->addWKTExtensionNode(formatter);
ObjectUsage::baseExportToWKT(formatter);
formatter->endNode();
@@ -2321,7 +2379,6 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
l_baseCRS->_exportToWKT(formatter);
}
- const auto &axisList = d->coordinateSystem()->axisList();
formatter->pushAxisLinearUnit(
common::UnitOfMeasure::create(axisList[0]->unit()));
@@ -2338,10 +2395,18 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
axisList[0]->unit()._exportToWKT(formatter);
}
- d->coordinateSystem()->_exportToWKT(formatter);
+ exportAxis();
if (!isWKT2 && !formatter->useESRIDialect()) {
- derivingConversionRef()->addWKTExtensionNode(formatter);
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ formatter->addQuotedString(extensionProj4);
+ formatter->endNode();
+ } else {
+ derivingConversionRef()->addWKTExtensionNode(formatter);
+ }
}
ObjectUsage::baseExportToWKT(formatter);
@@ -2355,6 +2420,16 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
void ProjectedCRS::_exportToPROJString(
io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
{
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->ingestPROJString(extensionProj4);
+ formatter->addNoDefs(false);
+ return;
+ }
+ }
+
baseExportToPROJString(formatter);
}
@@ -2384,6 +2459,8 @@ ProjectedCRS::create(const util::PropertyMap &properties,
crs->assignSelf(crs);
crs->setProperties(properties);
crs->setDerivingConversionCRS();
+ properties.getStringValue("EXTENSION_PROJ4",
+ crs->CRS::getPrivate()->extensionProj4_);
return crs;
}
@@ -2404,7 +2481,8 @@ void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter,
bool axisSpecFound) const {
const auto &axisList = d->coordinateSystem()->axisList();
const auto &unit = axisList[0]->unit();
- if (unit != common::UnitOfMeasure::METRE) {
+ if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
auto projUnit = unit.exportToPROJString();
const double toSI = unit.conversionToSI();
if (formatter->convention() ==
@@ -2426,6 +2504,12 @@ void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter,
formatter->addParam("units", projUnit);
}
}
+ } else if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ // could come from the hardcoded def of webmerc
+ if (!formatter->hasParam("units")) {
+ formatter->addParam("units", "m");
+ }
}
if (formatter->convention() ==
@@ -3234,8 +3318,17 @@ BoundCRS::create(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn,
BoundCRSNNPtr
BoundCRS::createFromTOWGS84(const CRSNNPtr &baseCRSIn,
const std::vector<double> &TOWGS84Parameters) {
+
+ auto geodCRS = baseCRSIn->extractGeodeticCRS();
+ auto targetCRS =
+ geodCRS.get() == nullptr ||
+ dynamic_cast<const crs::GeographicCRS *>(geodCRS.get())
+ ? util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeographicCRS::EPSG_4326)
+ : util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeodeticCRS::EPSG_4978);
return create(
- baseCRSIn, GeographicCRS::EPSG_4326,
+ baseCRSIn, targetCRS,
operation::Transformation::createTOWGS84(baseCRSIn, TOWGS84Parameters));
}
diff --git a/src/internal.cpp b/src/internal.cpp
index aa27192a..4bec1bf9 100644
--- a/src/internal.cpp
+++ b/src/internal.cpp
@@ -107,6 +107,16 @@ bool ci_equal(const char *a, const char *b) noexcept {
// ---------------------------------------------------------------------------
+bool ci_less(const std::string &a, const std::string &b) noexcept {
+#ifdef _MSC_VER
+ return _stricmp(a.c_str(), b.c_str()) < 0;
+#else
+ return strcasecmp(a.c_str(), b.c_str()) < 0;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
/**
* Convert to lower case.
*/
diff --git a/src/io.cpp b/src/io.cpp
index 83450745..87caddd0 100644
--- a/src/io.cpp
+++ b/src/io.cpp
@@ -128,7 +128,7 @@ struct WKTFormatter::Private {
bool primeMeridianInDegree_ = false;
bool use2018Keywords_ = false;
bool useESRIDialect_ = false;
- bool outputAxis_ = true;
+ OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES;
};
Params params_{};
DatabaseContextPtr dbContext_{};
@@ -225,10 +225,9 @@ WKTFormatter &WKTFormatter::setIndentationWidth(int width) noexcept {
// ---------------------------------------------------------------------------
/** \brief Set whether AXIS nodes should be output.
- *
- * This can typically be set to false for some variants of WKT1_GDAL.
*/
-WKTFormatter &WKTFormatter::setOutputAxis(bool outputAxisIn) noexcept {
+WKTFormatter &
+WKTFormatter::setOutputAxis(OutputAxisRule outputAxisIn) noexcept {
d->params_.outputAxis_ = outputAxisIn;
return *this;
}
@@ -308,6 +307,8 @@ WKTFormatter::WKTFormatter(Convention convention)
d->params_.outputAxisOrder_ = false;
d->params_.forceUNITKeyword_ = true;
d->params_.primeMeridianInDegree_ = true;
+ d->params_.outputAxis_ =
+ WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE;
break;
case Convention::WKT1_ESRI:
@@ -317,7 +318,7 @@ WKTFormatter::WKTFormatter(Convention convention)
d->params_.primeMeridianInDegree_ = true;
d->params_.useESRIDialect_ = true;
d->params_.multiLine_ = false;
- d->params_.outputAxis_ = false;
+ d->params_.outputAxis_ = WKTFormatter::OutputAxisRule::NO;
break;
default:
@@ -568,7 +569,9 @@ const UnitOfMeasureNNPtr &WKTFormatter::axisAngularUnit() const {
// ---------------------------------------------------------------------------
-bool WKTFormatter::outputAxis() const { return d->params_.outputAxis_; }
+WKTFormatter::OutputAxisRule WKTFormatter::outputAxis() const {
+ return d->params_.outputAxis_;
+}
// ---------------------------------------------------------------------------
@@ -1185,6 +1188,9 @@ struct WKTParser::Private {
const UnitOfMeasure &defaultLinearUnit,
const UnitOfMeasure &defaultAngularUnit);
+ static void addExtensionProj4ToProp(const WKTNode::Private *nodeP,
+ PropertyMap &props);
+
ConversionNNPtr buildConversion(const WKTNodeNNPtr &node,
const UnitOfMeasure &defaultLinearUnit,
const UnitOfMeasure &defaultAngularUnit);
@@ -1192,6 +1198,9 @@ struct WKTParser::Private {
static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode,
const WKTNodeNNPtr &projectionNode);
+ static bool projectionHasParameter(const WKTNodeNNPtr &projCRSNode,
+ const char *paramName);
+
ConversionNNPtr buildProjection(const WKTNodeNNPtr &projCRSNode,
const WKTNodeNNPtr &projectionNode,
const UnitOfMeasure &defaultLinearUnit,
@@ -1940,6 +1949,7 @@ GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame(
throw ParsingException("Invalid TOWGS84 node");
}
}
+
auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
const auto &extensionChildren = extensionNode->GP()->children();
if (extensionChildren.size() == 2) {
@@ -2403,6 +2413,19 @@ WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */
// ---------------------------------------------------------------------------
+void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP,
+ PropertyMap &props) {
+ auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
+ const auto &extensionChildren = extensionNode->GP()->children();
+ if (extensionChildren.size() == 2) {
+ if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
+ props.set("EXTENSION_PROJ4", stripQuotes(extensionChildren[1]));
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
GeodeticCRSNNPtr
WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) {
const auto *nodeP = node->GP();
@@ -2450,6 +2473,9 @@ WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) {
angularUnit = primeMeridian->longitude().unit();
}
+ auto props = buildProperties(node);
+ addExtensionProj4ToProp(nodeP, props);
+
auto datum =
!isNull(datumNode)
? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode)
@@ -2465,8 +2491,7 @@ WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) {
if (ellipsoidalCS) {
assert(!ci_equal(nodeName, WKTConstants::GEOCCS));
try {
- return GeographicCRS::create(buildProperties(node), datum,
- datumEnsemble,
+ return GeographicCRS::create(props, datum, datumEnsemble,
NN_NO_CHECK(ellipsoidalCS));
} catch (const util::Exception &e) {
throw ParsingException(std::string("buildGeodeticCRS: ") +
@@ -2487,8 +2512,8 @@ WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) {
"Cartesian CS for a GeodeticCRS should have 3 axis");
}
try {
- return GeodeticCRS::create(buildProperties(node), datum,
- datumEnsemble, NN_NO_CHECK(cartesianCS));
+ return GeodeticCRS::create(props, datum, datumEnsemble,
+ NN_NO_CHECK(cartesianCS));
} catch (const util::Exception &e) {
throw ParsingException(std::string("buildGeodeticCRS: ") +
e.what());
@@ -2498,8 +2523,8 @@ WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) {
auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
if (sphericalCS) {
try {
- return GeodeticCRS::create(buildProperties(node), datum,
- datumEnsemble, NN_NO_CHECK(sphericalCS));
+ return GeodeticCRS::create(props, datum, datumEnsemble,
+ NN_NO_CHECK(sphericalCS));
} catch (const util::Exception &e) {
throw ParsingException(std::string("buildGeodeticCRS: ") +
e.what());
@@ -2582,7 +2607,8 @@ UnitOfMeasure WKTParser::Private::guessUnitForParameter(
ci_find(paramName, "meridian") != std::string::npos ||
ci_find(paramName, "parallel") != std::string::npos ||
ci_find(paramName, "azimuth") != std::string::npos ||
- ci_find(paramName, "angle") != std::string::npos) {
+ ci_find(paramName, "angle") != std::string::npos ||
+ ci_find(paramName, "heading") != std::string::npos) {
unit = defaultAngularUnit;
} else if (ci_find(paramName, "easting") != std::string::npos ||
ci_find(paramName, "northing") != std::string::npos ||
@@ -2839,8 +2865,15 @@ ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
defaultLinearUnit, defaultAngularUnit);
}
+ struct ci_less_struct {
+ bool operator()(const std::string &lhs, const std::string &rhs) const
+ noexcept {
+ return ci_less(lhs, rhs);
+ }
+ };
+
// Build a map of present parameters
- std::map<std::string, std::string> mapParamNameToValue;
+ std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
for (const auto &childNode : projCRSNode->GP()->children()) {
if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
const auto &childNodeChildren = childNode->GP()->children();
@@ -2888,7 +2921,7 @@ ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
const auto *wkt2_mapping = getMapping(esriMapping->wkt2_name);
assert(wkt2_mapping);
- if (esriProjectionName == "Stereographic") {
+ if (ci_equal(esriProjectionName, "Stereographic")) {
try {
if (std::fabs(io::asDouble(
mapParamNameToValue["Latitude_Of_Origin"])) == 90.0) {
@@ -2937,9 +2970,20 @@ ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
if (iter == mapWKT2NameToESRIName.end()) {
continue;
}
- auto iter2 = mapParamNameToValue.find(iter->second);
- if (iter2 == mapParamNameToValue.end()) {
- continue;
+ const auto &esriParamName = iter->second;
+ auto iter2 = mapParamNameToValue.find(esriParamName);
+ auto mapParamNameToValueEnd = mapParamNameToValue.end();
+ if (iter2 == mapParamNameToValueEnd) {
+ // In case we don't find a direct match, try the aliases
+ for (iter2 = mapParamNameToValue.begin();
+ iter2 != mapParamNameToValueEnd; ++iter2) {
+ if (areEquivalentParameters(iter2->first, esriParamName)) {
+ break;
+ }
+ }
+ if (iter2 == mapParamNameToValueEnd) {
+ continue;
+ }
}
PropertyMap propertiesParameter;
@@ -2987,13 +3031,31 @@ WKTParser::Private::buildProjection(const WKTNodeNNPtr &projCRSNode,
return buildProjectionStandard(projCRSNode, projectionNode,
defaultLinearUnit, defaultAngularUnit);
}
+
+// ---------------------------------------------------------------------------
+
+bool WKTParser::Private::projectionHasParameter(const WKTNodeNNPtr &projCRSNode,
+ const char *paramName) {
+ for (const auto &childNode : projCRSNode->GP()->children()) {
+ if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
+ const auto &childNodeChildren = childNode->GP()->children();
+ if (childNodeChildren.size() == 2 &&
+ metadata::Identifier::isEquivalentName(
+ stripQuotes(childNodeChildren[0]).c_str(), paramName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
// ---------------------------------------------------------------------------
ConversionNNPtr WKTParser::Private::buildProjectionStandard(
const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode,
const UnitOfMeasure &defaultLinearUnit,
const UnitOfMeasure &defaultAngularUnit) {
- const std::string wkt1ProjectionName =
+ std::string wkt1ProjectionName =
stripQuotes(projectionNode->GP()->children()[0]);
std::vector<OperationParameterNNPtr> parameters;
@@ -3003,23 +3065,32 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
const auto &extensionChildren = extensionNode->GP()->children();
+ bool gdal_3026_hack = false;
if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
"Mercator_1SP") &&
- projCRSNode->countChildrenOfName("center_latitude") == 0) {
+ !projectionHasParameter(projCRSNode, "center_latitude")) {
- // The latitude of origin, which should always be zero, is
- // missing
- // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP
- // definition,
- // so add it manually.
- PropertyMap propertiesParameter;
- propertiesParameter.set(IdentifiedObject::NAME_KEY,
- "Latitude of natural origin");
- propertiesParameter.set(Identifier::CODE_KEY, 8801);
- propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
- parameters.push_back(OperationParameter::create(propertiesParameter));
- values.push_back(
- ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE)));
+ // Hack for https://trac.osgeo.org/gdal/ticket/3026
+ if (projectionHasParameter(projCRSNode, "latitude_of_origin")) {
+ wkt1ProjectionName = "Mercator_2SP";
+ gdal_3026_hack = true;
+ } else {
+ // The latitude of origin, which should always be zero, is
+ // missing
+ // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP
+ // definition,
+ // so add it manually.
+ PropertyMap propertiesParameter;
+ propertiesParameter.set(IdentifiedObject::NAME_KEY,
+ "Latitude of natural origin");
+ propertiesParameter.set(Identifier::CODE_KEY, 8801);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ parameters.push_back(
+ OperationParameter::create(propertiesParameter));
+ values.push_back(
+ ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE)));
+ }
} else if (metadata::Identifier::isEquivalentName(
wkt1ProjectionName.c_str(), "Polar_Stereographic")) {
@@ -3036,7 +3107,7 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
defaultLinearUnit,
defaultAngularUnit);
mapParameters.insert(std::pair<std::string, Measure>(
- wkt1ParameterName, Measure(val, unit)));
+ tolower(wkt1ParameterName), Measure(val, unit)));
} catch (const std::exception &) {
}
}
@@ -3051,25 +3122,28 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
Measure falseEasting = mapParameters["false_easting"];
Measure falseNorthing = mapParameters["false_northing"];
if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
- std::fabs(std::fabs(latitudeOfOrigin.convertToUnit(
- UnitOfMeasure::DEGREE)) -
- 90.0) < 1e-10) {
- return Conversion::createPolarStereographicVariantA(
+ scaleFactor.getSIValue() == 1.0) {
+ return Conversion::createPolarStereographicVariantB(
PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
Angle(centralMeridian.value(), centralMeridian.unit()),
- Scale(scaleFactor.value(), scaleFactor.unit()),
Length(falseEasting.value(), falseEasting.unit()),
Length(falseNorthing.value(), falseNorthing.unit()));
- } else if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
- scaleFactor.getSIValue() == 1.0) {
- return Conversion::createPolarStereographicVariantB(
+ }
+
+ if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
+ std::fabs(std::fabs(latitudeOfOrigin.convertToUnit(
+ UnitOfMeasure::DEGREE)) -
+ 90.0) < 1e-10) {
+ return Conversion::createPolarStereographicVariantA(
PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
Angle(centralMeridian.value(), centralMeridian.unit()),
+ Scale(scaleFactor.value(), scaleFactor.unit()),
Length(falseEasting.value(), falseEasting.unit()),
Length(falseNorthing.value(), falseNorthing.unit()));
}
+
tryToIdentifyWKT1Method = false;
// Import GDAL PROJ4 extension nodes
} else if (extensionChildren.size() == 2 &&
@@ -3124,13 +3198,32 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
if (childNodeChildren.size() < 2) {
ThrowNotEnoughChildren(WKTConstants::PARAMETER);
}
+ const auto &paramValue = childNodeChildren[1]->GP()->value();
+
PropertyMap propertiesParameter;
const std::string wkt1ParameterName(
stripQuotes(childNodeChildren[0]));
std::string parameterName(wkt1ParameterName);
+ if (gdal_3026_hack) {
+ if (ci_equal(parameterName, "latitude_of_origin")) {
+ parameterName = "standard_parallel_1";
+ } else if (ci_equal(parameterName, "scale_factor") &&
+ paramValue == "1") {
+ continue;
+ }
+ }
const auto *paramMapping =
mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
- if (paramMapping) {
+ if (mapping &&
+ mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ ci_equal(parameterName, "latitude_of_origin")) {
+ parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
+ propertiesParameter.set(
+ Identifier::CODE_KEY,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ } else if (paramMapping) {
parameterName = paramMapping->wkt2_name;
if (paramMapping->epsg_code != 0) {
propertiesParameter.set(Identifier::CODE_KEY,
@@ -3142,7 +3235,6 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName);
parameters.push_back(
OperationParameter::create(propertiesParameter));
- const auto &paramValue = childNodeChildren[1]->GP()->value();
try {
double val = io::asDouble(paramValue);
auto unit = guessUnitForParameter(
@@ -3302,6 +3394,8 @@ WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) {
}
}
+ addExtensionProj4ToProp(nodeP, props);
+
return ProjectedCRS::create(props, baseGeodCRS, conversion,
NN_NO_CHECK(cartesianCS));
}
@@ -4226,6 +4320,14 @@ IPROJStringExportable::~IPROJStringExportable() = default;
std::string IPROJStringExportable::exportToPROJString(
PROJStringFormatter *formatter) const {
_exportToPROJString(formatter);
+ if (formatter->getAddNoDefs() &&
+ formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4 &&
+ dynamic_cast<const crs::CRS *>(this)) {
+ if (!formatter->hasParam("no_defs")) {
+ formatter->addParam("no_defs");
+ }
+ }
return formatter->toString();
}
//! @endcond
@@ -4291,6 +4393,8 @@ struct PROJStringFormatter::Private {
bool omitZUnitConversion_ = false;
DatabaseContextPtr dbContext_{};
bool useETMercForTMerc_ = false;
+ bool useETMercForTMercSet_ = false;
+ bool addNoDefs_ = true;
std::string result_{};
@@ -4346,6 +4450,7 @@ PROJStringFormatter::create(Convention conventionIn,
* instead of tmerc */
void PROJStringFormatter::setUseETMercForTMerc(bool flag) {
d->useETMercForTMerc_ = flag;
+ d->useETMercForTMercSet_ = true;
}
// ---------------------------------------------------------------------------
@@ -4768,7 +4873,8 @@ PROJStringFormatter::Convention PROJStringFormatter::convention() const {
// ---------------------------------------------------------------------------
-bool PROJStringFormatter::getUseETMercForTMerc() const {
+bool PROJStringFormatter::getUseETMercForTMerc(bool &settingSetOut) const {
+ settingSetOut = d->useETMercForTMercSet_;
return d->useETMercForTMerc_;
}
@@ -4962,6 +5068,27 @@ void PROJStringFormatter::setCurrentStepInverted(bool inverted) {
// ---------------------------------------------------------------------------
+bool PROJStringFormatter::hasParam(const char *paramName) const {
+ if (!d->steps_.empty()) {
+ for (const auto &paramValue : d->steps_.back().paramValues) {
+ if (paramValue.keyEquals(paramName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; }
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; }
+
+// ---------------------------------------------------------------------------
+
void PROJStringFormatter::addParam(const std::string &paramName) {
if (d->steps_.empty()) {
d->addStep();
@@ -5136,6 +5263,8 @@ struct PROJStringParser::Private {
DatabaseContextPtr dbContext_{};
std::vector<std::string> warningList_{};
+ std::string projString_{};
+
std::vector<Step> steps_{};
std::vector<Step::KeyValue> globalParamValues_{};
std::string title_{};
@@ -5173,6 +5302,16 @@ struct PROJStringParser::Private {
}
// cppcheck-suppress functionStatic
+ const std::string &getParamValueK(const Step &step) {
+ for (const auto &pair : step.paramValues) {
+ if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) {
+ return pair.value;
+ }
+ }
+ return emptyString;
+ }
+
+ // cppcheck-suppress functionStatic
std::string guessBodyName(double a);
PrimeMeridianNNPtr buildPrimeMeridian(const Step &step);
@@ -5383,7 +5522,7 @@ static const struct DatumDesc {
// ---------------------------------------------------------------------------
-static bool isGeodeticStep(const std::string &name) {
+static bool isGeographicStep(const std::string &name) {
return name == "longlat" || name == "lonlat" || name == "latlong" ||
name == "latlon";
}
@@ -5483,19 +5622,38 @@ PROJStringParser::Private::buildDatum(const Step &step,
const auto &aStr = getParamValue(step, "a");
const auto &bStr = getParamValue(step, "b");
const auto &rfStr = getParamValue(step, "rf");
+ const auto &fStr = getParamValue(step, "f");
const auto &RStr = getParamValue(step, "R");
const util::optional<std::string> optionalEmptyString{};
PrimeMeridianNNPtr pm(buildPrimeMeridian(step));
PropertyMap grfMap;
+ // It is arguable that we allow the prime meridian of a datum defined by
+ // its name to be overriden, but this is found at least in a regression test
+ // of GDAL. So let's keep the ellipsoid part of the datum in that case and
+ // use the specified prime meridian.
+ const auto overridePmIfNeeded =
+ [&pm](const GeodeticReferenceFrameNNPtr &grf) {
+ if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
+ return grf;
+ } else {
+ return GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "Unknown based on " +
+ grf->ellipsoid()->nameStr() +
+ " ellipsoid"),
+ grf->ellipsoid(), grf->anchorDefinition(), pm);
+ }
+ };
+
if (!datumStr.empty()) {
if (datumStr == "WGS84") {
- return GeodeticReferenceFrame::EPSG_6326;
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
} else if (datumStr == "NAD83") {
- return GeodeticReferenceFrame::EPSG_6269;
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269);
} else if (datumStr == "NAD27") {
- return GeodeticReferenceFrame::EPSG_6267;
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267);
} else {
for (const auto &datumDesc : datumDescs) {
@@ -5621,6 +5779,29 @@ PROJStringParser::Private::buildDatum(const Step &step,
ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
}
+ else if (!aStr.empty() && !fStr.empty()) {
+ double a;
+ try {
+ a = c_locale_stod(aStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid a value");
+ }
+ double f;
+ try {
+ f = c_locale_stod(fStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid f value");
+ }
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ createMapWithUnknownName(), Length(a),
+ Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
else if (!RStr.empty()) {
double R;
try {
@@ -5637,7 +5818,7 @@ PROJStringParser::Private::buildDatum(const Step &step,
}
if (!aStr.empty() && bStr.empty() && rfStr.empty()) {
- throw ParsingException("a found, but b or rf missing");
+ throw ParsingException("a found, but b, f or rf missing");
}
if (!bStr.empty() && aStr.empty()) {
@@ -5648,14 +5829,11 @@ PROJStringParser::Private::buildDatum(const Step &step,
throw ParsingException("rf found, but a missing");
}
- if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
- return GeodeticReferenceFrame::EPSG_6326;
- } else {
- return GeodeticReferenceFrame::create(
- grfMap.set(IdentifiedObject::NAME_KEY,
- "Unknown based on WGS84 ellipsoid"),
- Ellipsoid::WGS84, optionalEmptyString, pm);
+ if (!fStr.empty() && aStr.empty()) {
+ throw ParsingException("f found, but a missing");
}
+
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
}
// ---------------------------------------------------------------------------
@@ -5830,15 +6008,20 @@ PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert,
bool ignorePROJAxis) {
const auto &step = steps_[iStep];
- const auto &title = isGeodeticStep(step.name) ? title_ : emptyString;
+ const bool l_isGeographicStep = isGeographicStep(step.name);
+ const auto &title = l_isGeographicStep ? title_ : emptyString;
auto datum = buildDatum(step, title);
+ auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+ if (l_isGeographicStep && hasParamValue(step, "wktext")) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
return GeographicCRS::create(
- PropertyMap().set(IdentifiedObject::NAME_KEY,
- title.empty() ? "unknown" : title),
- datum, buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignoreVUnits,
- ignorePROJAxis));
+ props, datum, buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap,
+ ignoreVUnits, ignorePROJAxis));
}
// ---------------------------------------------------------------------------
@@ -5889,11 +6072,14 @@ PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) {
}
}
+ auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+ if (hasParamValue(step, "wktext")) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
auto cs = CartesianCS::createGeocentric(unit);
- return GeodeticCRS::create(
- PropertyMap().set(IdentifiedObject::NAME_KEY,
- title.empty() ? "unknown" : title),
- datum, cs);
+ return GeodeticCRS::create(props, datum, cs);
}
// ---------------------------------------------------------------------------
@@ -5997,30 +6183,29 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo(
geogCRS->primeMeridian()->longitude(),
util::IComparable::Criterion::EQUIVALENT)) {
- throw ParsingException("inconsistant pm values between projectedCRS "
+ throw ParsingException("inconsistent pm values between projectedCRS "
"and its base geographicalCRS");
}
auto axisType = AxisType::REGULAR;
- if (step.name == "tmerc" && getParamValue(step, "axis") == "wsu") {
+ if (step.name == "tmerc" &&
+ ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) ||
+ (iAxisSwap > 0 &&
+ getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) {
mapping =
getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED);
} else if (step.name == "etmerc") {
mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
- // TODO: we loose the link to the proj etmerc method here. Add some
- // property to Conversion to keep it ?
} else if (step.name == "lcc") {
const auto &lat_0 = getParamValue(step, "lat_0");
const auto &lat_1 = getParamValue(step, "lat_1");
const auto &lat_2 = getParamValue(step, "lat_2");
- const auto &k_0 = getParamValue(step, "k_0");
- const auto &k = getParamValue(step, "k");
+ const auto &k = getParamValueK(step);
if (lat_2.empty() && !lat_0.empty() && !lat_1.empty() &&
getAngularValue(lat_0) == getAngularValue(lat_1)) {
mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
- } else if ((!k_0.empty() && getNumericValue(k_0) != 1.0) ||
- (!k.empty() && getNumericValue(k) != 1.0)) {
+ } else if (!k.empty() && getNumericValue(k) != 1.0) {
mapping = getMapping(
EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
} else {
@@ -6058,15 +6243,17 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
step.paramValues.emplace_back(Step::KeyValue("gamma", "90"));
step.paramValues.emplace_back(
Step::KeyValue("lonc", getParamValue(step, "lon_0")));
- } else if (step.name == "krovak" && getParamValue(step, "axis") == "swu") {
+ } else if (step.name == "krovak" &&
+ ((getParamValue(step, "axis") == "swu" && iAxisSwap < 0) ||
+ (iAxisSwap > 0 &&
+ getParamValue(steps_[iAxisSwap], "order") == "-2,-1"))) {
mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
} else if (step.name == "merc") {
if (hasParamValue(step, "a") && hasParamValue(step, "b") &&
getParamValue(step, "a") == getParamValue(step, "b") &&
(!hasParamValue(step, "lat_ts") ||
getAngularValue(getParamValue(step, "lat_ts")) == 0.0) &&
- (!hasParamValue(step, "k") ||
- getNumericValue(getParamValue(step, "k")) == 1.0) &&
+ getNumericValue(getParamValueK(step)) == 1.0 &&
getParamValue(step, "nadgrids") == "@null") {
mapping = getMapping(
EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR);
@@ -6091,7 +6278,16 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
} else {
axisType = AxisType::SOUTH_POLE;
}
- if (hasParamValue(step, "lat_ts")) {
+ const auto &lat_ts = getParamValue(step, "lat_ts");
+ const auto &k = getParamValueK(step);
+ if (!lat_ts.empty() &&
+ std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 &&
+ !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) {
+ throw ParsingException("lat_ts != lat_0 and k != 1 not "
+ "supported for Polar Stereographic");
+ }
+ if (!lat_ts.empty() &&
+ (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) {
mapping =
getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B);
} else {
@@ -6161,14 +6357,10 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
const auto *param = mapping->params[i];
std::string proj_name(param->proj_name ? param->proj_name : "");
const std::string *paramValue =
- !proj_name.empty() ? &getParamValue(step, proj_name)
- : &emptyString;
- // k and k_0 may be used indifferently
- if (paramValue->empty() && proj_name == "k") {
- paramValue = &getParamValue(step, "k_0");
- } else if (paramValue->empty() && proj_name == "k_0") {
- paramValue = &getParamValue(step, "k");
- }
+ (proj_name == "k" || proj_name == "k_0")
+ ? &getParamValueK(step)
+ : !proj_name.empty() ? &getParamValue(step, proj_name)
+ : &emptyString;
double value = 0;
if (!paramValue->empty()) {
bool hasError = false;
@@ -6233,6 +6425,10 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
: UnitOfMeasure::NONE)));
}
+ if (step.name == "etmerc") {
+ methodMap.set("proj_method", "etmerc");
+ }
+
conv = Conversion::create(mapWithUnknownName, methodMap, parameters,
values)
.as_nullable();
@@ -6296,10 +6492,14 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
auto cs = CartesianCS::create(emptyPropertyMap, axis[0], axis[1]);
- CRSNNPtr crs = ProjectedCRS::create(
- PropertyMap().set(IdentifiedObject::NAME_KEY,
- title.empty() ? "unknown" : title),
- geogCRS, NN_NO_CHECK(conv), cs);
+ auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+
+ if (hasParamValue(step, "wktext")) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
+ CRSNNPtr crs = ProjectedCRS::create(props, geogCRS, NN_NO_CHECK(conv), cs);
if (!hasParamValue(step, "geoidgrids") &&
(hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) {
@@ -6322,7 +6522,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
static bool isDatumDefiningParam(const std::string &param) {
return (param == "datum" || param == "ellps" || param == "a" ||
- param == "b" || param == "rf" || param == "R");
+ param == "b" || param == "rf" || param == "f" || param == "R");
}
// ---------------------------------------------------------------------------
@@ -6585,6 +6785,7 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
std::string vunits;
std::string vto_meter;
+ d->projString_ = projString;
PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_,
d->title_, vunits, vto_meter);
@@ -6613,10 +6814,11 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
if ((d->steps_.size() == 1 ||
(d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) &&
isGeocentricStep(d->steps_[0].name)) {
- return d->buildGeocentricCRS(
- 0, (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")
- ? 1
- : -1);
+ return d->buildBoundOrCompoundCRSIfNeeded(
+ 0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
+ d->steps_[1].name == "unitconvert")
+ ? 1
+ : -1));
}
int iFirstGeogStep = -1;
@@ -6633,7 +6835,7 @@ PROJStringParser::createFromPROJString(const std::string &projString) {
bool unexpectedStructure = false;
for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) {
const auto &stepName = d->steps_[i].name;
- if (isGeodeticStep(stepName)) {
+ if (isGeographicStep(stepName)) {
if (iFirstGeogStep < 0) {
iFirstGeogStep = i;
} else if (iSecondGeogStep < 0) {
diff --git a/src/pj_ellps.c b/src/pj_ellps.c
index 08b81c3f..8b3b8f0a 100644
--- a/src/pj_ellps.c
+++ b/src/pj_ellps.c
@@ -11,7 +11,7 @@ pj_ellps[] = {
{"SGS85", "a=6378136.0", "rf=298.257", "Soviet Geodetic System 85"},
{"GRS80", "a=6378137.0", "rf=298.257222101", "GRS 1980(IUGG, 1980)"},
{"IAU76", "a=6378140.0", "rf=298.257", "IAU 1976"},
-{"airy", "a=6377563.396", "b=6356256.910", "Airy 1830"},
+{"airy", "a=6377563.396", "rf=299.3249646", "Airy 1830"},
{"APL4.9", "a=6378137.0", "rf=298.25", "Appl. Physics. 1965"},
{"NWL9D", "a=6378145.0", "rf=298.25", "Naval Weapons Lab., 1965"},
{"mod_airy", "a=6377340.189", "b=6356034.446", "Modified Airy"},
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index 207a8cd1..8c9f114b 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -263,6 +263,22 @@ TEST_F(CApi, proj_obj_as_wkt) {
EXPECT_TRUE(std::string(wkt).find("AXIS") == std::string::npos) << wkt;
}
+ // OUTPUT_AXIS=AUTO
+ {
+ const char *const options[] = {"OUTPUT_AXIS=AUTO", nullptr};
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT1_GDAL, options);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("AXIS") == std::string::npos) << wkt;
+ }
+
+ // OUTPUT_AXIS=YES
+ {
+ const char *const options[] = {"OUTPUT_AXIS=YES", nullptr};
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT1_GDAL, options);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("AXIS") != std::string::npos) << wkt;
+ }
+
// unsupported option
{
const char *const options[] = {"unsupported=yes", nullptr};
@@ -307,7 +323,7 @@ TEST_F(CApi, proj_obj_as_proj_string) {
{
auto proj_4 = proj_obj_as_proj_string(obj, PJ_PROJ_4, nullptr);
ASSERT_NE(proj_4, nullptr);
- EXPECT_EQ(std::string(proj_4), "+proj=longlat +datum=WGS84");
+ EXPECT_EQ(std::string(proj_4), "+proj=longlat +datum=WGS84 +no_defs");
}
}
@@ -327,7 +343,7 @@ TEST_F(CApi, proj_obj_as_proj_string_incompatible_WKT1) {
// ---------------------------------------------------------------------------
-TEST_F(CApi, proj_obj_as_proj_string_etmerc_option) {
+TEST_F(CApi, proj_obj_as_proj_string_etmerc_option_yes) {
auto obj = proj_obj_create_from_proj_string(m_ctxt, "+proj=tmerc", nullptr);
ObjectKeeper keeper(obj);
ASSERT_NE(obj, nullptr);
@@ -335,8 +351,24 @@ TEST_F(CApi, proj_obj_as_proj_string_etmerc_option) {
const char *options[] = {"USE_ETMERC=YES", nullptr};
auto str = proj_obj_as_proj_string(obj, PJ_PROJ_4, options);
ASSERT_NE(str, nullptr);
- EXPECT_EQ(str, std::string("+proj=etmerc +lat_0=0 +lon_0=0 +k_0=1 +x_0=0 "
- "+y_0=0 +datum=WGS84"));
+ EXPECT_EQ(str, std::string("+proj=etmerc +lat_0=0 +lon_0=0 +k=1 +x_0=0 "
+ "+y_0=0 +datum=WGS84 +units=m +no_defs"));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_as_proj_string_etmerc_option_no) {
+ auto obj =
+ proj_obj_create_from_proj_string(m_ctxt, "+proj=utm +zone=31", nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+
+ const char *options[] = {"USE_ETMERC=NO", nullptr};
+ auto str = proj_obj_as_proj_string(obj, PJ_PROJ_4, options);
+ ASSERT_NE(str, nullptr);
+ EXPECT_EQ(str, std::string("+proj=tmerc +lat_0=0 +lon_0=3 +k=0.9996 "
+ "+x_0=500000 +y_0=0 +datum=WGS84 +units=m "
+ "+no_defs"));
}
// ---------------------------------------------------------------------------
@@ -356,7 +388,8 @@ TEST_F(CApi, proj_obj_crs_create_bound_crs_to_WGS84) {
EXPECT_EQ(std::string(proj_4),
"+proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 +x_0=500000 "
"+y_0=500000 +ellps=krass "
- "+towgs84=2.329,-147.042,-92.08,-0.309,0.325,0.497,5.69");
+ "+towgs84=2.329,-147.042,-92.08,-0.309,0.325,0.497,5.69 "
+ "+units=m +no_defs");
}
// ---------------------------------------------------------------------------
diff --git a/test/unit/test_crs.cpp b/test/unit/test_crs.cpp
index 43698252..84be1b22 100644
--- a/test/unit/test_crs.cpp
+++ b/test/unit/test_crs.cpp
@@ -302,22 +302,39 @@ TEST(crs, EPSG_4326_as_WKT2_2018_SIMPLIFIED) {
TEST(crs, EPSG_4326_as_WKT1_GDAL) {
auto crs = GeographicCRS::EPSG_4326;
- WKTFormatterNNPtr f(
- WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
- crs->exportToWKT(f.get());
- EXPECT_EQ(f->toString(),
- "GEOGCS[\"WGS 84\",\n"
- " DATUM[\"WGS_1984\",\n"
- " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
- " AUTHORITY[\"EPSG\",\"7030\"]],\n"
- " AUTHORITY[\"EPSG\",\"6326\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST],\n"
- " AUTHORITY[\"EPSG\",\"4326\"]]");
+ auto wkt = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ EXPECT_EQ(wkt, "GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT1_GDAL_with_axis) {
+ auto crs = GeographicCRS::EPSG_4326;
+ auto wkt = crs->exportToWKT(
+ &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)
+ ->setOutputAxis(WKTFormatter::OutputAxisRule::YES)));
+ EXPECT_EQ(wkt, "GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]]");
}
// ---------------------------------------------------------------------------
@@ -356,7 +373,7 @@ TEST(crs, EPSG_4326_as_PROJ_string) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +datum=WGS84");
+ "+proj=longlat +datum=WGS84 +no_defs");
}
// ---------------------------------------------------------------------------
@@ -403,28 +420,45 @@ TEST(crs, EPSG_4979_as_WKT2_2018_SIMPLIFIED) {
// ---------------------------------------------------------------------------
-TEST(crs, EPSG_4979_as_WKT1_GDAL) {
+TEST(crs, EPSG_4979_as_WKT1_GDAL_with_axis) {
auto crs = GeographicCRS::EPSG_4979;
- WKTFormatterNNPtr f(
- WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
- crs->exportToWKT(f.get());
- // FIXME? WKT1 only supports 2 axis for GEOGCS. So this is an extension of
+ auto wkt = crs->exportToWKT(
+ &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)
+ ->setOutputAxis(WKTFormatter::OutputAxisRule::YES)));
+ // WKT1 only supports 2 axis for GEOGCS. So this is an extension of
// WKT1 as it
// and GDAL doesn't really export such as beast, although it can import it
- EXPECT_EQ(f->toString(),
- "GEOGCS[\"WGS 84\",\n"
- " DATUM[\"WGS_1984\",\n"
- " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
- " AUTHORITY[\"EPSG\",\"7030\"]],\n"
- " AUTHORITY[\"EPSG\",\"6326\"]],\n"
- " PRIMEM[\"Greenwich\",0,\n"
- " AUTHORITY[\"EPSG\",\"8901\"]],\n"
- " UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST],\n"
- " AXIS[\"Ellipsoidal height\",UP],\n"
- " AUTHORITY[\"EPSG\",\"4979\"]]");
+ EXPECT_EQ(wkt, "GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AXIS[\"Ellipsoidal height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"4979\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4979_as_WKT1_GDAL) {
+ auto crs = GeographicCRS::EPSG_4979;
+ auto wkt = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ EXPECT_EQ(wkt, "GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4979\"]]");
}
// ---------------------------------------------------------------------------
@@ -512,8 +546,6 @@ TEST(crs, EPSG_4807_as_WKT1_GDAL) {
" AUTHORITY[\"EPSG\",\"8903\"]],\n"
" UNIT[\"grad\",0.015707963267949,\n"
" AUTHORITY[\"EPSG\",\"9105\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST],\n"
" AUTHORITY[\"EPSG\",\"4807\"]]");
}
@@ -554,7 +586,7 @@ TEST(crs, EPSG_4807_as_PROJ_string) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +ellps=clrk80ign +pm=paris");
+ "+proj=longlat +ellps=clrk80ign +pm=paris +no_defs");
}
// ---------------------------------------------------------------------------
@@ -580,7 +612,7 @@ TEST(crs, EPSG_4267) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +datum=NAD27");
+ "+proj=longlat +datum=NAD27 +no_defs");
}
// ---------------------------------------------------------------------------
@@ -619,7 +651,7 @@ TEST(crs, EPSG_4269) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +datum=NAD83");
+ "+proj=longlat +datum=NAD83 +no_defs");
}
// ---------------------------------------------------------------------------
@@ -687,7 +719,7 @@ TEST(crs, EPSG_27561_projected_with_geodetic_in_grad_as_PROJ_string_and_WKT1) {
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
"+proj=lcc +lat_1=49.5 +lat_0=49.5 +lon_0=0 +k_0=0.999877341 "
- "+x_0=600000 +y_0=200000 +ellps=clrk80ign +pm=paris");
+ "+x_0=600000 +y_0=200000 +ellps=clrk80ign +pm=paris +units=m +no_defs");
auto nn_crs = NN_CHECK_ASSERT(crs);
EXPECT_TRUE(nn_crs->isEquivalentTo(nn_crs.get()));
@@ -704,9 +736,7 @@ TEST(crs, EPSG_27561_projected_with_geodetic_in_grad_as_PROJ_string_and_WKT1) {
" DATUM[\"Nouvelle_Triangulation_Francaise_Paris\",\n"
" SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.4660213]],\n"
" PRIMEM[\"Paris\",2.33722917000759],\n"
- " UNIT[\"grad\",0.015707963268],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST]],\n"
+ " UNIT[\"grad\",0.015707963268]],\n"
" PROJECTION[\"Lambert_Conformal_Conic_1SP\"],\n"
" PARAMETER[\"latitude_of_origin\",55],\n"
" PARAMETER[\"central_meridian\",0],\n"
@@ -799,15 +829,15 @@ TEST(crs, EPSG_2222_projected_unit_foot_as_PROJ_string_and_WKT1) {
EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
"+proj=pipeline +step +proj=axisswap +order=2,1 +step "
"+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=tmerc "
- "+lat_0=31 +lon_0=-110.166666666667 +k_0=0.9999 +x_0=213360 "
+ "+lat_0=31 +lon_0=-110.166666666667 +k=0.9999 +x_0=213360 "
"+y_0=0 +ellps=GRS80 +step +proj=unitconvert +xy_in=m +z_in=m "
"+xy_out=ft +z_out=ft");
EXPECT_EQ(
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=tmerc +lat_0=31 +lon_0=-110.166666666667 +k_0=0.9999 "
- "+x_0=213360 +y_0=0 +datum=NAD83 +units=ft");
+ "+proj=tmerc +lat_0=31 +lon_0=-110.166666666667 +k=0.9999 "
+ "+x_0=213360 +y_0=0 +datum=NAD83 +units=ft +no_defs");
auto wkt1 = crs->exportToWKT(
WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
@@ -819,9 +849,7 @@ TEST(crs, EPSG_2222_projected_unit_foot_as_PROJ_string_and_WKT1) {
" PRIMEM[\"Greenwich\",0,\n"
" AUTHORITY[\"EPSG\",\"8901\"]],\n"
" UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST]],\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
" PROJECTION[\"Transverse_Mercator\"],\n"
" PARAMETER[\"latitude_of_origin\",31],\n"
" PARAMETER[\"central_meridian\",-110.166666666667],\n"
@@ -867,9 +895,7 @@ TEST(crs, projected_with_parameter_unit_different_than_cs_unit_as_WKT1) {
" SPHEROID[\"GRS 1980\",6378137,298.257222101]],\n"
" PRIMEM[\"Greenwich\",0],\n"
" UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST]],\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
" PROJECTION[\"Transverse_Mercator\"],\n"
" PARAMETER[\"latitude_of_origin\",0],\n"
" PARAMETER[\"central_meridian\",9],\n"
@@ -1081,9 +1107,6 @@ TEST(crs, geocentricCRS_as_WKT1_GDAL) {
" AUTHORITY[\"EPSG\",\"8901\"]],\n"
" UNIT[\"metre\",1,\n"
" AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"Geocentric X\",OTHER],\n"
- " AXIS[\"Geocentric Y\",OTHER],\n"
- " AXIS[\"Geocentric Z\",NORTH],\n"
" AUTHORITY[\"EPSG\",\"4328\"]]");
}
@@ -1097,7 +1120,7 @@ TEST(crs, geocentricCRS_as_PROJ_string) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=geocent +ellps=WGS84");
+ "+proj=geocent +datum=WGS84 +units=m +no_defs");
}
// ---------------------------------------------------------------------------
@@ -1597,8 +1620,6 @@ TEST(crs, projectedCRS_as_WKT1_GDAL) {
" AUTHORITY[\"EPSG\",\"8901\"]],\n"
" UNIT[\"degree\",0.0174532925199433,\n"
" AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST],\n"
" AUTHORITY[\"EPSG\",\"4326\"]],\n"
" PROJECTION[\"Transverse_Mercator\"],\n"
" PARAMETER[\"latitude_of_origin\",0],\n"
@@ -1654,7 +1675,7 @@ TEST(crs, projectedCRS_as_PROJ_string) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=utm +zone=31 +datum=WGS84");
+ "+proj=utm +zone=31 +datum=WGS84 +units=m +no_defs");
}
// ---------------------------------------------------------------------------
@@ -2870,8 +2891,6 @@ TEST(crs, compoundCRS_as_WKT1_GDAL) {
" AUTHORITY[\"EPSG\",\"8901\"]],\n"
" UNIT[\"degree\",0.0174532925199433,\n"
" AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST],\n"
" AUTHORITY[\"EPSG\",\"4326\"]],\n"
" PROJECTION[\"Transverse_Mercator\"],\n"
" PARAMETER[\"latitude_of_origin\",0],\n"
@@ -2913,7 +2932,7 @@ TEST(crs, compoundCRS_as_PROJ_string) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=utm +zone=31 +datum=WGS84 +vunits=m");
+ "+proj=utm +zone=31 +datum=WGS84 +units=m +vunits=m +no_defs");
}
// ---------------------------------------------------------------------------
@@ -3301,9 +3320,7 @@ TEST(crs, boundCRS_to_WKT1) {
" PRIMEM[\"Greenwich\",0,\n"
" AUTHORITY[\"EPSG\",\"8901\"]],\n"
" UNIT[\"degree\",0.0174532925199433,\n"
- " AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST]],\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
" PROJECTION[\"Transverse_Mercator\"],\n"
" PARAMETER[\"latitude_of_origin\",0],\n"
" PARAMETER[\"central_meridian\",3],\n"
@@ -3339,7 +3356,7 @@ TEST(crs, boundCRS_geographicCRS_to_PROJ_string) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +ellps=WGS84 +towgs84=1,2,3,4,5,6,7");
+ "+proj=longlat +ellps=WGS84 +towgs84=1,2,3,4,5,6,7 +no_defs");
}
// ---------------------------------------------------------------------------
@@ -3362,7 +3379,8 @@ TEST(crs, boundCRS_projectedCRS_to_PROJ_string) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=utm +zone=31 +ellps=WGS84 +towgs84=1,2,3,4,5,6,7");
+ "+proj=utm +zone=31 +ellps=WGS84 +towgs84=1,2,3,4,5,6,7 +units=m "
+ "+no_defs");
}
// ---------------------------------------------------------------------------
@@ -3435,9 +3453,7 @@ TEST(crs, WKT1_DATUM_EXTENSION_to_WKT1_and_PROJ_string) {
" SPHEROID[\"intl\",6378388,297],\n"
" EXTENSION[\"PROJ4_GRIDS\",\"nzgd2kgrid0005.gsb\"]],\n"
" PRIMEM[\"Greenwich\",0],\n"
- " UNIT[\"degree\",0.0174532925199433],\n"
- " AXIS[\"Longitude\",EAST],\n"
- " AXIS[\"Latitude\",NORTH]],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
" PROJECTION[\"New_Zealand_Map_Grid\"],\n"
" PARAMETER[\"latitude_of_origin\",-41],\n"
" PARAMETER[\"central_meridian\",173],\n"
@@ -3461,7 +3477,7 @@ TEST(crs, WKT1_DATUM_EXTENSION_to_WKT1_and_PROJ_string) {
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
"+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 "
- "+ellps=intl +nadgrids=nzgd2kgrid0005.gsb +units=m");
+ "+ellps=intl +nadgrids=nzgd2kgrid0005.gsb +units=m +no_defs");
}
// ---------------------------------------------------------------------------
@@ -3473,7 +3489,7 @@ TEST(crs, WKT1_VERT_DATUM_EXTENSION_to_WKT1) {
" AUTHORITY[\"EPSG\",\"1027\"]],\n"
" UNIT[\"metre\",1,\n"
" AUTHORITY[\"EPSG\",\"9001\"]],\n"
- " AXIS[\"Up\",UP],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
" AUTHORITY[\"EPSG\",\"3855\"]]";
auto obj = WKTParser().createFromWKT(wkt);
@@ -3561,7 +3577,7 @@ TEST(crs, WKT1_VERT_DATUM_EXTENSION_to_PROJ_string) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+geoidgrids=egm08_25.gtx +vunits=m");
+ "+geoidgrids=egm08_25.gtx +vunits=m +no_defs");
}
// ---------------------------------------------------------------------------
@@ -4197,9 +4213,7 @@ TEST(crs, engineeringCRS_WKT2) {
TEST(crs, engineeringCRS_WKT1) {
auto expected = "LOCAL_CS[\"Engineering CRS\",\n"
- " LOCAL_DATUM[\"Engineering datum\",32767],\n"
- " AXIS[\"Easting\",EAST],\n"
- " AXIS[\"Northing\",NORTH]]";
+ " LOCAL_DATUM[\"Engineering datum\",32767]]";
EXPECT_EQ(
createEngineeringCRS()->exportToWKT(
WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
@@ -4522,7 +4536,8 @@ TEST(crs, crs_createBoundCRSToWGS84IfPossible) {
.get()),
"+proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 +x_0=500000 "
"+y_0=500000 +ellps=krass "
- "+towgs84=2.329,-147.042,-92.08,-0.309,0.325,0.497,5.69");
+ "+towgs84=2.329,-147.042,-92.08,-0.309,0.325,0.497,5.69 "
+ "+units=m +no_defs");
}
{
// Pulkovo 42 Poland
@@ -4538,7 +4553,8 @@ TEST(crs, crs_createBoundCRSToWGS84IfPossible) {
.get()),
"+proj=sterea +lat_0=50.625 +lon_0=21.0833333333333 "
"+k=0.9998 +x_0=4637000 +y_0=5647000 +ellps=krass "
- "+towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84");
+ "+towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 "
+ "+units=m +no_defs");
}
{
// NTF (Paris)
@@ -4553,7 +4569,7 @@ TEST(crs, crs_createBoundCRSToWGS84IfPossible) {
PROJStringFormatter::Convention::PROJ_4)
.get()),
"+proj=longlat +ellps=clrk80ign +pm=paris "
- "+towgs84=-168,-60,320,0,0,0,0");
+ "+towgs84=-168,-60,320,0,0,0,0 +no_defs");
}
{
// NTF (Paris) / Lambert zone II + NGF-IGN69 height
@@ -4569,7 +4585,8 @@ TEST(crs, crs_createBoundCRSToWGS84IfPossible) {
.get()),
"+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 "
"+x_0=600000 +y_0=2200000 +ellps=clrk80ign +pm=paris "
- "+towgs84=-168,-60,320,0,0,0,0 +vunits=m");
+ "+towgs84=-168,-60,320,0,0,0,0 +units=m "
+ "+vunits=m +no_defs");
}
{
auto crs = createVerticalCRS();
@@ -4589,7 +4606,7 @@ TEST(crs, crs_createBoundCRSToWGS84IfPossible) {
.get()),
"+proj=stere +lat_0=-90 +lat_ts=-67 +lon_0=140 +x_0=300000 "
"+y_0=-2299363.482 +ellps=intl "
- "+towgs84=324.912,153.282,172.026,0,0,0,0");
+ "+towgs84=324.912,153.282,172.026,0,0,0,0 +units=m +no_defs");
}
{
auto factoryIGNF =
@@ -4604,7 +4621,8 @@ TEST(crs, crs_createBoundCRSToWGS84IfPossible) {
PROJStringFormatter::Convention::PROJ_4)
.get()),
"+proj=geocent +ellps=intl "
- "+towgs84=109.753,-528.133,-362.244,0,0,0,0");
+ "+towgs84=109.753,-528.133,-362.244,0,0,0,0 +units=m "
+ "+no_defs");
}
}
diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp
index 7d26d82a..26597f09 100644
--- a/test/unit/test_io.cpp
+++ b/test/unit/test_io.cpp
@@ -453,6 +453,69 @@ static std::string contentWKT2_EPSG_4326(
// ---------------------------------------------------------------------------
+TEST(wkt_parse, wkt1_geographic_with_PROJ4_extension) {
+ auto wkt = "GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"unknown\",\n"
+ " SPHEROID[\"WGS84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " EXTENSION[\"PROJ4\",\"+proj=longlat +foo=bar +wktext\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ wkt);
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +foo=bar +wktext");
+
+ EXPECT_TRUE(
+ crs->exportToWKT(WKTFormatter::create().get()).find("EXTENSION") ==
+ std::string::npos);
+
+ EXPECT_TRUE(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI).get())
+ .find("EXTENSION") == std::string::npos);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_geocentric_with_PROJ4_extension) {
+ auto wkt = "GEOCCS[\"WGS 84\",\n"
+ " DATUM[\"unknown\",\n"
+ " SPHEROID[\"WGS84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"Meter\",1],\n"
+ " EXTENSION[\"PROJ4\",\"+proj=geocent +foo=bar +wktext\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ wkt);
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=geocent +foo=bar +wktext");
+
+ EXPECT_TRUE(
+ crs->exportToWKT(WKTFormatter::create().get()).find("EXTENSION") ==
+ std::string::npos);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(wkt_parse, wkt2_GEODCRS_EPSG_4326) {
auto obj = WKTParser().createFromWKT("GEODCRS" + contentWKT2_EPSG_4326);
auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
@@ -860,6 +923,49 @@ TEST(wkt_parse, wkt1_projected_no_axis) {
// ---------------------------------------------------------------------------
+TEST(wkt_parse, wkt1_projected_with_PROJ4_extension) {
+ auto wkt = "PROJCS[\"unnamed\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"unknown\",\n"
+ " SPHEROID[\"WGS84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Mercator_1SP\"],\n"
+ " PARAMETER[\"central_meridian\",0],\n"
+ " PARAMETER[\"scale_factor\",1],\n"
+ " PARAMETER[\"false_easting\",0],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"Meter\",1],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " EXTENSION[\"PROJ4\",\"+proj=merc +wktext\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ wkt);
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=merc +wktext");
+
+ EXPECT_TRUE(
+ crs->exportToWKT(WKTFormatter::create().get()).find("EXTENSION") ==
+ std::string::npos);
+
+ EXPECT_TRUE(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI).get())
+ .find("EXTENSION") == std::string::npos);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(wkt_parse, wkt1_krovak_south_west) {
auto wkt =
"PROJCS[\"S-JTSK / Krovak\","
@@ -942,13 +1048,15 @@ TEST(wkt_parse, wkt1_krovak_south_west) {
auto expectedPROJString =
"+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
"+step +proj=krovak +axis=swu +lat_0=49.5 "
- "+lon_0=24.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel";
+ "+lon_0=24.8333333333333 +alpha=30.2881397222222 "
+ "+k=0.9999 +x_0=0 +y_0=0 +ellps=bessel";
EXPECT_EQ(projString, expectedPROJString);
obj = PROJStringParser().createFromPROJString(projString);
crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
ASSERT_TRUE(crs != nullptr);
auto wkt2 = crs->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(wkt2.find("METHOD[\"Krovak\"") != std::string::npos) << wkt2;
EXPECT_TRUE(
wkt2.find("PARAMETER[\"Latitude of pseudo standard parallel\",78.5,") !=
std::string::npos)
@@ -959,6 +1067,17 @@ TEST(wkt_parse, wkt1_krovak_south_west) {
<< wkt2;
EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
expectedPROJString);
+
+ obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=krovak +lat_0=49.5 "
+ "+lon_0=24.8333333333333 +alpha=30.2881397222222 "
+ "+k=0.9999 +x_0=0 +y_0=0 +ellps=bessel "
+ "+step +proj=axisswap +order=-2,-1");
+ crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ wkt2 = crs->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(wkt2.find("METHOD[\"Krovak\"") != std::string::npos) << wkt2;
}
// ---------------------------------------------------------------------------
@@ -1043,7 +1162,174 @@ TEST(wkt_parse, wkt1_krovak_north_oriented) {
EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
"+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
"+step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 "
- "+k=0.9999 +x_0=0 +y_0=0 +ellps=bessel");
+ "+alpha=30.2881397222222 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_polar_stereographic_latitude_of_origin_70) {
+ auto wkt = "PROJCS[\"unknown\",\n"
+ " GEOGCS[\"unknown\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
+ " PROJECTION[\"Polar_Stereographic\"],\n"
+ " PARAMETER[\"latitude_of_origin\",70],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"false_easting\",3],\n"
+ " PARAMETER[\"false_northing\",4],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto projString = crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get());
+ auto expectedPROJString = "+proj=stere +lat_0=90 +lat_ts=70 +lon_0=2 "
+ "+x_0=3 +y_0=4 +datum=WGS84 +units=m +no_defs";
+ EXPECT_EQ(projString, expectedPROJString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_polar_stereographic_latitude_of_origin_90) {
+ auto wkt = "PROJCS[\"unknown\",\n"
+ " GEOGCS[\"unknown\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
+ " PROJECTION[\"Polar_Stereographic\"],\n"
+ " PARAMETER[\"latitude_of_origin\",90],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"false_easting\",3],\n"
+ " PARAMETER[\"false_northing\",4],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto projString = crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get());
+ auto expectedPROJString = "+proj=stere +lat_0=90 +lat_ts=90 +lon_0=2 "
+ "+x_0=3 +y_0=4 +datum=WGS84 +units=m +no_defs";
+ EXPECT_EQ(projString, expectedPROJString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_polar_stereographic_latitude_of_origin_90_scale_factor_1) {
+ auto wkt = "PROJCS[\"unknown\",\n"
+ " GEOGCS[\"unknown\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
+ " PROJECTION[\"Polar_Stereographic\"],\n"
+ " PARAMETER[\"latitude_of_origin\",90],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"scale_factor\",1],\n"
+ " PARAMETER[\"false_easting\",3],\n"
+ " PARAMETER[\"false_northing\",4],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto projString = crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get());
+ auto expectedPROJString = "+proj=stere +lat_0=90 +lat_ts=90 +lon_0=2 "
+ "+x_0=3 +y_0=4 +datum=WGS84 +units=m +no_defs";
+ EXPECT_EQ(projString, expectedPROJString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_polar_stereographic_scale_factor) {
+ auto wkt = "PROJCS[\"unknown\",\n"
+ " GEOGCS[\"unknown\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
+ " PROJECTION[\"Polar_Stereographic\"],\n"
+ " PARAMETER[\"latitude_of_origin\",90],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"scale_factor\",0.99],\n"
+ " PARAMETER[\"false_easting\",3],\n"
+ " PARAMETER[\"false_northing\",4],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto projString = crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get());
+ auto expectedPROJString = "+proj=stere +lat_0=90 +lon_0=2 +k=0.99 +x_0=3 "
+ "+y_0=4 +datum=WGS84 +units=m +no_defs";
+ EXPECT_EQ(projString, expectedPROJString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_Spherical_Cross_Track_Height) {
+ auto wkt = "PROJCS[\"unknown\",\n"
+ " GEOGCS[\"unknown\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
+ " PROJECTION[\"Spherical_Cross_Track_Height\"],\n"
+ " PARAMETER[\"peg_point_latitude\",1],\n"
+ " PARAMETER[\"peg_point_longitude\",2],\n"
+ " PARAMETER[\"peg_point_heading\",3],\n"
+ " PARAMETER[\"peg_point_height\",4],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto projString = crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get());
+ auto expectedPROJString = "+proj=sch +plat_0=1 +plon_0=2 +phdg_0=3 +h_0=4 "
+ "+datum=WGS84 +units=m +no_defs";
+ EXPECT_EQ(projString, expectedPROJString);
}
// ---------------------------------------------------------------------------
@@ -3508,6 +3794,56 @@ TEST(wkt_parse, esri_projcs) {
// ---------------------------------------------------------------------------
+TEST(wkt_parse, wkt1_esri_case_insensitive_names) {
+ auto wkt = "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,"
+ "298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\","
+ "0.0174532925199433]],PROJECTION[\"transverse_mercator\"],"
+ "PARAMETER[\"false_easting\",500000.0],"
+ "PARAMETER[\"false_northing\",0.0],"
+ "PARAMETER[\"central_meridian\",3.0],"
+ "PARAMETER[\"scale_factor\",0.9996],"
+ "PARAMETER[\"latitude_of_origin\",0.0],UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ int zone = 0;
+ bool north = false;
+ EXPECT_TRUE(crs->derivingConversion()->isUTM(zone, north));
+ EXPECT_EQ(zone, 31);
+ EXPECT_TRUE(north);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_non_expected_param_name) {
+ // We try to be lax on parameter names.
+ auto wkt =
+ "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,"
+ "298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\","
+ "0.0174532925199433]],PROJECTION[\"transverse_mercator\"],"
+ "PARAMETER[\"false_easting\",500000.0],"
+ "PARAMETER[\"false_northing\",0.0],"
+ "PARAMETER[\"longitude_of_center\",3.0]," // should be Central_Meridian
+ "PARAMETER[\"scale_factor\",0.9996],"
+ "PARAMETER[\"latitude_of_origin\",0.0],UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ int zone = 0;
+ bool north = false;
+ EXPECT_TRUE(crs->derivingConversion()->isUTM(zone, north));
+ EXPECT_EQ(zone, 31);
+ EXPECT_TRUE(north);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(wkt_parse, wkt1_esri_krovak_south_west) {
auto wkt = "PROJCS[\"S-JTSK_Krovak\",GEOGCS[\"GCS_S_JTSK\","
"DATUM[\"D_S_JTSK\","
@@ -5305,6 +5641,58 @@ TEST(io, projparse_longlat_a_rf) {
// ---------------------------------------------------------------------------
+TEST(io, projparse_longlat_a_f_zero) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +a=2 +f=0");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"unknown\",2,0,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Reference meridian\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_a_f_non_zero) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +a=2 +f=0.5");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"unknown\",2,2,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Reference meridian\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(io, projparse_longlat_R) {
auto obj = PROJStringParser().createFromPROJString("+proj=longlat +R=2");
auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
@@ -5339,7 +5727,7 @@ TEST(io, projparse_longlat_pm_paris) {
f->simulCurNodeHasId();
crs->exportToWKT(f.get());
auto expected = "GEODCRS[\"unknown\",\n"
- " DATUM[\"Unknown based on WGS84 ellipsoid\",\n"
+ " DATUM[\"Unknown based on WGS 84 ellipsoid\",\n"
" ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
" LENGTHUNIT[\"metre\",1]]],\n"
" PRIMEM[\"Paris\",2.5969213,\n"
@@ -5393,7 +5781,7 @@ TEST(io, projparse_longlat_pm_numeric) {
f->simulCurNodeHasId();
crs->exportToWKT(f.get());
auto expected = "GEODCRS[\"unknown\",\n"
- " DATUM[\"Unknown based on WGS84 ellipsoid\",\n"
+ " DATUM[\"Unknown based on WGS 84 ellipsoid\",\n"
" ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
" LENGTHUNIT[\"metre\",1]]],\n"
" PRIMEM[\"unknown\",2.5,\n"
@@ -5411,6 +5799,21 @@ TEST(io, projparse_longlat_pm_numeric) {
// ---------------------------------------------------------------------------
+TEST(io, projparse_longlat_pm_overriding_datum) {
+ // It is arguable that we allow the prime meridian of a datum defined by
+ // its name to be overriden, but this is found at least in a regression test
+ // of GDAL. So let's keep the ellipsoid part of the datum in that case and
+ // use the specified prime meridian.
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84 +pm=ferro");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->datum()->nameStr(), "Unknown based on WGS 84 ellipsoid");
+ EXPECT_EQ(crs->datum()->primeMeridian()->nameStr(), "Ferro");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(io, projparse_longlat_complex) {
std::string input =
"+proj=pipeline +step +proj=longlat +ellps=clrk80ign "
@@ -5455,7 +5858,7 @@ TEST(io, projparse_longlat_towgs84_3_terms) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,0,0,0,0");
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,0,0,0,0 +no_defs");
}
// ---------------------------------------------------------------------------
@@ -5490,7 +5893,7 @@ TEST(io, projparse_longlat_towgs84_7_terms) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,7");
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,7 +no_defs");
}
// ---------------------------------------------------------------------------
@@ -5514,7 +5917,7 @@ TEST(io, projparse_longlat_nadgrids) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +ellps=GRS80 +nadgrids=foo.gsb");
+ "+proj=longlat +ellps=GRS80 +nadgrids=foo.gsb +no_defs");
}
// ---------------------------------------------------------------------------
@@ -5542,7 +5945,7 @@ TEST(io, projparse_longlat_geoidgrids) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +vunits=m");
+ "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +vunits=m +no_defs");
}
// ---------------------------------------------------------------------------
@@ -5839,6 +6242,13 @@ TEST(io, projparse_tmerc_south_oriented) {
" LENGTHUNIT[\"metre\",1]]]";
EXPECT_EQ(f->toString(), expected);
+
+ obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=tmerc +x_0=1 +lat_0=1 +k_0=2 +step "
+ "+proj=axisswap +order=-1,-2");
+ crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ EXPECT_EQ(crs->derivingConversion()->method()->nameStr(),
+ "Transverse Mercator (South Orientated)");
}
// ---------------------------------------------------------------------------
@@ -6093,14 +6503,26 @@ TEST(io, projparse_etmerc) {
auto obj = PROJStringParser().createFromPROJString("+proj=etmerc");
auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
ASSERT_TRUE(crs != nullptr);
- WKTFormatterNNPtr f(WKTFormatter::create());
- f->simulCurNodeHasId();
- f->setMultiLine(false);
- crs->exportToWKT(f.get());
- auto wkt = f->toString();
- EXPECT_TRUE(wkt.find("METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]]") !=
- std::string::npos)
- << wkt;
+ auto wkt2 = crs->exportToWKT(
+ &WKTFormatter::create()->simulCurNodeHasId().setMultiLine(false));
+ EXPECT_TRUE(
+ wkt2.find("METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]]") !=
+ std::string::npos)
+ << wkt2;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=etmerc +lat_0=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 "
+ "+datum=WGS84 +units=m +no_defs");
+
+ auto wkt1 = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ EXPECT_TRUE(wkt1.find("EXTENSION[\"PROJ4\",\"+proj=etmerc +lat_0=0 "
+ "+lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m "
+ "+no_defs\"]") != std::string::npos)
+ << wkt1;
}
// ---------------------------------------------------------------------------
@@ -6181,6 +6603,39 @@ TEST(io, projparse_merc_stere_polar_variant_A) {
// ---------------------------------------------------------------------------
+TEST(io, projparse_merc_stere_polar_k_and_lat_ts) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=stere +lat_0=90 +lat_ts=90 +k=1");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto wkt = crs->exportToWKT(
+ &(WKTFormatter::create()->simulCurNodeHasId().setMultiLine(false)));
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Polar Stereographic (variant B)\",ID[\"EPSG\",9829]]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Latitude of standard parallel\",90") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=stere +lat_0=90 +lat_ts=90 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 "
+ "+units=m +no_defs");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_stere_polar_k_and_lat_ts_incompatible) {
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=stere +lat_0=90 +lat_ts=70 +k=0.994"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(io, projparse_merc_stere) {
auto obj = PROJStringParser().createFromPROJString("+proj=stere +lat_0=30");
auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
@@ -6239,7 +6694,8 @@ TEST(io, projparse_utm_south) {
// ---------------------------------------------------------------------------
TEST(io, projparse_non_earth_ellipsoid) {
- std::string input("+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +R=1");
+ std::string input("+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +R=1 +units=m "
+ "+no_defs");
auto obj = PROJStringParser().createFromPROJString(input);
auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
ASSERT_TRUE(crs != nullptr);
@@ -6435,7 +6891,7 @@ TEST(io, projparse_projected_unknown) {
"84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY["
"\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\","
"\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\","
- "\"9122\"]],AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]],"
+ "\"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 "
@@ -6456,7 +6912,7 @@ TEST(io, projparse_projected_unknown) {
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
"+proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 +x_0=10 "
- "+y_0=0 +datum=WGS84");
+ "+y_0=0 +datum=WGS84 +units=m +no_defs");
EXPECT_EQ(
crs->exportToPROJString(
@@ -6512,6 +6968,39 @@ TEST(io, projparse_geocent) {
// ---------------------------------------------------------------------------
+TEST(io, projparse_geocent_towgs84) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=geocent +ellps=WGS84 +towgs84=1,2,3");
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("METHOD[\"Geocentric translations (geocentric domain)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"X-axis translation\",1") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Y-axis translation\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Z-axis translation\",3") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=geocent +ellps=WGS84 +towgs84=1,2,3,0,0,0,0 +units=m +no_defs");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(io, projparse_cart_unit) {
std::string input(
"+proj=pipeline +step +proj=cart +ellps=WGS84 +step "
@@ -6544,6 +7033,48 @@ TEST(io, projparse_cart_unit_numeric) {
// ---------------------------------------------------------------------------
+TEST(io, projparse_longlat_wktext) {
+ std::string input("+proj=longlat +foo +wktext");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_geocent_wktext) {
+ std::string input("+proj=geocent +foo +wktext");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_wktext) {
+ std::string input("+proj=merc +foo +wktext");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(io, projparse_ob_tran_longlat) {
std::string input(
"+proj=pipeline +step +proj=axisswap +order=2,1 +step "
@@ -6872,7 +7403,8 @@ TEST(io, projparse_longlat_title) {
crs->exportToPROJString(
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
- "+proj=longlat +ellps=intl +towgs84=109.753,-528.133,-362.244,0,0,0,0");
+ "+proj=longlat +ellps=intl +towgs84=109.753,-528.133,-362.244,0,0,0,0 "
+ "+no_defs");
}
// ---------------------------------------------------------------------------
@@ -6895,7 +7427,7 @@ TEST(io, projparse_projected_title) {
PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
.get()),
"+proj=utm +zone=43 +south +ellps=intl "
- "+towgs84=109.753,-528.133,-362.244,0,0,0,0");
+ "+towgs84=109.753,-528.133,-362.244,0,0,0,0 +units=m +no_defs");
}
// ---------------------------------------------------------------------------
@@ -6992,6 +7524,10 @@ TEST(io, projparse_longlat_errors) {
"+proj=longlat +a=1 +rf=invalid"),
ParsingException);
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=1 +f=invalid"),
+ ParsingException);
+
EXPECT_THROW(
PROJStringParser().createFromPROJString("+proj=longlat +R=invalid"),
ParsingException);
@@ -7005,6 +7541,9 @@ TEST(io, projparse_longlat_errors) {
EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=longlat +rf=1"),
ParsingException);
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=longlat +f=0"),
+ ParsingException);
+
EXPECT_THROW(
PROJStringParser().createFromPROJString("+proj=longlat +pm=unknown"),
ParsingException);
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index 2d2688a8..e62eddf9 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -1185,13 +1185,13 @@ TEST(operation, tmerc_export) {
auto conv = Conversion::createTransverseMercator(
PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=tmerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+ "+proj=tmerc +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5");
{
auto formatter = PROJStringFormatter::create();
formatter->setUseETMercForTMerc(true);
EXPECT_EQ(conv->exportToPROJString(formatter.get()),
- "+proj=etmerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+ "+proj=etmerc +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5");
}
EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
@@ -1270,7 +1270,7 @@ TEST(operation, tmerc_south_oriented_export) {
PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=tmerc +axis=wsu +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+ "+proj=tmerc +axis=wsu +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5");
EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
"CONVERSION[\"Transverse Mercator (South Orientated)\",\n"
@@ -1329,7 +1329,7 @@ TEST(operation, tmerc_south_oriented_export) {
EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
"+proj=pipeline +step +proj=axisswap +order=2,1 +step "
"+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=tmerc "
- "+axis=wsu +lat_0=0 +lon_0=29 +k_0=1 +x_0=0 +y_0=0 +ellps=WGS84");
+ "+axis=wsu +lat_0=0 +lon_0=29 +k=1 +x_0=0 +y_0=0 +ellps=WGS84");
}
// ---------------------------------------------------------------------------
@@ -1986,7 +1986,7 @@ TEST(operation, createEquidistantCylindrical) {
PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=eqc +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4");
+ "+proj=eqc +lat_ts=1 +lat_0=0 +lon_0=2 +x_0=3 +y_0=4");
EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
"CONVERSION[\"Equidistant Cylindrical\",\n"
@@ -2022,7 +2022,7 @@ TEST(operation, createEquidistantCylindricalSpherical) {
PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=eqc +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4");
+ "+proj=eqc +lat_ts=1 +lat_0=0 +lon_0=2 +x_0=3 +y_0=4");
EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
"CONVERSION[\"Equidistant Cylindrical (Spherical)\",\n"
@@ -2053,6 +2053,28 @@ TEST(operation, createEquidistantCylindricalSpherical) {
// ---------------------------------------------------------------------------
+TEST(operation, equidistant_cylindrical_lat_0) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=eqc +ellps=sphere +lat_0=-2 +lat_ts=1 +lon_0=-10");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto wkt = crs->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Latitude of natural origin\",-2") !=
+ std::string::npos)
+ << wkt;
+
+ auto projString = crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get());
+ EXPECT_EQ(projString,
+ "+proj=eqc +lat_ts=1 +lat_0=-2 +lon_0=-10 +x_0=0 +y_0=0 "
+ "+ellps=sphere +units=m +no_defs");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, gall_export) {
auto conv =
@@ -2173,10 +2195,18 @@ TEST(operation, geostationary_satellite_sweep_x_export) {
" LENGTHUNIT[\"metre\",1],\n"
" ID[\"EPSG\",8807]]]");
- EXPECT_THROW(
- conv->exportToWKT(
- WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
- FormattingException);
+ auto crs = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326, conv,
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto wkt1 = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ EXPECT_TRUE(wkt1.find("PROJECTION[\"Geostationary_Satellite\"]") !=
+ std::string::npos)
+ << wkt1;
+ EXPECT_TRUE(wkt1.find("EXTENSION[\"PROJ4\",\"+proj=geos +sweep=x +lon_0=1 "
+ "+h=2 +x_0=3 +y_0=4 +datum=WGS84 +units=m "
+ "+no_defs\"]]") != std::string::npos)
+ << wkt1;
}
// ---------------------------------------------------------------------------
@@ -2410,7 +2440,7 @@ TEST(operation, hotine_oblique_mercator_two_point_natural_origin_export) {
conv->exportToWKT(
WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
"PROJECTION[\"Hotine_Oblique_Mercator_Two_Point_Natural_Origin\"],\n"
- "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"latitude_of_center\",1],\n"
"PARAMETER[\"latitude_of_point_1\",2],\n"
"PARAMETER[\"longitude_of_point_1\",3],\n"
"PARAMETER[\"latitude_of_point_2\",4],\n"
@@ -2435,12 +2465,12 @@ TEST(operation, imw_polyconic_export) {
" PARAMETER[\"Longitude of natural origin\",1,\n"
" ANGLEUNIT[\"degree\",0.0174532925199433],\n"
" ID[\"EPSG\",8802]],\n"
- " PARAMETER[\"Latitude of 1st standard parallel\",3,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8823]],\n"
- " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n"
- " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
- " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Latitude of 1st point\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Latitude of 2nd point\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
" PARAMETER[\"False easting\",5,\n"
" LENGTHUNIT[\"metre\",1],\n"
" ID[\"EPSG\",8806]],\n"
@@ -2453,8 +2483,8 @@ TEST(operation, imw_polyconic_export) {
WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
"PROJECTION[\"International_Map_of_the_World_Polyconic\"],\n"
"PARAMETER[\"central_meridian\",1],\n"
- "PARAMETER[\"standard_parallel_1\",3],\n"
- "PARAMETER[\"standard_parallel_2\",4],\n"
+ "PARAMETER[\"Latitude_Of_1st_Point\",3],\n"
+ "PARAMETER[\"Latitude_Of_2nd_Point\",4],\n"
"PARAMETER[\"false_easting\",5],\n"
"PARAMETER[\"false_northing\",6]");
}
@@ -2467,7 +2497,8 @@ TEST(operation, krovak_north_oriented_export) {
Angle(78.5), Scale(0.9999), Length(5), Length(6));
EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=krovak +lat_0=49.5 +lon_0=42.5 +k=0.9999 +x_0=5 +y_0=6");
+ "+proj=krovak +lat_0=49.5 +lon_0=42.5 +alpha=30.2881397222222 "
+ "+k=0.9999 +x_0=5 +y_0=6");
EXPECT_EQ(
conv->exportToWKT(WKTFormatter::create().get()),
@@ -2517,7 +2548,8 @@ TEST(operation, krovak_export) {
Angle(78.5), Scale(0.9999), Length(5), Length(6));
EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=krovak +axis=swu +lat_0=49.5 +lon_0=42.5 +k=0.9999 +x_0=5 "
+ "+proj=krovak +axis=swu +lat_0=49.5 +lon_0=42.5 "
+ "+alpha=30.2881397222222 +k=0.9999 +x_0=5 "
"+y_0=6");
EXPECT_EQ(
@@ -2706,6 +2738,36 @@ TEST(operation, wkt1_import_mercator_variant_A) {
// ---------------------------------------------------------------------------
+TEST(operation, wkt1_import_mercator_variant_A_that_is_variant_B) {
+ // Adresses https://trac.osgeo.org/gdal/ticket/3026
+ auto wkt = "PROJCS[\"test\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS 1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Mercator_1SP\"],\n"
+ " PARAMETER[\"latitude_of_origin\",-1],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"scale_factor\",1],\n"
+ " PARAMETER[\"false_easting\",3],\n"
+ " PARAMETER[\"false_northing\",4],\n"
+ " UNIT[\"metre\",1]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto conversion = crs->derivingConversion();
+ auto convRef = Conversion::createMercatorVariantB(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(-1),
+ Angle(2), Length(3), Length(4));
+
+ EXPECT_TRUE(conversion->isEquivalentTo(convRef.get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, mercator_variant_B_export) {
auto conv = Conversion::createMercatorVariantB(
PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
@@ -2742,6 +2804,70 @@ TEST(operation, mercator_variant_B_export) {
// ---------------------------------------------------------------------------
+TEST(operation, odd_mercator_1sp_with_non_null_latitude) {
+ auto obj = WKTParser().createFromWKT(
+ "PROJCS[\"unnamed\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Mercator_1SP\"],\n"
+ " PARAMETER[\"latitude_of_origin\",30],\n"
+ " PARAMETER[\"central_meridian\",0],\n"
+ " PARAMETER[\"scale_factor\",0.99],\n"
+ " PARAMETER[\"false_easting\",0],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]");
+
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_THROW(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, odd_mercator_2sp_with_latitude_of_origin) {
+ auto obj = WKTParser().createFromWKT(
+ "PROJCS[\"unnamed\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Mercator_2SP\"],\n"
+ " PARAMETER[\"standard_parallel_1\",30],\n"
+ " PARAMETER[\"latitude_of_origin\",40],\n"
+ " PARAMETER[\"central_meridian\",0],\n"
+ " PARAMETER[\"false_easting\",0],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]");
+
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_THROW(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, webmerc_export) {
auto conv = Conversion::createPopularVisualisationPseudoMercator(
PropertyMap(), Angle(0), Angle(2), Length(3), Length(4));
@@ -2790,8 +2916,6 @@ TEST(operation, webmerc_export) {
" AUTHORITY[\"EPSG\",\"8901\"]],\n"
" UNIT[\"degree\",0.0174532925199433,\n"
" AUTHORITY[\"EPSG\",\"9122\"]],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST],\n"
" AUTHORITY[\"EPSG\",\"4326\"]],\n"
" PROJECTION[\"Mercator_1SP\"],\n"
" PARAMETER[\"central_meridian\",2],\n"
@@ -2994,9 +3118,7 @@ TEST(operation, webmerc_import_from_WKT2_EPSG_3785_deprecated) {
" SPHEROID[\"Popular Visualisation Sphere\",6378137,0],\n"
" TOWGS84[0,0,0,0,0,0,0]],\n"
" PRIMEM[\"Greenwich\",0],\n"
- " UNIT[\"degree\",0.0174532925199433],\n"
- " AXIS[\"Latitude\",NORTH],\n"
- " AXIS[\"Longitude\",EAST]],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
" PROJECTION[\"Mercator_1SP\"],\n"
" PARAMETER[\"central_meridian\",0],\n"
" PARAMETER[\"scale_factor\",1],\n"
@@ -3730,7 +3852,7 @@ TEST(operation, conversion_inverse) {
" ID[\"EPSG\",8807]]]");
EXPECT_EQ(inv->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k_0=3 "
+ "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k=3 "
"+x_0=4 +y_0=5");
EXPECT_TRUE(inv->isEquivalentTo(inv.get()));
@@ -5463,7 +5585,7 @@ TEST(operation, compoundCRS_to_compoundCRS_with_vertical_transform) {
compound2);
ASSERT_TRUE(op != nullptr);
EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k_0=3 "
+ "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k=3 "
"+x_0=4 +y_0=5 +ellps=WGS84 +step "
"+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step "
"+proj=utm +zone=32 "
@@ -5473,7 +5595,7 @@ TEST(operation, compoundCRS_to_compoundCRS_with_vertical_transform) {
formatter->setUseETMercForTMerc(true);
EXPECT_EQ(op->exportToPROJString(formatter.get()),
"+proj=pipeline +step +inv +proj=etmerc +lat_0=1 +lon_0=2 "
- "+k_0=3 +x_0=4 +y_0=5 +ellps=WGS84 +step "
+ "+k=3 +x_0=4 +y_0=5 +ellps=WGS84 +step "
"+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step "
"+proj=utm +zone=32 "
"+ellps=WGS84");
@@ -5485,7 +5607,7 @@ TEST(operation, compoundCRS_to_compoundCRS_with_vertical_transform) {
"+proj=pipeline +step +inv +proj=utm +zone=32 +ellps=WGS84 "
"+step +inv +proj=vgridshift +grids=bla.gtx "
"+multiplier=0.001 +step +proj=etmerc +lat_0=1 +lon_0=2 "
- "+k_0=3 +x_0=4 +y_0=5 +ellps=WGS84");
+ "+k=3 +x_0=4 +y_0=5 +ellps=WGS84");
}
auto opInverse = CoordinateOperationFactory::create()->createOperation(