aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2019-11-04 14:58:24 +0100
committerEven Rouault <even.rouault@spatialys.com>2019-11-04 14:58:24 +0100
commitc975c6d13f5c0c13c57834c15ecd265a9d91a705 (patch)
tree556baa55635a44b21e83366d9e92254f3cce0b23
parentcff22050faa67017eb4c5b44cb383b265c07d156 (diff)
parent90c1166e316cc95296cd6db3ab1fc81243310de6 (diff)
downloadPROJ-c975c6d13f5c0c13c57834c15ecd265a9d91a705.tar.gz
PROJ-c975c6d13f5c0c13c57834c15ecd265a9d91a705.zip
Merge remote-tracking branch 'origin/master' into geoid_model
-rw-r--r--cmake/ProjTest.cmake2
-rw-r--r--docs/source/_extensions/redirects.py39
-rw-r--r--docs/source/conf.py5
-rw-r--r--docs/source/download.rst6
-rw-r--r--docs/source/news.rst73
-rw-r--r--docs/source/usage/index.rst2
-rw-r--r--docs/source/usage/projjson.rst261
-rw-r--r--src/iso19111/c_api.cpp20
-rw-r--r--src/iso19111/coordinateoperation.cpp84
-rw-r--r--src/iso19111/io.cpp55
-rw-r--r--test/unit/test_c_api.cpp6
-rw-r--r--test/unit/test_io.cpp34
-rw-r--r--test/unit/test_operation.cpp43
13 files changed, 596 insertions, 34 deletions
diff --git a/cmake/ProjTest.cmake b/cmake/ProjTest.cmake
index 277e107a..ad64d0ba 100644
--- a/cmake/ProjTest.cmake
+++ b/cmake/ProjTest.cmake
@@ -36,7 +36,7 @@ endfunction()
function(proj_add_gie_test TESTNAME TESTCASE)
- set(GIE_BIN "gie")
+ set(GIE_BIN $<TARGET_FILE_NAME:gie>)
set(TESTFILE ${CMAKE_SOURCE_DIR}/test/${TESTCASE})
add_test(NAME ${TESTNAME}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test
diff --git a/docs/source/_extensions/redirects.py b/docs/source/_extensions/redirects.py
new file mode 100644
index 00000000..6a59b622
--- /dev/null
+++ b/docs/source/_extensions/redirects.py
@@ -0,0 +1,39 @@
+import os
+
+# https://tech.signavio.com/2017/managing-sphinx-redirects
+
+
+template="""<html>
+ <head>
+ <meta http-equiv="refresh" content="1; url=%s" />
+ <script>
+ window.location.href = "%s"
+ </script>
+ </head>
+</html>"""
+
+
+def gather_redirects():
+ output = {}
+
+ output.update({ 'projjson.html' : 'usage/projjson.html' })
+ return output
+
+from shutil import copyfile
+# copy legacy redirects
+def copy_legacy_redirects(app, docname): # Sphinx expects two arguments
+ if app.builder.name == 'html':
+ for key in app.config.redirect_files:
+ src = key
+ tgt = app.config.redirect_files[key]
+ html = template % (tgt, tgt)
+ with open(os.path.join(app.outdir, src), 'wb') as f:
+ f.write(html.encode('utf-8'))
+ f.close()
+
+
+
+def setup(app):
+ app.add_config_value('redirect_files', {}, 'html')
+ app.connect('build-finished', copy_legacy_redirects)
+ return { 'parallel_read_safe': False, 'parallel_write_safe': True }
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 243c93b7..cd004777 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -20,6 +20,7 @@ import datetime
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('_extensions'))
import bibstyle
@@ -35,6 +36,7 @@ extensions = [
'sphinx.ext.mathjax',
'sphinxcontrib.bibtex',
'breathe',
+ 'redirects',
]
# Add any paths that contain templates here, relative to this directory.
@@ -370,3 +372,6 @@ texinfo_documents = [
breathe_projects = {
"cpp_stuff":"../build/xml/",
}
+
+import redirects
+redirect_files = redirects.gather_redirects()
diff --git a/docs/source/download.rst b/docs/source/download.rst
index 9ec03368..36e2a897 100644
--- a/docs/source/download.rst
+++ b/docs/source/download.rst
@@ -13,7 +13,7 @@ distribution of the source code and various resource file archives. See
Current Release
--------------------------------------------------------------------------------
-* **2019-09-01** `proj-6.2.0.tar.gz`_ (`md5`_)
+* **2019-11-01** `proj-6.2.1.tar.gz`_ (`md5`_)
* **2018-09-15** `proj-datumgrid-1.8.zip`_
* **2019-09-01** `proj-datumgrid-europe-1.4.zip`_
* **2019-03-01** `proj-datumgrid-north-america-1.2.zip`_
@@ -24,6 +24,7 @@ Current Release
Past Releases
--------------------------------------------------------------------------------
+* **2019-09-01** `proj-6.2.0.tar.gz`_
* **2019-07-01** `proj-6.1.1.tar.gz`_
* **2019-05-15** `proj-6.1.0.tar.gz`_
* **2019-03-01** `proj-6.0.0.tar.gz`_
@@ -44,6 +45,7 @@ Past Releases
* **2018-03-01** `proj-datumgrid-europe-1.0.zip`_
* **2018-03-01** `proj-datumgrid-north-america-1.0.zip`_
+.. _`proj-6.2.1.tar.gz`: https://download.osgeo.org/proj/proj-6.2.1.tar.gz
.. _`proj-6.2.0.tar.gz`: https://download.osgeo.org/proj/proj-6.2.0.tar.gz
.. _`proj-6.1.1.tar.gz`: https://download.osgeo.org/proj/proj-6.1.1.tar.gz
.. _`proj-6.1.0.tar.gz`: https://download.osgeo.org/proj/proj-6.1.0.tar.gz
@@ -68,5 +70,5 @@ Past Releases
.. _`proj-datumgrid-north-america-1.2.zip`: https://download.osgeo.org/proj/proj-datumgrid-north-america-1.2.zip
.. _`proj-datumgrid-oceania-1.0.zip`: https://download.osgeo.org/proj/proj-datumgrid-oceania-1.0.zip
.. _`proj-datumgrid-world-1.0.zip`: https://download.osgeo.org/proj/proj-datumgrid-world-1.0.zip
-.. _`md5`: https://download.osgeo.org/proj/proj-6.2.0.tar.gz.md5
+.. _`md5`: https://download.osgeo.org/proj/proj-6.2.1.tar.gz.md5
.. _`proj.pdf`: https://raw.githubusercontent.com/OSGeo/PROJ/gh-pages/proj.pdf
diff --git a/docs/source/news.rst b/docs/source/news.rst
index 2a6e8e47..17e573a7 100644
--- a/docs/source/news.rst
+++ b/docs/source/news.rst
@@ -3,6 +3,77 @@
News
###############################################################################
+6.2.1 Release Notes
+++++++++++++++++++++++++++++++++++++++++
+*November 1st 2019*
+
+Updates
+-------
+
+* Update the EPSG database to version 9.8.2
+
+Bug fixes
+----------
+
+* Fixed erroneous spelling of "Potsdam" (`#1573 <https://github.com/OSGeo/PROJ/issues/1573>`_)
+
+* Calculate y-coordinate correctly in :ref:`bertin1953` in all cases (`#1579 <https://github.com/OSGeo/PROJ/issues/1579>`_)
+
+* :c:func:`proj_create_crs_to_crs_from_pj()`: make the PJ* arguments const PJ* (`#1583 <https://github.com/OSGeo/PROJ/issues/1583>`_)
+
+* :c:func:`PROJStringParser::createFromPROJString()`: avoid potential infinite
+ recursion (`#1574 <https://github.com/OSGeo/PROJ/issues/1574>`_)
+
+* Avoid core dump when setting ``ctx==NULL`` in functions
+ :c:func:`proj_coordoperation_is_instantiable` and
+ :c:func:`proj_coordoperation_has_ballpark_transformation` (`#1590 <https://github.com/OSGeo/PROJ/issues/1590>`_)
+
+* :c:func:`createOperations()`: fix conversion from/to PROJ.4 CRS strings with
+ non-ISO-kosher options and ``+towgs84``/``+nadgrids`` (`#1602 <https://github.com/OSGeo/PROJ/issues/1602>`_)
+
+* :c:func:`proj_trans_generic()`: properly set coordinate time to ``HUGE_VAL``
+ when no value is passed to the function (`#1604 <https://github.com/OSGeo/PROJ/issues/1604>`_)
+
+* Fix support for ``+proj=ob_tran +o_proj=lonlat/latlong/latlon`` instead of only
+ only allowing ``+o_proj=longlat`` (`#1601 <https://github.com/OSGeo/PROJ/issues/1601>`_)
+
+* Improve backwards compatibility of vertical transforms (`#1613 <https://github.com/OSGeo/PROJ/issues/1613>`_)
+
+* Improve emulation of deprecated ``+init`` style initialization (`#1614 <https://github.com/OSGeo/PROJ/issues/1614>`_)
+
+* :program:`cs2cs`: autopromote CRS to 3D when there's a mix of 2D and 3D (`#1563 <https://github.com/OSGeo/PROJ/issues/1563>`_)
+
+* Avoid divisions by zero in odd situations (`#1620 <https://github.com/OSGeo/PROJ/issues/1620>`_)
+
+* Avoid compile error on Solaris (`#1639 <https://github.com/OSGeo/PROJ/issues/1639>`_)
+
+* :c:func:`proj_create_crs_to_crs()`: fix when there are only transformations with
+ ballpark steps (`#1643 <https://github.com/OSGeo/PROJ/issues/1643>`_)
+
+* PROJ string CRS ingester: recognize more unit-less parameters, and general
+ handling of ``+key=string_value`` parameters (`#1645 <https://github.com/OSGeo/PROJ/issues/1645>`_)
+
+* Only call pkg-config in configure when necessary (`#1652 <https://github.com/OSGeo/PROJ/issues/1652>`_)
+
+* :ref:`aeqd`: for spherical forward path, go to higher precision ellipsoidal
+ case when the point coordinates are super close to the origin (`#1654 <https://github.com/OSGeo/PROJ/issues/1654>`_)
+
+* :c:func:`proj_create_crs_to_crs()`: remove elimination of Ballpark operations
+ that caused transformation failures in some cases (`#1665 <https://github.com/OSGeo/PROJ/issues/1665>`_)
+
+* :c:func:`createOperations()`: allow transforming from a compoundCRS of a bound
+ verticalCRS to a 2D CRS (`#1667 <https://github.com/OSGeo/PROJ/issues/1667>`_)
+
+* Avoid segfaults in case of out-of-memory situations (`#1679 <https://github.com/OSGeo/PROJ/issues/1679>`_)
+
+* :c:func:`createOperations()`: fix double vertical unit conversion from CompoundCRS
+ to other CRS when the horizontal part of the projected CRS uses non-metre
+ unit (#1683)(`#1683 <https://github.com/OSGeo/PROJ/issues/1683>`_)
+
+* :c:func:`importFromWkt()`: fix axis orientation for non-standard ESRI WKT (`#1690 <https://github.com/OSGeo/PROJ/issues/1690>`_)
+
+
+
6.2.0 Release Notes
++++++++++++++++++++++++++++++++++++++++
*September 1st 2019*
@@ -10,7 +81,7 @@ News
Updates
-------
- * Introduced PROJJSON, a JSON encoding of WKT2 (`#1547 <https://github.com/OSGeo/PROJ/issues/15475>`_)
+ * Introduced :ref:`PROJJSON`, a JSON encoding of WKT2 (`#1547 <https://github.com/OSGeo/PROJ/issues/1547>`_)
* Support CRS instantiation of OGC URN's (`#1505 <https://github.com/OSGeo/PROJ/issues/1505>`_)
* Expose scope and remarks of database objects (`#1537 <https://github.com/OSGeo/PROJ/issues/1537>`_)
diff --git a/docs/source/usage/index.rst b/docs/source/usage/index.rst
index 823e8fe7..c31c6dce 100644
--- a/docs/source/usage/index.rst
+++ b/docs/source/usage/index.rst
@@ -17,3 +17,5 @@ command line applications or the C API that is a part of the software package.
transformation
environmentvars
differences
+ projjson
+
diff --git a/docs/source/usage/projjson.rst b/docs/source/usage/projjson.rst
new file mode 100644
index 00000000..09123711
--- /dev/null
+++ b/docs/source/usage/projjson.rst
@@ -0,0 +1,261 @@
+.. _projjson:
+
+================================================================================
+PROJJSON
+================================================================================
+
+PROJJSON is a JSON encoding of
+`WKT2:2019 / ISO-19162:2019 <http://docs.opengeospatial.org/is/18-010r7/18-010r7.html>`_,
+which itself implements the model of
+`OGC Topic 2: Referencing by coordinates <http://docs.opengeospatial.org/as/18-005r4/18-005r4.html>`_.
+Apart from the difference of encodings, the semantics is intented to be exactly
+the same as WKT2:2019.
+
+PROJJSON is available as input and output of PROJ since PROJ 6.2.
+
+The current version is 0.1.
+
+Schema
+------
+
+A JSON schema of its grammar is available at
+https://proj.org/schemas/v0.1/projjson.schema.json
+
+Content
+-------
+
+The high level objects are:
+
+* Coordinate Reference Systems (CRS):
+
+ - Common ones:
+
+ + ``GeographicCRS``
+ + ``GeodeticCRS``
+ + ``ProjectedCRS``
+ + ``CompoundCRS``
+ + ``BoundCRS``
+
+ - More esoteric ones:
+
+ + ``VerticalCRS``
+ + ``EngineeringCRS``
+ + ``TemporalCRS``
+ + ``ParametricCRS``
+ + ``DerivedGeographicCRS``
+ + ``DerivedGeodeticCRS``
+ + ``DerivedProjectedCRS``
+ + ``DerivedVerticalCRS``
+ + ``DerivedEngineeringCRS``
+ + ``DerivedTemporalCRS``
+ + ``DerivedParametricCRS``
+
+* Coordinate operations:
+
+ - ``Transformation``
+ - ``Conversion``
+ - ``ConcatenatedOperation``
+
+* Others:
+
+ - ``PrimeMeridian``
+ - ``Ellipsoid``
+ - ``Datum``
+ - ``DatumEnsemble``
+
+Examples
+--------
+
+GeographicCRS
++++++++++++++
+
+The following invokation
+
+::
+
+ projinfo EPSG:4326 -o PROJJSON -q
+
+will output:
+
+.. code-block:: json
+
+ {
+ "$schema": "https://proj.org/schemas/v0.1/projjson.schema.json",
+ "type": "GeographicCRS",
+ "name": "WGS 84",
+ "datum": {
+ "type": "GeodeticReferenceFrame",
+ "name": "World Geodetic System 1984",
+ "ellipsoid": {
+ "name": "WGS 84",
+ "semi_major_axis": 6378137,
+ "inverse_flattening": 298.257223563
+ }
+ },
+ "coordinate_system": {
+ "subtype": "ellipsoidal",
+ "axis": [
+ {
+ "name": "Geodetic latitude",
+ "abbreviation": "Lat",
+ "direction": "north",
+ "unit": "degree"
+ },
+ {
+ "name": "Geodetic longitude",
+ "abbreviation": "Lon",
+ "direction": "east",
+ "unit": "degree"
+ }
+ ]
+ },
+ "area": "World",
+ "bbox": {
+ "south_latitude": -90,
+ "west_longitude": -180,
+ "north_latitude": 90,
+ "east_longitude": 180
+ },
+ "id": {
+ "authority": "EPSG",
+ "code": 4326
+ }
+ }
+
+
+ProjectedCRS
+++++++++++++
+
+The following invokation
+
+::
+
+ projinfo EPSG:32631 -o PROJJSON -q
+
+will output:
+
+.. code-block:: json
+
+ {
+ "$schema": "https://proj.org/schemas/v0.1/projjson.schema.json",
+ "type": "ProjectedCRS",
+ "name": "WGS 84 / UTM zone 31N",
+ "base_crs": {
+ "name": "WGS 84",
+ "datum": {
+ "type": "GeodeticReferenceFrame",
+ "name": "World Geodetic System 1984",
+ "ellipsoid": {
+ "name": "WGS 84",
+ "semi_major_axis": 6378137,
+ "inverse_flattening": 298.257223563
+ }
+ },
+ "coordinate_system": {
+ "subtype": "ellipsoidal",
+ "axis": [
+ {
+ "name": "Geodetic latitude",
+ "abbreviation": "Lat",
+ "direction": "north",
+ "unit": "degree"
+ },
+ {
+ "name": "Geodetic longitude",
+ "abbreviation": "Lon",
+ "direction": "east",
+ "unit": "degree"
+ }
+ ]
+ },
+ "id": {
+ "authority": "EPSG",
+ "code": 4326
+ }
+ },
+ "conversion": {
+ "name": "UTM zone 31N",
+ "method": {
+ "name": "Transverse Mercator",
+ "id": {
+ "authority": "EPSG",
+ "code": 9807
+ }
+ },
+ "parameters": [
+ {
+ "name": "Latitude of natural origin",
+ "value": 0,
+ "unit": "degree",
+ "id": {
+ "authority": "EPSG",
+ "code": 8801
+ }
+ },
+ {
+ "name": "Longitude of natural origin",
+ "value": 3,
+ "unit": "degree",
+ "id": {
+ "authority": "EPSG",
+ "code": 8802
+ }
+ },
+ {
+ "name": "Scale factor at natural origin",
+ "value": 0.9996,
+ "unit": "unity",
+ "id": {
+ "authority": "EPSG",
+ "code": 8805
+ }
+ },
+ {
+ "name": "False easting",
+ "value": 500000,
+ "unit": "metre",
+ "id": {
+ "authority": "EPSG",
+ "code": 8806
+ }
+ },
+ {
+ "name": "False northing",
+ "value": 0,
+ "unit": "metre",
+ "id": {
+ "authority": "EPSG",
+ "code": 8807
+ }
+ }
+ ]
+ },
+ "coordinate_system": {
+ "subtype": "Cartesian",
+ "axis": [
+ {
+ "name": "Easting",
+ "abbreviation": "E",
+ "direction": "east",
+ "unit": "metre"
+ },
+ {
+ "name": "Northing",
+ "abbreviation": "N",
+ "direction": "north",
+ "unit": "metre"
+ }
+ ]
+ },
+ "area": "World - N hemisphere - 0°E to 6°E - by country",
+ "bbox": {
+ "south_latitude": 0,
+ "west_longitude": 0,
+ "north_latitude": 84,
+ "east_longitude": 6
+ },
+ "id": {
+ "authority": "EPSG",
+ "code": 32631
+ }
+ }
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 32c8df45..7341131f 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -498,7 +498,7 @@ template <class T> static PROJ_STRING_LIST to_string_list(T &&set) {
* proj_string_list_destroy().
* @param out_grammar_errors Pointer to a PROJ_STRING_LIST object, or NULL.
* If provided, *out_grammar_errors will contain a list of errors regarding the
- * WKT grammaer. It must be freed with proj_string_list_destroy().
+ * WKT grammar. It must be freed with proj_string_list_destroy().
* @return Object that must be unreferenced with proj_destroy(), or NULL in
* case of error.
*/
@@ -522,6 +522,7 @@ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt,
if (dbContext) {
parser.attachDatabaseContext(NN_NO_CHECK(dbContext));
}
+ parser.setStrict(false);
for (auto iter = options; iter && iter[0]; ++iter) {
const char *value;
if ((value = getOptionValue(*iter, "STRICT="))) {
@@ -536,10 +537,19 @@ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt,
auto obj = nn_dynamic_pointer_cast<IdentifiedObject>(
parser.createFromWKT(wkt));
+ std::vector<std::string> warningsFromParsing;
if (out_grammar_errors) {
- auto warnings = parser.warningList();
- if (!warnings.empty()) {
- *out_grammar_errors = to_string_list(warnings);
+ auto rawWarnings = parser.warningList();
+ std::vector<std::string> grammarWarnings;
+ for (const auto &msg : rawWarnings) {
+ if (msg.find("Default it to") != std::string::npos) {
+ warningsFromParsing.push_back(msg);
+ } else {
+ grammarWarnings.push_back(msg);
+ }
+ }
+ if (!grammarWarnings.empty()) {
+ *out_grammar_errors = to_string_list(grammarWarnings);
}
}
@@ -548,6 +558,8 @@ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt,
if (derivedCRS) {
auto warnings =
derivedCRS->derivingConversionRef()->validateParameters();
+ warnings.insert(warnings.end(), warningsFromParsing.begin(),
+ warningsFromParsing.end());
if (!warnings.empty()) {
*out_warnings = to_string_list(warnings);
}
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index e8f195e3..c51b4d3d 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -6132,24 +6132,31 @@ void Conversion::_exportToPROJString(
if (!param->proj_name) {
continue;
}
- auto value =
+ const auto value =
parameterValueMeasure(param->wkt2_name, param->epsg_code);
+ double valueConverted = 0;
+ if (value == nullMeasure) {
+ // Deal with missing values. In an ideal world, this would
+ // not happen
+ if (param->epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
+ valueConverted = 1.0;
+ }
+ } else if (param->unit_type ==
+ common::UnitOfMeasure::Type::ANGULAR) {
+ valueConverted =
+ value.convertToUnit(common::UnitOfMeasure::DEGREE);
+ } else {
+ valueConverted = value.getSIValue();
+ }
+
if (mapping->epsg_code ==
EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
strcmp(param->proj_name, "lat_1") == 0) {
- formatter->addParam(
- param->proj_name,
- value.convertToUnit(common::UnitOfMeasure::DEGREE));
- formatter->addParam(
- "lat_0",
- value.convertToUnit(common::UnitOfMeasure::DEGREE));
- } else if (param->unit_type ==
- common::UnitOfMeasure::Type::ANGULAR) {
- formatter->addParam(
- param->proj_name,
- value.convertToUnit(common::UnitOfMeasure::DEGREE));
+ formatter->addParam(param->proj_name, valueConverted);
+ formatter->addParam("lat_0", valueConverted);
} else {
- formatter->addParam(param->proj_name, value.getSIValue());
+ formatter->addParam(param->proj_name, valueConverted);
}
}
@@ -6711,6 +6718,24 @@ static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn,
isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2;
isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2;
}
+
+// ---------------------------------------------------------------------------
+
+static int
+useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties,
+ int nDefaultOperationMethodEPSGCode) {
+ const auto *operationMethodEPSGCode =
+ properties.get("OPERATION_METHOD_EPSG_CODE");
+ if (operationMethodEPSGCode) {
+ const auto boxedValue = dynamic_cast<const util::BoxedValue *>(
+ (*operationMethodEPSGCode).get());
+ if (boxedValue &&
+ boxedValue->type() == util::BoxedValue::Type::INTEGER) {
+ return boxedValue->integerValue();
+ }
+ }
+ return nDefaultOperationMethodEPSGCode;
+}
//! @endcond
// ---------------------------------------------------------------------------
@@ -6739,12 +6764,13 @@ TransformationNNPtr Transformation::createGeocentricTranslations(
isGeog3D);
return create(
properties, sourceCRSIn, targetCRSIn, nullptr,
- createMethodMapNameEPSGCode(
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
isGeocentric
? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC
: isGeog2D
? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D),
+ : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)),
VectorOfParameters{
createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
@@ -6797,11 +6823,12 @@ TransformationNNPtr Transformation::createPositionVector(
isGeog3D);
return createSevenParamsTransform(
properties,
- createMethodMapNameEPSGCode(
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
isGeocentric
? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC
: isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D),
+ : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)),
sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
translationZMetre, rotationXArcSecond, rotationYArcSecond,
rotationZArcSecond, scaleDifferencePPM, accuracies);
@@ -6846,11 +6873,12 @@ TransformationNNPtr Transformation::createCoordinateFrameRotation(
isGeog3D);
return createSevenParamsTransform(
properties,
- createMethodMapNameEPSGCode(
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
isGeocentric
? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC
: isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D),
+ : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D)),
sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
translationZMetre, rotationXArcSecond, rotationYArcSecond,
rotationZArcSecond, scaleDifferencePPM, accuracies);
@@ -6989,12 +7017,13 @@ TransformationNNPtr Transformation::createTimeDependentPositionVector(
isGeog3D);
return createFifteenParamsTransform(
properties,
- createMethodMapNameEPSGCode(
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
isGeocentric
? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC
: isGeog2D
? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D),
+ : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D)),
sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
translationZMetre, rotationXArcSecond, rotationYArcSecond,
rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
@@ -7066,12 +7095,13 @@ TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation(
isGeog3D);
return createFifteenParamsTransform(
properties,
- createMethodMapNameEPSGCode(
+ createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
+ properties,
isGeocentric
? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC
: isGeog2D
? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D
- : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D),
+ : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D)),
sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
translationZMetre, rotationXArcSecond, rotationYArcSecond,
rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
@@ -7609,6 +7639,14 @@ createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom,
addModifiedIdentifier(map, op, true, derivedFrom);
+ const auto so = dynamic_cast<const SingleOperation *>(op);
+ if (so) {
+ const int soMethodEPSGCode = so->method()->getEPSGCode();
+ if (soMethodEPSGCode > 0) {
+ map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode);
+ }
+ }
+
return map;
}
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp
index aab1db4f..645bec0b 100644
--- a/src/iso19111/io.cpp
+++ b/src/iso19111/io.cpp
@@ -3476,6 +3476,14 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
}
propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName);
+ std::vector<bool> foundParameters;
+ if (mapping) {
+ size_t countParams = 0;
+ while (mapping->params[countParams] != nullptr) {
+ ++countParams;
+ }
+ foundParameters.resize(countParams);
+ }
for (const auto &childNode : projCRSNode->GP()->children()) {
if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
const auto &childNodeChildren = childNode->GP()->children();
@@ -3496,11 +3504,18 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
continue;
}
}
- const auto *paramMapping =
+ auto *paramMapping =
mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
if (mapping &&
mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
ci_equal(parameterName, "latitude_of_origin")) {
+ for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
+ if (mapping->params[idx]->epsg_code ==
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
+ foundParameters[idx] = true;
+ break;
+ }
+ }
parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
propertiesParameter.set(
Identifier::CODE_KEY,
@@ -3508,6 +3523,12 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
propertiesParameter.set(Identifier::CODESPACE_KEY,
Identifier::EPSG);
} else if (paramMapping) {
+ for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
+ if (mapping->params[idx] == paramMapping) {
+ foundParameters[idx] = true;
+ break;
+ }
+ }
parameterName = paramMapping->wkt2_name;
if (paramMapping->epsg_code != 0) {
propertiesParameter.set(Identifier::CODE_KEY,
@@ -3531,6 +3552,38 @@ ConversionNNPtr WKTParser::Private::buildProjectionStandard(
}
}
+ // Add back important parameters that should normally be present, but
+ // are sometimes missing. Currently we only deal with Scale factor at
+ // natural origin. This is to avoid a default value of 0 to slip in later.
+ // But such WKT should be considered invalid.
+ if (mapping) {
+ for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
+ if (!foundParameters[idx] &&
+ mapping->params[idx]->epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
+
+ emitRecoverableWarning(
+ "The WKT string lacks a value "
+ "for " EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN
+ ". Default it to 1.");
+
+ PropertyMap propertiesParameter;
+ propertiesParameter.set(
+ Identifier::CODE_KEY,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ propertiesParameter.set(
+ IdentifiedObject::NAME_KEY,
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
+ parameters.push_back(
+ OperationParameter::create(propertiesParameter));
+ values.push_back(ParameterValue::create(
+ Measure(1.0, UnitOfMeasure::SCALE_UNITY)));
+ }
+ }
+ }
+
return Conversion::create(
PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
propertiesMethod, parameters, values)
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index cb59563e..bdadc8b8 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -231,7 +231,8 @@ TEST_F(CApi, proj_create_from_wkt) {
" PRIMEM[\"Greenwich\",0],\n"
" UNIT[\"degree\",0.0174532925199433]]",
nullptr, nullptr, nullptr);
- EXPECT_EQ(obj, nullptr);
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
}
{
PROJ_STRING_LIST warningList = nullptr;
@@ -244,7 +245,8 @@ TEST_F(CApi, proj_create_from_wkt) {
" PRIMEM[\"Greenwich\",0],\n"
" UNIT[\"degree\",0.0174532925199433]]",
nullptr, &warningList, &errorList);
- EXPECT_EQ(obj, nullptr);
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
EXPECT_EQ(warningList, nullptr);
proj_string_list_destroy(warningList);
EXPECT_NE(errorList, nullptr);
diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp
index 79851695..a71b63bb 100644
--- a/test/unit/test_io.cpp
+++ b/test/unit/test_io.cpp
@@ -1174,6 +1174,40 @@ TEST(wkt_parse, wkt1_Mercator_1SP_with_latitude_origin_0) {
// ---------------------------------------------------------------------------
+TEST(wkt_parse, wkt1_Mercator_1SP_without_scale_factor) {
+ // See https://github.com/OSGeo/PROJ/issues/1700
+ 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[\"false_easting\",0],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"Meter\",1],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]";
+ WKTParser parser;
+ parser.setStrict(false).attachDatabaseContext(DatabaseContext::create());
+ auto obj = parser.createFromWKT(wkt);
+ EXPECT_TRUE(!parser.warningList().empty());
+
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto got_wkt = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ EXPECT_TRUE(got_wkt.find("PARAMETER[\"scale_factor\",1]") !=
+ std::string::npos)
+ << got_wkt;
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +units=m "
+ "+no_defs +type=crs");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(wkt_parse, wkt1_krovak_south_west) {
auto wkt =
"PROJCS[\"S-JTSK / Krovak\","
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index 328a04cc..07b0daea 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -5523,6 +5523,49 @@ TEST(operation, projCRS_no_id_to_geogCRS_context) {
// ---------------------------------------------------------------------------
+TEST(operation, geogCRS_3D_to_projCRS_with_2D_geocentric_translation) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto src =
+ authFactory->createCoordinateReferenceSystem("4979"); // WGS 84 3D
+
+ // Azores Central 1948 / UTM zone 26N
+ auto dst = authFactory->createCoordinateReferenceSystem("2189");
+
+ auto list =
+ CoordinateOperationFactory::create()->createOperations(src, dst, ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
+ "+step +proj=push +v_3 " // this is what we check
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=104 +y=-167 +z=38 "
+ "+step +inv +proj=cart +ellps=intl "
+ "+step +proj=pop +v_3 " // this is what we check
+ "+step +proj=utm +zone=26 +ellps=intl");
+
+ auto listReverse =
+ CoordinateOperationFactory::create()->createOperations(dst, src, ctxt);
+ ASSERT_GE(listReverse.size(), 1U);
+ EXPECT_EQ(
+ listReverse[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=26 +ellps=intl "
+ "+step +proj=push +v_3 " // this is what we check
+ "+step +proj=cart +ellps=intl "
+ "+step +proj=helmert +x=-104 +y=167 +z=-38 "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 " // this is what we check
+ "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, projCRS_to_projCRS) {
auto op = CoordinateOperationFactory::create()->createOperation(